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
要手动加上。目前的问题停留在了栈上字符串的构造上。