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构架

nsa构架_我如何使用NSA的Ghidra解决了一个简单的CrackMe挑战相关推荐

  1. python如何编写数据库_如何在几分钟内用Python编写一个简单的玩具数据库

    python如何编写数据库 MySQL, PostgreSQL, Oracle, Redis, and many more, you just name it - databases are a re ...

  2. python博弈论代码_使用 40 多行的 Python 代码实现一个简单的演化过程

    Python部落(python.freelycode.com)组织翻译,禁止转载,欢迎转发. 在纳米比亚的 PyCon 会议上,我发表了一篇名为 <使用 Python 解决"升级版的剪 ...

  3. redchat怎么编写shell脚本_如何写shell脚本?尝试自己编写一个简单脚本

    1. 创建Shell脚本文件 2. 显示消息 3. 使用变量 4. 反引号 5. 重定向输入输出 6. 管道 7. 执行数学运算 8. 退出脚本 1. 创建脚本文件的时候,必须将文件的第一行指定要使用 ...

  4. ios请求头解决参数中文乱码_花了一天时间就解决了一个的请求头传参参数格式bug...

    一天的时间就解决了一个bug就这么过去了,但不能让他就这么过去了,加班要加的有价值,所以现在记录一下这一天的经历,以防下次再踩坑 大致说下我的情况,入坑的不久的前端新手,在做一个项目的重构,用的框架式 ...

  5. 西门子触摸屏修改ip地址_基于博途V15 西门子S7-1200与触摸屏一个简单的例子

    本篇是<基于博途V15 西门子S7-1200 ...>系列的后续篇,看懂本篇文章之前请大家关注我,然后查找相关文章,学习之后再学习此篇.也请专业领域的大神批评指正. 感谢大家关注与支持! ...

  6. java erlang 游戏_游戏中最近可达点(用erlang写一个简单算法)

    游戏中有点击障碍点,人物走到离障碍物最近点的算法.我这给一个简单的算法,这个问题可以复杂化,另外这个问题应该由客户端来完成. 如图,玩家在b点,点击了a点,a在一个障碍区(红色,玩家只能在障碍区外移到 ...

  7. vue aplayer 进度条无法拖动_「最近项目小结」使用Vue实现一个简单的鼠标拖拽滚动效果插件...

    演示事例 http://www.longstudy.club/vue-drag-scroll/index.html 最近在做一个新的项目,有个需求是这样的: 简单描述一下,就是鼠标拖动页面,整个页面会 ...

  8. ios php mysql实例_如何用PHP/MySQL为 iOS App 写一个简单的web服务器(译) PART1

    原文:http://www.raywenderlich.com/2941/how-to-write-a-simple-phpmysql-web-service-for-an-ios-app 作为一个i ...

  9. java 不让滚动条随着拖拽滑动_「最近项目小结」使用Vue实现一个简单的鼠标拖拽滚动效果插件...

    演示事例 http://www.longstudy.club/vue-drag-scroll/index.html 最近在做一个新的项目,有个需求是这样的: 简单描述一下,就是鼠标拖动页面,整个页面会 ...

最新文章

  1. pt-tools系统:pt-kill 实战
  2. 20145240《信息安全系统设计基础》第十二周学习总结
  3. Cesium中级教程9 - Advanced Particle System Effects 高级粒子系统效应
  4. Visual Studio 2005 Professional Released
  5. github提交,却不显示提交记录
  6. 中国版“微软”要来了?某国产操作系统称已可以替代Windows 7
  7. IDT系列:(二)中断处理过程,使用bochs调试IDT中的中断服务程序
  8. 视频搜索时代到来了吗?[翻译]
  9. 大数据学习——虚拟机准备(改主机名)
  10. 征文通知:第三届(2016)科学数据大会——科学数据与创新发展
  11. 如何将学术经历整合为求职简历?
  12. 应用程序如何链接静态QT Plugin库
  13. 【c++实现】模拟银行叫号系统
  14. 计算机 标量,标量关系
  15. uni-app自定义二维码扫描及本地图片扫描
  16. iOS7 与 iOS8上 uisegementcontrol 设置image不显示问题
  17. 计算机病毒查杀记录,如何看eset nod32防病毒查杀历史记录?
  18. Javascript 面向对象编程(一):封装
  19. 五月总结 时光待我不薄
  20. Linux 如何挂载nas盘到服务器

热门文章

  1. 函数dup和dup2
  2. Java面试必备的集合源码详解,砥砺前行!
  3. serial port 的操作
  4. java并发编程实战:第十四章----构建自定义的同步工具
  5. scott登录查询常用语句
  6. [SoapUI] 通过SoapUI发送POST请求,请求的body是JSON格式的数据
  7. prefix.pch文件的一些简单使用
  8. IOS开发之----常用加密方法
  9. sqlite 字符串 转 整型 cast 函数 (强制类型转换 )
  10. IE8无法调试?IE进入不了调试状态