跟羽夏學 Ghidra ——工具

2022-09-11 21:01:16

寫在前面

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

你如果是從中間插過來看的,請仔細閱讀 跟羽夏學 Ghidra ——簡述 ,方便學習本教學。請認準 部落格園寂靜的羽夏 ,目前僅在該平臺釋出。

實驗程式碼

  在該教學開始之前,我先把實驗程式的程式碼放上,之後所有的文章的實驗程式都是依靠該程式碼,採用C編寫,是個Linux編譯器GCC都會有的:

// License : GPL
// Author : 寂靜的羽夏,部落格園,wingsummer
// Comment : 本程式碼由寂靜的羽夏進行編寫範例,提供講解介紹之用,未經授權不能用於商業用途

#include <stdio.h>

/*======= 變數 =======*/

// 全域性變數
char gvar1;
int gvar2;

struct tstruct
{
    char var1;
    short var2;
    int var3;
    long var4;
} gstruct;

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;
}

/*======= 迴圈 =======*/

void loop()
{
    puts("for 迴圈");
    for (int i = 0; i < 5; i++)
        printf("i | for : %d\n", i);

    puts("do while 迴圈");
    int i = 0;
    do
    {
        printf("i | do : %d\n", i);
    } while (i < 5);

    puts("while 迴圈");
    i = 0;
    while (i < 5)
    {
        printf("i | while : %d\n", i);
    }
}

/*======= 函數 =======*/

void test1() { puts("test1 func exec!"); }

void test2(int arg1) { printf("test2 func exec : %d\n", arg1); }

char test3(int arg1, char arg2) { printf("test3 func exec : %d , %c\n", arg1, arg2); }

int test4(int arg1, int arg2, int arg3, int arg4, int arg5, int arg6)
{
    printf("test4 func exec : %d , %d , %d , %d , %d , %d\n", arg1, arg2, arg3, arg4, arg5, arg6);
}

/*===== 破解範例 ======*/

int crackMe();
int getKey();

/*===================*/

int main()
{
    while (1)
    {
        puts("歡迎來到「寂靜的羽夏」的 Ghidra 教學教學,請輸入數位來進行破解訓練:\n"
             "0. 退出訓練\n"
             "1. 變數訓練\n"
             "2. 迴圈訓練\n"
             "3. 函數訓練\n"
             "4. 破解範例\n");

        int sw;
        scanf("%d", &sw);

        switch (sw)
        {
        case 1:
            variable();
            break;
        case 2:
            loop();
            break;
        case 3:
            test1();
            puts("===");
            test2(1);
            puts("===");
            char i1 = test3(5, 'A');
            printf("ret : %c", i1);
            puts("===");
            int i2 = test4(1, 2, 3, 4, 5, 6);
            printf("ret : %d", i2);
            break;
        case 4:
            if (crackMe())
                puts(">> 祝賀破解成功!");
            else
                puts("抱歉,沒成功哦,再試一次!");
            setbuf(stdin, NULL);
            break;
        default:
            goto _exit;
        }
    }

_exit:

    puts("按任意鍵繼續 ...");
    getchar();
    return 0;
}

int crackMe()
{
    int key = getKey();
    if (key ^ 5 == 0x123456)
        return 0;
    return 1;
}

int getKey()
{
    int key;
    puts("請輸入金鑰:");
    scanf("%d", &key);
    return key;
}

安裝 Ghidra

  下面來簡單介紹一下如何安裝。
  如果你電腦上沒有星火商店,請到Github,不過可能不能順利進入,它的 連結
  如果有星火商店,直接搜Ghidra就是,那個是我(寂靜的羽夏)單獨打的一個包,能夠把它需要的Java依賴給一塊安裝上,因為軟體比較大,安裝起來可能需要花費一些時間,尤其沒有預先裝依賴的:

  安裝好後,它的目錄為/opt/Ghidra。經過測試,在Deepin 20.7上使用我的打包不需要進行額外的設定。但為了以防萬一,請按照我在星火商店的宣告做好設定。如果正常啟動,最終將會是這個介面:

  下面我繼續介紹在 Linux 平臺幾個用於逆向分析的幾個實用工具。

file

  file是一個命令列工具,通過魔數識別檔案型別。給個例子:

┌─[wingsummer][wingsummer-PC][~/.../C/Code]
└─➞ file tutorial
tutorial: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=6d9b944f22066b0b4925a4471cdbf616d8d50da5, not stripped

  這個是將範例程式碼編譯後的檔案,使用file命令列識別,我們可以獲得它是一個ELF檔案,是64位的等資訊。
  如果想更加深入瞭解file這一個命令列工具,請在終端輸入man file,檢視幫助檔案。

nm

  將原始檔編譯為目標檔案時,編譯器必須嵌入有關全域性(外部)符號位置的資訊,以便連結器在組合目標檔案以建立可執行檔案時能夠解析對這些符號的參照。除非指示從最終可執行檔案中刪除符號,否則連結器通常將符號從目標檔案中帶到最終可執行程式中。nm就可以列出這樣的符號:

┌─[wingsummer][wingsummer-PC][~/.../C/Code]
└─➞ nm tutorial
0000000000404050 B __bss_start
0000000000404058 b completed.7325
0000000000401480 T crackMe
0000000000404040 D __data_start
0000000000404040 W data_start
00000000004010c0 t deregister_tm_clones
00000000004010b0 T _dl_relocate_static_pie
0000000000401130 t __do_global_dtors_aux
0000000000403e18 t __do_global_dtors_aux_fini_array_entry
0000000000404048 D __dso_handle
0000000000403e20 d _DYNAMIC
0000000000404050 D _edata
0000000000404088 B _end
0000000000401544 T _fini
0000000000401160 t frame_dummy
0000000000403e10 t __frame_dummy_init_array_entry
000000000040248c r __FRAME_END__
                 U getchar@@GLIBC_2.2.5
00000000004014a9 T getKey
0000000000404000 d _GLOBAL_OFFSET_TABLE_
                 w __gmon_start__
0000000000402214 r __GNU_EH_FRAME_HDR
0000000000404070 B gstruct
0000000000404080 B gvar1
0000000000404060 B gvar2
0000000000401000 T _init
0000000000403e18 t __init_array_end
0000000000403e10 t __init_array_start
0000000000402000 R _IO_stdin_used
                 U __isoc99_scanf@@GLIBC_2.7
0000000000401540 T __libc_csu_fini
00000000004014e0 T __libc_csu_init
                 U __libc_start_main@@GLIBC_2.2.5
00000000004011e1 T loop
0000000000401325 T main
                 U printf@@GLIBC_2.2.5
                 U puts@@GLIBC_2.2.5
00000000004010f0 t register_tm_clones
                 U setbuf@@GLIBC_2.2.5
0000000000401080 T _start
0000000000404050 B stdin@@GLIBC_2.2.5
0000000000401275 T test1
0000000000401286 T test2
00000000004012a8 T test3
00000000004012d3 T test4
0000000000404050 D __TMC_END__
0000000000401162 T variable

  上面是已經編譯好的範例程式碼,如果是obj檔案,就會成這樣:

┌─[wingsummer][wingsummer-PC][~/.../C/Code]
└─➞ nm tutorial.o                                                                                              
000000000000031e T crackMe
                 U getchar
0000000000000347 T getKey
0000000000000010 C gstruct
0000000000000001 C gvar1
0000000000000004 C gvar2
                 U __isoc99_scanf
000000000000007f T loop
00000000000001c3 T main
                 U printf
                 U puts
                 U setbuf
                 U stdin
0000000000000113 T test1
0000000000000124 T test2
0000000000000146 T test3
0000000000000171 T test4
0000000000000000 T variable

  從輸出中我可以看到中間有些單字母,它就有特殊含義,列一下:

字母 含義
U 未定義的符號(通常是外部符號參照)
T text中定義的符號(通常是函數名)
t text中定義的本地符號。在C程式中,這通常等同於靜態函數
D 初始化的資料值
C 未初始化到資料值

  當顯示可執行檔案中的符號時,會顯示更多資訊。在連結過程中,符號被解析為虛擬地址(如果可能),這導致在執行nm時可以獲得更多資訊,正如上面所示的。

ldd

  建立可執行檔案時,必須解析該可執行檔案參照的任何庫函數的位置。連結器有兩種方法來解析對庫函數的呼叫:靜態連結和動態連結。提供給連結器的命令列引數決定使用這兩種方法中的哪一種。可執行檔案可以被靜態連結、動態連結或兩者都有。
  當使用靜態連結時,連結器將應用程式的物件檔案與所需庫的副本組合,以建立可執行檔案。在執行時,不需要定位庫程式碼,因為它已經包含在可執行檔案中。
  動態連結不同於靜態連結,因為連結器不需要複製任何所需的庫。
  這兩種方式各有優缺,根據自己的需要進行。通過ldd,我們可以獲取該類資訊:

┌─[wingsummer][wingsummer-PC][~/.../C/Code]
└─➞ ldd tutorial                                                                                               
        linux-vdso.so.1 (0x00007fffb53ec000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fe91797e000)
        /lib64/ld-linux-x86-64.so.2 (0x00007fe917b6e000)

注意:該工具不要用於處理不受信任的可執行檔案,否則有可能會被執行惡意程式碼

strings

  strings專門用於從檔案中提取字串內容,通常不考慮這些檔案的格式。使用具有預設設定的字串(至少四個字元的7位ASCII序列):

┌─[wingsummer][wingsummer-PC][~/.../C/Code]
└─➞ strings tutorial                                                                                           
/lib64/ld-linux-x86-64.so.2
libc.so.6
__isoc99_scanf
puts
stdin
printf
getchar
setbuf
__libc_start_main
GLIBC_2.7
GLIBC_2.2.5
__gmon_start__
H=P@@
[]A\A]A^A_
for 
i | for : %d
do while 
i | do : %d
while 
i | while : %d
test1 func exec!
test2 func exec : %d
test3 func exec : %d , %c
test4 func exec : %d , %d , %d , %d , %d , %d
 Ghidra 
ret : %c
ret : %d
 ...
;*3$"
GCC: (Uos 8.3.0.3-3+rebuild) 8.3.0
crtstuff.c
deregister_tm_clones
__do_global_dtors_aux
completed.7325
__do_global_dtors_aux_fini_array_entry
frame_dummy
__frame_dummy_init_array_entry
tutorial.c
__FRAME_END__
__init_array_end
_DYNAMIC
__init_array_start
__GNU_EH_FRAME_HDR
_GLOBAL_OFFSET_TABLE_
__libc_csu_fini
puts@@GLIBC_2.2.5
stdin@@GLIBC_2.2.5
loop
_edata
crackMe
test3
test1
setbuf@@GLIBC_2.2.5
printf@@GLIBC_2.2.5
gvar2
gstruct
variable
getKey
__libc_start_main@@GLIBC_2.2.5
__data_start
getchar@@GLIBC_2.2.5
__gmon_start__
__dso_handle
_IO_stdin_used
__libc_csu_init
_dl_relocate_static_pie
__bss_start
main
test4
test2
gvar1
__isoc99_scanf@@GLIBC_2.7
__TMC_END__
.symtab
.strtab
.shstrtab
.interp
.note.ABI-tag
.note.gnu.build-id
.gnu.hash
.dynsym
.dynstr
.gnu.version
.gnu.version_r
.rela.dyn
.rela.plt
.init
.text
.fini
.rodata
.eh_frame_hdr
.eh_frame
.init_array
.fini_array
.dynamic
.got
.got.plt
.data
.bss
.comment

  雖然我們看到一些字串看起來像是程式輸出的,但其他字串似乎是函數名和庫名。我們應該小心,不要對程式的行為做出任何結論。記住,二進位制檔案中字串的存在絕不表示該二進位制檔案以任何方式使用該字串。
  如果該程式加上-t引數,會加上字串的位置:

┌─[wingsummer][wingsummer-PC][~/.../C/Code]
└─➞ strings -t x tutorial                                                                                    
    2a8 /lib64/ld-linux-x86-64.so.2
    409 libc.so.6
    413 __isoc99_scanf
    422 puts
    427 stdin
    42d printf
    434 getchar
    43c setbuf
    443 __libc_start_main
    ...

  加上-e引數可以指定字串格式,比如16位元的Unicode(範例程式沒有):

┌─[wingsummer][wingsummer-PC][~/.../C/Code]
└─➞ strings -e l tutorial 

結語

  本文提供的分析命令列工具並不一定是最好的。然而,它們確實代表了任何希望對二進位制檔案進行反向工程的人都可以使用的工具。更重要的是,它們代表了推動Ghidra發展的工具。之後的博文,我們將逐步深入Ghidra

下一篇

  跟羽夏學 Ghidra ——初識