此係列是本人一個字一個字碼出來的,包括範例和實驗截圖。本人非計算機專業,可能對本教學涉及的事物沒有了解的足夠深入,如有錯誤,歡迎批評指正。 如有好的建議,歡迎反饋。碼字不易,如果本篇文章有幫助你的,如有閒錢,可以打賞支援我的創作。如想轉載,請把我的轉載資訊附在文章後面,並宣告我的個人資訊和本人部落格地址即可,但必須事先通知我。
你如果是從中間插過來看的,請仔細閱讀 跟羽夏學 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 ——參照