传统的远程进程控制方式有利用HOOK技术注入DLL,和利用远线程在目标进程中建立新的执行线程的方式.
远线程不被win9x所支持,而hook技术会对目标进程性能造成一定的影响.并具可以通过枚举消息链的方式发现.
本文给出一种动态修改目标进程代码,注入DLL到目标进程的方法,效率高,不需要额外线程.缺点是使用难度大于上面二种办法,并且修改目标代码的方法,受到编译器的影响.使用不同的编译器时,需要根据编译器调整动态代码修改方式.
动态修改目标进程代码,使其加载自已的DLL的思路如下:
1. 得到目标进程句柄.
2. 在目标进程中分配一块可执行可读写的内存.
3. 写入加载DLL的代码到分配出来的内存中.
4. 修正动态写入的代码中的地址(LoadLibrary地址,DLL名字地址).
5. 修改目标进程本身的代码,使其执行到被修改代码时,跳到加载DLL的代码处.
6. 加载完DLL后,修正目标进程原来的代码,并跳回去继续执行.
这个过程中需要注意的地方是. 对寄存器的保护,堆栈的平衡.
其中第5步需要修改一块目标进程一定会执行的代码,消息循环是个不错的地方.这里以记事本为例.
打开ollydbg,在TranslateMessage函数下断点,中断后如下:
010029FC |. /75 14 |jnz short notepad.01002A12
010029FE |. |8D45 E0 |lea eax,dword ptr ss:[ebp-20]
01002A01 |. |50 |push eax ; /pMsg
01002A02 |. |FF15 98120001 |call dword ptr ds:[<&USER32.Translat>; /TranslateMessage
01002A08 |. |8D45 E0 |lea eax,dword ptr ss:[ebp-20]
01002A0B |. |50 |push eax ; /pMsg
01002A0C |. |FF15 94120001 |call dword ptr ds:[<&USER32.Dispatch>; /DispatchMessageW
01002A12 |> /56 push esi
01002A13 |. 56 |push esi
01002A14 |. 8D45 E0 |lea eax,dword ptr ss:[ebp-20]
01002A17 |. 56 |push esi
01002A18 |. 50 |push eax
01002A19 |. FFD7 |call edi
0x01002A02处是个不错的地方,可以动态修改它. 改成
mov eax, 加载dll的代码地址
jmp eax
需要注意的是,此程序基址在不同的系统上有可能不一样,可以从PE头中得到映像基址.所以此处的0x1002A02也许在你的机器上是另一个地址,请使用OD自行确定.(我是winxp sp2)
找到修改点后,然后就是分配可执行可读写内存,写入下面一段代码进去
pusha
//eax中是LoadLibrary函数地址,暂时为0xffffffff
//由程序动态改成真实函数地址
mov eax, 0xffffffff
//要加载的DLL的名字地址,暂时为0xffffffff
//由程序动态改成真实地址
mov ebx, 0xffffffff
push ebx
//调用LoadLibrary,加载自已的DLL
//这样DLL就注入到目标进程了
call eax
popa
//恢复 0x1002A02处的代码,并跳回去执行
//选执eax寄存器是因为0x1002A02后面的代码没有直接使用到eax
mov eax, CODE_OFFSET
mov dword ptr [eax], 0x129815FF
mov dword ptr [eax+4], 0x458D0100
jmp eax
下面的代码是在加载DLL后,修正目标进程原有的代码
mov eax, CODE_OFFSET
mov dword ptr [eax], 0x129815FF
mov dword ptr [eax+4], 0x458D0100
0x129815FF 0x458D0100 即为原有代码(字节顺序是倒过来的, 0x129815FF 倒过来即是 FF159812, 0x458D0100倒过来即是00018D45,请注意和下面代码对比
01002A02 |. |FF15 98120001 |call dword ptr ds:[<&USER32.Translat>; /TranslateMessage
01002A08 |. |8D45 E0 |lea eax,dword ptr ss:[ebp-20]
其它基本上就是编码了,例子如下,测试时,请自行编写一个 Test.dll ,放在记事本同一个目录下,不同的编译器,不同的系统请自行修正一些细节:(我是使用的Release模式,Debug模式代码会不同的)
#include <iostream>
#include <windows.h>
#include <psapi.h>
using std::cout;
using std::cin;
using std::endl;
//记事本的映象基址,不同的系统可能不一样
//这个值可以从PE头中读取
#define NOTEPAD_IMAGE_BASE 0x1000000;
#define CODE_OFFSET 0x1002A02;
//用来测试的DLL名
const char *dllname = "Test.dll";
void loaddll()
{
__asm
{
pusha
//eax中是LoadLibrary函数地址,暂时为0xffffffff
//由程序动态改成真实函数地址
mov eax, 0xffffffff
//要加载的DLL的名字地址,暂时为0xffffffff
//由程序动态改成真实地址
mov ebx, 0xffffffff
push ebx
//调用LoadLibrary,加载自已的DLL
//这样DLL就注入到目标进程了
call eax
popa
//恢复 0x1002A02处的代码,并跳回去执行
//选执eax寄存器是因为0x1002A02后面的代码没有直接使用到eax
mov eax, CODE_OFFSET
mov dword ptr [eax], 0x129815FF
mov dword ptr [eax+4], 0x458D0100
jmp eax
}
}
#pragma pack(push) //保存对齐状态
#pragma pack(1) //设定为1字节对齐
struct JmpCode
{
byte op;
char* address;
WORD jmp;
};
#pragma pack(pop)
int main()
{
//找记事本主窗口句柄
HWND wnd = FindWindow(NULL, "无标题 - 记事本");
if (!IsWindow(wnd))
{
cout << "记事窗口没找到" << std::endl;
return 1;
}
//读取进程ID,和进程句柄
DWORD pid;
HANDLE ph;
GetWindowThreadProcessId(wnd, &pid);
ph = OpenProcess(PROCESS_ALL_ACCESS, false, pid);
//在记事本进程中分配一页可读写可执行的虚拟内存
void *address = VirtualAllocEx(ph, NULL, 4096, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
//写代码的地址(写在字符串后面)
char *code = (char*)address + strlen(dllname) + 1;
if (NULL == address)
{
cout << "分配远端内存失败" << std::endl;
return 1;
}
DWORD ws;
//把代码写到内存页中
WriteProcessMemory(ph, code, &loaddll, 4000, &ws);
//把DLL名写到内存页中
WriteProcessMemory(ph, address, dllname, strlen(dllname), &ws);
//把loadlibrary的地址写到eax 0xfffffff处,把0xffffffff替换为真正的地址
//code+9为eax 0xffffffff中0xffffffff处的偏移,通过反汇编工具查看+ 9是为了跳过
//编译器给loadll函数生成的框架
HMODULE module = LoadLibrary("kernel32.dll");
FARPROC pro = GetProcAddress(module, "LoadLibraryA");
WriteProcessMemory(ph, code + 9, &pro, 4, &ws);
//把dllname的地址写到ebx 0xffffffff处,把dllname作为参数
WriteProcessMemory(ph, code + 14, &address, 4, &ws);
//修改记事本消息循环处的代码.
JmpCode jmp;
jmp.op = 0xB8;
jmp.address = code + 6; //这段代码是 mov eax, 地址
jmp.jmp = 0xE0FF; //jmp eax
address = (void*)CODE_OFFSET;
//让代码段可读写
VirtualProtectEx(ph, address, 4096, PAGE_EXECUTE_READWRITE, &ws);
//改写目标处的代码,让其跳到自已的代码处执行
WriteProcessMemory(ph, address, &jmp, 8, &ws);
return 0;
}
运行,测试,OK! Test.dll被正确加载,目标进程现在是你的了,随便玩吧
远线程不被win9x所支持,而hook技术会对目标进程性能造成一定的影响.并具可以通过枚举消息链的方式发现.
本文给出一种动态修改目标进程代码,注入DLL到目标进程的方法,效率高,不需要额外线程.缺点是使用难度大于上面二种办法,并且修改目标代码的方法,受到编译器的影响.使用不同的编译器时,需要根据编译器调整动态代码修改方式.
动态修改目标进程代码,使其加载自已的DLL的思路如下:
1. 得到目标进程句柄.
2. 在目标进程中分配一块可执行可读写的内存.
3. 写入加载DLL的代码到分配出来的内存中.
4. 修正动态写入的代码中的地址(LoadLibrary地址,DLL名字地址).
5. 修改目标进程本身的代码,使其执行到被修改代码时,跳到加载DLL的代码处.
6. 加载完DLL后,修正目标进程原来的代码,并跳回去继续执行.
这个过程中需要注意的地方是. 对寄存器的保护,堆栈的平衡.
其中第5步需要修改一块目标进程一定会执行的代码,消息循环是个不错的地方.这里以记事本为例.
打开ollydbg,在TranslateMessage函数下断点,中断后如下:
010029FC |. /75 14 |jnz short notepad.01002A12
010029FE |. |8D45 E0 |lea eax,dword ptr ss:[ebp-20]
01002A01 |. |50 |push eax ; /pMsg
01002A02 |. |FF15 98120001 |call dword ptr ds:[<&USER32.Translat>; /TranslateMessage
01002A08 |. |8D45 E0 |lea eax,dword ptr ss:[ebp-20]
01002A0B |. |50 |push eax ; /pMsg
01002A0C |. |FF15 94120001 |call dword ptr ds:[<&USER32.Dispatch>; /DispatchMessageW
01002A12 |> /56 push esi
01002A13 |. 56 |push esi
01002A14 |. 8D45 E0 |lea eax,dword ptr ss:[ebp-20]
01002A17 |. 56 |push esi
01002A18 |. 50 |push eax
01002A19 |. FFD7 |call edi
0x01002A02处是个不错的地方,可以动态修改它. 改成
mov eax, 加载dll的代码地址
jmp eax
需要注意的是,此程序基址在不同的系统上有可能不一样,可以从PE头中得到映像基址.所以此处的0x1002A02也许在你的机器上是另一个地址,请使用OD自行确定.(我是winxp sp2)
找到修改点后,然后就是分配可执行可读写内存,写入下面一段代码进去
pusha
//eax中是LoadLibrary函数地址,暂时为0xffffffff
//由程序动态改成真实函数地址
mov eax, 0xffffffff
//要加载的DLL的名字地址,暂时为0xffffffff
//由程序动态改成真实地址
mov ebx, 0xffffffff
push ebx
//调用LoadLibrary,加载自已的DLL
//这样DLL就注入到目标进程了
call eax
popa
//恢复 0x1002A02处的代码,并跳回去执行
//选执eax寄存器是因为0x1002A02后面的代码没有直接使用到eax
mov eax, CODE_OFFSET
mov dword ptr [eax], 0x129815FF
mov dword ptr [eax+4], 0x458D0100
jmp eax
下面的代码是在加载DLL后,修正目标进程原有的代码
mov eax, CODE_OFFSET
mov dword ptr [eax], 0x129815FF
mov dword ptr [eax+4], 0x458D0100
0x129815FF 0x458D0100 即为原有代码(字节顺序是倒过来的, 0x129815FF 倒过来即是 FF159812, 0x458D0100倒过来即是00018D45,请注意和下面代码对比
01002A02 |. |FF15 98120001 |call dword ptr ds:[<&USER32.Translat>; /TranslateMessage
01002A08 |. |8D45 E0 |lea eax,dword ptr ss:[ebp-20]
其它基本上就是编码了,例子如下,测试时,请自行编写一个 Test.dll ,放在记事本同一个目录下,不同的编译器,不同的系统请自行修正一些细节:(我是使用的Release模式,Debug模式代码会不同的)
#include <iostream>
#include <windows.h>
#include <psapi.h>
using std::cout;
using std::cin;
using std::endl;
//记事本的映象基址,不同的系统可能不一样
//这个值可以从PE头中读取
#define NOTEPAD_IMAGE_BASE 0x1000000;
#define CODE_OFFSET 0x1002A02;
//用来测试的DLL名
const char *dllname = "Test.dll";
void loaddll()
{
__asm
{
pusha
//eax中是LoadLibrary函数地址,暂时为0xffffffff
//由程序动态改成真实函数地址
mov eax, 0xffffffff
//要加载的DLL的名字地址,暂时为0xffffffff
//由程序动态改成真实地址
mov ebx, 0xffffffff
push ebx
//调用LoadLibrary,加载自已的DLL
//这样DLL就注入到目标进程了
call eax
popa
//恢复 0x1002A02处的代码,并跳回去执行
//选执eax寄存器是因为0x1002A02后面的代码没有直接使用到eax
mov eax, CODE_OFFSET
mov dword ptr [eax], 0x129815FF
mov dword ptr [eax+4], 0x458D0100
jmp eax
}
}
#pragma pack(push) //保存对齐状态
#pragma pack(1) //设定为1字节对齐
struct JmpCode
{
byte op;
char* address;
WORD jmp;
};
#pragma pack(pop)
int main()
{
//找记事本主窗口句柄
HWND wnd = FindWindow(NULL, "无标题 - 记事本");
if (!IsWindow(wnd))
{
cout << "记事窗口没找到" << std::endl;
return 1;
}
//读取进程ID,和进程句柄
DWORD pid;
HANDLE ph;
GetWindowThreadProcessId(wnd, &pid);
ph = OpenProcess(PROCESS_ALL_ACCESS, false, pid);
//在记事本进程中分配一页可读写可执行的虚拟内存
void *address = VirtualAllocEx(ph, NULL, 4096, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
//写代码的地址(写在字符串后面)
char *code = (char*)address + strlen(dllname) + 1;
if (NULL == address)
{
cout << "分配远端内存失败" << std::endl;
return 1;
}
DWORD ws;
//把代码写到内存页中
WriteProcessMemory(ph, code, &loaddll, 4000, &ws);
//把DLL名写到内存页中
WriteProcessMemory(ph, address, dllname, strlen(dllname), &ws);
//把loadlibrary的地址写到eax 0xfffffff处,把0xffffffff替换为真正的地址
//code+9为eax 0xffffffff中0xffffffff处的偏移,通过反汇编工具查看+ 9是为了跳过
//编译器给loadll函数生成的框架
HMODULE module = LoadLibrary("kernel32.dll");
FARPROC pro = GetProcAddress(module, "LoadLibraryA");
WriteProcessMemory(ph, code + 9, &pro, 4, &ws);
//把dllname的地址写到ebx 0xffffffff处,把dllname作为参数
WriteProcessMemory(ph, code + 14, &address, 4, &ws);
//修改记事本消息循环处的代码.
JmpCode jmp;
jmp.op = 0xB8;
jmp.address = code + 6; //这段代码是 mov eax, 地址
jmp.jmp = 0xE0FF; //jmp eax
address = (void*)CODE_OFFSET;
//让代码段可读写
VirtualProtectEx(ph, address, 4096, PAGE_EXECUTE_READWRITE, &ws);
//改写目标处的代码,让其跳到自已的代码处执行
WriteProcessMemory(ph, address, &jmp, 8, &ws);
return 0;
}
运行,测试,OK! Test.dll被正确加载,目标进程现在是你的了,随便玩吧