보통 프로그램 내부적으로 DLL Injection을 수행하고 인라인후킹을 하면 전형적으로 타겟 주소 명령어 부분에 5Byte의 jmp 명령어를 박게 됩니다. 하지만 이러한 방법은 32bit 응용 프로그램에서나 100% 확율로 통하는 방법이고 64bit 응용 프로그램에서는 단순 jmp 명령어가지곤 후킹이 실패할 수 있습니다.
먼저 jmp 명령어 설명을 보겠습니다.
jmp 명령어는 jmp 0x12345678 이라는 명령어가 있을 때 Opcode는 0x12345678 이 그대로 박히는게 아닌 현재 EIP 기준 offset 값이 들어갑니다. 이는 32bit이던, 64bit이던 똑같이 적용됩니다. 그리고 명령어 사이즈도 현재 64bit 모드라고해서 offset을 담을 수 있는 공간이 64bit로 커지는것도 아닙니다.
그러면 Opcode E9 다음에 4Byte의 offset이 들어가는데 4Byte의 offset으로 최대 접근할 수 있는 범위는 ±2GB 입니다.
하지만 64bit 응용프로그램이 메모리에 올라왔을 때 로드된 모듈들을 봐보겠습니다.
AsmTest.exe 0000000140000000-000000014000D000 [8260] AsmTest.exe
ntdll.dll 00000000771E0000-0000000077389000 [8260] AsmTest.exe
kernel32.dll 0000000076F60000-000000007707F000 [8260] AsmTest.exe
KernelBase.dll 000007FEFD790000-000007FEFD7FB000 [8260] AsmTest.exe
msvcr110d.dll 000007FEE9690000-000007FEE9878000 [8260] AsmTest.exe
Visual Studio 에서 바로 가져온 것 입니다.
만약 KernelBase.dll 에 있는 함수를 후킹해서 AsmTest.exe 모듈로 제어를 옮기는 경우
대략 0x7F GB라는 주소차이를 뛰어야합니다. 4GB 따위는 훌쩍넘기 때문에 더이상 jmp xxxxxxxx 같은 명령어 따위론 64bit 에선 후킹을 할수가 없습니다. 그러면 어떠한 방법을 사용할 수 있을까요? 방법은 다양하게 있겠죠.
가장쉽게는 아래와 같은 방법을 사용하면 됩니다.
0x00000000`00000000 jmp qword ptr [0x00000000`00000006]
0x00000000`00000006 내 모듈 주소 넣어놓음..! ( 8Byte )
이렇게 총 14Byte를 후킹함으로서 손쉽게 해결이 됩니다.
아무튼 후킹은 이렇게 되지만 추가적인 테스트에서 이상한 점을 발견했습니다.
특정 주소로 점프할 수 있는 방법으로는 call, ret, jmp 가 있습니다.
먼저 전 이렇게 하면 모듈단위 이동이 다 되는걸로 생각했습니다.
mov rcx, 0x1111222233334444
call rcx
* intel instruction 문서를 찾아봤을 때 유일하게 imm64 값을 사용할 수 있는 명령어는 mov 뿐입니다.
먼저 위 명령어가 수행되게되면 0x1111222233334444 로 뛰게 될까요?
아래는 위 명령어를 수행했을 때 뜨는 창입니다.
(0x1111222233334444 는 없는 주소이므로 테스트할 땐 실제 명령어가 있는 주소(0x000007FEEA7EC670)로 바꿔서 해봤습니다.)
으잉? call이 정상적으로 수행되지 못하고 엑세스 위반 에러가 발생했습니다.
cpu가 call 명령어 처리하면서 어떠한 조건을 만족하지 못했기 때문에 이런 에러가 발생했겠군요.
그러면 call 설명을 보겠습니다.
일단 call 함으로서 CS(코드세그먼트)는 바뀌지 않으므로 near call 이라는 것은 알 수 있습니다. 또한 위 코드는 범용레지스터를 사용해 call을 수행하고 있으므로 absolute라는 것을 알 수 있습니다. 그 near call absolute에 대한 설명입니다. 읽어봐도 위 코드를 수행할 때 call 명령어에서 에러가 나는 이유는 전혀 알 수 없네요.
그러면 실제 작동에 대한 슈도코드 부분을 보겠습니다.
이 슈도코드를 봐도 역시나 에러나는 원인에 대한 힌트는 알 수 없네요... 왜? 에러가 난걸까요?;;;;;;;;;;
그리고 [RSP]에 점프할 주소를 넣고 ret로 4GB가 넘는 모듈 단위 이동을 시도해보았지만 call과 똑같은 현상이 벌어집니다.
왜 이러는걸까요?
하지만 jmp는 된다는 것입니다. 주소가 얼마나 차이나든 상관없이 jmp 됩니다....
결론
32bit offset으로 커버될 수 없는 주소공간으로 제어를 이동하게 될 시
call, ret 를 사용하면 안되고 jmp를 사용해야한다.
왜 call, ret를 쓰면 뻑이나는지 이유아시는 분은 저에게 가르침을...
'My Study > Programming&Theory' 카테고리의 다른 글
CPU 캐시의 원리 (1) | 2013.08.11 |
---|---|
CPU의 파이프라인 실행의 원리 (8) | 2013.08.08 |
ZwProtectVirtualMemory 의 악몽 2 (0) | 2013.07.25 |
Windows Hang Dump 분석 (3) | 2013.07.24 |
ZwProtectVirtualMemory 의 악몽 (11) | 2013.07.04 |