'콜스택'에 해당되는 글 2건

  1. 2015.03.22 수동으로 64비트 콜스택 추적하기 (2) 2
  2. 2015.02.26 수동으로 64비트 콜스택 추적하기
WinDbg 디버깅2015. 3. 22. 22:48
반응형

이번 포스팅에서는 지난 번에 다뤘던 수동으로 64비트 콜스택 추적하기의 이론적인 배경을 알아본다.


지난번 포스팅에서 함수 하나가 사용하는 스택의 크기를 계산하기 위한 공식은 다음과 같았다.


stacksize = 리턴주소 push 크기 + 함수 프롤로그에서 사용한 push 크기 + 함수 프롤로그에서 rsp를 빼는 크기


이런 계산이 가능한 이유는 함수에서 사용하는 스택 크기가 항상 정해져 있기 때문이다.


이를 이해하기 위해서는 일단 64비트의 기본적인 스택 구조를 이해해야 한다.

그림으로 표현해 보면 다음과 같다.



 리턴주소

특수목적 임시저장

(레지스터, 보안쿠키 등)

... 

지역변수

지역변수

지역변수

 ...

... 

파라미터6

 파라미터5

 파라미터4

 파라미터3

 파라미터2

 파라미터1

리턴주소


<= 상위 함수의 rsp

상위 함수의 리턴 주소


함수 내부에서 사용하는 스택 영역







함수 내부에서 호출하는 Sub 함수 중 파라미터가 가장 많은 함수의 파라미터만큼 예약한 공간

(최소 4개 예약)




<= 이 함수의 rsp (파라미터 1의 스택 주소)

Sub 함수 호출시 저장되는 이 함수의 리턴 주소



어떤 함수가 실행 중일 때 스택을 사용하는 형태를 보면 그림과 같이 구성된다.

특별한 경우를 제외하고는 대부분 이와 같은 형태를 보인다.

따라서 이렇게 사용되는 원리를 이해하면 수동으로 콜스택 추적이 가능해 진다.


각 영역들에 대해 알아보면 다음과 같다.


1. 상위 함수 리턴 주소

설명이 필요없이 이 함수를 호출할 때 스택에 저장되는 호출자 주소다.



2. 함수 내부에서 사용하는 스택 영역

지역변수, 임시 저장 영역 등으로 컴파일러가 구성한 스택 영역이다.

컴파일 시에 컴파일러가 이 영역의 크기를 계산하기 때문에 컴파일 시에 크기가 정해진다.



3. Sub 함수 파라미터 예약 공간

이 함수 내부에서 여러 개의 Sub 함수를 호출할 수 있을 텐데 그 중에서 가장 많은 파라미터 수 만큼 예약해 놓는다. 컴파일 시에 컴파일러가 계산해 놓을 수 있다.


함수 내부에서 사용하는 스택 크기는 2번하고 3번을 더한 값이다.

컴파일러는 컴파일시 함수 프롤로그에서 push와 sub rsp로 2, 3번을 구성해 준다.


컴파일러가 함수에서 사용하는 스택의 크기를 미리 계산해서 함수 프롤로그에 코드를 만들어 주기 때문에...

거꾸로 함수 프롤로그를 보고 스택을 사용하는 크기를 계산해서 함수가 사용하는 스택 크기를 알 수 있는 것이다. 



4. 최소 4개 예약?

함수 내부에서 호출하는 Sub 함수들이 파라미터를 1개 밖에 받지 않는다고 하더라도 이 파라미터 공간은 최소 4개가 예약된다. 4개로 정해지는 이유는 64비트에서 파라미터 전달이 4개까지는 rcx, rdx, r8, r9 레지스터를 이용하기 때문인 것 같다. Sub 함수의 파라미터가 5개라면 5개의 파라미터를 저장할 공간을 예약해 놓는다.


재미있는 것은 Sub 함수를 호출할 때 현재 함수에서는 파라미터1~파라미터4 예약 공간을 채우지 않는다는 것이다. 파라미터는 이미 rcx, rdx, r8, r9에 넣어 놨으므로 스택에 다시 저장하지 않는다. 다만 파라미터가 5개 이상인 경우는 스택을 통해 파라미터를 전달하기로 되어 있으니 파라미터5 이후는 스택에 저장해 준다.


파라미터가 7개인 PsCreateSystemThread를 호출하는 코드를 보면서 확인해 본다.


fffff800`01795328 488364243000    and     qword ptr [rsp+30h],0

fffff800`0179532e 488d055bfeffff  lea     rax,[nt!InbvRotateGuiBootDisplay]

fffff800`01795335 488d4c2458      lea     rcx,[rsp+58h]

fffff800`0179533a 4889442428      mov     qword ptr [rsp+28h],rax

fffff800`0179533f 488364242000    and     qword ptr [rsp+20h],0

fffff800`01795345 4533c9          xor     r9d,r9d

fffff800`01795348 4533c0          xor     r8d,r8d

fffff800`0179534b 33d2            xor     edx,edx

fffff800`0179534d e802a21900      call    nt!PsCreateSystemThread 


첫번째 라인은 마지막 7번째 파라미터 스택 위치인 [rsp+30h]에 0을 저장하는 것이다. 네번째 라인에서 [rsp+28h]에 6번째 파라미터를 저장하고 다섯번째 라인에서 [rsp+20h]에 5번째 파라미터를 저장한다.

첫번째 파라미터부터 네번째 파라미터까지는 스택에 저장하는 모양은 볼 수 없고 rcx, edx, r8d, r9d에 저장하는 모양이 나타나 있다.


그러면 예약해 놓은 앞 네개의 파라미터 스택 공간은 언제 사용할까?

MSDN 상으로는 호출된 Sub 함수가 레지스터로 전달된 파라미터를 스택에 저장하고 싶으면 사용할 수 있다고 되어 있다. 디버깅을 편하게 하기 위해 Sub 함수 프롤로그에서 레지스터로 전달된 파라미터를 여기에 저장하게 할 수도 있다고 설명은 하고 있지만... 실제로 대부분 이 영역은 사용하지 않아서 쓰레기값만 저장되어 있다. 그래서 콜스택을 디버깅할 때 함수간에 전달된 파라미터를 찾기가 매우 힘든 것이다.



5. 이 함수의 rsp

64비트에서 함수가 실행될 때 사용하는 스택 베이스는 rsp다.

32비트에서는 함수에 진입하면 ebp를 스택 베이스로 잡고 함수 내부에서 사용하는 스택은 [ebp-xx]로 접근한다. 

하지만 64비트에서는 함수에 진입하면 이 함수에서 사용하는 스택 크기를 모두 계산해서 미리 빼놓고 rsp에 저장한 후에 [rsp+xx]로 접근한다.


그림에서 알 수 있듯이 rsp는 호출할 함수에 전달할 파라미터 1의 주소를 가리키게 된다. 

그래서 다음 위치는 항상 다음과 같은 의미를 가진다.


[rsp+00h] : 호출 함수에 전달할 파라미터 1을 저장

[rsp+08h] : 호출 함수에 전달할 파라미터 2를 저장

[rsp+10h] : 호출 함수에 전달할 파라미터 3을 저장

[rsp+18h] : 호출 함수에 전달할 파라미터 4를 저장


하지만 현재 함수에서 이 위치를 참조하는 일은 없다. ㅠㅠ

그냥 의미만 알아 놓자. 현재 함수가 호출하는 sub 함수에 파라미터 5가 있다면 [rsp+20h]에 저장한다는 것은 알았으니... 



이렇게 해서 64비트 콜스택 구성에 대해 모두 알아봤다.

일단 이정도만 알아도 64비트를 탐험하는 작업이 좀 더 편해질 것 같다.


반응형
Posted by GreeMate
WinDbg 디버깅2015. 2. 26. 23:15
반응형

64비트 콜스택을 좀 보다보니 수동으로 따라갈 수 있는 방법을 하나 알게 되었다.

거의 포기하고 있었는데 보다보니 이런 것들이 보이기 시작하는구나... ㅋㅋ


이론을 먼저 설명하면 머리만 복잡해지니 예제부터 보기로 한다.

다음과 같은 콜스택이 있다고 해 보자.


kd> k

Child-SP          RetAddr           Call Site

fffff880`009adeb0 fffff800`019a6135 nt!ExAllocatePoolWithTag+0x326

fffff880`009adfa0 fffff800`019a3e3e nt!RtlpInheritAcl+0xc5

fffff880`009ae070 fffff800`0197f6c2 nt!RtlpNewSecurityObject+0x8ce

fffff880`009ae300 fffff800`0197e582 nt!ObpAssignSecurity+0x82

fffff880`009ae370 fffff800`01980db3 nt!ObInsertObjectEx+0x1e2

fffff880`009ae5c0 fffff800`019801be nt!PspInsertThread+0x2f3

fffff880`009ae740 fffff800`0192f679 nt!PspCreateThread+0x246

fffff880`009ae9c0 fffff800`01795352 nt!PsCreateSystemThread+0x125

fffff880`009aeab0 fffff800`01bc751d nt!DisplayBootBitmap+0x162

fffff880`009aeb00 fffff800`01b18e29 nt!Phase1InitializationDiscard+0x17d

fffff880`009aecd0 fffff800`0192f73a nt!Phase1Initialization+0x9

fffff880`009aed00 fffff800`016848e6 nt!PspSystemThreadStartup+0x5a

fffff880`009aed40 00000000`00000000 nt!KiStartSystemThread+0x16


ExAllocatePoolWithTag 중간쯤에서 멈춰있는 상태다.

ExAllocatePoolWithTag 함수에서 사용중인 스택 베이스 포인터가 Child-SP fffff880`009adeb0로 나타나고 있다.


콜스택을 수동으로 추적하려면 각 함수의 스택 프레임의 기준을 찾는 것이 중요한데 Child-SP가 기준이라고 보면 된다.

Child-SP를 따라갈 수 있으면 되는데... 어떻게 할 수 있는지 확인해 보자.


ExAllocatePoolWithTag 함수에서 멈춰 있는 상태에서 레지스터 상태를 보면 다음과 같다.


kd> r

rax=fffff8a000006000 rbx=fffffa8000c2d280 rcx=0000000000000001

rdx=0000000000000001 rsi=000000000000000e rdi=0000000000000001

rip=fffff800017c7f36 rsp=fffff880009adeb0 rbp=0000000000001000

 r8=0000000000000000  r9=0000000000000000 r10=0000000000000000

r11=fffff880009ade80 r12=fffffa8000c2c140 r13=0000000000000000

r14=0000000000000002 r15=0000000063416553

iopl=0         nv up ei ng nz na po nc

cs=0010  ss=0018  ds=002b  es=002b  fs=0053  gs=002b             efl=00000286

nt!ExAllocatePoolWithTag+0x326:

fffff800`017c7f36 448928          mov     dword ptr [rax],r13d ds:002b:fffff8a0`00006000=fe05a30d


rsp가 Child-SP와 같은 값을 가지고 있다.


일단 이론을 보기 전에 하나는 그냥 외우고 시작하자.

64비트에서는 기본적으로 rsp가 32비트에서의 ebp와 비슷한 역할을 한다.

64비트 코드를 분석해 보면 알겠지만 대부분 rsp를 거의 변경하지 않고 사용한다.

따라서 ExAllocatePoolWithTag는 rsp를 기준으로 자신의 스택을 사용하고 있다.


여기서 상위 함수의 rsp 값을 찾으려면 ExAllocatePoolWithTag의 코드 앞부분(프롤로그)을 보면 된다.


kd> u nt!ExAllocatePoolWithTag

nt!ExAllocatePoolWithTag:

fffff800`017c7c10 fff5            push    rbp

fffff800`017c7c12 57              push    rdi

fffff800`017c7c13 4157            push    r15

fffff800`017c7c15 4881ecd0000000  sub     rsp,0D0h

fffff800`017c7c1c 8bc1            mov     eax,ecx

fffff800`017c7c1e 458bf8          mov     r15d,r8d

fffff800`017c7c21 448944245c      mov     dword ptr [rsp+5Ch],r8d

fffff800`017c7c26 83e044          and     eax,44h


함수에 진입하자마자 push를 3번하면서 스택을 약간 사용하고 이 함수가 사용할 스택 크기를 확보하는 코드가 있다.

여기에 상위함수가 ExAllocatePoolWithTag를 호출하면서 자신의 리턴 주소를 push하는 것까지만 추가로 고려하면 함수 호출시에 사용된 스택 크기를 알 수 있다.

현재 rsp에서 이 크기를 계산해 주면 상위 함수의 rsp가 나온다.


따라서 상위 함수의 기준 rsp를 정확히 구하는 식은 다음과 같다.


stacksize = 8 (리턴주소 push) + 8*3 (push 3번) + 0D0h (rsp에서 빼준 값)

상위 함수 rsp = 현재 함수 rsp + stacksize 


ExAllocatePoolWithTag의 stacksize 계산 결과는 0F0h다.

다음과 같이 계산할 수 있다.


kd> ? fffff880`009adeb0 + F0

Evaluate expression: -8246327058528 = fffff880`009adfa0


계산된 fffff880`009adfa0는 콜스택을 확인해 보면 두번째 함수인 RtlpInheritAcl의 Chile-SP와 일치한다.


계속해서 같은 방법으로 계속 따라갈 수 있다.

기계적으로 한번 진행해 보자.


함수 프롤로그 보고...


kd> u nt!RtlpInheritAcl

nt!RtlpInheritAcl:

fffff800`019a6070 fff3            push    rbx

fffff800`019a6072 55              push    rbp

fffff800`019a6073 56              push    rsi

fffff800`019a6074 57              push    rdi

fffff800`019a6075 4881eca8000000  sub     rsp,0A8h

fffff800`019a607c 410fb6f1        movzx   esi,r9b

fffff800`019a6080 418bd8          mov     ebx,r8d

fffff800`019a6083 488bea          mov     rbp,rdx


stacksize를 계산한다.

stacksize = 8 + 8*4 + 0A8h = D0h


kd> ? fffff880`009adfa0 + D0

Evaluate expression: -8246327058320 = fffff880`009ae070


fffff880`009ae070은 RtlpNewSecurityObject의 Child-SP와 일치한다.


RtlpNewSecurityObject를 따라가 보자.


kd> u nt!RtlpNewSecurityObject

nt!RtlpNewSecurityObject:

fffff800`019a3570 fff3            push    rbx

fffff800`019a3572 55              push    rbp

fffff800`019a3573 57              push    rdi

fffff800`019a3574 4881ec70020000  sub     rsp,270h

fffff800`019a357b 488b055eb4e6ff  mov     rax,qword ptr [nt!_security_cookie (fffff800`0180e9e0)]

fffff800`019a3582 4833c4          xor     rax,rsp

fffff800`019a3585 4889842450020000 mov     qword ptr [rsp+250h],rax

fffff800`019a358d 488b8424d0020000 mov     rax,qword ptr [rsp+2D0h]


stacksize = 8 + 8*3 + 270h = 290h


kd> ? fffff880`009ae070 + 290

Evaluate expression: -8246327057664 = fffff880`009ae300


fffff880`009ae300은 ObpAssignSecurity의 Child-SP와 일치한다.


ObpAssignSecurity를 확인한다.


kd> u nt!ObpAssignSecurity

nt!ObpAssignSecurity:

fffff800`0197f640 48895c2410      mov     qword ptr [rsp+10h],rbx

fffff800`0197f645 48896c2418      mov     qword ptr [rsp+18h],rbp

fffff800`0197f64a 56              push    rsi

fffff800`0197f64b 57              push    rdi

fffff800`0197f64c 4154            push    r12

fffff800`0197f64e 4883ec50        sub     rsp,50h

fffff800`0197f652 488364247000    and     qword ptr [rsp+70h],0

fffff800`0197f658 4d8be0          mov     r12,r8


stacksize = 8 + 8*3 + 50h = 70h


kd> ? fffff880`009ae300 + 70

Evaluate expression: -8246327057552 = fffff880`009ae370


fffff880`009ae370은 ObInsertObjectEx의 Child-SP와 일치한다.


계속해서 ObInsertObjectEx를 따라가 본다.


kd> u nt!ObInsertObjectEx

nt!ObInsertObjectEx:

fffff800`0197e3a0 fff3            push    rbx

fffff800`0197e3a2 55              push    rbp

fffff800`0197e3a3 56              push    rsi

fffff800`0197e3a4 57              push    rdi

fffff800`0197e3a5 4154            push    r12

fffff800`0197e3a7 4155            push    r13

fffff800`0197e3a9 4156            push    r14

fffff800`0197e3ab 4157            push    r15

kd> u

nt!ObInsertObjectEx+0xd:

fffff800`0197e3ad 4881ec08020000  sub     rsp,208h

fffff800`0197e3b4 488b052506e9ff  mov     rax,qword ptr [nt!_security_cookie (fffff800`0180e9e0)]

fffff800`0197e3bb 4833c4          xor     rax,rsp

fffff800`0197e3be 48898424f0010000 mov     qword ptr [rsp+1F0h],rax

fffff800`0197e3c6 488bac2480020000 mov     rbp,qword ptr [rsp+280h]

fffff800`0197e3ce 4c8bf9          mov     r15,rcx

fffff800`0197e3d1 4533d2          xor     r10d,r10d

fffff800`0197e3d4 410fb647e8      movzx   eax,byte ptr [r15-18h]


이번엔 u 명령 한번으로는 sub rsp, 208h까지 보이지 않아서 u 명령을 한번 더 사용했다.

마찬가지로 모든 push와 sub rsp 명령에서 사용한 스택 사용량을 계산하면 된다.


stacksize = 8 + 8*8 + 208h = 250h


kd> ? fffff880`009ae370 + 250

Evaluate expression: -8246327056960 = fffff880`009ae5c0


fffff880`009ae5c0는 PspInsertThread의 Child-SP와 일치한다.



이 정도면 충분히 연습이 됐을 것 같다.

모두 같은 방법으로 따라간 것이고 계속해서 같은 방법으로 진행할 수 있다


너무 길어졌으므로 이에 대한 이론은 다음 포스팅에서 다루도록 하겠다.


반응형
Posted by GreeMate