C语言如何知自身函数的实际地址与大小

点击蓝字

1eb86d19aabfba125bf102b9536ddb1d.png

关注我们

因公众号更改推送规则,请点“在看”并加“星标”第一时间获取精彩技术分享

来源于网络,侵删

事情的起因大概是这样……

在很久很久以前,我最早用的是MASM(Win32ASM)写程序,从平台兼容性、开发效率和规范等方面考虑,后来我义无反顾地转成了C、C++和C++++……

主要是为了保持队形,但那真的是4个+

当然也很顺手,不用像汇编那样,x86是一份代码,x64又大相径庭,再换成ARM那就没法玩了。至于运行效率,更多的可以考虑开发效率、可维护性等等,至少我是没有蜜汁自信来认为自己所写的汇编源码全文会比当今C编译器所产生的更高效。如果MASM问我,我会说爱过。

很快,根据王境泽大师的真香定理,C语言在代码注入上让我一度考虑重操旧业(与MASM混编)。接下来我们先一起教科书式地复习Windows下传统远程代码注入的套路,如果学不会也没关系,你只需要记住这一套连招,1433223、1433223、1433223、1433223。

1. 打开远端进程(OpenProcess/NtOpenProcess),权限至少包含以下步骤所需。

2. 以可读可写可执行的页面保护属性(PAGE_EXECUTE_READWRITE)为远端进程分配新的内存区域(VirtualAllocEx/NtAllocateVirtualMemory),需要PROCESS_VM_OPERATION权限。

3. 将代码写入远端进程(WriteProcessMemory/NtWriteVirtualMemory),需要PROCESS_VM_WRITE权限。

4. 刷新指令缓存(FlushInstructionCache/NtFlushInstructionCache),严谨起见,根据MSDN描述,即使是刚申请下来用于执行代码的内存,也需要刷新。

5.以刚申请用于执行代码的内存为入口点,创建远端线程(CreateRemoteThread/RtlCreateUserThread/NtCreateThreadEx)并执行。后续可以自行发挥,比如同步和获取退出码

6. 最后记得收尾。有借有还,再借不难。

期间我们会遇到两个问题。

第一,远程注入的代码中,如果调用了外部函数,很可能导致违规访问、任意代码执行等问题,因为在远端进程中调用的外部函数很可能无法被正确地寻址,甚至都不存在于远端进程中。所以我们应该要保证所注入的代码没有直接的外部函数调用,而是自己寻址。

严谨的方式是从PEB中的模块链和这些模块的导出表出发,去一步步找需要的东西,这个不是我们现在要讨论的。只要对PEB、导出表结构理解到位便不复杂,顺带一提,DLL有按序号和名称两种导出方式,导出为重定向(Forwarder Name)的情况最好也纳入考虑,可以参考ReactOS的实现(GetProcAddress -> LdrGetProcedureAddress -> LdrpGetProcedureAddress -> LdrpSnapThunk)。

第二,在第3步,如果注入本地函数,我们需要知道本地函数的实际地址与大小,才能正确地写入到远端进程中。MASM中我们可以放飞自我地定义标签:

ExampleProc_Start:
ExampleProc PROC a, b
MOV     EAX, a
ADD     EAX, b
RET
ExampleProc ENDP
ExampleProc_End:

"offset ExampleProc_Start"是过程"ExampleProc"的起始地址,"offset ExampleProc_End"是其结束地址,二者之差则是其大小。

在C语言中,我们还能如此顺风顺水地获得自身定义函数的实际地址和大小吗?

我们先看地址。C语言无法定义函数外标签,函数内标签从使用到访问处处受限,我们好像只剩函数名可以用。但函数名表达式未必等同于函数的实际地址,它可能会指向JMP stub,再由该JMP stub跳转到函数实际地址:

ea6cde6154a7df46ed4abee4761dd0a8.png

有的甚至经由JMP stub跳转两次才到实际地址。这样的JMP stub自有用处,比如增量链接,或者兼容没有"__declspec(dllimport)"修饰的外部函数声明等等。关闭增量链接后,本地函数的函数名作表达式,应该就是正确的内存地址了。

至于函数体大小,"sizeof"操作符是用不了的。我看到网上有如下的写法:

int ExampleProc() {
return 0;
}void ExampleProcEnd() {}

然后用"ExampleProcEnd"减去"ExampleProc"。我用的是VS2019,关闭了MSVC编译器和链接器的各种优化选项、SDL和增量链接等操作,结果是从来没对过。

话说,编译器本身好像也没有责任去安排函数体的内存顺序,倒是恨不得给它们折叠一下(COMDAT)或者内联一下。

综上,关闭增量链接后,函数体实际地址有解,虽然算不上理想的解决方案;至于函数体大小,仍然是C语言本身不可及的地方。当然也可以硬编码将大小写大一些,足够覆盖该函数体,只要访问没越界应该还是可以正常工作的,我想寻求更为严谨的方式。

似乎此时我们不得不借助汇编语言。MSVC中,x86支持内联汇编,参考MSDN: Inline assembly in MSVC;x64不支持内联,但可以外置汇编源码在工程中,独立生成目标文件与其它源文件生成的目标文件链接,参考MSDN: MASM for x64 (ml64.exe)一文中"Add an assembler-language file to a Visual Studio C++ project"章节。用汇编来写要注入的函数(过程),此时可知其实际地址与大小,再供C语言中引用。

可是,这样x86写一份,x64写一份,说不准ARM也可以来凑个热闹,这不又回到了以前嘛,说好的兔子不吃……哦不,好马不吃回头草!是的,此时我们需要借助汇编,但未必非得以这样的方式。

我记得MSVC编译器可以产生相应的汇编输出,如果我们能利用它,那么或许可以保持注入函数一样使用C来编写了。下面举个栗子:

我们有C语言函数"ExampleProc",是我们要拿来注入的函数:

int __stdcall ExampleProc(int a, int b) {
return a + b;
}

我们先只考虑Release构建,对应的x64汇编输出大概是这个亚子,x86在PROC的定义上大同小异:

ExampleProc PROC
lea eax, DWORD PTR [rcx+rdx]
ret 0
ExampleProc ENDP

然后让我们朵蜜一下它,给它头上戴个帽子,还送一双鞋:

它就长这样了:

E4C_Start_ExampleProc:
ExampleProc PROC
lea eax, DWORD PTR [rcx+rdx]
ret 0
ExampleProc ENDP
E4C_End_ExampleProc:

当然,"E4C_Start"之类的前缀自拟,后面用的时候对得上号就行。最后把我们需要的定义为常量,并且公开给其它模块使用:

PUBLIC E4C_Addr_ExampleProc
PUBLIC E4C_Size_ExampleProcCONST   SEGMENT
E4C_Addr_ExampleProc DQ OFFSET E4C_Start_ExampleProc
E4C_Size_ExampleProc DQ OFFSET E4C_End_ExampleProc - OFFSET E4C_Start_ExampleProc
CONST   ENDS

x86就把DQ改为DD,对应到C语言中的size_t。汇编输出改好了,我们调用ml.exe或者ml64.exe把它重新汇编,生成新的目标文件并替换之前MSVC编译器生成的,此时它多了"E4C_Addr_ExampleProc"和

"E4C_Size_ExampleProc"两个导出符号,分别是"ExampleProc"函数(过程)的实际地址和计以字节的大小。

在同一工程的其它C语言源文件中,添加以下外部符号定义,即可引用它们了:

typedef int(__stdcall* PEXAMPLEPROC)(int a, int b);extern PEXAMPLEPROC E4C_Addr_ExampleProc;
extern size_t E4C_Size_ExampleProc;

地址的定义可以直接void*,像上面这样声明成相同的proto就可以调用它,当然是多此一举(同一工程下的直接用函数名调用就好了)。大小这里用的是size_t,总之和之前在汇编输出里定义的一致就行。

但整个实现过程并不顺利,因为MSVC编译器似乎管汇编输出称为"Assembler Listing"(汇编列表),与源文件有不小差距。实际上我们之所以争取保持使用C语言写注入的函数就是因为需要它实现的逻辑相对复杂,而不像上述例子那样仅仅实现a+b这样的小儿科,从而生成的汇编输出也复杂。

这时,把MSVC生成的汇编输出直接丢给MASM汇编那可就凉了,会产生很多错误,尤其是语法错误。比如x86汇编输出缺少"assume fs:nothing",导致fs访问出错;x64输出了"FLAT:"这样只在x86中可用的标识;"$LN??"这样的标签被后向引用、重定义等;用到的浮点数被定义成以"__real@"开头的公开符号,与其它模块产生冲突等等。

最后,我将这套流程写成了PowerShell脚本(Export4C),可集成在VS生成过程中。关于之前提到MSVC汇编输出中的错误,已有一些相应修复措施,但我们仍应保持注入函数尽可能简单,没有外部函数调用,最好自己在一个独立的C源文件中凉快。

下面看一下效果:

在工程中,将要注入的函数独立放在一个源文件"InjectProc.c"中,这个函数定义为"LPTHREAD_START_ROUTINE",会给调用它的老铁返回666:

/*** @warning Disable features like JMC (Just My Code) , Security Cookie, SDL and RTC to prevent external procedure calls generated.* @see See also the C/C++ settings for this file*/#include <Windows.h>DWORD WINAPI InjectProc(LPVOID lpThreadParameter) {UNREFERENCED_PARAMETER(lpThreadParameter);
return 666;
}

打开"InjectProc.c"文件属性,【C/C++】设置里关闭JMC (Just My Code) 、Security Cookie、SDL和RTC,它们会在prologue和epilogue部分产生外部函数调用,注入到远程那就凉了。

在"Source.c"中我们把它注入指定进程里,例子中用的是当前进程PID:

/*Example3:Inject and execute code in a process.
*/#include <Windows.h>
#include <stdio.h>// Export4C externs
EXTERN_C LPTHREAD_START_ROUTINE E4C_Addr_InjectProc;
EXTERN_C SIZE_T E4C_Size_InjectProc;int main() {DWORD   dwPID, dwLastError, dwResult;HANDLE  hProc, hRemoteThread;LPVOID  lpRemoteMem;dwLastError = ERROR_SUCCESS;
// Use current process ID in this example.
// Architecture (x64/x86) of target process should be the same with this example.dwPID = GetCurrentProcessId();hProc = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | SYNCHRONIZE, FALSE, dwPID);
if (hProc == INVALID_HANDLE_VALUE) {dwLastError = ERROR_INVALID_HANDLE;
goto Label_3;}
// Allocate memory for the processlpRemoteMem = VirtualAllocEx(hProc, NULL, E4C_Size_InjectProc, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!lpRemoteMem) {dwLastError = GetLastError();printf_s("Allocate memory failed with error: %d", dwLastError);
goto Label_2;}
// Write code to the memory and flush cache
if (!WriteProcessMemory(hProc, lpRemoteMem, E4C_Addr_InjectProc, E4C_Size_InjectProc, NULL)) {dwLastError = GetLastError();printf_s("Write code failed with error: %d", dwLastError);
goto Label_1;}FlushInstructionCache(hProc, lpRemoteMem, E4C_Size_InjectProc);
// Create remote thread and wait for the resulthRemoteThread = CreateRemoteThread(hProc, NULL, 0, lpRemoteMem, NULL, 0, NULL);
if (!hRemoteThread) {dwLastError = GetLastError();printf_s("Create remote thread failed with error: %d", dwLastError);
goto Label_1;}WaitForSingleObject(hRemoteThread, INFINITE);
if (!GetExitCodeThread(hRemoteThread, &dwResult)) {dwLastError = GetLastError();printf_s("Get exit code of remote thread failed with error: %d", dwLastError);
goto Label_0;}
// "InjectProc" function returns "666"printf_s("Remote thread returns: %d", dwResult);
// Cleanup and exit
Label_0:CloseHandle(hRemoteThread);
Label_1:VirtualFreeEx(hProc, lpRemoteMem, 0, MEM_RELEASE);
Label_2:CloseHandle(hProc);
Label_3:
return dwLastError;
}

打开项目属性,【C/C++】 - 【Output Files】,设置“Assembler Output”为"Assembly Only"(/FA)或者"Assembly With Source Code"(/FAs)。切换到【Advanced】,关闭“Whole Program Optimization”,至此,在默认情况下,汇编输出会生成于中间目录$(IntDir)。

切换到【Build Events】 - 【Pre-Link Event】,命令行输入“PowerShell -ExecutionPolicy RemoteSigned -File $(SolutionDir)Export4C\Export4C.ps1 -IntDir $(IntDir) -Source InjectProc.c -NoLogo”以在相应的时候调用Export4C。

注意Export4C路径、IntDir(包含了汇编输出和原目标文件输出的中间目录)、Source(要Export4C公开其中函数实际地址和大小的源文件)要配置正确。

至此,项目可以正常生成和运行了,预期会输出“Remote thread returns: 666”。Export4C会为输入的源文件(InjectProc.c)增加其中所有函数实际地址和大小的公开符号,函数实际地址命名为"E4C_Addr_[函数名]",可定义为LPVOID或该函数实际原型;函数体大小命名为"E4C_Size_[函数名]",数据类型为SIZE_T。如同例子中"Source.c"对"E4C_Addr_InjectProc"和"E4C_Size_InjectProc"的引用。

如上,我们可以在C语言中引用源码自身中函数的实际地址与大小,全程不需要手动写一句汇编指令,并且编译器、链接器可以按原本的方式工作,支持x86和x64目标,基本不需要强行关闭什么优化。

上述例子中关闭了全程序优化,是因为它将影响汇编输出的位置,手动处理一下也可以保持它开启的状态,只要Export4C的IntDir参数目录能找到它和目标文件即可。

关闭"InjectProc.c"文件的JMC (Just My Code) 、Security Cookie、SDL和RTC功能是远程代码注入的业务需要,防止产生外部函数调用。JMC与RTC启用时是会导致一些汇编输出里的语法错误,但在Export4C脚本已对其进行处理。

这个脚本已开源于GitHub:KNSoft/Export4C,包含VS解决方案和3个示例工程(Example1 ~ Example3, VS 2019),脚本本身由PowerShell所写,在"Export4C"工程(目录)内,可以用"Get-Help" cmdlet获取它参数的详细说明。

有空的时候会继续维护和更新,增加更多的功能,导出更多有用的符号供使用。最早写这个脚本的时候,分析汇编输出时我在正则中放飞自我,结果速度有些感人,现在尽量老老实实匹配字符串去了。目前只是用它满足个人需求,进而分享,所以不算完善,暂时也只能确保在我个人的使用场景下(如VS 2019,业务需求等)符合预期。

目前只是在个人所写的小程序中使用,通过Export4C获取线程函数"RProc_LoadProcAddr_InjectThread"的实际地址与大小,供注入远端进程使用。而在"RProc_LoadProcAddr_InjectThread"线程函数中,可以根据传入的DLL模块名与函数名,得到该函数在远端进程的地址。

先遍历PEB的已加载模块链表,查找指定的DLL。如果找到,则遍历其导出表,获取指定的函数实际地址。若未被加载,则调用ntdll!LdrLoadDll尝试加载进来,再进行操作。

这或许是个新的思路,借助MASM的特性,来扩展C的功能。MASM中的第一个M是Macro而不是Microsoft,它对宏的支持可谓功能强大,并且现在ML64

是支持宏的。

d9efed89dcbd862f38f7c9630297eb0e.gif

如果你年满18周岁以上,又觉得学【C语言】太难?想尝试其他编程语言,那么我推荐你学Python,现有价值499元Python零基础课程限时免费领取,限10个名额!
▲扫描二维码-免费领取

戳“阅读原文”我们一起进步

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/337140.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

xtext_使用Xtext为Eclipse和IntelliJ开发DSL

xtext在这篇文章中&#xff0c;我们将看到如何开发一种简单的语言。 我们的目标是&#xff1a; 语言的解析器 IntelliJ的编辑器 。 编辑器应具有语法突出显示&#xff0c;验证和自动完成功能 我们还将免费提供Eclipse和Web编辑器的编辑器 &#xff0c;但请包含您的兴奋之处&…

sed 插入多行_Linux三剑客之sed

sed命令用法小记版本&#xff1a;CentOS7▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼好久没更新文章了&#xff0c;项目的事情太多&#xff0c;总得给自己的懒惰找个借口&#xff0c;哈哈~话不多说进入正题创建测试数据[aliscaspark02 a]$ cat data#test the sedThis is the header l…

分享一些超级炫酷的C语言小技巧

点击蓝字关注我们因公众号更改推送规则&#xff0c;请点“在看”并加“星标”第一时间获取精彩技术分享来源于网络&#xff0c;侵删C语言常常让人觉得它所能表达的东西非常有限。它不具有类似第一级函数和模式匹配这样的高级功能。但是C非常简单&#xff0c;并且仍然有一些非常…

C++编程新手容易犯的 10 种编程错误

点击蓝字关注我们因公众号更改推送规则&#xff0c;请点“在看”并加“星标”第一时间获取精彩技术分享来源于网络&#xff0c;侵删公司每年都会有一定的人员流动&#xff0c;相应地也会招一些应届生补充进来&#xff0c;指导应届生已经成为老员工的必修课了。平日里会我们会经…

Linux上C语言程序编译过程详解

点击蓝字关注我们因公众号更改推送规则&#xff0c;请点“在看”并加“星标”第一时间获取精彩技术分享来源于网络&#xff0c;侵删本文将介绍如何将高层的C/C语言编写的程序转换成为处理器能够执行的二进制代码的过程&#xff0c;包括四个步骤&#xff1a;预处理&#xff08;P…

C语言,动图展示十大经典排序算法

点击蓝字关注我们因公众号更改推送规则&#xff0c;请点“在看”并加“星标”第一时间获取精彩技术分享来源于网络&#xff0c;侵删以前也零零碎碎发过一些排序算法&#xff0c;但排版都不太好&#xff0c;又重新整理一次&#xff0c;排序算法是数据结构的重要部分&#xff0c;…

java ee各类组件_在Java EE组件中使用骆驼路线

java ee各类组件从现在开始我一直在与Camel合作&#xff0c;我真的很喜欢它的简单性。 在Java EE之上使用它一直是一个挑战&#xff0c;我最近发表了一篇关于如何做到这一点的演讲&#xff0c;而在Java EE中引导Camel的不同方法实际上建议使用WildFly-Camel Subsystem 。 在正在…

5gnr帧结构特点有哪些_真空离子束刻蚀设备的结构特点有哪些

离子束刻蚀设备有立式、卧式两种结构。通常情况下&#xff0c;该设备以卧式的结构居多&#xff0c;因为采用卧式结构的话&#xff0c;离子源发出的离子束为水平喷射方向&#xff0c;大部分的刻蚀溅射物将落在真空室的底部&#xff0c;可在一定程度上将溅射材料的重新沉积减少。…

老了就不能编程?大龄程序员在线“辟谣”:15 年后,我变得更好了

点击蓝字关注我们因公众号更改推送规则&#xff0c;请点“在看”并加“星标”第一时间获取精彩技术分享来源于网络&#xff0c;侵删几年前&#xff0c;Quora 上的一个提问在程序员圈内掀起热议&#xff1a;“随着年龄的增长&#xff0c;人们会对编程失去兴趣吗&#xff1f;预计…

wildfly管理控制台_WildFly管理控制台已更新–请求反馈

wildfly管理控制台红帽JBoss企业应用程序平台&#xff08;EAP&#xff09;和WildFly具有共生关系 。 简而言之&#xff0c;红帽JBoss企业应用程序平台&#xff08;JBoss EAP&#xff09;保留了WildFly社区项目&#xff08;以前称为JBoss Application Server&#xff09;的所有创…

性能测试中脚本怎么写_脚本在流程中的性能影响

性能测试中脚本怎么写我们经常看到人们出于各种目的而使用脚本&#xff08;例如&#xff0c;在服务任务&#xff0c;执行侦听器等中&#xff09;。 使用脚本和Java逻辑通常很有意义&#xff1a; 它不需要打包到jar中并放在classpath上 它使流程定义更易于理解&#xff1a;无需…

超炫酷的C语言技巧

点击蓝字关注我们因公众号更改推送规则&#xff0c;请点“在看”并加“星标”第一时间获取精彩技术分享来源于网络&#xff0c;侵删C语言常常让人觉得它所能表达的东西非常有限。它不具有类似第一级函数和模式匹配这样的高级功能。但是C非常简单&#xff0c;并且仍然有一些非常…

java8默认垃圾收集器_Java 8中最快的垃圾收集器是什么?

java8默认垃圾收集器OpenJDK 8具有几种垃圾收集器算法&#xff0c;例如Parallel GC &#xff0c; CMS和G1 。 哪一个最快&#xff1f; 如果默认GC从Java 8中的并行GC更改为Java 9中的G1&#xff08;当前建议&#xff09;&#xff0c;将会发生什么&#xff1f; 让我们对其进行基…

u盘启动 联想一体机_联想Y430pAT-ISE(H)U盘安装Win7系统教程

最近听到有人在问联想Y430pAT-ISE(H)笔记本安装WIN 7系统的方法&#xff0c;联想Y430pAT-ISE(H)笔记本从发行到现在也快5年了&#xff0c;不过有人在问该电脑安装系统就说明有人还在使用&#xff0c;关于联想Y430pAT-ISE(H)安装Win 7系统的方法有很多&#xff0c;不过大多过时了…

收藏|C语言常用的一些转换工具函数!

点击蓝字关注我们因公众号更改推送规则&#xff0c;请点“在看”并加“星标”第一时间获取精彩技术分享来源于网络&#xff0c;侵删1、字符串转十六进制代码实现&#xff1a;void StrToHex(char *pbDest, char *pbSrc, int nLen) {char h1,h2;char s1,s2;int i;for (i0; i<n…

apache camel_学习Apache Camel –实时索引推文

apache camel在大多数软件开发项目中&#xff0c;有一点需要使应用程序开始与其他应用程序或第三方组件通信。 无论是发送电子邮件通知&#xff0c;调用外部api&#xff0c;写入文件还是将数据从一个地方迁移到另一个地方&#xff0c;您都可以推出自己的解决方案或利用现有框架…

让你不再害怕指针——C指针详解(经典,非常详细)

点击蓝字关注我们因公众号更改推送规则&#xff0c;请点“在看”并加“星标”第一时间获取精彩技术分享来源于网络&#xff0c;侵删前言:复杂类型说明要了解指针,多多少少会出现一些比较复杂的类型,所以我先介绍一下如何完全理解一个复杂类型,要理解复杂类型其实很简单,一个类型…

C语言实现可写入文件的账号密码登录系统

点击蓝字关注我们因公众号更改推送规则&#xff0c;请点“在看”并加“星标”第一时间获取精彩技术分享来源于网络&#xff0c;侵删账号登录系统在很多系统设计时都时必不可少的&#xff0c;今天这个登录系统功能较全&#xff0c;可以注册&#xff0c;登录&#xff0c;找回密码…

一文搞懂 | Linux 内核的 4 大 IO 调度算法

点击蓝字关注我们因公众号更改推送规则&#xff0c;请点“在看”并加“星标”第一时间获取精彩技术分享来源于网络&#xff0c;侵删Linux 内核包含4个IO调度器&#xff1a;Noop IO schedulerAnticipatory IO schedulerDeadline IO scheduler CFQ IO scheduler。anticipatory, 预…

众神进入瓦尔哈拉_一时冲动:“通往瓦尔哈拉之路的冒险”

众神进入瓦尔哈拉通过所有有关Java 9和Project Jigsaw的讨论&#xff0c;我们不应忽视Java的另一重大变化。 希望在第10版或第11版中&#xff0c; Valhalla项目能够实现并介绍价值类型和专业化。 那么&#xff0c;这是什么一回事&#xff0c;项目进展如何&#xff0c;面临什么…