[컴][Hack][디버그] disassembly code 분석 방법 - 1

리버싱 기초 / 리버스 엔지니어링 시작 방법 / 어디서부터 리버스 엔지니어링을 시작할까 / 함수 분석 /



이 글에서는 같은 data structure 를 사용하는 함수들을 사용해서 data structure 를 유추 해 보는 과정이다.

먼저, data structure 를 initialize 하는 함수를 통해 data structure 를 유추해 보자. 이 곳에 있는 글은 ref.1 의 내용을 조금 각색한 내용이다.

ntdll.dll 에 있는 RtlInitializeGenericTable 의 disassembly code 를 통해 GenericTable 이 어떤 구조인지를 유추 해 보는 글이다.

dll 에서 API 를 찾고, 이 dll 을 사용하는 binary 를 찾는다. 이를 통해 어떤 동작을 하는 지 짐작할 수 있고, 어떻게 이 함수를 사용하는 지 알 수 있다.

RtlInitializeGenericTable

Immunity Debugger 나 Olly Debugger 를 이용해서 ntdll.dll 에서 RtlInitializeGenericTable 을 찾아가보면, 아래와 같은 코드가 보인다. 아래 코드는 Immunity Debugger 를 사용해서 가져온 내용이다.
770D6698 > 8BFF             MOV EDI,EDI
770D669A   55               PUSH EBP
770D669B   8BEC             MOV EBP,ESP
770D669D   8B45 08          MOV EAX,DWORD PTR SS:[EBP+8]
770D66A0   33D2             XOR EDX,EDX
770D66A2   8D48 04          LEA ECX,DWORD PTR DS:[EAX+4]
770D66A5   8910             MOV DWORD PTR DS:[EAX],EDX
770D66A7   8949 04          MOV DWORD PTR DS:[ECX+4],ECX
770D66AA   8909             MOV DWORD PTR DS:[ECX],ECX
770D66AC   8948 0C          MOV DWORD PTR DS:[EAX+C],ECX
770D66AF   8B4D 0C          MOV ECX,DWORD PTR SS:[EBP+C]
770D66B2   8948 18          MOV DWORD PTR DS:[EAX+18],ECX
770D66B5   8B4D 10          MOV ECX,DWORD PTR SS:[EBP+10]
770D66B8   8948 1C          MOV DWORD PTR DS:[EAX+1C],ECX            ; generict.004110C8
770D66BB   8B4D 14          MOV ECX,DWORD PTR SS:[EBP+14]
770D66BE   8948 20          MOV DWORD PTR DS:[EAX+20],ECX
770D66C1   8B4D 18          MOV ECX,DWORD PTR SS:[EBP+18]
770D66C4   8950 14          MOV DWORD PTR DS:[EAX+14],EDX
770D66C7   8950 10          MOV DWORD PTR DS:[EAX+10],EDX
770D66CA   8948 24          MOV DWORD PTR DS:[EAX+24],ECX
770D66CD   5D               POP EBP
770D66CE   C2 1400          RETN 14
함수 호출 부분에는 아래 처럼 ebp 를 새롭게 setting 하는 부분이 들어 있다. 그러니 그냥 무시해도 된다. 참고로, parameter(그림에서는 arg) 를 stack 에 넣는 부분(instructions)은 함수를 call 하기 전에 이루어진다.
push ebp
mov ebp, esp

stack_Frame


Calling Convention 유추

함수의 함수호출규약(calling convention) 을 찾아보자. 그러기 위해서는 일단 RET 를 확인하자.
RET 14
14 는 stack 에서 20(0x14) 바이트를 unwind 해야 된다고 알려준다. 32 bit 컴퓨터이기 때문에, parameter 들이 4 byte 에 맞춰서 만들어졌을 테니, 20 byte 는 5개의 parameter 라고 알려준다.

만약 parameter 하나가 4 byte 보다 큰 경우에는 parameter 의 개수가 5개보다 적을 수 있다. 그러나, parameter 들이 32-bit 로 aligned 되어 있기에, 5 개보다 많을 수는 없다. [1]

function(callee, 호출이 된 함수) 이 unwind 를 하는 것은 cdecl 함수가 아니다. cdecl calling convention 은 caller(호출하는 함수) 가 stack 을 unwind 하게 되어 있다.(참고: http://i5on9i.egloos.com/4841016 )

caller 로 부터 어떤 register 도 가져오지 않았다. _fastcall calling convention 은 parameter들 중 처음 2개 까지만 레지스터(ecx, edx)에 저장하고, 나머지는 스택에 push 한다.[2] 스택 프레임 반환 작업(unwind)은 callee 가 한다. 그러므로 _fastcall 도 아니다.

그로므로 stdcall 이라고 유추할 수 있다.(물론 다른 convention 도 있지만, 많이 쓰이는 것 중에서 유추한 듯 하다.)



자료구조 분석

분석 Step 1.

이제 그 다음 line 을 보자.
mov eax,dword ptr ss:[ebp+8]
xor  edx,edx
lea  ecx,dword ptr ds:[eax+4]
ss 는 여기서는 무시해도 된다. ss 가 궁금하다면, 아래 글을 참고 하자.
ss 관한 설명 : How are the segment registers (fs, gs, cs, ss, ds, es) used in x86 and amd64?

그럼 이제, 한 줄 씩 살펴보자.
mov eax, dword ptr ss:[ebp+8]   // ebp+8 에 있는 녀석을 eax 로 옮긴다.
  • ebp+4 에는 return address 가 있으며,
  • ebp+8 에는 1번 째 parameter 가 들어있다.
여하튼 ebp+8 에 있는 녀석(첫번째 parameter)을 eax 로 옮기는 instruction 이다.
xor edx, edx     ; '0' 으로 초기화 '0' 으로 만들 때는 보통 xor 이 쓰인다. mov edx, 0 보다 machine code 가 짧기 때문이라고 한다.[1]
lea ecx, dword ptr ds:[eax+4]
load effective address는 주로 address 를 계산하는데 쓰인다고 한다. [] 가 있지만, lea 는 실제로 메모리에 접근하지 않는다. 여기서는
ecx = eax + 4     ; eax 가 "첫 번째 element를 가리키는 주소" 이니 eax+4 는 "두 번째 element의 주소" 이다.
로 봐도 무방하다.

간단히 c-code 형식으로 나타내면 아래와 같다.
eax = param1
ecx = param2

분석 Step 2.

이제 그 다음 line 들도 분석 해 보자.
mov dword ptr ds:[eax], edx      ; [eax] 를 '0'(edx)으로 초기화 
mov dword ptr ds:[ecx+4], ecx   ; structure 의 3번째 member 에 2번째 member의 address 를 넣는다.
mov dword ptr ds:[ecx], ecx      ; 2번째 member의 address 를 [ecx] 에 넣는다.
mov dword ptr ds:[ecx+c], ecx   ; c 는 0xC, 12 이다. 4번째 member의 address 를 [ecx] 에 넣는다.
c 코드로 나타내면, 아래 같은 모양이다.
SomeStruct->m1 = 0;
SomeStruct->m2 = &SomeStruct->m2;
SomeStruct->m3 = &SomeStruct->m2;
SomeStruct->m4 = &SomeStruct->m2;

분석 Step 3.

mov ecx, dword ptr ss:[ebp+c]
mov dword ptr ds:[eax+18], ecx  ; 0x18 는 24
mov ecx, dword ptr ss:[ebp+10]
mov dword ptr ds:[eax+1c], ecx
; c code
SomeStruct->m7 = param2;
SomeStruct->m8 = param3;

mov ecx, dword ptr ss:[ebp+14]
mov dword ptr ds:[eax+20], ecx
mov ecx. dword ptr ss:[ebp+18], ecx
mov dword ptr ds:[eax+14], edx
mov dword ptr ds:[eax+10], edx
mov dword ptr ds:[eax+24], ecx
; c code
SomeStruct->m9 = param4;
SomeStruct->m6 = 0;
SomeStruct->m7 = 0;
SomeStruct->m10 = param5;

정리

이 함수disassembly code 의 [eax+24] 를 통해 이 GenericTable structure 의 size 가 40-byte 정도 된다는 것을 알 수 있고, 만약 structure 의 member 한 개가 4-byte 라고 가정하면, 약 10개의 member 를 가지고 있다고 유추할 수 있다. 이것을 c-code 로 나타내면 아래와 같을 것이다.
struct TABLE
{
  UNKNOWN       m1;
  UNKNOWN_PTR m2;
  UNKNOWN_PTR m3;
  UNKNOWN_PTR m4;
  UNKNOWN       m5;
  UNKNOWN       m6;
  UNKNOWN       m7;
  UNKNOWN       m8;
  UNKNOWN       m9;
  UNKNOWN       m10;
};


References

  1. p. 146, Beyond Documentation, Reversing: Secrets of Reverse Engineering, 2005
  2. http://data-forge.blogspot.kr/2012/01/2-cdecl-stdcall.html

댓글 1개: