让我们看一下CreateRemoteThread的声明:
HANDLE CreateRemoteThread(
HANDLE hProcess, // handle to process to create thread in
LPSECURITY_ATTRIBUTES lpThreadAttributes, // pointer to security
// attributes
DWORD dwStackSize, // initial thread stack size, in bytes
LPTHREAD_START_ROUTINE lpStartAddress, // pointer to thread
// function
LPVOID lpParameter, // argument for new thread
DWORD dwCreationFlags, // creation flags
LPDWORD lpThreadId // pointer to returned thread identifier
);
switch( expression ) {
case constant1: statement1; goto END;
case constant2: statement2; goto END;
case constant3: statement2; goto END;
}
switch( expression ) {
case constant4: statement4; goto END;
case constant5: statement5; goto END;
case constant6: statement6; goto END;
}
END:
(参考附录E)
如果你不按照这些游戏规则玩的话,你注定会使目的进程挂掉!记住,不要妄想远程进程中的任何数据会和你本地进程中的数据存放在相同内存地址!(参看附录F)
(原话如此:You will almost certainly crash the target process if you don’t play by those rules. Just remember: Don’t assume anything in the target process is at the same address as it is in your process.)
GetWindowTextRemote(A/W)
所有取得远程edit中文本的工作都被封装进这个函数:GetWindowTextRemote(A/W):
int GetWindowTextRemoteA( HANDLE hProcess, HWND hWnd, LPSTR lpString );
int GetWindowTextRemoteW( HANDLE hProcess, HWND hWnd, LPWSTR lpString );
// This function marks the memory address after ThreadFunc.
// int cbCodeSize = (PBYTE) AfterThreadFunc – (PBYTE) ThreadFunc.
static void AfterThreadFunc (void)
{
}
然而,如果一个函数被声明为static,就算使用增量连接,也不会被替换为JMP指令。这就是为什么我在规则#4中说把ThreadFunc和AfterThreadFunc声明为static或禁止增量连接的原因了。(关于增量连接的其他方面请参看Matt Pietrek写的“Remove Fatty Deposits from Your Applications Using Our 32-bit Liposuction Tools”)
我能确定你们对上面的叙述(“栈探针将温和地按照存储函数局部变量所需要的空间大小来移动”)感到奇怪。这些编译选项(他们的描述!)有时候真的让人很恼火,特别是当你想真的了解它们是怎么工作的时候。打个比方,如果一个函数需要12kb的空间来存放局部变量,栈上的内存是这样“分配”的
sub esp, 0x1000 ; 先“分配”4 Kb
test [esp], eax ; touches memory in order to commit a
; new page (if not already committed)
sub esp, 0x1000 ; “分配”第二个 4 Kb
test [esp], eax ; …
sub esp, 0x1000
test [esp], eax
注意栈指针是如何以4Kb为单位移动的,更重要的是每移动一步后使用test对栈底的处理(more importantly, how the bottom of the stack is “touched” after each step)。这可以确保了在“分配”下一个页之前,包含栈底的页已经被提交。
E) 为什么我要把多于3个case分支的swith分割开来呢?
同样,用例子来说明会简单些:
int Dummy( int arg1 )
{
int ret =0;
switch( arg1 ) {
case 1: ret = 1; break;
case 2: ret = 2; break;
case 3: ret = 3; break;
case 4: ret = 0xA0B0; break;
}
return ret;
}
将会被编译为类似下面的代码:
Address OpCode/Params Decoded instruction
————————————————–
; arg1 -> ECX
:00401000 8B4C2404 mov ecx, dword ptr [esp+04]
:00401004 33C0 xor eax, eax ; EAX = 0
:00401006 49 dec ecx ; ECX —
:00401007 83F903 cmp ecx, 00000003
:0040100A 771E ja 0040102A
; JMP to one of the addresses in table ***
; note that ECX contains the offset
:0040100C FF248D2C104000 jmp dword ptr [4*ecx+0040102C]
:00401013 B801000000 mov eax, 00000001 ; case 1: eax = 1;
:00401018 C3 ret
:00401019 B802000000 mov eax, 00000002 ; case 2: eax = 2;
:0040101E C3 ret
:0040101F B803000000 mov eax, 00000003 ; case 3: eax = 3;
:00401024 C3 ret
:00401025 B8B0A00000 mov eax, 0000A0B0 ; case 4: eax = 0xA0B0;
:0040102A C3 ret
:0040102B 90 nop
; 地址表 ***
:0040102C 13104000 DWORD 00401013 ; jump to case 1
:00401030 19104000 DWORD 00401019 ; jump to case 2
:00401034 1F104000 DWORD 0040101F ; jump to case 3
:00401038 25104000 DWORD 00401025 ; jump to case 4