首页
社区
课程
招聘
[原创]让C语言源码可知自身函数的实际地址与大小
发表于: 2020-7-25 20:42 7348

[原创]让C语言源码可知自身函数的实际地址与大小

2020-7-25 20:42
7348

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


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


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


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


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

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

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

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

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

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

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


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

第一,远程注入的代码中,如果调用了外部函数,很可能导致违规访问、任意代码执行等问题,因为在远端进程中调用的外部函数很可能无法被正确地寻址,甚至都不存在于远端进程中。所以我们应该要保证所注入的代码没有直接的外部函数调用,而是自己寻址。严谨的方式是从PEB中的模块链和这些模块的导出表出发,去一步步找需要的东西,这个不是我们现在要讨论的。只要对PEB、导出表结构理解到位便不复杂,顺带一提,DLL有按序号和名称两种导出方式,导出为重定向(Forwarder Name)的情况最好也纳入考虑,可以参考ReactOS的实现(GetProcAddress -> LdrGetProcedureAddress -> LdrpGetProcedureAddress -> LdrpSnapThunk)。

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

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

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


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

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

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

然后用"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",是我们要拿来注入的函数:

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

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

它就长这样了:

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

x86就把DQ改为DD,对应到C语言中的size_t。汇编输出改好了,我们调用ml.exe或者ml64.exe把它重新汇编,生成新的目标文件并替换之前MSVC编译器生成的,此时它多了"E4C_Addr_ExampleProc"和"E4C_Size_ExampleProc"两个导出符号,分别是"ExampleProc"函数(过程)的实际地址和计以字节的大小。

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


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2020-7-25 21:22 被Ratin编辑 ,原因: 订正错别字
收藏
免费 5
支持
分享
最新回复 (8)
雪    币: 6314
活跃值: (952)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
2
牛的
2020-7-26 13:13
0
雪    币: 161
活跃值: (528)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
编译器本身好像也没有责任去安排函数体的内存顺序 -> 编译器是没有但链接器有 https://docs.microsoft.com/en-us/cpp/build/reference/order-put-functions-in-order?view=vs-2019,可以指定函数的内存布局顺序,应该可以满足你的需求
2020-8-5 23:52
1
雪    币: 1787
活跃值: (2055)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
4
liowmark 编译器本身好像也没有责任去安排函数体的内存顺序 -> 编译器是没有但链接器有 https://docs.microsoft.com/en-us/cpp/build/reference/order ...

当初走到这一步我有注意到这个链接器选项,初步尝试后没能如愿。当然因为我只是浅尝辄止,可能步骤上有所疏忽遗漏(貌似在VS项目设置里设置此值有点随意,没严格遵循MSDN来,所以没起作用),我这段时间会继续跟进此选项,感谢提醒!

最后于 2020-8-6 00:39 被Ratin编辑 ,原因:
2020-8-6 00:37
0
雪    币: 99
活跃值: (2378)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
5
函数的编译当然是有顺序的。单个文件,默认情况下就是按照源码的顺序排列的。只有在LTO优化时,才可能按照某些函数调用的频率打乱个别函数的顺序。楼主遇到不对的问题应该出在debug版本上,debug版的函数入口多了一个跳转,release版就不会有问题。
2020-8-6 10:33
0
雪    币: 2229
活跃值: (3758)
能力值: ( LV6,RANK:81 )
在线值:
发帖
回帖
粉丝
6
我是先把 c/c++ 优化关掉,&func 拿到函数地址,然后往后面查找函数结束的字节
2020-8-6 10:45
0
雪    币: 256
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
7
C++++?c#?
2020-8-10 11:41
0
雪    币: 396
活跃值: (2613)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
我遇到过一个函数编译出来的内存是不连续的情况,这种怎么调整编译选项?
2020-8-17 09:41
0
雪    币: 123
活跃值: (316)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
saloyun 我遇到过一个函数编译出来的内存是不连续的情况,这种怎么调整编译选项?
用map文件
2020-8-24 18:44
0
游客
登录 | 注册 方可回帖
返回
//