欧美free性护士vide0shd,老熟女,一区二区三区,久久久久夜夜夜精品国产,久久久久久综合网天天,欧美成人护士h版

目錄

柚子快報(bào)邀請(qǐng)碼778899分享:Rust逆向?qū)W習(xí) (1)

柚子快報(bào)邀請(qǐng)碼778899分享:Rust逆向?qū)W習(xí) (1)

http://yzkb.51969.com/

文章目錄

Hello, Rust Reverse0x01. main函數(shù)定位0x02. main函數(shù)分析line 1line 2line 3line 4~9

0x03. IDA反匯編0x04. 總結(jié)

近年來,Rust語(yǔ)言的熱度越來越高,很多人都對(duì)Rust優(yōu)雅的代碼和優(yōu)秀的安全性贊不絕口。對(duì)于開發(fā)是如此,對(duì)于CTF也是如此,在逆向題和pwn題中都有出現(xiàn)。從本文開始我們將開始進(jìn)行Rust逆向的學(xué)習(xí),筆者將盡可能通過現(xiàn)有的IDA(7.7版本)對(duì)Rust ELF文件中包含的特性進(jìn)行分析與總結(jié),盡可能地減少Rust逆向的難度,盡可能地解決分析過程中產(chǎn)生的每一個(gè)問題,最終爭(zhēng)取達(dá)到能夠通過IDA反匯編結(jié)果還原Rust代碼的程度。

本系列將跟隨《Rust權(quán)威指南》的學(xué)習(xí)路線完成Rust逆向工程的學(xué)習(xí)。

閱讀本文前,建議首先掌握:

? x86-64逆向的基礎(chǔ)知識(shí)? Rust語(yǔ)言的基本使用

Hello, Rust Reverse

首先我們寫一個(gè)流程較猜數(shù)字稍簡(jiǎn)單一些的Rust程序,完成Rust ELF的第一次分析。 以下是Rust源碼:

use std::io;

fn main() {

let mut input: String = String::new();

io::stdin().read_line(&mut input).expect("Read Error!");

let mut num = input.trim().parse().expect("Input not a number!");

println!("{}", match num {

1 => "one",

2 => "two",

x if x < 10 => "Something smaller than 10",

_ => "Something not smaller than 10"

});

}

使用cargo build編譯后將ELF文件放入IDA中進(jìn)行分析。這個(gè)ELF文件沒有去除符號(hào)表,便于分析。

0x01. main函數(shù)定位

反匯編完成后,可以看到,左邊欄的函數(shù)名大多很長(zhǎng),但也有一些規(guī)律可循。定位到main函數(shù)發(fā)現(xiàn),main函數(shù)本身只有很少的幾行代碼,但Rust真正的main函數(shù)也不難找。看到0xA020處有一個(gè)main函數(shù),這個(gè)項(xiàng)目筆者將其命名為revlab,而這個(gè)函數(shù)名中也正好就有revlab,因此可以推測(cè)出,這就是我們要找的Rust main函數(shù)。

但我們可以先不急著查看main函數(shù)的具體內(nèi)容,單是這個(gè)main函數(shù)名就有一番研究的必要。_ZN6revlab4main17h512e681518e409c2E,這是Rust編譯器賦予我們自己的main函數(shù)的函數(shù)名。有沒有覺得這個(gè)函數(shù)名的命名規(guī)則很熟悉呢?沒錯(cuò),這種函數(shù)命名方式被稱為name mangling,與C++編譯器對(duì)函數(shù)的命名規(guī)則類似。這里參考資料。我們就可以將這個(gè)函數(shù)名進(jìn)行簡(jiǎn)單的翻譯:revlab::main,前面的_ZN是固定開頭,6代表下一個(gè)模塊的名字長(zhǎng)度,也就是后面的revlab,4相同,即解析main,17h后面是函數(shù)的哈希值,可以忽略。這里通過左邊欄可以看到,IDA能夠自動(dòng)為我們完成函數(shù)名的解析。

0x02. main函數(shù)分析

別看我們第一次寫的main函數(shù)只有短短的幾行,轉(zhuǎn)換成匯編之后卻有點(diǎn)讓人頭疼??紤]到這是我們第一次進(jìn)行分析,筆者嘗試借助其他的工具輔助分析——傳送門。這個(gè)網(wǎng)站可以幫助我們將源代碼與匯編代碼對(duì)應(yīng)起來,幫助我們進(jìn)行分析。

可以看到,main函數(shù)的匯編邏輯還是比較復(fù)雜的,這也是Rust ELF的一個(gè)特點(diǎn),使得Rust反匯編較C/C++更難。

line 1

第一行定義了一個(gè)字符串變量,使用String::new()方法。但是在匯編中可以發(fā)現(xiàn),call調(diào)用String::new()函數(shù)并沒有對(duì)返回值進(jìn)行操作,而是將rdi進(jìn)行了賦值,這與C語(yǔ)言不同,如果按照C語(yǔ)言的邏輯,則更像是String::new(&input)。隨后,筆者修改了代碼進(jìn)行試驗(yàn),發(fā)現(xiàn)Vec的new方法流程類似??梢姼鱾€(gè)對(duì)象的new方法實(shí)際上是傳了參的。

line 2

第二行就比第一行熱鬧多了,由于io::stdin()返回的是Stdin,代碼中使用的返回值與C語(yǔ)言一樣,保存在rax中。不過這里是首先將函數(shù)地址賦值給rax,通過call rax完成調(diào)用。調(diào)用完stdin()后,Rust不知道為什么用了一個(gè)jmp指令,跨越了幾條指令再繼續(xù)執(zhí)行后面的read_line方法。對(duì)于read_line方法,可以看到前3個(gè)寄存器進(jìn)行了賦值。其中rsi是io::stdin()的返回值,也就是Stdin對(duì)象實(shí)例,rdx是字符串input的地址,這一點(diǎn)可以通過第一行對(duì)[rsp+80]賦值得知,那么rdi是什么呢?這里就需要返回到IDA界面查看。

從上圖可知,IDA將第一個(gè)參數(shù)解析為self,類型為core::result::Result,而這個(gè)是read_line函數(shù)的返回值。這與io::stdin()不同,也是沒有將返回值保存到rax。隨后,代碼繼續(xù)向下,繼續(xù)調(diào)用了expect方法,傳入的d第一個(gè)參數(shù)就是Result實(shí)例,第二個(gè)參數(shù)是我們?cè)O(shè)置的錯(cuò)誤字符串Read Error!地址,第三個(gè)參數(shù)為11,推測(cè)是錯(cuò)誤字符串的長(zhǎng)度,第四個(gè)參數(shù)通過查看發(fā)現(xiàn),是這段匯編代碼對(duì)應(yīng)的源代碼在工程中的路徑。由此我們可以發(fā)現(xiàn),如果今后我們需要分析一個(gè)不帶符號(hào)的Rust ELF,發(fā)現(xiàn)有一個(gè)函數(shù)有4個(gè)參數(shù),其中第2、4個(gè)參數(shù)均為字符串,且第4個(gè)參數(shù)是源文件地址、第3個(gè)參數(shù)是第2個(gè)參數(shù)字符串的長(zhǎng)度,那么這個(gè)函數(shù)很有可能就是expect,通過跟蹤第一個(gè)參數(shù)Result對(duì)象,可以繼續(xù)進(jìn)行分析。

匯編代碼看到這里,我們能夠發(fā)現(xiàn),即使代碼順序執(zhí)行,Rust編譯器也一定要在一個(gè)函數(shù)調(diào)用結(jié)束后插入一個(gè)jmp指令,這一點(diǎn)可以從調(diào)用read_line方法可以得知,向下不斷滑動(dòng)窗口也能發(fā)現(xiàn),整個(gè)main函數(shù)似乎是被許多jmp指令劃分為許多小部分。

line 3

第三行首先看到,代碼中使用了deref這個(gè)方法,至于為什么使用這個(gè)方法其實(shí)很好理解。deref傳入的是String實(shí)例,返回的是字符串切片&str,而trim方法實(shí)際上是以切片作為self的,因此這里Rust隱式地將String轉(zhuǎn)成切片之后再執(zhí)行trim。

調(diào)用deref方法后需要注意,這里將rdx和rax保存到了棧中。記得在學(xué)習(xí)字符串切片的時(shí)候,書中有提及字符串切片實(shí)際上由兩個(gè)部分組成——指針與長(zhǎng)度。這里我們只通過靜態(tài)分析無法判斷rdx和rax到底是多少,雖然我們心中可能已經(jīng)知道答案,但這里還是通過簡(jiǎn)單的調(diào)試來驗(yàn)證一下。

可以看到,這與我們的預(yù)期是相同的,rdx保存的是長(zhǎng)度,rax保存的是字符串指針。因此我們知道了,String類型的deref方法會(huì)將返回值保存在兩個(gè)寄存器——rdx與rax中。

好繼續(xù)往下看。隨后就是trim方法的調(diào)用,傳入的第1個(gè)參數(shù)是字符串指針,第2個(gè)參數(shù)是長(zhǎng)度。其返回值依然是保存在兩個(gè)寄存器中??梢妼?duì)于返回值為&str的Rust方法,其返回的方式也有一定規(guī)律。

trim之后是parse,返回值是Result類型,和read_line不同的是,read_line返回的Result實(shí)例沒有泛型(Result),但是parse的返回值是Result,可能是這個(gè)原因,導(dǎo)致read_line可以將Result指針直接作為參數(shù)傳遞,而parse只能通過rax返回。不過目前這只是猜測(cè),有關(guān)于Rust編譯器對(duì)泛型的處理,就留到后面的文章中進(jìn)行分析吧。

隨后,有幾行看似沒有意義的匯編代碼,像是mov qword ptr [rsp + 240], rax,這里的[rsp+240]在main函數(shù)自始至終只有這里被使用過。所以直接忽略。隨后expect的傳參與之前規(guī)則相同。

不過這里的expect是需要將返回值保存在num中的,也就是mov dword ptr [rsp + 28], eax這條語(yǔ)句,可見num是保存在[rsp+0x28]的位置。

line 4~9

下面的幾行是一個(gè)println!一個(gè)match語(yǔ)句的值。在學(xué)Rust的時(shí)候我們了解到,match語(yǔ)句可以實(shí)現(xiàn)類似于lambda函數(shù)的功能,每一個(gè)分支的=>后都可以看成這個(gè)條件下match的返回值。就如這幾行是將match的每一個(gè)分支語(yǔ)句都定義一個(gè)字符串切片作為傳入println! 的格式化參數(shù)。

在上一行語(yǔ)句執(zhí)行結(jié)束后,匯編代碼首先將num的值放到eax中,隨后進(jìn)行分支判斷。判斷順序是:是否等于1、是否等于2、是否小于10,而且match的判斷語(yǔ)句是統(tǒng)一寫在前面,具體的語(yǔ)句內(nèi)容則放在后面。

通過對(duì)分支語(yǔ)句簡(jiǎn)單分析,容易得到match語(yǔ)句的“返回值”是保存在[rsp+208]和[rsp+216],因?yàn)檫@個(gè)是&str,所以要用0x10大小保存。

不過在匯編代碼中,println!的處理流程可能不是都在所有match流程之后,而是在中間插入了一段,隨后又在跳轉(zhuǎn)到后面。使用1.69.0的rustc版本編譯發(fā)現(xiàn)所有的match分支都位于println!之后,而更新版本的1.73.0則是將println!前半部分放在match分支部分中間。

隨后則是println!的宏展開部分,考慮到println!太常見,通過IDA的反匯編輸出的源代碼可以識(shí)別出其特征??梢钥吹皆趨R編中調(diào)用了core::fmt::ArgumentV1::new_display、core::fmt::Arguments::new_v1、std::io::stdio::_print這三個(gè)方法。其中前面兩個(gè)推測(cè)是Rust宏的轉(zhuǎn)換函數(shù),也就是將宏中大括號(hào)部分替換為具體的參數(shù),而最后一個(gè)方法則是輸出內(nèi)容到控制臺(tái)。

對(duì)于第一個(gè)函數(shù),其唯一一個(gè)參數(shù)是match返回的字符串切片的棧地址。而對(duì)于第二個(gè)函數(shù),傳參情況則比較復(fù)雜。根據(jù)下文的_print函數(shù)傳入的參數(shù)判斷,第一個(gè)參數(shù)應(yīng)該是返回值字符串的地址,第二個(gè)參數(shù)指向一個(gè)換行符的地址,但意義不明,第三個(gè)參數(shù)為2,第四個(gè)參數(shù)為第一個(gè)函數(shù)的返回值rax內(nèi)容。第五個(gè)參數(shù)為1。目前只能確定第1個(gè)參數(shù)的含義,因此我們需要請(qǐng)求gdb的幫助。

可以看到,第1個(gè)函數(shù)返回的rax是要輸出的字符串。注意到在ELF中并沒有找到左右大括號(hào){}這個(gè)字符串,判斷可能是Rust使用了其他的方式進(jìn)行解析。但是除了第一個(gè)參數(shù)之外其他參數(shù)的意義還是不明。我們不妨稍稍修改一下println!格式化字符串的值,看看代碼有什么變化。

這里我們將字符串修改為a{}a{},在后面添加一個(gè)1作為第二個(gè)括號(hào)的占位符。隨后我們發(fā)現(xiàn),core::fmt::ArgumentV1::new_display函數(shù)被調(diào)用了兩次。第一次調(diào)用傳入match返回的字符串,而第二次調(diào)用傳入的是這個(gè)東西:

.L__unnamed_27:

.asciz "\001\000\000"

這不正好就是1嗎?也就是說,core::fmt::ArgumentV1::new_display這個(gè)函數(shù)是用來解析println!后面的參數(shù)的,將其轉(zhuǎn)換為字符串切片,有幾個(gè)大括號(hào)就需要調(diào)用幾次。隨后繼續(xù)進(jìn)行分析,發(fā)現(xiàn)匯編代碼將兩個(gè)函數(shù)解析得到的兩個(gè)字符串切片放到了一個(gè)連續(xù)的棧地址空間,并將其作為參數(shù)4(rcx)傳入。

如上圖所示,這里紅框部分就是賦值過程,這個(gè)地方像是一個(gè)數(shù)組的結(jié)構(gòu),按照順序排列每個(gè)大括號(hào)對(duì)應(yīng)的字符串切片。由此便可以判斷出參數(shù)5(r8d)的含義,其實(shí)就是解析的字符串切片的數(shù)量。

接下來我們?cè)倏匆幌聟?shù)2到底是什么東西。參數(shù)2指向了一個(gè)這樣的結(jié)構(gòu):

.L__unnamed_28:

.quad .L__unnamed_36

.asciz "\001\000\000\000\000\000\000"

.quad .L__unnamed_36

.asciz "\001\000\000\000\000\000\000"

.quad .L__unnamed_37

.asciz "\001\000\000\000\000\000\000"

其中有:

.L__unnamed_36:

.byte 97 ; 'a'

.L__unnamed_37:

.byte 10 ; '\n'

這樣看來,這里的含義也就清楚了。編譯器在對(duì)宏進(jìn)行展開時(shí)轉(zhuǎn)義大括號(hào)的內(nèi)容是這樣操作的:

首先將含有大括號(hào)的字符串以大括號(hào)分隔,并形成上面的這個(gè)數(shù)組結(jié)構(gòu)。對(duì)于每一個(gè)大括號(hào),都調(diào)用一次轉(zhuǎn)義函數(shù)進(jìn)行轉(zhuǎn)義,在棧中形成一個(gè)&str的數(shù)組。隨后再調(diào)用另外一個(gè)函數(shù)(core::fmt::Arguments::new_v1)將這些切片拼起來組成最終的字符串。

core::fmt::Arguments::new_v1的5個(gè)參數(shù)含義分別就是:

rdi:輸出字符串指針rsi:預(yù)編譯的數(shù)組結(jié)構(gòu),表示宏不需要轉(zhuǎn)義的字符串部分rdx:預(yù)編譯數(shù)組結(jié)構(gòu)的長(zhǎng)度rcx:運(yùn)行時(shí)解析的已經(jīng)被轉(zhuǎn)義的&str數(shù)組r8:運(yùn)行時(shí)解析的&str數(shù)組長(zhǎng)度

這個(gè)函數(shù)調(diào)用完之后,就可以進(jìn)行宏展開的后續(xù)代碼了。對(duì)于println!而言是輸出,也即調(diào)用std::io::stdio::_print。

輸出之后,后面就沒有多少代碼了:

.LBB60_18:

lea rdi, [rsp + 80]

call qword ptr [rip + core::ptr::drop_in_place@GOTPCREL]

add rsp, 248

ret

mov rax, qword ptr [rip + core::panicking::panic_cannot_unwind@GOTPCREL]

call rax

ud2

.LBB60_20:

mov rdi, qword ptr [rsp + 224]

call _Unwind_Resume@PLT

ud2

這里的core::ptr::drop_in_place應(yīng)該是Rust將這個(gè)String對(duì)象實(shí)例回收了。隨后將棧上抬,main函數(shù)就正常返回了。

0x03. IDA反匯編

上一節(jié)我們對(duì)Rust ELF的分析大多是基于匯編層面進(jìn)行的,當(dāng)代碼量比較多的時(shí)候,基本塊之間的跳轉(zhuǎn)關(guān)系可能會(huì)更加復(fù)雜,不利于我們的分析。不過IDA提供了非常實(shí)用的反匯編功能,在分析時(shí),筆者認(rèn)為如果我們能夠?qū)⒎磪R編的內(nèi)容與純匯編代碼相結(jié)合,效果會(huì)更好。

但I(xiàn)DA的反匯編功能一開始畢竟是為C/C++設(shè)計(jì)的,對(duì)于Rust的反匯編結(jié)果不很直觀也是正常的。

在反匯編的輸出結(jié)果中,出現(xiàn)了比較奇怪的地方。

最為明顯的就是字符串的解析。通過查看ELF中保存字符串的地方可以發(fā)現(xiàn),Rust的字符串與字符串之間有的是以換行符隔開的,有的根本就沒有分割的字符,這與C/C++使用0字符分割每個(gè)字符串不同。因?yàn)镽ust字符串切片的特性,對(duì)一個(gè)字符串切片的操作必然需要使用到這個(gè)切片的長(zhǎng)度。既然已經(jīng)知道了字符串的長(zhǎng)度,字符串與字符串之間的分隔就顯得沒有那么必要了。

不過慶幸的是,反匯編中對(duì)于main函數(shù)的主要邏輯的解析還是比較清楚的,第一行的String::new()表示創(chuàng)建了一個(gè)String實(shí)例,隨后多個(gè)函數(shù)的調(diào)用連在一起就組成了第二行的讀取字符串內(nèi)容,就是expect函數(shù)的解析看上去不是很舒服,畢竟其與C/C++的函數(shù)調(diào)用規(guī)則有些許不同。

再往下,可以看到deref、trim、parse、expect,這些函數(shù)組成了第三行的內(nèi)容。

對(duì)于接下來的match,在反匯編界面中是將其解析成了多個(gè)if-else語(yǔ)句。隨后就是println!的宏展開,輸出字符串。輸出后通過drop_in_place刪除了一開始創(chuàng)建的String實(shí)例,函數(shù)返回。

0x04. 總結(jié)

以上就是我們的第一次Rust逆向嘗試,還是有很多收獲的,下面是本文的總結(jié):

Rust的main函數(shù)與ELF中的main不同,但很好找。Rust編譯器喜歡將代碼用jmp指令分割為一個(gè)個(gè)小部分。對(duì)于返回&str的方法,是將切片的指針和長(zhǎng)度分別保存在rax和rdx之中。對(duì)于struct的new方法,一般可在反匯編界面中直接識(shí)別,在匯編中實(shí)際執(zhí)行的更像是通過xxx.new(&target)的方式進(jìn)行初始化。Rust對(duì)宏展開的處理有一定的規(guī)律,可通過這些規(guī)律在反匯編界面中識(shí)別出宏展開的部分。

不得不說,Rust編譯器在匯編層面的處理還是有點(diǎn)意思的。在后面的文章中,我們將嘗試分析更加復(fù)雜的代碼,嘗試整理出更多Rust語(yǔ)言特性在匯編層面中的實(shí)現(xiàn)方式。

柚子快報(bào)邀請(qǐng)碼778899分享:Rust逆向?qū)W習(xí) (1)

http://yzkb.51969.com/

相關(guān)閱讀

評(píng)論可見,查看隱藏內(nèi)容

本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點(diǎn)和立場(chǎng)。

轉(zhuǎn)載請(qǐng)注明,如有侵權(quán),聯(lián)系刪除。

本文鏈接:http://gantiao.com.cn/post/17823058.html

發(fā)布評(píng)論

您暫未設(shè)置收款碼

請(qǐng)?jiān)谥黝}配置——文章設(shè)置里上傳

掃描二維碼手機(jī)訪問

文章目錄