Reversing GetProcAddress()

Reverse Code Engineering 2008. 9. 18. 01:19 posted by jz-

dll 익스포트 함수를 파싱하는 모듈을 만들다가... 이상한걸 발견했습니다.

사용자 삽입 이미지


포워딩된 함수??

AddVectoredExceptionHandler 말고도 몇개의 함수가 저런식으로 "포워딩" 돼 있었습니다.

실제로 저 함수명을 GetProcAddress() 해 보면
Kernel32.dll의 함수인데도 불구하고 ntdll.dll의 RtlAddVectoredExceptionHandler의 주소가 얻어지더군요.



Export 관련해서 내가 모르는 헤더나 세팅정보가 있나??

사용자 삽입 이미지
 
그럴리가....


저 함수들만을 가리키는 정보라던지, 다른 어떤 것이 있나 아무리 찾아봐도 없었습니다.

그럼 export address에서 가리키는 주소를 직접 가보죠.

rva가 8f63 이고 Kernel32.dll의 imagebase는 7c80 이므로, reloc되지 않았다면 7c808f63이 함수주소입니다.

시스템dll은 효율적으로 배치되고 맨 첨에 배치되므로 reloc될 일은 없으므로 아무거나 프로세스를 띄워서 바로 7c808f63으로 가보죠.


자, 미스 IDA 로 봤고, 함수가 아래와 같습니다.
사용자 삽입 이미지
 
굳이 코드로 변환하면 위와 같지만, 사실 ida는 다른 모듈은 코드변환을 안하므로..
아래와 같이 보입니다.





사용자 삽입 이미지
 
NTDLL.AddVectoredExceptionHandler 가 코드대신 있습니다.
 물론 문자열도 코드도 모두 이진수일뿐입니다.




사용자 삽입 이미지
 
똑똑한 올리는 파싱해서 보여줍니다.(Ctrl-A = 분석실행)
진짜 두 Ring3 디버거 합쳐놓은 제품 어디 안나오나요




코드 대신에 문자열이 있는걸 확인했습니다. 그럼 Windows Dll에서 함수 주소를 얻는 Mechanism 이 어떻게 돼있는지 두 가지로 추측할 수 있겠죠.
① 아직 못찾았지만, 어딘가 Export function의 forwarding 여부를 지정한 곳이 있다.
② 함수 코드의 첫부분을 strcmp해서 문자열이면 forwarding한다... (설마...)


궁금하니까 GetProcAddress를 까뒤집어보도록 합시다.



 Reversing Kernel32.GetProcAddress()


사용자 삽입 이미지

GetProcAddress()의 첫 모습입니다.

보면 처음엔 지역변수([ebp-8])를 RtlInitString을 하여 STRING 구조체로 초기화합니다,

STRING 구조체는 <winternl.h> 에 정의돼있으며 형식은 아래와 같습니다.
typedef struct _STRING {
    USHORT Length;
    USHORT MaximumLength;
    PCHAR Buffer;
} STRING;
typedef STRING *PSTRING;

그다음엔 NTDLL.LdrGetProcedureAddress를 호출하는데,
인자를 세팅하는 중간에 함수 7c8098f4 를 호출하여 결과값을 인자로 쓰는 것을 볼 수 있습니다.(push eax)

LdrGetProcedureAddress를 보기 전에 7c8098f4가 무슨 함수인지 먼저 보죠.

사용자 삽입 이미지

마지막의 retn 08을 보면 _stdcall이며, 인자는 2개입니다.

호출하는 부분을 보면
사용자 삽입 이미지
1번째 인자는 GetProcAddress의 1번째 인자, hModule이며 2번째 인자는 0입니다.

함수가 하는 일은
첫번째 인자가 0이라면 7c80b584로 가고(je 7c80b584)
첫번째 인자의 마지막 비트가 1로 세팅돼있다면 7c80a004로 갑니다.
먼저 7c80b584를 보죠.
사용자 삽입 이미지
첫번째 라인은 eax := teb
두번째 라인은 eax := peb
세번째 라인은 eax := peb->ImageBase
즉, GetProcAddress의 첫번째 인자인 hModule을 0으로 넣으면 프로세스의 ImageBase를 hModule로 넣는단 소리입니다.
( hModule은 사실 해당 dll의 imagebase 값입니다. )
즉 0을 hModule로 넣으면 프로세스에서 exe파일의 export 함수를 찾는단 뜻입니다.

흠.. 이게 msdn에 써있나?
안써있습니다 ^-^
엉망이구만.

다음으로 7c80a004를 보죠.
사용자 삽입 이미지
7c80a004로 뛰는 경우는 첫번째인자인 hModule의 마지막 비트가 1이면 오게 돼있습니다.
말했지만 hModule은 dll의 imagebase 인 고로 이게 imagebase라면 마지막 비트가 1일 리는 없습니다.
즉, 0을 넣을 때 처럼 특수한 값을 넣을 때 이리 오게 돼있는 것인데...

Kernel32.dll 의 unexport된 함수인 7c8098f4를 호출하는 곳이 kernel32.dll 내에서 여러곳 있었습니다.
그중 LoadResource에서 hModule의 마지막 비트를 1로 넣는 경우가 있었는데,
7c80a004에서 이뤄지는 일을 보니, 그냥 ecx를 -1(0xffffffff)로 만드는 것이었습니다.

뭥미? 뭔지 몰겠지만 넘어가죠.

아무튼 0인경우 process의 imagebase를 리턴하고, hModule인 경우 별거없이 그냥 hModule자체를 리턴하는 함수입니다.
이제 본격적으로 LdrGetProcedureAddress로 들어갑시다.

사용자 삽입 이미지

ntdll.LdrGetProcedureAddress 코드는 위와 같습니다. 마지막에 인자 1 하나 추가해서 내부 함수로 래핑해주는 것을 볼 수 있습니다. 내부함수인 7c9499b5로 가보죠.

여기서부턴 그림으로만 보겠습니다. 편집하기가 힘드니까. 진작 이렇게 할껄그랬네요 -_-;

글을 주석으로 써놓았으므로, 주석을 자세히 보세요

클릭하셔서 보셔야 하는거 아시죠?^^

사용자 삽입 이미지

LdrGetProcedureAddress의 내부함수인 0x7c9499b5의 시작입니다. seh를 걸고 .. 별로 하는짓 없네요.
다음!

사용자 삽입 이미지

지역변수인 STRING구조체를 하나 세팅하고 7C943317을 호출하고,
그다음엔 RtlImageDirectoryEntryToData를 호출하네요.
먼저 7C943317 를 분석하죠.

사용자 삽입 이미지

ntdll에 전역변수 하나가 dll info list entry 를 저장하고 있었습니다.
아마 exe,dll 하나 로드할때 같은 dll의 여러 export 함수 주소를 구하므로, 최근 dll정보를 저장하는 것으로 보입니다. 확실히 효율적이겠지요^^
그리고 사용자 입력인 imagebase, 즉 hModule을 갖고 list entry 내의 imagebase 정보와 비교하여 dll을 찾아내네요.


사용자 삽입 이미지
7C943317 은 분석이 끝났고, 그럼 RtlImageDirectoryEntryToData 함수를 분석해야겠네요.
msdn에도, undocumented nt 에도, 구글에도 잘 나와있지 않습니다.
직접 분석해보죠.

RtlImageDirectoryEntryToData의 시작부분입니다.
사용자 삽입 이미지

시작하자마자 내부함수 또나오는군요. 또 들어가서 분석해보죠.

사용자 삽입 이미지

음 input imagebase를 MZ와 PE를 확인하는 방법으로 검증하고, nt헤더 포인터를 리턴하는 함수였군요.
다시 돌아가서.

사용자 삽입 이미지
optional header의 magic word도 확인하고, 또 모든 인자 + nt header를 인자로 받는 내부함수를 호출하네요.
또들어가보죠..

사용자 삽입 이미지
Export dir 정보를 얻는 함수였군요.

사용자 삽입 이미지
결국 RtlImageDirectoryEntryToData함수는 인자로 datadirectory의 enum값을 받고
(export의 경우 0)
그 data directory의 rva와 size를 얻어 주는 함수인 것입니다.
그럼 RtlImageDirectoryEntryToData 다음으로 넘어가보죠.

사용자 삽입 이미지
imagebase와 export dir rva/size, list entry 등이 모두 들어가는 걸 보니
여기서 함수주소 구하는 루틴이 나오겠네요. 들어가보죠.

사용자 삽입 이미지
Export dir을 파싱하여, 1차적으로 함수 이름 비교를 하네요.
함수 이름이 다르면 아래로 뜁니다. 같이 뛰죠.

사용자 삽입 이미지
오호.. 점점 가까와 지는 것 같네요.고고씽

사용자 삽입 이미지

본격적인 함수 검색입니다. 재밌는건, 이진검색을 하네요.
그렇다면 함수 이름은 알파벳 순으로 정렬되는게 표준이겠군요. 후후

사용자 삽입 이미지
그렇습니다. 방금 함수는 찾는 함수의 ordinal을 구하는 함수였습니다.
다시 돌아가서


사용자 삽입 이미지
잘 따라오세요. 계속 고고~

사용자 삽입 이미지
얻은 ordinal로 export address table에서 원하는 함수의 주소를 구합니다.
여기서 우리가 찾던게 드디어 나옵니다.
함수주소를 구하고 나서, 이 주소가 export directory보다 뒤에 있다면 7c949c5c로 jmp합니다.

사용자 삽입 이미지


캬~ 나왔습니다.
일단 선조건이 export directory에 지정된 size보다 아래쪽 주소에 있는 export address에 대해서
함수 첫 시작을 memcmp 를 해버립니다 ㅡㅡ;;;


함수 코드가 "NTDLL."로 시작한다면 forwarding 해버리네요







사용자 삽입 이미지

if( memcmp( (void*)MessageBoxA, "NTDLL.", 6 ) == 0 )  // <- 헐

뭐.. 대단합니다 ms. 아무튼 알아냈으니 궁금증은 풀렸네요.
결론적으로
   ② 함수 코드의 첫부분을 strcmp해서 문자열이면 forwarding한다... (설마...)
가 정답이었습니다. 딩동댕~~


알아내는데 10분 걸리고 포스팅하는데 또 몇시간 걸렸습니다.
대단한 블로거들..