2018. 3. 14. 21:18ㆍ0x03 Reversing Theory
집에 와서 디버깅을 조금 해보다가 재밌는것을 알게 되었습니다.
제목은 우연히 삽질 후 알게 된 메인함수의 뒷 이야기입니다.
많은 이들이 메인함수가 끝났을 때 return 0을 습관처럼 사용합니다.
무심결에 return 0을 사용하는데 그 근본을 알고 싶어 이번에 우연찮게 시도해보게 되었습니다.
테스트에 사용되었던 코드입니다.
#include <stdio.h>
#pragma warning(disable:4716)
char format[] = "ㅋ";
int i = 0;
__declspec(naked)int main(int argc, char*argv[])
{
__asm
{
push ebp
mov ebp,esp
mov ecx, i
compare:
cmp ecx,0x4
jl _loop
je _out
_loop:
mov eax, offset format
push eax
call printf
xor ecx, ecx
add i, 0x1
mov ecx, i
jmp compare
_out:
pop eax
mov esp,ebp
pop ebp
retn
}
}
디버거가 메인의 에필로그 까지 디버깅을 하고 retn을 빠져 나갔을 때 이러한 코드를 만나게 됩니다.
static int __cdecl invoke_main() throw()
{
return main(__argc, __argv, _get_initial_narrow_environment());
}
적힌 메인 코드의 반환 값은 0이기 때문에 invoke_main에도 0이 들어가게 될 것입니다.
throw라고 적혀있는 것으로 보아 예외처리에도 한 몫 할 것 같다는 생각이 듭니다.
다시 코드에 대한 얘기로 돌아가보겠습니다.
그 후 만나게 되는 코드입니다.
int const main_result = invoke_main();
그 이후에 scrt를 만나서 이러한 조건문도 만나게 되는데요.
if(!__scrt_is_managed_app())
{
exit(main_result);
}
해당 조건문도 한번 들어가보겠습니다.
extern "C" bool __cdecl __scrt_is_managed_app()
{
PIMAGE_DOS_HEADER const dos_header = reinterpret_cast<PIMAGE_DOS_HEADER>(GetModuleHandleW(nullptr));
if (dos_header == nullptr)
return false;
if (dos_header->e_magic != IMAGE_DOS_SIGNATURE)
return false;
PIMAGE_NT_HEADERS const pe_header = reinterpret_cast<PIMAGE_NT_HEADERS>(
reinterpret_cast<BYTE*>(dos_header) + dos_header->e_lfanew);
if (pe_header->Signature != IMAGE_NT_SIGNATURE)
return false;
if (pe_header->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR_MAGIC)
return false;
// prefast assumes we are overrunning __ImageBase
#pragma warning(push)
#pragma warning(disable: 26000)
if (pe_header->OptionalHeader.NumberOfRvaAndSizes <= IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR)
return false;
#pragma warning(pop)
if (pe_header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR].VirtualAddress == 0)
return false;
return true;
}
이곳이 __scrt_is_managed_app()인데요.
가만히 들여다보면 PE의 시그니처도 살펴보기도 하는걸로 보아 4D 5A가 아닐 때 여기서 인지를 한다고도
재밌게 추측을 해볼 수 있네요
그 다음 헤더부분의 rva와 size를 엔트리 디스크립터와 비교를 하게되고 정확하겐 모르겠다만 직역을 쭉 해보면
이 함수의 특징은 우선 GetModuleHandle에 의해 현재 프로세스의 핸들을 반환-> 이것으로 베이스 주소 추출가능
(베이스주소 추출가능 이유는 PIMAGE_DOS_HEADER 구조체 dos_header이기 때문이겠죠.
PIMAGE_DOS_HEADER는 제가 알기로 파일 시그니처를 체크하는 놈이 있어요. 아니나 다를까 두번째 비교문에서 시그니처
체크를 하고 있군요.
구조체에서 -> 라는 의미는 그 안의 참조값을 들여다 보겠다는 건데요.
즉 구조체의 포인터입니다.
pe_header안에 Signature가 있어요.
잠시 PEVIEW를 참조한 결과
typedef struct IMAGE_NT_HEADERS
{
Signature
IMAGE_FILE_HEADER
IMAGE_OPTIONAL_HEADER
}pe_header; 이렇게 쓰이는 구조입니다.
아니나 다를까 위 소스코드 구조체 형태를 보면 Signature, OptionalHeader가 있지요
OptionalFileHeader는 지금 보이지 않는 듯 하군요.
pe_header->OptionalHeader.Magic 이런건 어떻게 해석을 하냐면
IMAGE_NT_HEADERS의 별칭 pe_header구조체의 포인터 OptionalHeader에 접근해서
그 안의 멤버 Magic을 들여다 봐라 이런식으로 해석을 하면 됩니다.
PE 구조 그 자체를 모두 다 설명할 순 없어요. 저도 아직 모든 섹션과 데이터의 Description을
다 알진 못하기 때문에
지금 모든 조건문에 만족하지 못하다가 이 조건문에서 만족을 하게 됩니다.
f (pe_header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR].VirtualAddress == 0)
return false;
이 부분은 이해가 덜 되어있어서 이해 되는 대로 포스팅에 추가하도록 하겠습니다.
if (!__scrt_is_managed_app()) // 여기서 false 성립
exit(main_result); // exit(0)이 되어서 정상종료가 됨.
리버싱에 조금이라도 깊게 관심이 있다면 아무 exe 열어서 PEVIEW로 저 구조체 형태를 따라가보시면
놀라운 공부가 될 거라고 생각해서 글을 써보게 되었습니다
'0x03 Reversing Theory' 카테고리의 다른 글
PE 64비트에서 최적화에 대한 이야기 (4) | 2018.05.15 |
---|---|
64비트 간단한 예제로 다루어보는 최적화 모드들에 대한 분석 (0) | 2018.03.15 |
ASLR 걸린 모듈 후킹 (0) | 2018.03.13 |
프로세스 명 가져오기 (0) | 2018.03.12 |
Self HomeWork (0) | 2018.03.11 |