nsa構架
by Denis Nu?iu
丹尼斯·努尤(Denis Nu?iu)
我如何使用NSA的Ghidra解決了一個簡單的CrackMe挑戰 (How I solved a simple CrackMe challenge with the NSA’s Ghidra)
Hello!
你好!
I’ve been playing recently a bit with Ghidra, which is a reverse engineering tool that was recently open sourced by the NSA. The official website describes the tool as:
我最近在玩Ghidra ,這是一種反向工程工具,最近由NSA開源。 官方網站將該工具描述為:
A software reverse engineering (SRE) suite of tools developed by NSA’s Research Directorate in support of the Cybersecurity mission.
由NSA研究部開發的軟件逆向工程(SRE)工具套件,用于支持網絡安全任務。
I’m at the beginning of my reverse engineering career, so I didn’t do anything advanced. I don’t know what features to expect from a professional tool like this, if you’re looking to read about advanced Ghidra features this is likely not the article for you.
我正處于逆向工程職業的開始,所以我沒有做任何高級工作。 我不知道從這樣的專業工具中可以獲得什么功能,如果您想閱讀有關Ghidra的高級功能的信息,那么這篇文章可能不適合您。
In this article I will try to solve a simple CrackMe challenge that I’ve found on the website root-me. The challenge I’m solving is called ELF - CrackPass. If you want to give it try by yourself, then you should consider not reading this article because it will spoil the challenge from you.
在本文中,我將嘗試解決在root-me網站上發現的一個簡單的CrackMe挑戰。 我要解決的挑戰稱為ELF-CrackPass 。 如果您想自己嘗試一下,那么您應該考慮不閱讀本文,因為它會破壞您的挑戰。
Let’s get started! I open up Ghidra and create a new Project which I call RootMe.
讓我們開始吧! 我打開Ghidra并創建一個名為RootMe的新項目。
Then I import the challenge file by dragging it to the project folder. I will go with the defaults.
然后,通過將挑戰文件拖到項目文件夾中來導入它。 我將使用默認值。
After being presented with some info about the binary file, I press OK, select the file, and double click it. This opens up Ghidra’s code browser utility and asks if I want to analyse the file, then I press Yes and go on with the defaults.
在顯示了有關二進制文件的一些信息之后,我按OK,選擇文件,然后雙擊它。 這將打開Ghidra的代碼瀏覽器實用程序,并詢問我是否要分析文件,然后按“是”并繼續使用默認值。
After we import the file, we get some information about the binary file. If we press OK and dismiss this window, and then double click the file we imported, this opens up Ghidra’s code browser utility. I select Yes when prompted to analyze the binary and go on with the defaults.
導入文件后,我們將獲得有關二進制文件的一些信息。 如果我們按OK并關閉此窗口,然后雙擊我們導入的文件,這將打開Ghidra的代碼瀏覽器實用程序。 當提示您分析二進制文件并繼續使用默認值時,我選擇“是”。
The Code Browser is quite convenient. In the left panel we get to see the disassembly view and in the right panel the decompile view.
代碼瀏覽器非常方便。 在左側面板中,我們可以看到反匯編視圖,在右側面板中,可以看到反編譯視圖。
Ghidra shows us directly the ELF header info and the entry point of the binary. After double clicking the entry point, the dissembler view jumps to the entry function.
Ghidra直接向我們顯示了ELF標頭信息和二進制文件的入口點。 雙擊入口點后,反匯編器視圖跳轉到入口功能。
Now we can successfully identify the main function, which I rename to main. It would be nice if the tool would attempt to automatically detect the main function and rename it accordingly.
現在我們可以成功識別main函數,我將其重命名為main。 如果該工具嘗試自動檢測主要功能并相應地對其進行重命名,那就太好了。
Before analyzing the main function, I wanted to change its signature. I changed the return type to int and corrected the parameters’ type and name. This change has taken effect in the decompile view which is cool! ?
在分析主要功能之前,我想更改其簽名。 我將返回類型更改為int并更正了參數的類型和名稱。 此更改已在反編譯視圖中生效,這很酷! ?
Highlighting a line in the decompile view also highlights it in the assembly view.
在反編譯視圖中突出顯示一行,在裝配視圖中也突出顯示該行。
Let’s explore the FUN_080485a5 function, which I’ll rename to CheckPassword.
讓我們探索FUN_080485a5函數,我將其重命名為CheckPassword。
The contents of the CheckPassword function can be found below. I’ve copied the code directly from Ghidra’s decompile view, which is a neat feature that many tools of this type lack! Being able to copy assembly and code is a nice to have feature.
CheckPassword函數的內容可以在下面找到。 我直接從Ghidra的反編譯視圖復制了代碼,這是許多此類工具所缺乏的一項精巧功能! 能夠復制程序集和代碼是很不錯的功能。
void CheckPassword(char *param_1) { ushort **ppuVar1; int iVar2; char *pcVar3; char cVar4; char local_108c [128]; char local_100c [4096]; cVar4 = param_1; if (cVar4 != 0) { ppuVar1 = __ctype_b_loc(); pcVar3 = param_1; do { if (((byte )(ppuVar1 + (int)cVar4) & 8) == 0) { puts("Bad password !"); /* WARNING: Subroutine does not return */ abort(); } cVar4 = pcVar3[1]; pcVar3 = pcVar3 + 1; } while (cVar4 != 0); } FUN_080484f4(local_100c,param_1); FUN_0804851c(s_THEPASSWORDISEASYTOCRACK_08049960,local_108c); iVar2 = strcmp(local_108c,local_100c); if (iVar2 == 0) { printf("Good work, the password is : \n\n%s\n",local_108c); } else { puts("Is not the good password !"); } return; }
After taking a look at the code, I’ve come to the following conclusions. The block with the if
checks if the user has provided a password and inspects the provided password to check if it’s a valid character or something. I’m not exactly sure what it’s checking for, but here’s what __ctype_b_loc()’s documentation says:
看了一下代碼之后,我得出以下結論。 帶有if
的塊檢查用戶是否提供了密碼,并檢查提供的密碼以檢查它是否是有效字符或其他內容。 我不確定要檢查什么,但這是__ctype_b_loc()的文檔所說的內容:
The __ctype_b_loc() function shall return a pointer into an array of characters in the current locale that contains characteristics for each character in the current character set. The array shall contain a total of 384 characters, and can be indexed with any signed or unsigned char (i.e. with an index value between 128 and 255). If the application is multi-threaded, the array shall be local to the current thread.
__ctype_b_loc()函數應將指針返回到當前語言環境中的字符數組,該數組包含當前字符集中每個字符的特征。 該數組總共應包含384個字符,并且可以使用任何有符號或無符號char(即,索引值介于128和255之間)進行索引。 如果應用程序是多線程的,則該數組應位于當前線程的本地。
Anyways, that block of code is not really worth the time, because it doesn’t modify our password in any way, it just verifies it. So we can skip this kind of verification.
無論如何,那段代碼確實不值得花時間,因為它不會以任何方式修改我們的密碼,而只是對其進行驗證。 因此,我們可以跳過這種驗證。
The next function called is FUN_080484f4. Looking at its code, we can tell that it’s just a custom memcopy implementation. Instead of copying the C code from the decompiler view, I copied the assembly code — yes, this is fun.
下一個調用的函數是FUN_080484f4。 查看其代碼,我們可以看出它只是一個自定義的內存復制實現。 我沒有從反編譯器視圖復制C代碼,而是復制了匯編代碼-是的,這很有趣。
************************************************************* * FUNCTION ************************************************************* undefined FUN_080484f4 (undefined4 param_1 , undefined4 p undefined AL:1 <RETURN> undefined4 Stack[0x4]:4 param_1 XREF[1]: 080484f8 (R) undefined4 Stack[0x8]:4 param_2 XREF[1]: 080484fb (R) FUN_080484f4 XREF[1]: CheckPassword:080485f5 (c) 080484f4 55 PUSH EBP 080484f5 89 e5 MOV EBP ,ESP 080484f7 53 PUSH EBX 080484f8 8b 5d 08 MOV EBX ,dword ptr [EBP + param_1 ] 080484fb 8b 4d 0c MOV ECX ,dword ptr [EBP + param_2 ] 080484fe 0f b6 11 MOVZX EDX ,byte ptr [ECX ] 08048501 84 d2 TEST DL,DL 08048503 74 14 JZ LAB_08048519 08048505 b8 00 00 MOV EAX ,0x0 00 00 LAB_0804850a XREF[1]: 08048517 (j) 0804850a 88 14 03 MOV byte ptr [EBX + EAX *0x1 ],DL 0804850d 0f b6 54 MOVZX EDX ,byte ptr [ECX + EAX *0x1 + 0x1 ] 01 01 08048512 83 c0 01 ADD EAX ,0x1 08048515 84 d2 TEST DL,DL 08048517 75 f1 JNZ LAB_0804850a LAB_08048519 XREF[1]: 08048503 (j) 08048519 5b POP EBX 0804851a 5d POP EBP 0804851b c3 RETComment: param_1 is dest, param_2 is src. 08048501 checks if src is null and if it is it returns else it initializes EAX (index, current_character) with 0. The next instructions move bytes into EBX (dest) from EDX (src).The loop stops when EDX is null.
And the other function FUN_0804851c generates the password from the “THEPASSWORDISEASYTOCRACK” string. Looking at the decompiled view. we can roughly see how this function works. If we didn’t have that, we would need to manually analyze every assembly instruction from the function to understand what it does.
另一個功能FUN_0804851c從“ THEPASSWORDISEASYTOCRACK”字符串生成密碼。 查看反編譯視圖。 我們可以大致了解此功能的工作原理。 如果沒有,我們將需要手動分析函數中的每個匯編指令以了解其功能。
Then, we compare the previously generated password with the password that we got from the user (the first argument, argv[1]). If it matches, the program says good job and prints it, else it prints an error message.
然后,我們將先前生成的密碼與從用戶那里獲得的密碼(第一個參數argv [1])進行比較。 如果匹配,則程序會說好并打印,否則會打印錯誤消息。
From this basic analysis, we can conclude that if we patch the program in various places, we can get it to spit the password without us needing to reverse any C function and write code. Patching the program means changing some of its instructions.
從這個基礎分析中,我們可以得出結論,如果我們在不同的地方打補丁程序,我們就可以吐出密碼,而無需反轉任何C函數和編寫代碼。 修補程序意味著更改其某些指令。
Let’s see what we have to patch:
讓我們看看我們需要修補的內容:
At address 0x0804868c we patch the JNS instruction into a JMP. And voilà, the change is reflected in the decompiler view. The ptrace result check is bypassed.
在地址0x0804868c,我們將JNS指令修補到JMP中。 而且,更改反映在反編譯器視圖中。 ptrace結果檢查被繞過。
{ ptrace(PTRACE_TRACEME,0,1,0); if (argc != 2) { puts("You must give a password for use this program !"); /* WARNING: Subroutine does not return */ abort(); } CheckPassword(argv[1]); return 0;}
At address 0x080485b8 we patch the JZ instruction into a JMP. We bypass that password verification block we saw earlier.
在地址0x080485b8,我們將JZ指令修補為JMP。 我們繞過了前面看到的密碼驗證塊。
void CheckPassword(undefined4 param_1) { int iVar1; char local_108c [128]; char local_100c [4096]; CustomCopy(local_100c,param_1); GeneratePassword(s_THEPASSWORDISEASYTOCRACK_08049960,local_108c); iVar1 = strcmp(local_108c,local_100c); if (iVar1 == 0) { printf("Good work, the password is : \n\n%s\n",local_108c); } else { puts("Is not the good password !"); } return; }
At address 0x0804861e we patch JNZ to JZ. This inverts the if/else condition. Since we don’t know the password, we’re going to submit a random password that is not equal to the generated one, thus executing the printf on the else block.
在地址0x0804861e,我們將JNZ修補到JZ。 這將反轉if / else條件。 由于我們不知道密碼,因此我們將提交一個與生成的密碼不相等的隨機密碼,從而在else塊上執行printf。
void CheckPassword(undefined4 param_1) { int iVar1; char local_108c [128]; char local_100c [4096]; CustomCopy(local_100c,param_1); // constructs the password from the strings and stores it in // local_108c GeneratePassword(s_THEPASSWORDISEASYTOCRACK_08049960,local_108c); iVar1 = strcmp(local_108c,local_100c); if (iVar1 == 0) { // passwords are equal puts("Is not the good password !"); } else { printf("Good work, the password is : \n\n%s\n",local_108c); } return; }
That’s all!
就這樣!
Now we run the program. In other tools we just save the file and it works, but in Ghidra it seems that we need to export it.
現在我們運行程序。 在其他工具中,我們只保存文件即可使用,但是在Ghidra中,似乎我們需要將其導出。
To export the program, we go to File -> Export Program (O). We change the format to binary and click OK.
要導出程序,我們轉到文件->導出程序(O)。 我們將格式更改為二進制,然后單擊“確定”。
I get the exported program on my desktop but it doesn’t work — I couldn’t manage to run the exported program. After trying to read it’s header with the readelf -h program, I get the following output:
我在桌面上獲得了導出的程序,但是它不起作用-我無法設法運行導出的程序。 嘗試使用readelf -h程序讀取其標頭后,得到以下輸出:
root@DESKTOP:/mnt/c/users/denis/Desktop# readelf -h Crack.bin ELF Header: Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: EXEC (Executable file) Machine: Intel 80386 Version: 0x1 Entry point address: 0x8048440 Start of program headers: 52 (bytes into file) Start of section headers: 2848 (bytes into file) Flags: 0x0 Size of this header: 52 (bytes) Size of program headers: 32 (bytes) Number of program headers: 7 Size of section headers: 40 (bytes) Number of section headers: 27 Section header string table index: 26 readelf: Error: Reading 1080 bytes extends past end of file for section headers
Shame. It looks like Ghidra has messed up the file header… and, right now I don’t want to manually fix headers. So I fired up another tool and applied the same patches to the file, saved it, ran it with a random argument and validated the flag.
恥辱。 看來Ghidra弄亂了文件頭 ……而且,現在我不想手動修復頭。 因此,我啟動了另一個工具,并對文件應用了相同的補丁程序,將其保存,并使用隨機參數運行它并驗證了標志。
結論 (Conclusions)
Ghidra is a nice tool with a lot of potential. In its current state, it’s not that great but it works. I’ve also encountered a weird scrolling bug while running it on my laptop.
Ghidra是一個很有潛力的好工具。 在目前的狀態下,它還不是很好,但是可以工作。 在筆記本電腦上運行時,我還遇到了奇怪的滾動錯誤。
The alternatives would be to pay $$ for other tools of this kind, make your own tools, or work with free but not so user friendly tools.
替代方案是為此類其他工具支付$$費用,自己制作工具或使用免費但不那么用戶友好的工具。
Let’s hope that once the code is released, the community will start doing fixes and improve Ghidra.
希望一旦代碼發布,社區將開始進行修復并改進Ghidra。
Thanks for reading!
謝謝閱讀!
翻譯自: https://www.freecodecamp.org/news/how-i-solved-a-simple-crackme-challenge-with-the-nsas-ghidra-d7e793c5acd2/
nsa構架