WINDOWS黑客基础(3):注入代码
有使用过外挂的朋友应该知道,我们在玩游戏的时候,有很多辅助功能给你使用,比如吃药,使用物品等功能,这个时候我们就是使用注入代码的技术,简单的来将就是我们让另外一个进程去执行我们想让它执行的代码,这中间的关键函数是CreateRemoteThread
HANDLE WINAPI CreateRemoteThread(
_In_ HANDLE hProcess,
_In_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
_In_ SIZE_T dwStackSize,
_In_ LPTHREAD_START_ROUTINE lpStartAddress,
_In_ LPVOID lpParameter,
_In_ DWORD dwCreationFlags,
_Out_ LPDWORD lpThreadId
);
CreateRemoteThread的参数跟CreateThread的参数差不多,多出来的hProcess是我们要对其操作的进程HANDLE,在早期的WINDOWS版本CreateThread确实是使用CreateRemoteThread实现的,就是把hProcess传入我们自己的进程HANDLE
CreateRemoteThread的功能就是在指定的进程创建一个线程,这个线程运行我们指定的函数,看起来很简单,但是有一个问题,就是虚拟内存导致的问题
大家都知道,在WINDOWS下是使用虚拟内存来进行数据管理的,每个进程都有自己独立的地址空间,假设进程A准备向进程B注入一段代码,他要让进程B执行他进程空间的函数InjectionCode(),这个函数在进程A的地址空间地址为0X3000
现在我们开始进行代码注入,利用CreateRemoteThread我们告诉B进程,请执行虚拟内存地址为0X3000的代码,这个时候B进程该干什么呢??B进程收到这个命令后,他很听话地创建了线程,然后乖乖得CALL了0X3000的内容,请注意,现在B进程CALL的是它自己内存空间内0X3000的代码而不是A进程的,那么现在B进程的0X3000是什么内容??没人知道,运气好的话说不定真的有段代码给你执行,运气不好你自己也不知道会发生什么事情,这就跟你进错了学生公寓一样,同样号码的房间,运气好是校花的房间,运气不好就是如花的房间
那么我们怎么才能让进程去执行我们对应的代码呢??我们只要在B进程内开辟一块内存,然后把我们的代码或者数据复制进去,再执行对应的代码就可以了,我们需要用到这几个函数:
LPVOID WINAPI VirtualAllocEx(
_In_ HANDLE hProcess,
_In_opt_ LPVOID lpAddress,
_In_ SIZE_T dwSize,
_In_ DWORD flAllocationType,
_In_ DWORD flProtect
); BOOL WINAPI WriteProcessMemory(
_In_ HANDLE hProcess,
_In_ LPVOID lpBaseAddress,
_In_ LPCVOID lpBuffer,
_In_ SIZE_T nSize,
_Out_ SIZE_T *lpNumberOfBytesWritten
);
这两个函数跟我们平常用的函数都差不多,只是多了个进程的选项,大概步骤如下图:
现在我们将实际操作一下:
下面是我们要注入的程序,在这之前,我们最好把基地址固定掉,这样我们不会每次重新运行程序的时候函数的地址都会改变,在VS2008中,项目属性->链接器->高级,把随机基址和固定基址选择默认值
void PrintMsg(const char *msg)
{
printf("ThreadI D:%d Msg:%s\n",GetCurrentThreadId(),msg);
}
int main(void)
{
printf("%d\n",GetCurrentThreadId());
printf("Print Msg Function Address:%X\n",PrintMsg);
system("pause");
}
假设我们的PrintMsg的地址是0x401000,现在我们需要往这个进程里面注入一段代码,让她可以自动调用PrintMsg这个函数
static const char *msg = "INJECTION CODE SUCESS\n";
static const unsigned int PARAM_SIZE = ;
static const unsigned int EXE_SIZE = ; void InjectionCode(const char *msg)
{
__asm
{
push eax
push msg
mov eax,0x401000
call eax //因为在我们要注入的进程中,PrintMsg位于0x401000这个位置
pop eax
pop eax
}
}
int main(void)
{
HANDLE hProcess = OpenProcessByProcessNmae("main.exe"); //这个函数在上一章 if (hProcess == INVALID_HANDLE_VALUE)
{
printf("error open process %d\n",GetLastError());
return ;
}
//一定要把函数的代码和msg写入要注入的进程,否则会发生位置错误(一般是崩溃)
LPVOID RemoteExe = VirtualAllocEx(hProcess,NULL,EXE_SIZE,MEM_COMMIT,PAGE_EXECUTE);
LPVOID RemoteParam = VirtualAllocEx(hProcess,NULL,PARAM_SIZE,MEM_COMMIT,PAGE_READWRITE); SIZE_T WriteCount = ;
int ret = ;
ret = WriteProcessMemory(hProcess,RemoteParam,msg,PARAM_SIZE,&WriteCount);
ret = WriteProcessMemory(hProcess,RemoteExe,InjectionCode,0x13,&WriteCount); HANDLE hThread = CreateRemoteThread(hProcess,NULL,,(LPTHREAD_START_ROUTINE)RemoteExe,RemoteParam,,NULL);
WaitForSingleObject(hThread,INFINITE);
}
运行上面的程序,我们就可以在另外一个进程中创建一个线程,并且这个线程将会输出该线程的ID以及我们要输出的消息
上面的程序还有几个要注意的:
1.资源竞争
由于是创建线程执行相应代码所以肯定会有资源竞争的问题,以后要写代码一定要注意,在本例中我忽略了这个问题
2. 关于代码的长度问题
在本例中,我们的代码长度是0X13,但是要知道,汇编代码的长度随便懂一下就可能更改,可能因为一个指令,也可能因为一个参数,所以我们需要时刻注意这点,关于代码长度怎么测量,我是看了反汇编的代码后计算的,这个方法比较准确,也可以大概估计下,只要能把代码复制完整就可以,超出也没关系,只要不超出申请的内存大小就可以
3.记得备份我们使用的寄存器
这个十分重要,一旦你更改了寄存器,如果没有后面没有恢复,可能会导致一系列错误,特别是ESP,EBP等重要的寄存器
3.注入代码多次调用系统DLL中的函数
<<WINDOWS核心编程>>里面说,系统的DLL都会加载到一个固定的地址,比如VirtualAllocEx,一般我们在A进程和B进程的时候,call或者jmp的地址都是一样的,所以一般我们如果调用的是系统函数,一般我们不需要担心,但是,昨天我想到了一个问题,比如我们进程A要命令进程B调用CreateToolhelp32Snapshot这个系统API,现在我们假设CreateToolhelp32Snapshot这个API在单独的TLHELP32.DLL里面(实际上这个在KERNEL32.DLL里面,所有进程都会加载这个DLL,所以不需要担心下面的问题,这个只是举例),操作系统在加载DLL的时候,会统一把这个API的地址映射到虚拟内存的0XFF40100的地址,按照我们原来的想法进程B会自己跑去call 0XFF40100这个地址。但问题在于,如果我们的进程根本就没有加载TLHELP32.DLL这个DLL,那么进程call 0XFF40100会怎么样??这个就要看你这个地方是什么代码了,有人说操作系统会帮你加载这个DLL,但我觉得是错的,因为操作系统要帮你加载的DLL都在PE头里面的导入表里面,要嘛就是我们要显示地去加载,否则操作系统不会知道我们的API在哪个DLL里面
4.注入代码多次调用我们自己编写的函数
比如我们有IntejectionCode,里面调用了IntejectionCode1,这个时候我们需要把IntejectionCode1也写入对方进程里面,不能只写入IntejectionCode,并且,我们需要更改IntejectionCode里面call IntejectionCode1跳转指令,让其跳转到正确的位置。总之,别人的地盘别人做主,对方进程想把代码放哪里就放哪里,我们无法管理(实际上virtualAllocEx是可以指定位置的,但是一般我们都尽量让操作系统去指定),我们只能入乡随俗,人家让我们去哪里调用我们就要去哪里调用,不然很容易导致进程崩溃
5.关于代码的基地址
在本例中PrintMsg的基地址是固定的,是我们人为去固定基地址的,我们在开发的时候很少人会去把基地址固定掉,所以在进程运行的时候,PrintMsg这个函数的地址是会改变的,当然我们也可以算出来这段代码在运行的时候会放在哪里,因为PrintMsg这段代码以二进制放在EXE文件的时候,也有一个文件偏移量,当操作系统把EXE文件加载进内存后,会根据基地址和文件偏移量,来算出PrintMsg在虚拟内存中的位置,所以我们只要能拿到进程运行时候的基地址,并且把EXE反汇编查看这段代码的文件偏移量,也能算出每次运行的时候PrintMsg的地址,虽然很麻烦,特别是反汇编找代码的那部分,但也没办法,这个在后面我会讲
WINDOWS黑客基础(3):注入代码的更多相关文章
- WINDOWS黑客基础(5):利用内存来进行获取计算结果
在前面的注入代码的章节中,我们利用了VirtualAllocEx来在对方的进程开辟了一块内存,并且将代码复制进对方进程的内存里面,从而执行那段内存的代码,但是这里有一个问题,就是代码不是执行在我们进程 ...
- WINDOWS黑客基础(4):查找进程运行的基址
从WINDOWS VISITA开始以后,windows已经开始支持随机基址的关系,也就是说以前我们的进程基址都是从0x40000开始的,如果一个变量在我们第一次运行的时候地址为0x50000,那么以后 ...
- WINDOWS黑客基础(6):查看文件里面的导入表
int main(void) { HANDLE hFile = CreateFile("D:\\Shipyard.exe", GENERIC_READ, FILE_SHARE_RE ...
- 【黑客基础】Windows PowerShell 脚本学习(上)
视频地址:[黑客基础]Windows PowerShell 脚本学习 2019.12.05 学习笔记 1.$PSVersionTable :查看PowerShell的版本信息. 2.PowerShel ...
- android黑科技系列——自动注入代码工具icodetools
一.前言 在前面已经介绍完了 自动给apk中注入日志代码工具icodetools原理了,在那里我们曾经说过其实离真正的可使用价值有点距离,本篇就对这个工具进行一些优化,让其真正意义上开始能工作量产.当 ...
- Windows用户模式下注入方式总结
注入技术在病毒木马.游戏.打补丁等编程中应用很广泛,学习该技术不仅能帮助理解Windows工作原理,还能对病毒木马技术手段有更加深刻的理解,下面我们了解下各种注入方式吧. 一.DLL注入 在注入技术中 ...
- PE病毒初探——向exe注入代码
PE文件其实就是Windows可执行文件,关于它的一些简要介绍摘自百度: PE文件被称为可移植的执行体是Portable Execute的全称,常见的EXE.DLL.OCX.SYS.COM都是PE文件 ...
- 带你开发一款给Apk中自己主动注入代码工具icodetools(开凿篇)
一.前言 从这篇開始咋们開始一个全新的静态方式逆向工具icodetools的实现过程.这个也是我自己第一次写的个人认为比較实用的小工具,特别是在静态方式逆向apk找关键点的时候.兴许会分为三篇来具体介 ...
- 【转】Windows socket基础
转自:http://blog.csdn.net/ithzhang/article/details/8448655 Windows socket 基础 Windows socket是一套在Windows ...
随机推荐
- ZOJ Problem Set - 3643 Keep Deleting
题目大意: 给出a和b串,a是b串的子串,如果b串有连续的a串,那么就将b串的a串删除,问删除多少次: 题目分析: 打比赛的时候没敲出来,后来想到用栈的思想去模拟就行,网上还有用KMP+栈去做的,没有 ...
- iis 管理员执行 aspnet_iis.exe
如果我们在注册iis的时候,出现上图的问题,我们需要在桌面上新建一个快捷方式 然后在目标处添上我们的命令. 比如:C:\Windows\Microsoft.NET\Framework\v4.0.303 ...
- 转载:LBP的初步理解
转自http://blog.csdn.net/ty101/article/details/8905394 本文的PDF版本,以及涉及到的所有文献和代码可以到下列地址下载: 1.PDF版本以及文献:ht ...
- JAVA基础总结一:
一.数据类型及变量名和注释 1. Java 中的数据类型分为两大类: 1) 原生数据类型 (Primitive Data Type) :8个(byte.char.short.int.long.floa ...
- MACOS,LINUX,IOS上可用的毫秒级精度时间获取
二话不说,先上代码 void outputCurrentTime(uint32_t seq,const char* type){ struct timeval t_curr; gettimeofday ...
- sgu546 Ternary Password
题目链接:http://acm.sgu.ru/problem.php?contest=0&problem=546 这题还好,1Y,考虑情况周全,就没问题了,还好提交之前把想到的情况都测试了一遍 ...
- exit()和_exit()函数
进程就好比人一样有其生命,我们通过fork()函数来创建一个进程,那么我们又是如何来中止进程呢. 进程退出 1.在Linux中任何让一个进程退出 进程退出表示进程即将结束.在Linux中进程退出分为了 ...
- LeetCode() Minimun Size Subarray Sum
别人的代码 class Solution { public: int minSubArrayLen(int s, vector<int>& nums) { int l, r, cu ...
- python 核心编程第5章(习题)
1.标准类型运算符. 写一段脚本,输入一个测验成绩,根据下面的标准,输出他的评分成绩(A-F). #coding:utf8 a = raw_input() a = int(a) if (a > ...
- 编码规范(二)之Code Templates的设置(转)
http://swiftlet.net/archives/1199 编码规范(二)之Code Templates的设置(转) 文件(Files)注释标签:/** * @Title: ${file_na ...