最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊

【CTF】 [DC29 Quals] Reverse-Tiamat WriteUp

2021-05-12 17:35 作者:吾愛破解論壇  | 我要投稿

作者論壇賬號(hào):CHILAS_LEE


[DC29 Quals] Reverse-Tiamat WriteUp

題目傳送門

Protovision just launched their new licensing server yesterday and the sysop over at Pirate's Harbor BBS already has a copy. You should download it and try to be the first one to hack it.

前言:我將盡量以自己做題時(shí)的思考過程來組織本文,所以本文可能不適合閱讀,知識(shí)點(diǎn)也會(huì)比較散碎的出現(xiàn)。

1. qemu-user 簡介

簡單介紹一點(diǎn)本題所涉及的 qemu 相關(guān)知識(shí),需要聲明的是這一節(jié)不是對 qemu 的源碼分析,僅僅包含 qemu-user 執(zhí)行過程的一個(gè)概括,省略了大量 qemu 的細(xì)節(jié),甚至很多地方為了方便理解本題表述并不準(zhǔn)確。


圖中黃底為比較重要的函數(shù),整體的執(zhí)行流程大概是:從?main?函數(shù)出發(fā),執(zhí)行一些初始化操作之后進(jìn)入?cpu_loop?函數(shù),cpu_loop?函數(shù)循環(huán)調(diào)用?cpu_exec?。cpu_exec?也包含一個(gè)循環(huán),負(fù)責(zé)一條一條(并不準(zhǔn)確)反匯編 guest 程序的指令、生成能夠在 host 主機(jī)執(zhí)行的代碼,并執(zhí)行所生成的代碼。當(dāng)?cpu_exec?遇到中斷時(shí),會(huì)返回到?cpu_loop?交由?cpu_loop?進(jìn)行處理。

在?cpu_exec?函數(shù)內(nèi)部更詳細(xì)的調(diào)用過程如圖所示,需要注意的有 3 個(gè)函數(shù):

  • gen_intermedia_code:負(fù)責(zé)反匯編 guest instruction,生成中間代碼(TCG operations),通常被稱作前端。

  • tcg_gen_code:負(fù)責(zé)把中間代碼轉(zhuǎn)換為在 host 機(jī)器上執(zhí)行的代碼,通常被稱作后端。

  • tcg_qemu_tb_exec:負(fù)責(zé)調(diào)用執(zhí)行由?tcg_gen_code?生成的 host 代碼。這里是調(diào)試的關(guān)鍵點(diǎn),在這里下斷點(diǎn)就可以知道 guest 指令被翻譯成了什么樣的 host 指令。

舉個(gè)例子對于 mips 指令?sw $zero, 8($sp)?其得到的 host 指令可能是這個(gè)樣子的:

CPUArchState 類型就是 qemu 模擬的 target cpu 架構(gòu)。[0] 處的代碼是主要生效的代碼,其將 0 寫入 29 號(hào)寄存器指向的地址 + 8 的位置(在 mips 中 29 號(hào)寄存器就是 $sp)。

2. 題目初分析

題目總共給了七個(gè)文件,首先查看?Dockerfile?,除了把現(xiàn)有的文件復(fù)制過去之外還新增了幾個(gè)文件:

  • /flag:flag

  • /lic:flag 的 md5

  • /1.mz-f.mz:同樣的內(nèi)容?STRANGE GAME\nTHE ONLY WINNING MOVE IS\nNOT TO PLAY.\n

其他文件大致的意思就是執(zhí)行?/qemooo /liccheck.bin?,于是理所應(yīng)當(dāng)將 liccheck.bin 丟進(jìn) IDA,但是出了一點(diǎn)問題。


IDA 只能反匯編幾條指令,結(jié)合 qemooo 這個(gè)文件名字可以猜到作者魔改了 qemu。好在 qemooo 是帶符號(hào)的,這個(gè)時(shí)候可以在 Functions 窗口看到一些奇怪的單詞:aarch64、riscv。難道說那些指令不是 mips 指令?有了上一節(jié)的掃盲,現(xiàn)在我們知道或許應(yīng)該去 tb_gen_code() 里面看看 qemooo 是怎么反匯編這些指令的。


蕪湖,我們好像發(fā)現(xiàn)了關(guān)鍵。不過這個(gè)看起來可不太妙,四種架構(gòu) X 大小端切換,讓我想起了上個(gè)月做的我?guī)煾?yype?的?gatesXgame?。簡單通過交叉引用確定了?tmap_arch?數(shù)組沒有在其他地方被修改之后,將數(shù)組 dump 下來開始寫一個(gè)簡單的反匯編程序。

這里有個(gè)小地方可能需要注意下:capstone 得切換到 next 分支才能反匯編 riscv。

3. 人類可讀代碼計(jì)劃


Emmm,雖然確實(shí)成功了但是我不確實(shí)太認(rèn)為這個(gè)能夠幫助我們理解程序邏輯。至少我們可以給所有寄存器換一下名,或許 r0-r32 是更好的表示。這里需要去查找各種架構(gòu)的寄存器對應(yīng)關(guān)系,手冊、capstone 源碼都會(huì)有幫助。


我覺得好些了,但我突然覺得還想更好一些,所以稍微修改了反匯編器輸出類似高級(jí)語言的代碼。(事實(shí)上我最開始想生成 C 代碼丟給 IDA 幫忙分析,但是后面踩坑太多就放棄了 XD)


看起來可以大干一場了,但是初始化之后的第一條指令就有點(diǎn)奇怪,r15 寄存器是個(gè)啥?我一調(diào)試發(fā)現(xiàn)這條指令生成的 host 代碼甚至沒有訪問 r15 寄存器,我一回頭看最開始的匯編,發(fā)現(xiàn)這里是對 pc 寄存器的操作,所以我這里有個(gè)未驗(yàn)證的猜測,前端 gen_intermedia_code(或許是)在反匯編生成 TCG 的時(shí)候,可能會(huì)對一些特殊寄存器有特殊的操作,例如 pc 寄存器會(huì)被硬編碼為當(dāng)前 pc 的數(shù)值常量。

知道這個(gè)后繼續(xù)往下走,調(diào)試沒幾步寄存器的變化又和預(yù)想的不一樣了,測試了一下發(fā)現(xiàn)是 sparc 這一類指令的問題,所以我回到?cpu_tb_exec()來確認(rèn)寄存器映射的情況,發(fā)現(xiàn) sparc 類指令的寄存器映射和手冊上的不一致,例如對于指令?add? ???%g1, 0xd00, %i7?生成的 host 代碼如下所示。按照手冊上的說法 g1 對應(yīng) r1,但是這里卻取了 r2 和 r3 的數(shù)值(_QWORD);i7 應(yīng)該對應(yīng) r31,卻存到了一個(gè)不屬于通用寄存器的內(nèi)存,并且也是 64 位的操作。

對 sparc 類指令的寄存器對應(yīng)關(guān)系進(jìn)行進(jìn)一步分析之后可以發(fā)現(xiàn)行為大致是這樣:

  • 首先對 sparc 類寄存器的操作不同于其他架構(gòu),它是 64 位的。

  • 對于 o0-o7、l0-l7、i0-i7,其被映射到了不屬于通用寄存器內(nèi)存的一塊地方(regwptr),可以把它看作是擴(kuò)展 cpu 的擴(kuò)展寄存器,在本文中用 eR0-eR46 表示,為了一致性(其實(shí)沒必要)把它們都定義是 32 位寄存器,所以相當(dāng)于 o0 映射到了 eR0、eR1 兩個(gè) 32 位寄存器。

  • 對于 g0-g7,他們被映射到了 r0-r15,相當(dāng)于 g0 映射到了 r0、r1 兩個(gè)寄存器。

這里按理要修改一下反匯編器生成類似 (rx, rx) = (rx, rx) op (rx, rx) 類型的代碼,但是我粗略看了一下,sparc 指令執(zhí)行后高位的寄存器都是沒有被使用的,所以我做題的時(shí)候偷了個(gè)懶只是把這個(gè)點(diǎn)記在腦子里,然后還是把 g0 映射到了 r0,給后面留下了一個(gè)隱患。

幾乎是最后,我們還需要為系統(tǒng)調(diào)用確定調(diào)用約定。本題中四種架構(gòu)都有涉及系統(tǒng)調(diào)用的指令,前面提到過,在遇到中斷的時(shí)候 qemu 會(huì)返回到?cpu_loop()?進(jìn)行處理,在?cpu_loop()?中可以找到類似下面的調(diào)用:


找到所有的調(diào)用然后還原出所有的調(diào)用約定:


確定系統(tǒng)調(diào)用所對應(yīng)的具體操作,還需要知道 syscall table。對于 syscall number,在?linux/Documentation/ABI/stable/syscalls?有寫道:Note that this interface is different for every architecture that Linux supports. Please see the architecture-specific documentation for details on the syscall numbers that are to be mapped to each syscall.

所以對于不同的架構(gòu),我們需要在?qemu/linux-user/?目錄下面去尋找對應(yīng)架構(gòu)的 syscall table,用于確定系統(tǒng)調(diào)用所對應(yīng)的具體操作,以便下一步程序執(zhí)行邏輯的還原。這一步相當(dāng)枯燥,且相當(dāng)容易出錯(cuò)

要生成一份準(zhǔn)確的代碼對我來說并非易事,除了上面提到的,還有一些細(xì)節(jié)需要注意,例如:

  • sparc 的 dest 寄存器在最后一個(gè)操作數(shù),而其他架構(gòu)是第一個(gè)操作數(shù)。

  • branch 類指令目標(biāo)地址的確定,例如 riscv 的 j 指令和 mips 的 b 指令有所區(qū)別,b 是當(dāng)前地址加上偏移,j 是當(dāng)前地址減 4 加上偏移。

  • call 類指令的特殊處理,作者為了惡心人用了兩種方法來調(diào)用函數(shù):jal 指令、手動(dòng)存入返回地址到寄存器然后 jmp。retrun 指令也有兩種表示:ret 指令,手動(dòng)將寄存器賦值給 pc。最惡心的是有一個(gè)語義應(yīng)該是 goto 的指令是用 call 來實(shí)現(xiàn)的。

4. 程序邏輯分析


有了上面的工作,我們可以比較輕松的著手分析程序的邏輯,不過這依舊是一個(gè)需要耐心的工作,特別是在我沒有 IDA 幫助的情況下。我考慮過要不要給出分析過程,不過那樣可能文章就太長了,在這里我只給出分析的結(jié)果。

程序是一個(gè)菜單題,初始化的時(shí)候主要會(huì)調(diào)用一個(gè)獲得隨機(jī)數(shù)的函數(shù)(見下面 'n' 對應(yīng)的操作),之后就進(jìn)入菜單選項(xiàng)。還原出來的選項(xiàng)和對應(yīng)的操作如下:

  • e:輸入 input,并對 input 進(jìn)行校驗(yàn),要求值其在 ['0'-'f']。

  • v:要求在輸入 input 后調(diào)用。讀入 license,用隨機(jī)數(shù)對其進(jìn)行異或加密,然后與 input 比較,若相同則輸出 flag。(限制執(zhí)行次數(shù) 0x8 )

  • n:從 /dev/random 讀入四字節(jié)用于更新隨機(jī)數(shù),如果此時(shí)已經(jīng)讀入了 license,就用其對 license 進(jìn)行異或加密。(限制執(zhí)行次數(shù) 0x18)

  • p:打印 input。

  • joshua:打印一點(diǎn)沒用的東西。

  • l:對解題沒有幫助的一個(gè)無聊的函數(shù) XD。

  • r:NOP。

在這一步確定全局變量寄存器以及內(nèi)存數(shù)據(jù)的分布也很重要。

4. 找到 BUG(s)

BUG1: r0 misuse


’p‘ 操作對應(yīng)的操作很短,實(shí)際有意義就三行,第一行將 input 的地址賦值給 r10 寄存器,第二行將?r0+0x20?賦值給 r11 寄存器,在?print_sth?函數(shù)中,r11 用來控制泄露的長度。通過上一節(jié)的分析可以知道加密后的 license 就存放在 input 后面,所以 r0 寄存器很可能可以控制然后用來泄露。

注意在 riscv 和 mips 中 r0 是 zero 寄存器,它和 pc 一樣屬于比較特殊的寄存器,(應(yīng)該)會(huì)被直接翻譯為常量 0,在程序中有很多 + zero 的無用操作來迷惑你。不過好在之前看到 pc 寄存器的時(shí)候就對 r0 寄存器留了一個(gè)心眼,我迅速定位了所有使用真 r0 寄存器的指令(所以我的反匯編代碼里為什么不早點(diǎn)對 zero 特殊處理 XD),發(fā)現(xiàn)除了這條指令確實(shí)使用 r0 寄存器外,還有一個(gè)地方存在對 r0 寄存器的賦值。


賦值發(fā)生在 'n' 操作對應(yīng)的 get_random 里,open 作為 svc 系統(tǒng)調(diào)用,返回值存到了 r0 寄存器里,后續(xù)返回到 menu_loop 之前也沒有對 r0 寄存器的再賦值,意味著我們可以在 'n' 操作后馬上調(diào)用 'p' 操作進(jìn)行泄露。所以我們第一個(gè) payload 就是?"e"+"1"*0x20+"vnp"?!


BUG2: syscall number misuse

蕪湖,看起來我們已經(jīng)摸到 flag 了!但是,等一下,為什么泄露了五個(gè)字節(jié),fd 不是應(yīng)該為 3 才對嗎?再回頭審計(jì)代碼發(fā)現(xiàn) 'v' 操作里面讀取 license 的時(shí)候,open 后沒有 close,這確實(shí)會(huì)讓 fd 加 1,但是只執(zhí)行一次 ’v‘ 操作為什么 fd 會(huì)加 2?這里可以調(diào)試跟蹤所有 open 和 close 系統(tǒng)調(diào)用的執(zhí)行情況,最后會(huì)發(fā)現(xiàn)在 'n' 操作里,看似是 close 的操作其實(shí)根本沒有執(zhí)行 do_syscall,因?yàn)樗鼈鬟f了另一個(gè)架構(gòu)的系統(tǒng)調(diào)用號(hào)!這里感受到了作者的惡意,在還原系統(tǒng)調(diào)用的時(shí)候真的很枯燥,看到 open、read 自然就覺得之后應(yīng)該是 write。

事實(shí)上我在做題的時(shí)候沒有發(fā)現(xiàn)這個(gè)漏洞,因?yàn)槲疫€犯了另一個(gè)錯(cuò)誤,我忘記在根目錄創(chuàng)建 lic 文件,導(dǎo)致 'v' 操作的 open 不會(huì)成功,從而導(dǎo)致之后只能泄露出四個(gè)字節(jié)。

現(xiàn)在我們有兩個(gè)可以讓 fd 增加的函數(shù),并且我們可以調(diào)用他們共計(jì) 0x20 次,加上 stdio 給我們貢獻(xiàn)了 3 個(gè)文件描述符,足以讓我們泄露所有的 license。是時(shí)候構(gòu)建我們的第二個(gè) payload:?"e"+"1"*32+"v"*7+"n"*0x16+"p"


5. 還原 license

我們泄露的不是 license,是 license 與一個(gè)四字節(jié)隨機(jī)數(shù)循環(huán)異或后的數(shù)值,不過這足以給我們很多信息了:異或是四字節(jié)進(jìn)行的,意味著對于隨機(jī)數(shù)的每一個(gè)字節(jié),license 中都有 8 個(gè)字節(jié)都使用它來異或加密。而 license 每個(gè)字節(jié)取值區(qū)間為 [‘0‘-’f'],所以對于泄露的每個(gè)字節(jié),可以確定一個(gè)長度為 16 的集合,包含了所有隨機(jī)數(shù)字節(jié)可能的取值。而對于使用同一個(gè)隨機(jī)數(shù)字節(jié)來加密的 8 個(gè)字節(jié),這 8 個(gè)隨機(jī)數(shù)字節(jié)集合得取個(gè)交集。這聽起來可以把可能的 license 縮小到一個(gè)可以接受的范圍,馬上寫一個(gè)腳本跑一下:


OMG,難以相信真正的 license 就在這 8 字符串當(dāng)中!我甚至可以接受手工測試的開銷,我已經(jīng)等不及了,我早就已經(jīng)想好了怎么進(jìn)行測試。

事實(shí)上我不確定這是否是預(yù)期解,因?yàn)閷τ谄渌?flag,很可能候選的 license 數(shù)量在 3 位數(shù)以上,雖然暴力也花不了多少時(shí)間,但是總覺得有點(diǎn)奇怪。不過我確實(shí)知道有兩個(gè)做出這道題的隊(duì)伍也使用了這種解法。

6. 錯(cuò)誤的道路

似乎我們只需要先輸入?ex...xvnp?就可以泄露?License ^ Rand0 ^ Rand1?的數(shù)值,然后輸入?np?就可以泄露?License ^ Rand0 ^ Rand1 ^ Rand2?的數(shù)值,兩個(gè)泄露的數(shù)值異或就可以得到?Rand2?的數(shù)值。?Rand2?是最新的隨機(jī)數(shù),校驗(yàn)函數(shù)將用它來異或 License 然后與我們的輸入做比較,那我們使用 Rand2 來異或我們可能的 Licese,將其輸入然后調(diào)用驗(yàn)證函數(shù),如果是正確的 License 就可以通過校驗(yàn)輸出?flag?了?

我激動(dòng)地寫完腳本,然后發(fā)現(xiàn)所有的 License 全部校驗(yàn)失敗。在確定正確的 License 一定在之中后,我突然意識(shí)到,輸入的時(shí)候要求所有字符都是在?'0'-'f'!怎么可能?這意味著輸入一定是正確的 License,但是與輸入比較的數(shù)據(jù)是與隨機(jī)數(shù)異或之后的 License。難道說有辦法讓讀入的 License 不被隨機(jī)數(shù)異或?

7. 最后的 Flag

這個(gè)時(shí)候馬上就想起來了,隨機(jī)數(shù)是存在 r15 寄存器里的,雖然之前檢查過所有對 r15 寄存器賦值的語句,但是遺漏了一點(diǎn),sparc 的指令在對 r14 寄存器賦值的時(shí)候會(huì)把 r15 清零!所以我立馬搜索所有對 r14 賦值的語句,最后在?joshua?操作里找到了它。


雖然 r14 被偽裝成了一個(gè)傳參的臨時(shí)變量,但在這個(gè)沒用的函數(shù)里面它就是顯得那么的突兀。

所以,我們只需要很簡單地在校驗(yàn)之前調(diào)用一次這個(gè)函數(shù):?ed64be88c7427f0255c5002f81a9350fbjoshua\nv


參考

https://dttw.tech/posts/HJ9TU7J_O

https://github.com/o-o-overflow/dc2021q-tiamat-public/tree/main/service/src/chal_builder

https://github.com/o-o-overflow/qemooo


原文地址:https://www.52pojie.cn/thread-1437363-1-1.html


【CTF】 [DC29 Quals] Reverse-Tiamat WriteUp的評(píng)論 (共 條)

分享到微博請遵守國家法律
岱山县| 南丹县| 泗阳县| 墨竹工卡县| 永泰县| 上林县| 屏山县| 黔西县| 巩义市| 正安县| 孟津县| 新兴县| 宁蒗| 兴海县| 保德县| 乌审旗| 仙居县| 钦州市| 望谟县| 湟中县| 云南省| 依安县| 会同县| 南皮县| 汨罗市| 甘南县| 汾西县| 昌黎县| 青川县| 平潭县| 海丰县| 凤山市| 钟祥市| 龙陵县| 罗平县| 牡丹江市| 商水县| 白水县| 库伦旗| 东丽区| 湖南省|