尝试一下在拥有C语言源码的情况下,将编译出来的函数shellcode扣出来,可以直接利用。
从C源码中提取Shellcode的尝试
前提
这里说的是Windows程序。Linux程序可能会简单不少。
提取的函数,如果要轻松提取的话,可以选择满足以下条件:
- 没有POSIX库函数调用(Windows上的printf那种会有一大堆的内敛库函数代码
- 没有Windows库函数调用(可能可行,但太麻烦了)
- 没有子函数调用
Demo——RC4加密程序
这里选择的是用一个RC4的脚本来尝试。
初始版本
decode.c:
1 |
|
现在编译,在Linux下执行:
1 | gcc -pie -m32 decode.c -o decode |
获得了PIE的ELF32位程序。
然后用capa去解析,发现确确实实的能搜到rc4_init
和rc4_crypt
这两个函数,只不过地址有点奇怪,PIE程序在IDA里面加载出来直接是从0开始的,而Capa分析的默认基址时0x20000000
anyway,项目稍微报了些错,不过这是因为没有传入动态report的问题,以及基址设置有问题。这里后续再解决。
version2
首先为了方便,把rc4_init
和rc4_crypt
两个函数整合在一个函数里面。然后要把编译时的一些保护机制给关掉。
1 | #include <stdio.h> |
其中,新增函数rc4
,此函数只需要传入key和data就好了(以及分别的长度)。
编译源码
现在只保留rc4
,编译一次。(Linux上)
1 | gcc -pie -m32 -fno-pic -fno-stack-protector decode.c -o decode |
去除编译器为我们生成的__x86.get_pc_thunk.ax - 知乎 (zhihu.com)
执行TTP定位项目
(整体项目有点bug没完全跑完,但是部分可用)
然后走一下项目,看看找到的结果:
1 | {'T1027': {'function': {33559053}, 'basic block': set()}} |
第一个是capa分析的结果,看了一下指向的就是rc4函数的头。
提取shellcode
现在尝试提取shellcode。
先注意一下传参规则,看一下汇编:
1 | .text:00001536 add esp, 10h |
简单来说就是从右到左传参,同时用栈传递参数。
然后可能还需要注意一下栈的平衡。
用IDA脚本直接提取shellcode:
1 | import ida_funcs |
然后读取出来,得到Python bytes对象,然后再美化成可以塞入C语言里的列表:
1 | m = ",".join([hex(i) for i in a]) |
装填shellcode
现在是抠出来重组的结果:
1 | #include <stdio.h> |
编译并执行装入shellcode的程序(Linux)
编译:
1 | gcc -m32 -z execstack test.c -o test |
执行:
1 | ❯ ./test |
shellcode被正确执行了。
当然这样做不是很严谨,应该尝试使用mprotect+malloc来执行shellcode。关闭栈保护有点安全隐患。
编译并执行装入shellcode的程序(Windows)
如果要用cuckoo动态分析的话,需要编译成Windows的版本。
MSVC应该可以做到
我的mingw似乎没准备32位程序的编译器,-m32
选项会有bug
偷懒了最后,选择用Visual Studio。并且对代码进行了少许的调整,选择使用VirtualProtect修改堆属性,从而执行。这算是比较正常的方式了。
1 | #include <stdio.h> |
编译出来成功执行。
参考
mprotect(2) - Linux manual page (man7.org)
virtualProtect 函数 (memoryapi.h) - Win32 apps | Microsoft Learn
- 本文作者: Taardis
- 本文链接: https://taardisaa.github.io/2023/06/01/从C源码中提取Shellcode的尝试/
- 版权声明: 本博客所有文章除特别声明外,均采用 Apache License 2.0 许可协议。转载请注明出处!