未来工作

# 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;
}
1
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
...
1
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
...
1
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);
}
1
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
1
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
1
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)]
1
2
3
4

该技术存在一些限制:

  • 仍然需要处理导入函数;
  • 仍然需要对代码进行一些修改:替换字符串、添加编译指令;
  • 无法得到真正的shellcode,因为它包含硬编码地址,且必须映射到特定地址。

不过,这可能是一个有趣的解决方案。

上次更新: 2026/07/13, 09:10:59

社区交流

讨论与留言

前往 GitHub Issues →

本留言区仅对应当前文章,欢迎补充观点、提出问题或帮助修正文中疏漏。 留言由 GitHub/Gitalk 提供,需要使用 GitHub 登录。

最近更新
第 1 课:专栏导论与安全边界
07-01
第 3 课:安装编译与调试环境
07-01
第 4 课:第三方库下载、编译与依赖管理
07-01
更多文章>