首页
社区
课程
招聘
[分享][原创]Radare2+Frida实现破解
2020-12-26 23:37 6288

[分享][原创]Radare2+Frida实现破解

2020-12-26 23:37
6288

黑客攻击-软件破解(2) 中通过Radare2的静态分析实现了破解。本文使用frida和radare2进行动态分析来对crackerMe系列中后面的例子进行破解。

crackme0x04

流程图

首先通过agf查看流程图。

[0x080484fb]> agf
[0x08048484]>  # sym.check (char *s);
                                                  ┌─────────────────────────────────────┐
                                                  │  0x8048484                          │
                                                  │   ; CALL XREF from main @ 0x8048559
                                                  │ 133: sym.check (char *s);           │
                                                  │ ; var char *var_dh @ ebp-0xd        │
                                                  │ ; var uint32_t var_ch @ ebp-0xc     │
                                                  │ ; var uint32_t var_8h @ ebp-0x8     │
                                                  │ ; var int32_t var_4h @ ebp-0x4      │
                                                  │ ; arg char *s @ ebp+0x8             │
                                                  │ ; var char *format @ esp+0x4        │
                                                  │ ; var int32_t var_sp_8h @ esp+0x8   │
                                                  │ push ebp                            │
                                                  │ mov ebp, esp                        │
                                                  │ sub esp, 0x28                       │
                                                  │ mov dword [var_8h], 0               │
                                                  │ mov dword [var_ch], 0               │
                                                  └─────────────────────────────────────┘
                                                      v
                                                      │
                                                      │
┌────────────────────────────────────────────────────────┐
│                                                      │ │
│                                                ┌────────────────────────────────────────┐
│                                                │  0x8048498                             │
│                                                │ ; CODE XREF from sym.check @ 0x80484f9
│                                                │ mov eax, dword [s]                     │
│                                                │ ; const char *s                        │
│                                                │ mov dword [esp], eax                   │
│                                                │ ; size_t strlen(const char *s)         │
│                                                │ call sym.imp.strlen;[oa]               │
│                                                │ cmp dword [var_ch], eax                │
│                                                │ jae 0x80484fb                          │
│                                                └────────────────────────────────────────┘
│                                                        f t
│                                                        │ │
│                                                        │ └───────────────────┐
│                ┌───────────────────────────────────────┘                     │
│                │                                                             │
│            ┌────────────────────────────────────────────────────────┐    ┌────────────────────────────────────────────┐
│            │  0x80484a8                                             │    │  0x80484fb                                 │
│            │ mov eax, dword [var_ch]                                │    │ ; const char *format                       │
│            │ add eax, dword [s]                                     │    │ ; CODE XREF from sym.check @ 0x80484a6     │
│            │ movzx eax, byte [eax]                                  │    │ ; [0x8048649:4]=0x73736150                 │
│            │ mov byte [var_dh], al                                  │    │ ; "Password Incorrect!\n"                  │
│            │ lea eax, [var_4h]                                      │    │ mov dword [esp], str.Password_Incorrect    │
│            │ ;   ...                                                │    │ ; int printf(const char *format)           │
│            │ mov dword [var_sp_8h], eax                             │    │ call sym.imp.printf;[oc]                   │
│            │ ; const char *format                                   │    │ leave                                      │
│            │ ; [0x8048638:4]=0x50006425                             │    │ ret                                        │
│            │ mov dword [format], 0x8048638                          │    └────────────────────────────────────────────┘
│            │ lea eax, [var_dh]                                      │
│            │ ; const char *s                                        │
│            │ mov dword [esp], eax                                   │
│            │ ; int sscanf(const char *s, const char *format,   ...) │
│            │ call sym.imp.sscanf;[ob]                               │
│            │ mov edx, dword [var_4h]                                │
│            │ lea eax, [var_8h]                                      │
│            │ add dword [eax], edx                                   │
│            │ cmp dword [var_8h], 0xf                                │
│            │ jne 0x80484f4                                          │
│            └────────────────────────────────────────────────────────┘
│                    f t
│                    │ │
│                    │ └───────────────────────┐
│    ┌───────────────┘                         │
│    │                                         │
│┌─────────────────────────────────────┐   ┌────────────────────────────────────────┐
││  0x80484dc                          │   │  0x80484f4                             │
││ ; const char *format                │   │ ; CODE XREF from sym.check @ 0x80484da
││ ; [0x804863b:4]=0x73736150          │   │ lea eax, [var_ch]                      │
││ ; "Password OK!\n"                  │   │ inc dword [eax]                        │
││ mov dword [esp], str.Password_OK    │   │ jmp 0x8048498                          │
││ ; int printf(const char *format)    │   └────────────────────────────────────────┘
││ call sym.imp.printf;[oc]            │       v
││ ; int status                        │       │
││ mov dword [esp], 0                  │       │
││ ; void exit(int status)             │       │
││ call sym.imp.exit;[od]              │       │
│└─────────────────────────────────────┘       │
│                                              │
│                                              │
└──────────────────────────────────────────────┘

指令和函数

sscanf() 函数的声明。

int sscanf(const char *str, const char *format, ...)

作用是从字符串读取格式化输入。

cmp指令: 该指令与SUB指令一样执行减法的操作,但它并不保存运算结果,只是根据结果设置相关的条件标志位(SF、ZF、CF、OF)。CMP指令后往往跟着条件转移指令,实现根据比较的结果产生不同的程序分支的功能。 inc是增量指令。 inc指令: 该指令对操作数oprd加1(增量),它是一个单操作数指令。操作数可以是寄存器或存储器。由于增量指令主要用于对计数器和地址指针的调整,所以它不影响进位标志CF,对其他状态标志位的影响与add指令一样。

伪代码

通过pdg查看以下decompiler的代码

[0x080484fb]> pdg

// WARNING: [r2ghidra] Detected overlap for variable var_dh

void sym.check(char *s)
{
   uint32_t uVar1;
   char var_dh;
   uint32_t var_ch;
   uint32_t var_8h;
   int32_t var_4h;

   var_8h = 0;
   var_ch = 0;
   while( true ) {
       uVar1 = sym.imp.strlen(s);
       if (uVar1 <= var_ch) break;
       var_dh = s[var_ch];
       sym.imp.sscanf(&var_dh, 0x8048638, &var_4h);
       var_8h = var_8h + var_4h;
       if (var_8h == 0xf) {
           sym.imp.printf("Password OK!\n");
           sym.imp.exit(0);
       }
       var_ch = var_ch + 1;
   }
   sym.imp.printf("Password Incorrect!\n");
   return;
}

逻辑分析

通过流程图可知核心算法在中间部分,并且可以看出存在循环。在这里插入图片描述第一次循环:

1.把var_ch内存中的值赋值给eax mov eax, dword [var_ch]2.把arg_8h(check函数参数,也就是输入的字符串)中的值和eax相加 add eax, dword [arg_8h]3.将eax取byte扩充(相当于取低8位) movzx eax, byte [eax] mov byte [var_dh], al4.将var_4h赋值给eax,注意这里是lea指令lea eax, [var_4h]5.将eax的值赋给var_sp_8h指向的内存(为sscanf传递参数,从右向左入栈) mov dword [var_sp_8h], eax6.将0x8048638("%d")赋给var_sp_4h指向的内存 mov dword [var_sp_4h], 0x80486387.eax入栈(eax和var_dh的值是一样的) lea eax, [var_dh] mov dword [esp], eax8.调用用sscanf函数 call sym.imp.sscanf9.将var_4h指向的值赋给edx mov edx, dword [var_4h]10.将var_8h的值赋给eax lea eax, [var_8h]11.edx和eax指向的值相加 add dword [eax], edx12.比较var_8h内存中的值和0xf cmp dword [var_8h], 0xf13.如果相等,则出现成功的提示,如果不相等,则跳转到0x80484f4 cmp dword [var_8h], 0xf jne 0x80484f4 假设不相等,开始第二轮的循环。

通过上面的分析,可知check函数的功能:对字符串中的每个字符取整(类似atoi),然后对每个字符相加,和0xf进行比较,只有相等的情况,才能Pass.

破解

下面通过多种方式来破解这个程序。

输入正确的值

知道逻辑,很容易破解这个程序。输入12345(和是0xf),Pass.在这里插入图片描述

frida修改逻辑

修改参数

修改参数为96(和是0xf),则无论输入什么都可以使程序Pass. 32位的函数参数传递方式在strcpy为何不安全 中有所介绍。

"use strict"

var targetModule = Process.findModuleByName("crackme0x04");
var symbols = targetModule.enumerateSymbols();
var targetFun = null;
for(var i = 0; i < symbols.length; i++) {
   targetFun =  ptr(symbols[i].address);
   if (symbols[i].name == "check" && symbols[i].type == "function"){
       Interceptor.attach(targetFun,{
           onEnter:function(args){
               this.tmp = ptr(args[0]);
               this.tmp.writeByteArray([0x39,0x36]);
               console.log("success");
               console.log(hexdump(this.tmp, {
                   offset: 0,
                   length: 64,
                   header: true,
                   ansi: true
               }));
              Thread.sleep(6);//注意这里,让线程睡眠6秒,否则console不能输出
           }
       });
       break;      
   }
}

通过r2来获取96对应的asci码是0x39 0x36在这里插入图片描述运行frida脚本,这样无论输入什么的都可以Pass.在这里插入图片描述Thread.sleep(delay): suspend execution of the current thread for delay seconds specified as a number. 由于Ansi相关的函数只能用在Windows平台中,所以这里才使用byte数组。

Note that writeAnsiString() is only available (and relevant) on Windows.

修改指令

修改跳转指令。使得程序无论如何都可以成功运行。

1.如果相等,则出现成功的提示,如果不相等,则跳转到0x80484f4 cmp dword [var_8h], 0xf jne 0x80484f4

代码实现如下:

"use strict"

var targetModule = Process.findModuleByName("crackme0x04");
var symbols = targetModule.enumerateSymbols();
var targetFun = null;
for(var i = 0; i < symbols.length; i++) {
   targetFun =  ptr(symbols[i].address);
   if (symbols[i].name == "check" && symbols[i].type == "function"){
       const maxPatchSize = 64; // Do not write out of bounds, may be a temporary buffer!
       Memory.patchCode(targetFun.add(0x56), maxPatchSize, code => {
           const cw = new X86Writer(code);
           cw.putJmpAddress(targetFun.add(0x56).add(0x2));//跳转指令,跳转到success分支
           console.log(hexdump(targetFun.add(0x56).add(0x2), {
               offset: 0,
               length: 64,
               header: true,
               ansi: true
           }));
           cw.flush();
       });
       break;    
   }
}

代码中的0x56是跳转指令与check函数地址的偏移(offset),0x2是跳转指令的长度。

运行脚本,无论输入什么,都可以Pass。在这里插入图片描述

r2pipe

官方文档如下,简单点说就是r2脚本编程的接口。

Radare2 provides a wide set of a features to automate boring work. It ranges from the simple sequencing of the commands to the calling scripts/another programs via IPC (Inter-Process Communication), called r2pipe.

举个栗子(本文基于的是python3.8), 获取通过r2分析得到的函数列表并且以json格式输出

import r2pipe

r2 = r2pipe.open("/bin/ls")
r2.cmd('aa')
print(r2.cmd("afl"))
print(r2.cmdj("aflj")) # evaluates JSONs and returns an object

基于此可以调试和破解程序。

fuzz

这个方法综合了上述的一些方式, 我们可以用暴力破解的方式来获取密码, 也可以利用libFuzzer 来自动化找出该程序潜在的bug. 这种方式的坏处是太暴力了, 让妹子不敢靠近(逃); 好处则是在一定程度上解放了大脑, 用计算机来帮我们计算, 算力越强就越有可能找到突破点!

对于libfuzz的使用,在漏洞挖掘(1) 中对libFuzzer进行了介绍。

写在最后

crackme0x04开始涉及到算法,crackerMe0x05之后难度会逐步增加,后续文章会陆续介绍。R2pipe和fuzz在这里挖个坑(主要是一两句说不清楚),哈哈。

公众号

更多逆向破解内容,欢迎关注我的微信公众号:无情剑客。在这里插入图片描述


[培训]《安卓高级研修班(网课)》月薪三万计划,掌 握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

收藏
点赞2
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回