软安实验3:shellcode注入
overflow_exe
1 |
|
程序分析
主函数:读取password.txt 并调用验证函数,根据比较结果进行显示

验证函数:比较两字符串,将原字符串复制到Destination数组。Dst大小为44,但Source的长度并未限制,在strcpy时可能造成栈溢出,此处即为shellcode注入点

获取所需的MessageBoxA和ExitProcess函数地址
用Dependency Walker打开exe
点击菜单“剖析”->“开始剖析”
弹出设置信息,直接默认设置点击确认即可。
获取 MessageBoxA 地址:
- 在左侧模块列表中,点击
USER32.DLL。 - 在中间下方的模块窗口中,找到
USER32.DLL并记下它的实际基址 (Actual Base) 。 (0x77DF0000) - 在右下角的函数列表中,找到
MessageBoxA并记下它的入口点 (Entrypoint) 偏移 。 (0x00033D68) - 计算绝对地址:基址 + 入口点 = 绝对地址。(
0x77DF0000 + 0x00033D68 = 0x77E23D68)
入口点0x00033D68
实际基址0x77DF0000

获取 ExitProcess 地址:
- 在左侧模块列表中,点击
KERNEL32.DLL。 - 记下
KERNEL32.DLL的实际基址 (Actual Base) 。 (0x77E60000) - 在右下角的函数列表中,找到
ExitProcess并记下它的入口点 (Entrypoint) 偏移 。 (0x0001B0BB) - 计算绝对地址:基址 + 入口点 = 绝对地址。(
0x77E60000 + 0x0001B0BB = 0x77E7B0BB)

编写并提取 Shellcode 机器码
修改shellcode的main.cpp
1 |
|
编译运行,生成exe文件

成功弹出了对话框
打开 OllyDbg,将 shellcode.exe 拖入其中 。
直接F8运行至弹出对话框,找到了main函数,下断点

步入main函数内部,找到核心shellcode代码

- 开头 (
xor ebx, ebx): 开始准备参数 。 - 中间 (
call eaxforMessageBoxA): 执行主要任务(弹窗) 。 - 结尾 (
call eaxforExitProcess): 安全地结束任务 。
保存到文件

方法一 (淹没静态地址)
这种方法利用缓冲区溢出 ,用 shellcode 和填充数据 填满栈帧,最后用 shellcode 的起始地址 覆盖函数的返回地址
分析栈结构与漏洞
- 漏洞点:
verify_password函数中的strcpy(Destination, Source); - 栈帧:
Destination(即buffer) 大小为 44 字节 。在它之上是authenticated(4字节) 和 EBP (4字节) ,再往上就是返回地址 。 - 偏移量:你需要覆盖
44 + 4 + 4 = 52个字节才能到达返回地址 。

在exe文件目录下创建一个password.txt文档

查找 Buffer 的静态地址
call strcpy在地址00401064

将overflow_exe拖入OllyDbg分析,在call strcpy处下断点

获取地址:当程序在断点处暂停时,查看 strcpy 的目标地址 (Destination) 。这就是 buffer 在栈上的起始地址。记下这个地址 (0x0012FAF0 )
看到函数的第一个参数(dest)存放在EDX,因此可以查看EDX此时的值得到dest。也可以看栈的当前值,就是dest

构造 Payload
- Shellcode:粘贴提取的 shellcode 机器码 。
- Padding (填充):在 shellcode 后面填充
0x90(NOP 指令),直到 shellcode + 填充的总长度刚好为 52 字节 。 - 返回地址:写入
buffer起始地址 (0x0012FAF0) 。- 注意:地址必须倒序 (Little-endian) 写入。
0x0012FAF0应写作F0 FA 12 00。
- 注意:地址必须倒序 (Little-endian) 写入。
打开editor,ctrl+h进入16进制编辑

运行overflow_exe成功弹出对话框

动态调试查看跳转逻辑
strcpy的返回地址被覆盖为0012FAF0
继续运行跳转到了shellcode

方法二 (通过跳板 JMP ESP)
这种方法更可靠。我们不跳回 buffer 的 固定 地址,而是跳到一个固定的 JMP ESP 指令的地址 。RET 指令执行后,ESP 寄存器刚好指向返回地址的 下一个 位置 ,我们把 shellcode 放在那里 ,JMP ESP 就会跳转到我们的 shellcode 上 。

查找 JMP ESP 跳板
手动查找JMP ESP。
点击 “M”,打开内存映射窗口。
找到ntdll的.text区的起始地址,跳转查看。
77F8948B

构造 Payload
- Padding (填充):写入 52 字节的填充数据 。(
0x61[‘a’] )。 - 返回地址 (跳板):写入
JMP ESP地址 。(0x77F8948B)。同样,必须倒序写入 - Shellcode:紧接着返回地址,粘贴提取的 shellcode 机器码 。


成功弹出对话框
运行调试查看跳转逻辑

返回地址被覆盖为77F8948B,即JMP ESP的地址
运行JMP ESP,此时ESP指向0012FB28,正好是shellcode的起始地址


修改 Shellcode (修改标题)
MessageBoxA 的参数是通过栈传递的,顺序从右到左 :uType, lpCaption (标题), lpText (文本), hWnd (句柄) 。要修改标题,我们只需修改 lpCaption 参数指向的字符串
编写新 Shellcode (修改 shellcode.cpp)
1 | sub sp,0x440 |
编译新的shellcode.cpp

用OllyDbg提取shellcode


- 汇编指令:
SUB SP, 440 - OllyDbg 显示:
66:81EC(操作码) 和4004(立即数/参数) - 实际的机器码 (Hex):
66 81 EC 40 04
通过淹没静态地址实现
构造payload


成功弹出对话框
通过跳板JMP ESP实现
构造payload

攻击成功

StackOverrun.exe
目标:不修改 StackOverrun.c ,构造 shellcode,通过 JMP ESP 方式调用 WinExec 打开 shellcode.txt
分析程序

- 代码中有大量的字符串操作指令:
repne scasb(计算长度),rep movsd,rep movsb(复制数据)。 - 这些指令组合起来,实际上是在执行一个内联的
strcpy操作。
攻击思路
- 注入点:通过命令行参数 (
argv[1]) 输入 Payload。 - 漏洞:
foo函数中的字符串复制操作没有边界检查,导致栈溢出。 - 利用方式:
- 构造一个超长的字符串作为参数。
- 字符串的前面部分是填充数据(Padding),用来填满缓冲区。
- 接下来是跳板地址(覆盖掉原始的返回地址),指向一个
JMP ESP指令。 - 最后是我们的 Shellcode(比如打开记事本或弹窗的代码)。
- 执行流程:
main->foo->strcpy(溢出发生) ->foo返回 ->JMP ESP-> 执行 Shellcode。
获取 WinExec 地址和 ExitProcess 地址
用Dependency Walker打开exe文件,并剖析

基址0x77E60000
入口点0x00018601
加起来是77E78601

入口点0x0001B0BB
加起来是77E7B0BB
编写并提取 Shellcode
功能是执行 WinExec("notepad shellcode.txt", SW_SHOWNORMAL)
1 | int main() { |

找JMP ESP

记录下地址77F8948B
构造 Payload 字符串

函数开头分配了12字节,因此padding长度为12字节
即构造90 90 90 90 90 90 90 90 90 90 90 90 (12个) + 0x77F8948B[JMP ESP 地址] + [Shellcode]
得到payload

内存补丁
先在参数里设置AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA用来占位
通过动态调试的方法,将输入从“AAA…”改为payload

直接将这部分的数据改成payload
61 61 61 61 61 61 61 61 61 61 61 61 8B 94 F8 77
83 EC 50 33 DB 6A 74 68 65 2E 74 78 68 6C 63 6F
64 68 73 68 65 6C 68 70 61 64 20 68 6E 6F 74 65
8B C4 6A 01 50 B8 01 86 E7 77 FF D0 53 B8 BB B0
E7 77 FF D0

F9运行,成功弹出了shellcode.txt

证明payload的有效的,但是需要解决坏字符问题
Loader加载器
1 |
|
- 根本机制:绕过命令行解析
- Loader 启动目标程序时,传入的是一串完全无害的占位符(如
"PPPPPPPP...")。这些字符都是标准的 ASCII 字符,系统解析时绝不会出错。 - 当程序启动并暂停后,Loader 使用
WriteProcessMemoryAPI。这个函数的作用是直接写入原始二进制数据。
- Loader 启动目标程序时,传入的是一串完全无害的占位符(如
- 覆盖策略:全内存暴力替换
- C 语言程序启动时,C 运行时库 (CRT) 会把命令行参数复制一份到堆(Heap)中生成
argv数组。如果你只修改了命令行源头,程序实际执行时读取的可能是堆上的副本(依然是占位符),导致攻击失败。 - 代码中的
PatchAllOccurrences函数遍历了目标进程所有的可读写内存,全部替换
- C 语言程序启动时,C 运行时库 (CRT) 会把命令行参数复制一份到堆(Heap)中生成
- 容错机制:安全跳板 (Safety Jump / NOP Sled)
- Shellcode 注入后,前端的某些字节(大约第 7-8 字节处)经常会出现写入截断或未完全覆盖的情况(变成了
0x61),这导致 Shellcode 执行到一半直接崩溃。 - 在 Payload 的最开头加入了一个短跳转指令:
0xEB, 0x08(JMP SHORT +8)。跳过了紧随其后的 8 个字节
- Shellcode 注入后,前端的某些字节(大约第 7-8 字节处)经常会出现写入截断或未完全覆盖的情况(变成了
上一步的动态调试确保了payload的核心逻辑和地址引用是没有问题的,因此这里只需要额外处理坏字符的问题。
最终payload如下:
1 | ; --- 第一部分:填充与劫持控制流 --- |


