跟羽夏學 Ghidra ——數據
- 2022 年 9 月 14 日
- 筆記
- 跟羽夏學 Ghidra, 逆向
寫在前面
此系列是本人一個字一個字碼出來的,包括示例和實驗截圖。本人非計算機專業,可能對本教程涉及的事物沒有了解的足夠深入,如有錯誤,歡迎批評指正。 如有好的建議,歡迎反饋。碼字不易,如果本篇文章有幫助你的,如有閑錢,可以打賞支持我的創作。如想轉載,請把我的轉載信息附在文章後面,並聲明我的個人信息和本人博客地址即可,但必須事先通知我。
你如果是從中間插過來看的,請仔細閱讀 跟羽夏學 Ghidra ——簡述 ,方便學習本教程。請認準 博客園 的 寂靜的羽夏 ,目前僅在該平台發佈。
前言
本篇涉及一些底層的知識,主要是變量在內存的分配情況。如果不清楚,可以參考我的 羽夏看C語言系列教程 。雖然是Win
平台的,但相差無幾,原理相通。
在開始之前,一定要將代碼編譯好(其實上一篇就開始用了),這次我們要開始用示例進行學習工作。注意在簡述說的基礎知識都一定要會,不過我用的時候會提一下。
本篇寂靜的羽夏的博文,將專註於variable
函數的分析,緊扣「數據」關鍵詞。
函數定位
我們從分析variable
函數開始,講解與數據相關的知識,首先需要定位。那麼如何定位呢?
通過上一篇博文的學習,我們知道了需要從Symbol Tree
就能找到,雙擊就能跳轉到函數位置:
與此同時,我們可以看到反彙編的內容:
void variable(void)
{
gvar1 = 0x31;
gvar2 = 5;
puts("===");
puts("===");
gvar2 = 5;
gstruct[0] = 1;
gstruct._2_2_ = 2;
gstruct._4_4_ = 3;
gstruct._8_8_ = 4;
gvar1 = 0x50;
return;
}
和我們的源碼作比較:
// Written by WingSummer
void variable()
{
//局部變量
gvar1 = '1';
gvar2 = 5;
puts("===");
struct tstruct lstruct;
lstruct.var1 = 1;
lstruct.var2 = 2;
lstruct.var3 = 3;
lstruct.var4 = 4;
// 全局變量賦值
puts("===");
gvar1 = 'P';
gvar2 = 5;
gstruct.var1 = 1;
gstruct.var2 = 2;
gstruct.var3 = 3;
gstruct.var4 = 4;
}
可以看出,類型對不上,局部變量的結構體相關賦值被吃掉了。但是,Ghidra
已經在反彙編結果中有了提示:
undefined AL:1 <RETURN>
undefined8 Stack[-0x10]:8 local_10 XREF[1]: 00401196(W)
undefined4 Stack[-0x14]:4 local_14 XREF[1]: 0040118f(W)
undefined2 Stack[-0x16]:2 local_16 XREF[1]: 00401189(W)
undefined1 Stack[-0x18]:1 local_18 XREF[1]: 00401185(W)
Ghidra
已經識別到了局部變量,但是,並沒有使用,反彙編並沒有將其列入。不過,我們先把反彙編的變量名和源代碼的名字弄的一致。有如下三種方法:
- 在符號樹列表:
- 在反編譯窗口:
- 在反彙編窗口:
你可以使用以上方式來修改。
在我們的源代碼中gvar1 = '1'
,在反彙編就成了gvar1 = 0x31
,這個是由於反彙編是使用的ASCII
表示的字符1
,我們有兩種方式進行轉化:
- 在反編譯窗口:
- 在反彙編窗口:
與此同時,我們在程序中用到了大量的結構體,但是Ghidra
並沒有完全給識別出來,那麼怎麼創建呢:
在Data Type Manager
中,選中tutorial
(因為我的程序的名字叫這個),右擊菜單的New
,然後找到Structure
,點擊,最終的編輯結果:
在這裡注意的一點是,結構體是四個位元組對齊的,至於為什麼自己回去複習功課。
我們編輯完畢後,點擊保存按鈕,那麼我們如何使用我們自定義類型呢:
點擊後,搜索找到我們的類型,點擊確定,最終會得到下面的結果:
void variable(void)
{
tstruct lstruct;
gvar1 = '1';
gvar2 = 5;
puts("===");
puts("===");
gvar2 = 5;
gstruct.var1 = 1;
gstruct.var2 = 2;
gstruct.var3 = 3;
gstruct.var4 = 4;
gvar1 = 'P';
return;
}
至此,創建結構體的方式學會了,我們就可以創建枚舉、別名、共用體。這些我就不在本篇贅述了,請自行觸類旁通。
回到主函數
下面我們回到主函數,繼續學習如何更改函數簽名以及修正原是字符串的未知類型。
主函數的反編譯結果如下:
undefined8 main(void)
{
int iVar1;
int local_14;
uint local_10;
char local_9;
while( true ) {
while( true ) {
while( true ) {
while( true ) {
puts(&DAT_004020d8);
__isoc99_scanf(&DAT_0040218c,&local_14);
if (local_14 != 2) break;
loop();
}
if (2 < local_14) break;
if (local_14 != 1) goto LAB_0040146a;
variable();
}
if (local_14 != 3) break;
test1();
puts("===");
test2(1);
puts("===");
local_9 = test3(5,0x41);
printf("ret : %c",(ulong)(uint)(int)local_9);
puts("===");
local_10 = test4(1,2,3,4,5,6);
printf("ret : %d",(ulong)local_10);
}
if (local_14 != 4) break;
iVar1 = crackMe();
if (iVar1 == 0) {
puts(&DAT_004021c0);
}
else {
puts(&DAT_004021a1);
}
setbuf(stdin,(char *)0x0);
}
LAB_0040146a:
puts(&DAT_004021e8);
getchar();
return 0;
}
首先我們修改一下主函數的函數聲明,也就是函數簽名(有三種方式):
- 在符號樹列表:
- 在反編譯窗口:
- 在反彙編窗口:
點擊會彈出一個窗體,我們修改一下如下圖所示:
可以看到,通過該對話框可以修改函數名、返回值、調用約定以及函數屬性(不定參數、內斂函數、無返回值),更改參數,這些可以自行探索。
接下來我們看到puts(&DAT_004020d8)
這樣的代碼,這明明是字符串,但並沒有識別到,僅僅被認為是普通數據。我們可以通過修改識別為ASCII
字符串:
修改後,是如下結果:
s__Ghidra_0._1._2._3._4._004020d8 XREF[2]: main:0040132d(*),
main:0040132d(*)
004020d8 e6 ac ds E6h,ACh,A2h,E8h,BFh,8Eh,E6h,9Dh,A5h,
a2 e8
bf 8e
但是這完全沒有正常字符串的樣子,這個是編碼問題。在 Linux 下,中文的編碼通常是UTF-8
,我們需要修改一下:
點擊後,就會彈出一下彈窗,修改如下:
最終,我們的結果如下(由於字符串太長,被隱掉了):
s__Ghidra_0._1._2._3._4._004020d8 XREF[2]: main:0040132d(*),
main:0040132d(*)
004020d8 e6 ac ds u8"歡迎來到「寂靜的羽夏」的 Ghidra 教學教程
a2 e8
bf 8e
拓展
在本寂靜的羽夏的實驗示例中,我們還沒有涉及數組以及如果將代碼識別為數據或者把數據是別為代碼,這怎樣處理,下面開始介紹。
由於沒有提前設計,我們假設variable
函數中的gvar2
是一個數組,長度是5,那麼我們如何轉化呢?
在gvar2
的位置右擊,找到Data
,選中Create Array
,你將會得到如下界面:
輸入數字即可,由於它不是,就不用點確定了。
下面我們取消之前的假設,我們做一個新的,假設variable
函數開頭的一句彙編是數據,其實不是代碼,我們如何將其轉為數據呢?
我們先把代碼轉為數據:
點擊後,該處彙編將會變成未定義類型的位元組。但我反悔了,我又想把它弄成數據(不要撤銷):
右鍵菜單通常比較麻煩,這裡只是為了介紹才這麼做,通常用C
和D
這兩個快捷鍵,對代碼和數據之間進行轉化。
其次,我還沒有介紹如何更改類型,只是說了如何使用自己定義的類型。下面看一下:
點擊後,會彈出一個對話框,輸入正確的類型名字即可。
至此,該博文結束。
下一篇
跟羽夏學 Ghidra ——引用