吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 8792|回复: 22
收起左侧

[C&C++ 原创] 代码注入初探 ,c++实现CE 的代码注入功能

  [复制链接]
tony666 发表于 2019-11-26 17:03
1、背景
  看论坛大佬的教程做了CS1.6的内存修改 ,其中用到了CE的代码注入功能,在CE里代码注入很好用,也很方便。我就想到,能否脱离CE直接自己实现CE的修改内存和代码注入功能,然后做一款CS的修改器。
  其中修改内存的操作很简单,直接ReadProcessMemory、WriteProcessMemory就可以了,但是代码注入的功能似乎不太容易实现(对于我),接下来便记录了我的探索过程和最终实现的思路。


2、目标
本次实验的目标并不是直接对CS进行代码注入,而是自己写了一个MFC的累加器,代码注入这个程序,这样程序结构简单,也方便自己调试。
累加器程序长这个样子:

图1 累加器(被注入程序)界面

图1  累加器(被注入程序)界面
    (图1)

这个程序的逻辑很简单,有一个数字,这个数字每秒增加2,然后把这个数字转化成字符串显示到界面上。
我们要做的就是把每秒增加2的这个逻辑更改掉,改成每秒 加4 再 加5 (别问为什么不直接加9,这里只是代表两条指令,任何合理的指令都行)
另外 此次注入的代码不使用远程线程的方式启动,而是用原代码逻辑中嵌入跳转的方式启动。



3、思路


首先用CE找到注入点(一定要找到基址) ,即我们要更改程序逻辑的地方,过程本文暂且不表。
获取进程句柄   ->          申请虚拟地址空间    ->      在注入点跳转到我们新申请的地址         ->            虚拟地址空间内写入我们想实现的逻辑      ->    跳回原位置  

示意图:

图2 示意图

图2  示意图
(图2)
其中,A是 注入点
          B是虚拟地址空间首地址
          C是执行完注入代码需要返回的地址,至于为什么是这个地址,后面详说
          D是在虚拟地址空间的注入代码的后面  ,此处添加一个跳转指令,跳转回原处

4.具体实现(示例代码中删除了所有  与操作不直接有关的代码,如返回值判断等)

4.1 找到注入点,过程本文暂且不表。


找到的注入点机器码和汇编码:
   

图3 注入点代码

图3 注入点代码
    (图3)

双击查看代码地址:

图4 注入点地址

图4 注入点地址
   (图4)
其中 0x01360000是进程首地址(每次启动可能不相同)
0x1332 是此处代码的偏移 (每次都相同)


4.2获取进程句柄


[C++] 纯文本查看 复制代码
	//获取目标进程窗口句柄
	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字节足够了
[C++] 纯文本查看 复制代码
	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指令 复制到 虚拟地址空间中 。

此处应先把原来注入点的两条指令保存下来
[C++] 纯文本查看 复制代码
	list<DWORD> mlist;
	DWORD OldAddr = 0x1332;
	mlist.push_back(OldAddr);
	BYTE OldCode[10] = {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)补齐。


具体代码
[C++] 纯文本查看 复制代码
	//把原来的指令改成 跳转指令 跳转地址为virAddr
	DWORD jmpOffset = (DWORD)virAddr - (baseAddr +0x1332 +5);
	BYTE ByteOffset[4];
	IntTo4Bytes(jmpOffset,ByteOffset);
	BYTE JmpToNewCode[9] = {0};
	JmpToNewCode[0] = 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[esi+0x78],05
                add dword ptr[esi+0x78],04

那么如何将 汇编代码改成机器码呢
第一种,类比图3中的机器码,直接改数字,但是此种方法不具有普遍性。
第二种,用其他工具把汇编转化成机器码,把机器码复制过来就好了
第三种,也是我用的方法
写一个函数
void InjFunc(){  
        _asm{
                add dword ptr[esi+0x78],05
                add dword ptr[esi+0x78],04
        }
}

那么 InjFunc 往后的第4个字节就是 _asm里的汇编码  (注意一定要是release编译,debug会产生许多额外代码)
前三个字节是  push        ebp
和                    mov         ebp,esp
我们并不需要
[C++] 纯文本查看 复制代码
	//虚拟地址空间内写入新的机器码
	BYTE NewCode[64];
	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、相关功能代码
[C++] 纯文本查看 复制代码
//读取内存数据
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{
		return  HELPER_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{
		return  HELPER_ERROR_OFFSET_INVALID;
	}
	return HELPER_ERROR_SUCCESS;
}




//获取目标进程首地址
int  mem_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);
	return  HELPER_ERROR_SUCCESS;
}


其中读写内存的函数用ReadProcessMemory 和WriteProcessMemory 就好,封装成这样,主要是为了通过多级指针来查找内存。
mem_GetProcessAddr  是 网上搜的某大神写的源码

6、结语


通过做这中类型的东西,感觉自己对汇编和内存的理解更深了一步,继续努力吧。
第一次发帖,思路不是很清晰,有问题的地方希望大佬们多加指正。
另外我还有个疑问,还有没有更优雅的方法 把汇编码转化成机器码 写到内存里呢?

免费评分

参与人数 8威望 +1 吾爱币 +13 热心值 +7 收起 理由
越行勤 + 1 用心讨论,共获提升!
iTruth + 1 + 1 用心讨论,共获提升!
yimo_chenai + 1 + 1 我很赞同!
笙若 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
sniper9527 + 1 + 1 谢谢@Thanks!
5omggx + 1 + 1 热心回复!
苏紫方璇 + 1 + 6 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
wangxp + 1 + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

ggxxuser 发表于 2019-11-26 18:02
这里面每一个字我都认识,但是放在一起就不认识了,关于内存和指针的也太难了。

免费评分

参与人数 1吾爱币 +1 热心值 +1 收起 理由
我的盘由我做主 + 1 + 1 用心讨论,共获提升!

查看全部评分

苏紫方璇 发表于 2019-11-26 17:25
 楼主| tony666 发表于 2020-2-21 10:37
iTruth 发表于 2020-2-19 10:20
楼主,我按照你的步骤去做了,代码确实也修改成功了但为什么改完程序一会就闪退了呢?
这时我测试程序的源码
...

不是 JMP 00760000吗,你OD看看在哪个地方崩的
sniper9527 发表于 2019-11-26 19:16
感谢分享
我的盘由我做主 发表于 2019-11-26 19:16
ggxxuser 发表于 2019-11-26 18:02
这里面每一个字我都认识,但是放在一起就不认识了,关于内存和指针的也太难了。

其实咱们都一样,我们太难了!!!!
 楼主| tony666 发表于 2019-11-26 20:41
苏紫方璇 发表于 2019-11-26 17:25
汇编到机器码还可以用汇编引擎

大佬能说详细一点吗? 查了一下,结果似乎都是反汇编,是先编译再用工具反汇编?

点评

可以查一下x64dbg用的那两个,asmjit和xedparse  详情 回复 发表于 2019-11-26 20:52
 楼主| tony666 发表于 2019-11-26 20:42
ggxxuser 发表于 2019-11-26 18:02
这里面每一个字我都认识,但是放在一起就不认识了,关于内存和指针的也太难了。

加油!谁都可以的
 楼主| tony666 发表于 2019-11-26 20:43
我的盘由我做主 发表于 2019-11-26 19:16
其实咱们都一样,我们太难了!!!!

加油,你也很厉害
苏紫方璇 发表于 2019-11-26 20:52
tony666 发表于 2019-11-26 20:41
大佬能说详细一点吗? 查了一下,结果似乎都是反汇编,是先编译再用工具反汇编?

可以查一下x64dbg用的那两个,asmjit和xedparse
 楼主| tony666 发表于 2019-11-26 20:53
苏紫方璇 发表于 2019-11-26 20:52
可以查一下x64dbg用的那两个,asmjit和xedparse

多谢指点,我去学习学习
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-26 03:33

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表