이 글에서는 같은 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함수 호출 부분에는 아래 처럼 ebp 를 새롭게 setting 하는 부분이 들어 있다. 그러니 그냥 무시해도 된다. 참고로, parameter(그림에서는 arg) 를 stack 에 넣는 부분(instructions)은 함수를 call 하기 전에 이루어진다.
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
push ebp
mov ebp, esp
Calling Convention 유추
함수의 함수호출규약(calling convention) 을 찾아보자. 그러기 위해서는 일단 RET 를 확인하자.RET 1414 는 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]ss 는 여기서는 무시해도 된다. ss 가 궁금하다면, 아래 글을 참고 하자.
xor edx,edx
lea ecx,dword ptr ds:[eax+4]
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 가 들어있다.
xor edx, edx ; '0' 으로 초기화 '0' 으로 만들 때는 보통 xor 이 쓰인다. mov edx, 0 보다 machine code 가 짧기 때문이라고 한다.[1]load effective address는 주로 address 를 계산하는데 쓰인다고 한다. [] 가 있지만, lea 는 실제로 메모리에 접근하지 않는다. 여기서는
lea ecx, dword ptr ds:[eax+4]
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)으로 초기화c 코드로 나타내면, 아래 같은 모양이다.
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] 에 넣는다.
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
- p. 146, Beyond Documentation, Reversing: Secrets of Reverse Engineering, 2005
- http://data-forge.blogspot.kr/2012/01/2-cdecl-stdcall.html
좋은 글입니다.
답글삭제