未来工作
# 5 未来工作
WiShMaster 版本2已能够生成shellcode,但仍处于开发阶段。其主要目标是改进C代码分析,以消除正则表达式解析对代码施加的最后几项限制。
以下段落总结了我正在研究的一些思路。
# 5.1 pycparser工具(pycparser)
pycparser是一个用Python编写的C语言解析器(C parser)和抽象语法树生成器(AST generator),可在谷歌代码网站[13]上获取。与使用浏览文件(browse file)解析代码相比,它可能是一个更好的解决方案:首先,它似乎能提供更多信息;其次,它解除了WiShMaster与微软编译器(Microsoft compiler)之间的关联。
# 5.2 GCC编译器的fPIC选项(Option fPIC of gcc)
GCC编译器集成了一个有趣的选项“fPIC”,允许生成不包含硬编码地址(hardcoded address)的代码。PIC是“位置无关代码(Position Independent Code)”的缩写。
启用该选项后,GCC会在每次访问全局数据(global data)之前插入一个函数调用,将当前的指令指针寄存器(eip)的值移至基址指针寄存器(ebx),然后相对于基址指针寄存器(ebx)访问数据。
我们以Linux系统为例:
文件 test.c
#include <stdio.h>
char g_szMessage[] = "Hello !";
void DisplayMessage(void)
{
printf(">>> %s <<<\n", g_szMessage);
}
int main(int argc, char * argv[])
{
DisplayMessage();
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
正常编译时,生成的代码包含硬编码地址:
标准编译生成的汇编代码(Assembly generated with a standard compilation)
$ gcc test.c -o test
$ objdump -d test
...
8048354 <DisplayMessage>:
8048354: 55 push %ebp
8048355: 89 e5 mov %esp,%ebp
8048357: 83 ec 08 sub $0x8,%esp
804835a: c7 44 24 04 bc 95 04 08 movl $0x80495bc,0x4(%esp)
8048362: c7 04 24 a8 84 04 08 movl $0x80484a8,(%esp)
8048369: e8 22 ff ff ff call 8048290 <printf@plt>
804836e: c9 leave
804836f: c3 ret
...
2
3
4
5
6
7
8
9
10
11
12
13
但如果使用fPIC选项:
fPIC选项编译生成的汇编代码(Assembly generated with the fPIC option)
$ gcc test.c -o test -fPIC
$ objdump -d test
...
08048384 <DisplayMessage>:
8048384: 55 push %ebp
8048385: 89 e5 mov %esp,%ebp
8048387: 53 push %ebx
8048388: 83 ec 14 sub $0x14,%esp
804838b: e8 4f 00 00 00 call 80483df <__i686.get_pc_thunk.bx>
8048390: 81 c3 5c 12 00 00 add $0x125c,%ebx
8048396: 8b 83 f8 ff ff ff mov 0xfffffff8(%ebx),%eax
804839c: 89 44 24 04 mov %eax,0x4(%esp)
80483a0: 8d 83 0c ef ff ff lea 0xffffef0c(%ebx),%eax
80483a6: 89 04 24 mov %eax,(%esp)
80483a9: e8 0a ff ff ff call 80482b8 <printf@plt>
80483ae: 83 c4 14 add $0x14,%esp
80483b1: 5b pop %ebx
80483b2: 5d pop %ebp
80483b3: c3 ret
080483df <__i686.get_pc_thunk.bx>:
80483df: 8b 1c 24 mov (%esp),%ebx
...
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
然而,该选项无法解决导入函数(imported function)的解析问题;此外,数据之间的间隔是硬编码的,因此必须在shellcode中保持相同的内存映射(mapping)。但最重要的是,该选项在Windows系统上似乎无法工作。我尝试了Cygwin[14]和MinGW[15]中的GCC编译器,每次都得到相同的提示“不支持该选项”(或其他不太明确的提示)。实际上,该选项是在Linux系统中添加的,用于支持真正的共享库(shared library)。Windows系统的工作方式不同,当动态链接库(dll)未在首选加载地址(preferred load address)加载时,它完全依赖重定位表(relocation table)来修补硬编码地址。以下链接提供了关于此主题的有趣信息[16]。
Philippe Biondi开发的Linux系统shellcode生成器ShellForge[17]特别使用了该选项。
# 5.3 趣味使用编译指令(Playing with pragma for fun...)
我们可以使用一些编译指令(pragma directive)将各种元素分组到一个特殊的节(section)中。
例如,如果我们对“simpletest”的代码稍作修改:
修补后代码片段(Extract of patched code)
#pragma code_seg("mysection")
__declspec(allocate("mysection"))
CHAR g_szMessage[] = "This is a message stored as a global variable";
__declspec(allocate("mysection"))
CHAR sz00000001[] = ">>> %s <<<";
__declspec(allocate("mysection"))
CreateFileTypeDef fp_CreateFile;
#define CreateFile fp_CreateFile
VOID DisplayMessage(IN CHAR * szMessage)
{
PrintMsg(LOG_LEVEL_TRACE, sz00000001, szMessage);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
这些指令指示将函数放置在新的节“mysection”中。“__declspec”允许将全局数据添加到代码节(code section)中。
最后,查看生成的映射文件(.map file):
生成的映射文件片段(Extract of the generated map file)
...
0003:00000000 0000127eH mysection CODE
...
0003:00000000 ?fp_CreateFile@@...KKPAX@ZA
0003:00000004 ?sz00000001@@3PADA
0003:00000010 ?g_szMessage@@3PADA
0003:00000050 ?DisplayMessage@@YAXPAD@Z
0003:00000080 ?DisplayFile@@YAHPAD@Z
0003:00000200 ?DisplayData@@YAHXZ
...
0042f000 f display.obj
0042f004 f display.obj
0042f010 f display.obj
0042f050 f display.obj
0042f080 f display.obj
0042f200 f display.obj
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
可以看到,函数和全局数据位于同一个节中,因此在地址空间(address space)中非常接近。查看汇编代码:
函数PrintMsg的调用(Call of the function PrintMsg)
PrintMsg(LOG_LEVEL_TRACE, sz00000001, szMessage);
0042F059 mov eax, dword ptr [szMessage] ; szMessage = g_szMessage = 0x0042F010
0042F05C push eax
0042F05D push offset sz00000001 (42F004h)
0042F062 push 2
0042F064 push 0
2
3
4
5
6
代码中仍然包含硬编码地址,但如果我们提取整个“mysection”节,并将其映射到另一个进程中的相同地址,它可能会正常工作。
函数CreateFile的调用(Call of the function CreateFile)
if ((hFile = CreateFile(szFilePath, ..., NULL)) != INVALID_HANDLE_VALUE)
0042F0AC push 0
...
0042F0C2 call dword ptr [fp_CreateFile (42F000h)]
2
3
4
该技术存在一些限制:
- 仍然需要处理导入函数;
- 仍然需要对代码进行一些修改:替换字符串、添加编译指令;
- 无法得到真正的shellcode,因为它包含硬编码地址,且必须映射到特定地址。
不过,这可能是一个有趣的解决方案。