Shellcode 利用
目标
理解 shellcode 注入原理,通过淹没静态地址和跳板两种方法实现 shellcode 代码植入,尝试修改汇编语句的 shellcode 实现修改标题等简单操作。
测试步骤与结果
源码如下
1 |
|
整体来说,是从password.txt文件中读取内容,并进行验证。


在strcpy处依然存在溢出漏洞。不过buffer的长度更长了,可以承载一些 shellcode。User32.dll的载入可以让我们在注入 shellcode 时调用MessageBox函数。
shellcode 代码理解
-
由于涉及到了
dll库的加载,我们打开源程序,用 dependency walker 分析 dll 依赖。![]()
![]()
-
查出
kernel32.dll的实际基址地址为0x77e60000,ExitProcess的地址的入口点为0x01b0bb,加起来就是ExitProcess的实际地址0x77e7b0bb。这是 shellcode 代码中exitprocess地址的来源。 -
同样方法找出
user32.dll的基址为0x77df0000,MessageBoxA的入口点为0x033d68,相加得到MessageBoxA的实际地址为0x77e23d68。这是 shellcode 代码中messageBoxA的地址来源。![]()
![]()
![]()
![]()
![]()
![]()
-
接下来调试 shellcode 代码。shellcode 源码如下
shellcode.c 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
int main()
{
HINSTANCE LibHandle;
char dllbuf[11] = "user32.dll";
LibHandle = LoadLibrary(dllbuf);
_asm{
sub sp,0x440
xor ebx,ebx
push ebx
push 0x74707562 //bupt
push 0x74707562 //bupt
mov eax,esp
push ebx
push eax
push eax
push ebx
mov eax,0x77E23D68 //messageboxA 入口地址
call eax
push ebx
mov eax,0x77E7B0BB //exitprocess 入口地址
call eax
}
return 0;
} -
运行该代码,发现程序弹出了一个含有
"buptbupt"标题的对话框
![]()
-
用 ollydbg 打开 shellcode 程序,把我们写的汇编代码复制出来
淹没静态地址的 shellcode 注入
- 用 ollydbg 打开
overflow_exe.exe程序,在strcpy处下一个断点。![]()
strcpy将拷贝字符串到0x12faf0地址处,这也是我们将要放 shellcode 的地址。![]()
- 下面构造 payload,我们构造的形式类似于 Shellcode +
0x90若干 + shellcode 在缓冲区的起始地址,要注意逆序书写。Shellcode 的返回地址应当在 buff 的容量 + authenticated 变量空间 + ebp 之后,也就是第 53~56 字节处。综上,我们构造 payload 如下
- 再运行程序,发现
0x12faf0开始的空间已经覆盖为了我们所需要的样子。![]()
- 返回到 shellcode 地址后,其中所写的内容都作为代码执行了。
![]()
- 当然也可以弹出对话框了,脱离 ollydbg 环境也可以成功。
![]()
利用跳板的 shellcode 注入
- 仍在
strcpy函数上设置断点,运行至其附近。 - 右键选择 overflow return address → ASCII overflow returns → search JMP / CALL ESP
- 根据提示查看日志,发现有很多可以利用的地方。选择一条在
user32.txt中的地址,0x77e2e32a。这个是jmp esp的地址。
- 这次的 payload 形式为 52 字节填充物 + 4 字节
JMP ESP地址(逆序) + shellcode (可选 + 若干0x90)。下图 payload 也可以成功弹框出来。但是程序没退出,后来发现 shellcode 中忘记调用
ExitProcess函数,补上就更加完美了
![]()
修改汇编语句,改变弹窗标题
通过阅读汇编代码得知,这个"buptbupt"字符串实际上来自两个 push 操作,每次是 4 个字母。那么改变 push 的参数,我们也应该能对标题框的显示进行改变。
如果尝试输出中文,需要注意一个汉字由两个字节表示,push 输出时应该全部(字内和字间)逆序书写
测试结论


思考题
本段文字并未完全完成实验,这里只是列出了部分思路。
程序源代码如下
1 | /* |
这次的程序比上次多了LoadLibrary调用和Windows.h头文件,但大体逻辑没有变化。目标是通过 dir 命令将 C 盘根目录结构保存在shellcode.txt中。
-
shellcode 需要利用系统调用
CreateProcessA或WinExec,这里选择WinExec("cmd.exe /c dir C:\\ > shellcode.txt", SW_HIDE)。- 其中
SW_HIDE=0意味着隐藏窗口,更具隐蔽性
- 其中
-
首先找到
WinExec的地址,还是打开 dependency walker 查看。![]()
- 通过计算可得
WinExec的地址为kernel32.dll的基址加上WinExec的入口点地址,为0x77e78601。 - 用同样的方法计算出
ExitProcess的地址为0x77e7b0bb。
- 通过计算可得
-
通过试运行程序得知,我们的命令行输入被存储在了
0x12ff68开头的位置上。bar的地址为0x401070,jmp esp后将从0x12ff78开始执行我们的 shellcode。![]()
![]()
-
扫描到了很多
jmp esp的地址,本次选择0x77e2e32a中user32.text的地址作为跳板![]()
-
首先,完成前半部分的填充和
bar函数调用。![]()
![]()
-
把我们的所有操作写成一个 c 文件,编译,把生成的 exe 拿到 ollydbg 运行一遍,拿到我们的 shellcode。这是需要的操作部分:
1
2
3const char command[40] = "cmd.exe /c dir C:\\>shellcode.txt";
WinExec(command, SW_HIDE);
ExitProcess(0);
表面上看非常不错,但这样的字符串是在 rdata 段里的,复制出来的操作码取的就是内存中的相对值,所以只能靠 push 推入。

把command数组写成一个一个字符分开的模式,末尾的\x00要手动加上。目前的问题停留在了栈上字符串的构造上。




















