代码注入初探 ,c++实现CE 的代码注入功能
1、背景看论坛大佬的教程做了CS1.6的内存修改 ,其中用到了CE的代码注入功能,在CE里代码注入很好用,也很方便。我就想到,能否脱离CE直接自己实现CE的修改内存和代码注入功能,然后做一款CS的修改器。
其中修改内存的操作很简单,直接ReadProcessMemory、WriteProcessMemory就可以了,但是代码注入的功能似乎不太容易实现(对于我),接下来便记录了我的探索过程和最终实现的思路。
2、目标
本次实验的目标并不是直接对CS进行代码注入,而是自己写了一个MFC的累加器,代码注入这个程序,这样程序结构简单,也方便自己调试。
累加器程序长这个样子:
(图1)
这个程序的逻辑很简单,有一个数字,这个数字每秒增加2,然后把这个数字转化成字符串显示到界面上。
我们要做的就是把每秒增加2的这个逻辑更改掉,改成每秒 加4 再 加5 (别问为什么不直接加9,这里只是代表两条指令,任何合理的指令都行)
另外 此次注入的代码不使用远程线程的方式启动,而是用原代码逻辑中嵌入跳转的方式启动。
3、思路
首先用CE找到注入点(一定要找到基址) ,即我们要更改程序逻辑的地方,过程本文暂且不表。
获取进程句柄 -> 申请虚拟地址空间 -> 在注入点跳转到我们新申请的地址 -> 虚拟地址空间内写入我们想实现的逻辑 -> 跳回原位置
示意图:
(图2)
其中,A是 注入点
B是虚拟地址空间首地址
C是执行完注入代码需要返回的地址,至于为什么是这个地址,后面详说
D是在虚拟地址空间的注入代码的后面,此处添加一个跳转指令,跳转回原处
4.具体实现(示例代码中删除了所有与操作不直接有关的代码,如返回值判断等)
4.1 找到注入点,过程本文暂且不表。
找到的注入点机器码和汇编码:
(图3)
双击查看代码地址:
(图4)
其中 0x01360000是进程首地址(每次启动可能不相同)
0x1332 是此处代码的偏移 (每次都相同)
4.2获取进程句柄
//获取目标进程窗口句柄
hWindow = FindWindow("#32770", "HelperTest")->GetSafeHwnd();
//获取进程pid
GetWindowThreadProcessId(hWindow, &pid);
//获取进程句柄
hProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE,pid);
//获取进程首地址
int rv = mem_GetProcessAddr(pid,baseAddr);
4.3申请虚拟地址空间
由于我们注入的代码量很少,64字节足够了
LPVOID virAddr = NULL;
virAddr = VirtualAllocEx(hProcess,NULL,64,MEM_COMMIT, PAGE_EXECUTE_READWRITE);
4.4 注入点跳转到virAddr
此处有一个小问题。根据图3原代码 ,注入点的第一条指令是4个字节 ,而跳转指令需要5个字节(参照图3中的call 指令),如果直接把此处代码改写成跳转指令,势必会影响下一条指令的运行。
所以我们的跳转指令需要占用 4+5 = 9个字节 ,然后再把后面的call指令 复制到 虚拟地址空间中 。
此处应先把原来注入点的两条指令保存下来
list<DWORD> mlist;
DWORD OldAddr = 0x1332;
mlist.push_back(OldAddr);
BYTE OldCode = {0};
ULONG ReadddLen = sizeof(OldCode);
int rv = mem_ReadBlock(hProcess,baseAddr,mlist,9,OldCode,&ReadddLen);
关于跳转的偏移量,根据所查资料和实际调试,
有 跳转指令下一条指令的地址 + 偏移量= 目标地址
细化一下 跳转指令地址 + 跳转指令长度(5) + 偏移量 = 目标地址
又 跳转指令地址= 注入点地址
且 注入点地址=程序首地址 + 注入点代码的偏移
所以有 程序首地址 + 注入点代码的偏移 + 跳转指令长度 + 偏移量= 目标地址
偏移量=目标地址-(程序首地址 + 注入点代码的偏移 + 跳转指令长度)
带入 偏移量=virAddr -( 程序首地址 + 0x1332 +5 ) ,其中程序首地址可由相关函数求得
所以跳转的机器码为 E9偏移量(4个字节) ,把这5个字节写到注入点即可,后面4个字节用nop(0x90)补齐。
具体代码
//把原来的指令改成 跳转指令 跳转地址为virAddr
DWORD jmpOffset = (DWORD)virAddr - (baseAddr +0x1332 +5);
BYTE ByteOffset;
IntTo4Bytes(jmpOffset,ByteOffset);
BYTE JmpToNewCode = {0};
JmpToNewCode = 0xE9; //jmp指令
memcpy(JmpToNewCode+1,ByteOffset,4);
memset(JmpToNewCode+5,0x90,4); //用nop补全
ULONG WrittenLen = sizeof(JmpToNewCode);
rv = mem_WriteBlock(hProcess,baseAddr,mlist,9,JmpToNewCode,&WrittenLen);
4.5将自己的新逻辑写到 virAddr里
为了简便我们的逻辑就简单点。
类比图3我们写两条指令
add dword ptr,05
add dword ptr,04
那么如何将 汇编代码改成机器码呢
第一种,类比图3中的机器码,直接改数字,但是此种方法不具有普遍性。
第二种,用其他工具把汇编转化成机器码,把机器码复制过来就好了
第三种,也是我用的方法
写一个函数
void InjFunc(){
_asm{
add dword ptr,05
add dword ptr,04
}
}
那么 InjFunc 往后的第4个字节就是 _asm里的汇编码(注意一定要是release编译,debug会产生许多额外代码)
前三个字节是push ebp
和 mov ebp,esp
我们并不需要
//虚拟地址空间内写入新的机器码
BYTE NewCode;
memcpy(NewCode,(void*)((char*)InjFunc+3),8);
4.6 解决4.4遗留的问题,复原call指令
按照4.4的方法计算好偏移量,把E8 偏移量追加到NewCode 中
memcpy(NewCode+8,0xE8,1); //call指令
memcpy(NewCode+9,bnewOffset,4); //偏移量
4.7 跳转回图2中的C地址
A至C共9个字节,前5个字节是我们在4.4中写入的跳转指令 ,后 4个字节全是nop 所以跳转到 任意nop地址或者nop+ 的地址都可以
跳转方法参照4.4
5、相关功能代码
//读取内存数据
int mem_ReadBlock(IN HANDLE hProcess,IN DWORD baseAddr,IN list<DWORD> &offSetList,IN DWORD readLen, OUT BYTE *OutBuffer,IN OUT ULONG* BufferLen){
if(*BufferLen <readLen){
return HELPER_ERROR_BUFFER_TOO_SMALL;
}
if(offSetList.size()> 1){
DWORD newBaseAddr,rv,ReadddLen;
DWORD CurBaseAddr = baseAddr + offSetList.front();
offSetList.pop_front();
rv = ReadProcessMemory(hProcess,(LPVOID)CurBaseAddr,(LPVOID)&newBaseAddr,4,&ReadddLen);
if(rv == FALSE){
return GetLastError();
}
rv = mem_ReadBlock(hProcess,newBaseAddr,offSetList,readLen,(BYTE*)OutBuffer,BufferLen);
return rv;
}
else if(offSetList.size() == 1){
DWORD CurBaseAddr = baseAddr + offSetList.front();
int rv = ReadProcessMemory(hProcess,(LPVOID)CurBaseAddr,OutBuffer,readLen,BufferLen);
if(rv == FALSE){
return GetLastError();
}
}
else{
returnHELPER_ERROR_OFFSET_INVALID;
}
return HELPER_ERROR_SUCCESS;
}
//读取内存数据
int mem_WriteBlock(IN HANDLE hProcess,IN DWORD baseAddr,IN list<DWORD>& offSetList,IN DWORD WriteLen, IN BYTE *InBuffer,OUT ULONG* WrittenLen){
if(offSetList.size()> 1){
DWORD newBaseAddr,rv,ReadddLen;
DWORD CurBaseAddr = baseAddr + offSetList.front();
offSetList.pop_front();
rv = ReadProcessMemory(hProcess,(LPVOID)CurBaseAddr,(LPVOID)&newBaseAddr,4,&ReadddLen);
if(rv == FALSE){
return GetLastError();
}
rv = mem_WriteBlock(hProcess,newBaseAddr,offSetList,WriteLen,(BYTE*)InBuffer,WrittenLen);
return rv;
}
else if(offSetList.size() == 1){
DWORD CurBaseAddr = baseAddr + offSetList.front();
int rv = WriteProcessMemory(hProcess,(LPVOID)CurBaseAddr,InBuffer,WriteLen,WrittenLen);
if(rv == FALSE){
return GetLastError();
}
}
else{
returnHELPER_ERROR_OFFSET_INVALID;
}
return HELPER_ERROR_SUCCESS;
}
//获取目标进程首地址
intmem_GetProcessAddr(DWORD dwPID, DWORD& baseAddr)
{
HANDLE hModuleSnap = INVALID_HANDLE_VALUE;
MODULEENTRY32 me32;
// 在目标进程中获取所有进程的snapshot
hModuleSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID);
if (hModuleSnap == INVALID_HANDLE_VALUE)
{
return HELPER_ERROR_NOTFIND_PROADDR;
}
// 设置MODULEENTRY32数据结构大小字段
me32.dwSize = sizeof(MODULEENTRY32);
//检索第一个模块的信息,不成功则返回
if (!Module32First(hModuleSnap, &me32))
{
CloseHandle(hModuleSnap); // 清除句柄对象
return HELPER_ERROR_NOTFIND_PROADDR;
}
// 从me32中得到基址
baseAddr = (DWORD)me32.modBaseAddr;
// 别忘了最后清除模块句柄对象
CloseHandle(hModuleSnap);
returnHELPER_ERROR_SUCCESS;
}
其中读写内存的函数用ReadProcessMemory 和WriteProcessMemory 就好,封装成这样,主要是为了通过多级指针来查找内存。
mem_GetProcessAddr是 网上搜的某大神写的源码
6、结语
通过做这中类型的东西,感觉自己对汇编和内存的理解更深了一步,继续努力吧。
第一次发帖,思路不是很清晰,有问题的地方希望大佬们多加指正。
另外我还有个疑问,还有没有更优雅的方法 把汇编码转化成机器码 写到内存里呢? 这里面每一个字我都认识,但是放在一起就不认识了,关于内存和指针的也太难了。 汇编到机器码还可以用汇编引擎 iTruth 发表于 2020-2-19 10:20
楼主,我按照你的步骤去做了,代码确实也修改成功了但为什么改完程序一会就闪退了呢?
这时我测试程序的源码
...
不是 JMP 00760000吗,你OD看看在哪个地方崩的 感谢分享 ggxxuser 发表于 2019-11-26 18:02
这里面每一个字我都认识,但是放在一起就不认识了,关于内存和指针的也太难了。
其实咱们都一样,我们太难了!!!! 苏紫方璇 发表于 2019-11-26 17:25
汇编到机器码还可以用汇编引擎
大佬能说详细一点吗? 查了一下,结果似乎都是反汇编,是先编译再用工具反汇编? ggxxuser 发表于 2019-11-26 18:02
这里面每一个字我都认识,但是放在一起就不认识了,关于内存和指针的也太难了。
加油!谁都可以的:lol 我的盘由我做主 发表于 2019-11-26 19:16
其实咱们都一样,我们太难了!!!!
加油,你也很厉害:lol tony666 发表于 2019-11-26 20:41
大佬能说详细一点吗? 查了一下,结果似乎都是反汇编,是先编译再用工具反汇编?
可以查一下x64dbg用的那两个,asmjit和xedparse 苏紫方璇 发表于 2019-11-26 20:52
可以查一下x64dbg用的那两个,asmjit和xedparse
多谢指点,我去学习学习:lol