吾爱汇编

 找回密码
 立即注册

QQ登录

绑定QQ避免忘记帐号

查看: 7048|回复: 118

[安卓逆向图文] 详解一道安卓中级逆向题

  [复制链接]
白云点缀的蓝 发表于 2021-11-1 13:12 | 显示全部楼层 |阅读模式

安装app后打开,点击验证提示我们flag格式错误,请重试
春节提.jpg

我们打开jeb分析工具进行分析
红色图部分为源码目录,也就是dex文件反编译后的源码

春节题1.png

R文件存储了资源相关的id,比如图片资源,按钮,文字等信息都存储在这个R文件里
在需要使用时就用R.xxx.xxx调用即可。例如R.anim.abc_fade_in,因为都是static修饰的,
所以可以直接打点直接调用,不需要创建对象。

春节题2.png

如何知道当前类是什么呢?
执行如下命令即可获取,方便进行分析
adb shell dumpsys activity top
因为手机没有root,所以这里以虚拟机演示。然后虚拟机又不支持26版本的sdkapp
所以只好以mt管理器为例子
bin.mt.plus为包
.Main为activity,也就是类
bin.mt.plus/.Main
春节4.png

因为这里类就怎么一个MainActivity所以我直接分析了。
如下MainActivity为关键类,里面有点击按钮后弹出的相关信息。

春节题3.png

源码刨析

[Java] 纯文本查看 复制代码
源码刨析
package cn.pojie52.cm01;//当前类MainActivity所在的包,也就是文件夹

import android.os.Bundle;//导入Bundle相关方法,变量
import android.view.View.OnClickListener;//导入按钮点击事件相关方法,变量
import android.view.View;//导入视图相关方法,变量。
import android.widget.EditText;//导入编辑框相关方法,变量
import android.widget.Toast;//导入吐司弹窗相关方法,变量
import androidx.appcompat.app.AppCompatActivity;//主类,一个activity必须要继承一个activity父类


//加载so文件的方法。
//static{}为静态代码块,在MainActivity创建时会优先执行里面的方法
//调用System对象里面的LoadLibrary方法,传入so文件的名字,去除lib,.so,后的名字
//在调用LoadLibrary方法后,底层会把lib,so进行拼接,然后去lib目录里寻找相关的so进行加载
static {
        System.loadLibrary("native-lib");
}

//如下是一个native方法,返回值为Boolean类型,也就是true和false,
//方法名为check,参数为String类型
//因为是native方法,所以实现逻辑必定在so里面,而so里面的代码是由c或者c++编译的
//所以要执行这个System.loadLibrary("native-lib");代码,把so文件提前加载到内存中
public native boolean check(String arg1) {
}

[Java] 纯文本查看 复制代码
@Override  // androidx.appcompat.app.AppCompatActivity
//@Override说明这是一个重写方法,
//这个方法OnCreate在AppCompatActivity类里
//因为主类MainActivity后面加了extends AppCompatActivity
//说明AppCompatActivity里的方法,变量都会继承过来,也就是说我们可以直接使用
//参数为Bundle
    protected void onCreate(Bundle arg3) {
        super.onCreate(arg3);//调用父类的OnCreate方法放入arg3参数
//父类也就是AppCompatActivity
        this.setContentView(0x7F0A001C);  // layout:activity_main
//调用setContentView方法初始化界面
//也就是我们在开始的时候看到的那个界面,
//有个编辑框,一个验证按钮
        EditText v3 = (EditText)this.findViewById(0x7F070058);  // id:flag
//调用findViewById方法传入编辑框的id,然后转为EditText,
//因为获取到的是一个view对象,这个view对象范围太大了
//我们直接转为EditText方便些
        this.findViewById(0x7F070045).setOnClickListener(new View.OnClickListener() {  // id:check
//这个是一种链式编程写法,简便,省去了定义变量来接收他
//这个是调用findViewById传入按钮的id,
//this指的是当前activity,因为继承了AppCompatActivity类
//所以我们可以直接使用里面的findViewById方法
//调用这个方法后返回的是一个对象,然后调用setOnclickListener方法
//传入匿名内部类对象,给按钮绑定一个监听事件
//onClick方法是重写的
            @Override  // android.view.View$OnClickListener
            public void onClick(View arg4) {

            }
        });
}

[Java] 纯文本查看 复制代码
在Onclick方法里的代码逻辑
 		String v4 = v3.getText().toString().trim();
//v3为编辑框,获取编辑框里面的信息,转为string,去除空格,
//赋值为string变量v4
                if(v4.length() != 30) {
//判断v4的长度是否等于30
//如果不等于就提示flag格式错误,请重试
//return为返回
                    Toast.makeText(MainActivity.this, "flag格式错误,请重试", 0).show();
//调用Toast对象里面的makeText方法,放入MainActiviy
//因为onclick方法在内部类里面,所以是MainActivity.this,而不是this.MainActiviy
                    return;
                }

                if(MainActivity.this.check(v4)) {//调用native层的check方法,传入v4字符串
                    Toast.makeText(MainActivity.this, "恭喜你,验证正确!", 0).show();
//如果check方法的返回值为true那么弹出恭喜你,验证正确的提示
                    return;
                }

                Toast.makeText(MainActivity.this, "flag错误,再接再厉", 0).show();
//否则提示flag错误,再接再厉
            }

我们已经知道了具体逻辑是在so层,且密码长度是30位的
接下来我们进入so层进行分析
我们需要用到的工具是ida
在拖入ida前,我们需要把apk包进行解压,获取lib目录里面的so文件

春节6.png

春节7.png

拖入64位的ida
我们默认即可,点击ok


春节8.png

因为这个check方法不是系统方法,所以在export就可以看到
Java_cn_pojie52_cm01_MainActivity_check
在ida中显示为Java_类名_方法名

春节9.png

我们双击进入

春节10.png

按tab键转为c伪代码

春节11.png

我们改一下参数
第一个参数固定为JNIEnv*
在改之前,我们需要导入jni的头文件
Jni头文件里存放了相关的jni方法,方便分析

春节12.png

春节13.png



春节14.png

春节15.png

春节16.png

第二个参数要么是jclass。要么是jobject
如果是类就是jclass,如果是对象就是jobject
可以看到这个check方法并没有static修饰,也就是说要用对象才能调用
也就是说是jobject

春节17.png

我们对着int64的参数右键,把他修改成jobject

春节18.png

春节19.png

这个check方法的参数是一个string类型的
所以这个native方法第三个参数也是string类型的
在c语言中string被定义为jstring


春节20.png

查找jni头文件可以知道。这个jstring是一个jstring*,而jstring是jobject,
所以这个jstring是jobject*类型
Typedef是给一个变量进行重定义。*这个代表这是一个一级指针
多少个星花代表几级指针
指针是一个地址,地址里面有一块空间,用来存放变量的数据
比如int a=0;这个a是一个地址,给这个地址取名为a,地址里面存放了0这个数据
当我们&a时就是获取a的地址,相当于把a还原成一个地址,我们可以用
Printf(“%x”,&a);来打印这个地址
春节21.png

我们修改一下第三个参数

春节22.png

春节23.png

如下是改完后的完整代码

[C] 纯文本查看 复制代码
__int64 __fastcall Java_cn_pojie52_cm01_MainActivity_check(_JNIEnv *a1, jobject a2, jstring a3)
{
  const char *v5; // x21
  size_t v6; // w0
  int v7; // w0
  __int64 v8; // x0
  _BYTE *v9; // x0
  int8x16_t v10; // q0
  int8x16_t v11; // q4
  int8x16_t v12; // q2
  int8x16_t v13; // q5
  int8x16_t v14; // q1
  int8x16_t v15; // q0
  __int64 v16; // x8
  unsigned int v17; // w19
  _BYTE v19[33]; // [xsp+0h] [xbp-A0h]
  int v20; // [xsp+21h] [xbp-7Fh]
  char v21; // [xsp+25h] [xbp-7Bh]
  char v22; // [xsp+26h] [xbp-7Ah]
  char v23; // [xsp+27h] [xbp-79h]
  char v24; // [xsp+28h] [xbp-78h]
  char dest[16]; // [xsp+38h] [xbp-68h] BYREF
  __int128 v26; // [xsp+48h] [xbp-58h]
  __int128 v27; // [xsp+58h] [xbp-48h]
  __int128 v28; // [xsp+68h] [xbp-38h]
  __int64 v29; // [xsp+78h] [xbp-28h]

  v29 = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
  if ( a1->functions->GetStringUTFLength((JNIEnv *)a1, a3) == 30 )
  {
    v5 = a1->functions->GetStringUTFChars(a1, a3, 0LL);
    v28 = 0u;
    v27 = 0u;
    v26 = 0u;
    *(_OWORD *)dest = 0u;
    v6 = strlen(v5);
    strncpy(dest, v5, v6);
    a1->functions->ReleaseStringUTFChars((JNIEnv *)a1, a3, v5);
    v7 = strlen(dest);
    sub_B90((int)dest, v7, "areyousure??????");
    v8 = strlen(dest);
    v9 = (_BYTE *)sub_D90(dest, v8);
    *(_OWORD *)v19 = unk_11A1;
    *(_OWORD *)&v19[16] = unk_11B1;
    *(_QWORD *)&v19[25] = unk_11BA;
    v10.n128_u64[0] = 0xB2B2B2B2B2B2B2B2LL;
    v10.n128_u64[1] = 0xB2B2B2B2B2B2B2B2LL;
    v11.n128_u64[0] = 0xFEFEFEFEFEFEFEFELL;
    v11.n128_u64[1] = 0xFEFEFEFEFEFEFEFELL;
    v19[0] = 53;
    v12 = veorq_s8(
            vaddq_s8(veorq_s8(vaddq_s8(*(int8x16_t *)&v19[1], v10), (int8x16_t)xmmword_1130), (int8x16_t)xmmword_1140),
            v11);
    v13.n128_u64[0] = 0x101010101010101LL;
    v13.n128_u64[1] = 0x101010101010101LL;
    v14.n128_u64[0] = 0x3E3E3E3E3E3E3E3ELL;
    v14.n128_u64[1] = 0x3E3E3E3E3E3E3E3ELL;
    *(int8x16_t *)&v19[1] = vaddq_s8(
                              veorq_s8(
                                vsubq_s8(v13, vorrq_s8(vshrq_n_u8(v12, 7uLL), vshlq_n_s8(v12, 1uLL))),
                                (int8x16_t)xmmword_1150),
                              v14);
    v20 = 1782990162;
    v15 = veorq_s8(
            vaddq_s8(veorq_s8(vaddq_s8(*(int8x16_t *)&v19[17], v10), (int8x16_t)xmmword_1160), (int8x16_t)xmmword_1170),
            v11);
    v21 = ((1
          - ((2 * ((((unk_11C6 - 78) ^ 0xB2) - 117) ^ 0xFE)) | ((((unsigned __int8)(((unk_11C6 - 78) ^ 0xB2) - 117) ^ 0xFE) & 0x80) != 0))) ^ 0x25)
        + 62;
    v16 = 0LL;
    v22 = ((1
          - ((2 * ((((unk_11C7 - 78) ^ 0xB1) - 118) ^ 0xFE)) | ((((unsigned __int8)(((unk_11C7 - 78) ^ 0xB1) - 118) ^ 0xFE) & 0x80) != 0))) ^ 0x26)
        + 62;
    *(int8x16_t *)&v19[17] = vaddq_s8(
                               veorq_s8(
                                 vsubq_s8(v13, vorrq_s8(vshrq_n_u8(v15, 7uLL), vshlq_n_s8(v15, 1uLL))),
                                 (int8x16_t)xmmword_1180),
                               v14);
    v23 = ((1
          - ((2 * ((((unk_11C8 - 78) ^ 0xB0) - 119) ^ 0xFE)) | ((((unsigned __int8)(((unk_11C8 - 78) ^ 0xB0) - 119) ^ 0xFE) & 0x80) != 0))) ^ 0x27)
        + 62;
    v24 = ((1
          - ((2 * ((((unk_11C9 - 78) ^ 0xBF) - 120) ^ 0xFE)) | ((((unsigned __int8)(((unk_11C9 - 78) ^ 0xBF) - 120) ^ 0xFE) & 0x80) != 0))) ^ 0x28)
        + 62;
    while ( v9[v16] == v19[v16] )
    {
      if ( v9[v16] )
      {
        if ( ++v16 != 41 )
          continue;
      }
      v17 = 1;
      goto LABEL_9;
    }
    v17 = 0;
LABEL_9:
    free(v9);
  }
  else
  {
    return 0;
  }
  return v17;
}

为了方便分析,我们把参数的名字改一下

春节24.png



春节25.png

重复前面的操作
春节26.png

//判断password的长度是否为30
if ( env->functions->GetStringUTFLength((JNIEnv *)env, password) == 30 )
//这个env是一个指针,指针里面有一个函数指针,函数指针里面有一个GetStringUTFLength函数
//这个函数用于判断字符串的长度
//这个涉及到了结构体相关知识
具体参考如下
帖子地址:
分析一个CrackMe
https://www.52hb.com/forum.php?mod=viewthread&tid=51824&fromuid=77058

(出处: 吾爱汇编论坛)


结构.png


结构1.png






//把password转为c语言中的char类型
env->functions->GetStringUTFChars(env, password, 0LL);
为了方便,我提前改好了相关名字



春节28.png

password_length = strlen(password_);//获取password_的长度
//把password_拷贝到password__里面,拷贝长度为password_length
strncpy(password__, password_, password_length);
//释放password_指向的字符串空间
//也就是归还空间,让其他程序使用
env->functions->ReleaseStringUTFChars((JNIEnv *)env, password, password_);
//获取password__的长度
password__length = strlen(password__);
//细心的朋友可以知道这个16是有问题的,因为我们的password是30位的,然而这个password确是16位的
char password__[16]
这里我改为了32
春节30.png

我们看一下下面的这个函数,可以看到传入了我们password__,password_length,密码与长度,
还有"areyousure??????"



春节31.png

我们双击进入这个函数,然后修改一下如下参数


春节32.png


源码分析

[C] 纯文本查看 复制代码
//获取threeStr的长度
threeStrLen = strlen(threeStr);
do
  {
v9 = *((unsigned __int8 *)v20 + v7);  
//v7加上转换为无符号int类型的指针v20,也就是加上无符号int类型的步长。
然后取*取出相加后里面空间的值
v10 = v8 + v9 + (unsigned __int8)threeStr[v7 % threeStrLen];
//v9加上v8然后加上无符号整型的threeStr[v7%threeStrlen];
//对v7以threeStrlen进行取余,然后threeStr[取余的值]取出threeStr里面的字符串数据
    v11 = v10 + 255;//v10加上255赋值给v11
    if ( v10 >= 0 )//如果v10大于等于0就执行v10赋值给v11的操作
      v11 = v10;把v10赋值给v11
    v8 = v10 - (v11 & 0xFFFFFF00);//对v11与0XFFFFFF00进行与运算,然后v10减去运算结果,赋值给v8
*((_BYTE *)v20 + v7++) = *((_BYTE *)v20 + v8);
//把v20转为Byte*类型然后加上v8,步长为Byte *的长度,然后*取值
//然后把值赋值给把v20转为Byte*类型加上自增的v7,获得值后进行取*,赋值给这个取*花后的值
*((_BYTE *)v20 + v8) = v9;
//把v9赋值给把v20转为Byte*类型然后加上v8个Byte*步长里面的值
  }
  while ( v7 != 256 );如果v7不等于256就一直循环

[C] 纯文本查看 复制代码
//判断如果password__length是否为空
//在c语言中,非0就是true,也就是说可以用于判断是否为空
if ( password__length )
  {
    v12 = 0;//给v12赋值0
    v13 = 0;给v13赋值0
v14 = password__length;//把密码长度赋值给v14
    do
    {
      v15 = v12 + 1; //把v12加上1的值赋值给v15
      if ( v12 + 1 >= 0 )//判断v12+1是否大于等于0
        v16 = v12 + 1;//把v12加1的值赋值给v16
      else
        v16 = v12 + 256;//把v12加上256赋值给v16
      v12 = v15 - (v16 & 0xFFFFFF00);//对v16与0XFFFFFF00进行与运算,然后v15减去运算结果,赋值给v12
      v17 = *((unsigned __int8 *)v20 + v12);//把v20转为无符号int整型指针然后加上这个指针的步长,
//对这个值进行取星花赋值给v17
      v18 = v13 + v17;//把v13加上v17的值赋值给v18
      v19 = v18 + 255;//把v18加上255的值赋值给v19
      if ( v18 >= 0 )//判断v18是否大于0
        v19 = v18;//如果大于0就把v18赋值给v19
      v13 = v18 - (v19 & 0xFFFFFF00);//对v19与0XFFFFFF00进行与运算,然后v18减去运算结果,赋值给v13
      --v14;//v14减一
      *((_BYTE *)v20 + v12) = *((_BYTE *)v20 + v13);
//把v20转为Byte*类型加上v13个Byte*的步长,然后取*取出里面的值,
//把这个值赋值给v20转为Byte*类型加上v12个Byte*步长的地址空间

      *((_BYTE *)v20 + v13) = v17;
//把v17的值赋值给v20转为Byte*类型加上v13个Byte*步长的地址空间

      *password__++ ^= *((_BYTE *)v20 + (unsigned __int8)(*((_BYTE *)v20 + v12) + v17));
//取出password里面的值
//把v20转为Byte*类型,加上把V20转为Byte*类型后加上v12进行取*加上v17后转为无符号int类型
//然后进行取*取出里面的值,把值与password进行异或然后赋值给password
    }
    while ( v14 );//如果v14不为空就一直循环
  }

这两块代码分别操作着threeStr,还有password

春节38.png

如下代码是关键:
[C] 纯文本查看 复制代码
while ( v9[v16] == v19[v16] ) //循环对比v9[v16]的值与v19[v16]的值

    {

      if ( v9[v16] ) //判断v9[16]里是否有值

      {

        if ( ++v16 != 41 )//如果v16加一后不等于41就continue跳过代码

          continue;

      }

      v17 = 1;//如果为1那么flag正确

      goto LABEL_9;//跳到LABEL_9的位置

    }

    v17 = 0;//如果为0那么flag错误

LABEL_9:

    free(v9);//释放内存

  }

  else

  {

    return 0;

  }

return v17;//影响验证结果


下面hook一下这个函数。看看这个password变成了什么

[Python] 纯文本查看 复制代码
import frida, sys//因为是frida hook,所以要导入frida模块,
//读取系统输入需要sys模块
 
jscode = ''' 
  
function inline_hook() {

var so_addr = Module.findBaseAddress("libnative-lib.so");

    if (so_addr) {
        console.log("so_addr:", so_addr);
        var addr_b90 = so_addr.add(0xb90);
        var sub_b90 = new NativeFunction(addr_b90 , 'int', ['pointer', 'int','pointer']);
        var arg1 = Memory.allocUtf8String('111111111111111111111111111111');
        var arg2 = 30;
        var arg3 = Memory.allocUtf8String('areyousure??????');
        var ret_b90 = sub_b90(arg1,arg2,arg3);
        console.log(Memory.readByteArray(arg1,64));
         
         
        var addr_d90 = so_addr.add(0xd90);
        var sub_d90 = new NativeFunction(addr_d90 , 'pointer', ['pointer', 'int' ]);
        var arg1 = Memory.allocUtf8String('111111111111111111111111111111');
        var arg2 = 30; 
        var ret_d90 = sub_d90(arg1,arg2);
        console.log(Memory.readByteArray(ret_d90,64));
         
         
    }
     
}
setImmediate(inline_hook)
   
'''
def on_message(message, data):
    if message['type'] == 'send':
        print(" {0}".format(message['payload']))
    else:
        print(message)
pass
#print(frida.enumerate_devices())
# 查找USB设备并附加到目标进程
device =  frida.get_remote_device()
#pid = device.spawn(["com.live.xctv"])
  
#session = device.attach(pid)
session =device.attach('cn.pojie52.cm01') #这里是要注入的apk包名
# 在目标进程里创建脚本
script = session.create_script(jscode)
# 注册消息回调
script.on('message', on_message)
print(' Start attach')
# 加载创建好的javascript脚本
script.load()
# 读取系统输入
sys.stdin.read()

var so_addr = Module.findBaseAddress("libnative-lib.so");//查找so的基址。
在ida中基址为0000000000000
在动态调试时基址会变
所以我们需要通过基址加上函数的偏移来定位一个函数
春节33.png


在这里我们需要hook的函数是sub_B90()


春节34.png

我们按tab键查看函数偏移

春节35.png

代码分析
console.log("so_addr:", so_addr);//打印so的基址

var addr_b90 = so_addr.add(0xb90);//so的基址加上b90就是这个函数的地址

if (so_addr) //判断基础是否获取到了
var sub_b90 = new NativeFunction(addr_b90 , 'int', ['pointer', 'int','pointer']);

//创建一个本地函数,类似于指针函数,给指针函数赋值函数的地址。
//然后进行调用,参数为_BYTE *password__, unsigned int password__length, char *threeStr
//返回值为unsigned __int64
//返回值对应'int',参数对应着['pointer', 'int','pointer']
下面为封装函数的参数,进行调用
//分配内存创建111111111111111111111111111111字符串
var arg1 = Memory.allocUtf8String('111111111111111111111111111111');

var arg2 = 30;//长度

//分配内存创建areyousure??????字符串
var arg3 = Memory.allocUtf8String('areyousure??????');

//调用sub_b90函数,传入arg1,arg2,arg3
var ret_b90 = sub_b90(arg1,arg2,arg3);

//返回值为ret_b90
Memory.readByteArray(arg1,64)//读取arg1内存中数据,也就是password,读64位byte的数据

console.log(Memory.readByteArray(arg1,64));//打印读取出来的password

这里还有一个函数传入了password还有密码的长度

春节37.png

源码分析:

var addr_d90 = so_addr.add(0xd90);//基址加上0xb90的偏移就是sub_d90函数的地址
var sub_d90 = new NativeFunction(addr_d90 , 'pointer', ['pointer', 'int' ]);
//创建一个本地函数,类似于指针函数,给指针函数赋值函数的地址。
//然后进行调用,参数为char *a1, __int64 a2
//返回值为void * 万能指针,任何指针都可以赋值
//返回值对应'pointer',参数对应着['pointer', 'int']
       
//分配内存创建111111111111111111111111111111字符串
var arg1 = Memory.allocUtf8String('111111111111111111111111111111');
//密码长度
var arg2 = 30;
//调用sub_d90()函数,输入arg1,arg2参数
var ret_d90 = sub_d90(arg1,arg2);//返回值赋值给ret_d90
读取sub_d90返回值地址里面的空间的64位byte的数据
console.log(Memory.readByteArray(ret_d90,64));


以下是dump的数据
我们输入的密码是111111111111111111111111111111
当执行完异或操作后变为了下面的数据
30个1对应着16进制为30个0x31
在ASCII码中,49的ASCII码为‘1’
这个49是10进制的,转为16进制就是0x31
e0,6b,37,a1,75,d7,f6,d4,ef,19,c6,c3,57,a0,f9,b4
73,ee,c8,d1,b3,30,1a,0a,09,52,06,8c,1f,7c
dump.png

在计算机中,异或运算是可以进行解密的。
10 xor 5 =15

春节39.png

15 xor 10 = 5

春节40.png


把10当成我们输入的密码,把5当成要异或的值,把15当成异或后的值
我们知道了异或后的值,当我们再次异或我们的密码时,就可以得到要异或的值
e0,6b,37,a1,75,d7,f6,d4,ef,19,c6,c3,57,a0,f9,b4
73,ee,c8,d1,b3,30,1a,0a,09,52,06,8c,1f,7c
与30个0x31进行异或即可得到密码

下面简单写了个java代码进行异或获取密码

[Java] 纯文本查看 复制代码
public static  void Xor(){

    int xorData[]={0xe0,0x6b,0x37,0xa1,0x75,0xd7,0xf6,0xd4,0xef,0x19,0xc6,0xc3,0x57,0xa0,0xf9,0xb4,
    0x73,0xee,0xc8,0xd1,0xb3,0x30,0x1a,0x0a,0x09,0x52,0x06,0x8c,0x1f,0x7c};
    int xorDataMy[]={0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,
            0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31};
    System.out.print("[");
    for (int i = 0; i < xorData.length; i++) {

        System.out.print(xorData[i]^xorDataMy[i]);
        if(i<xorData.length-1){
        System.out.print(",");
        }

    }
    System.out.print("]");

}

春节41.png

得到结果如下
[209,90,6,144,68,230,199,229,222,40,247,242,102,145,200,133,66,223,249,224,130,1,43,59,56,99,55,189,46,77]
如下是sub_d90函数的返回进行地址空间dump的数据
可以看到ASCII码全是字母

春节43.png

们可以尝试推断一下是不是某种加密算法,比如aes,base64,md5加密。
aes加密是需要秘钥的,有时还需要iv偏移,base64加密不需要秘钥,也不需要iv偏移。
Md5加密的话,要么全是大写,全是小写,aes加密后缀有个等于,很复杂。
而这个sub_d90函数返回值不可能的aes加密,因为sub_d90中并没有任何的秘钥,iv偏移相关
Md5也不太可能,因为md5要么全是大写字母,或者全是小写字母,并且不规则
写个java代码测试一下

[Java] 纯文本查看 复制代码
public static void baseEncode(){
    System.out.println();
    byte xorDataMy[]={0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,
            0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31};
    String by=Base64.getEncoder().encodeToString( xorDataMy);

    System.out.println(by);

}
public static void md5(){
    System.out.println(MD5Utils.stringToMD5("111111111111111111111111111111"));
}

可以看到base64加密的值与sub_d90的值一样

春节42.png

春节44.png

我们回到这里
V9[v16]这个是返回的base64编码的数据
V19[16]是真码,也是base64的
只要我们把这个真码的base64得到,
然后通过解密base64,然后把解密后的数据与前面的key进行异或即可获得flag

[C] 纯文本查看 复制代码
while ( v9[v16] == v19[v16] )
    {
      if ( v9[v16] )
      {
        if ( ++v16 != 41 )
          continue;
      }
      v17 = 1;
      goto LABEL_9;
    }
    v17 = 0;
LABEL_9:
    free(v9);
  }
  else
  {
    return 0;
  }
  return v17;

如何获取v19呢?
如下是v19的定义,地址为xsp+0
_BYTE v19[33]; // [xsp+0h] [xbp-A0h]
while ( v9[v16] == v19[v16] )
//我们在v9[v16]这个位置按一下tab键即可看到偏移
//这块代码的偏移的是b2c


春节45.png

我们需要在v19[v16]初始化后才能进行hook拦截,也就是获取数据
最好的时机是在sub_b90执行后进行截取。我们可以看到第一个参数是xsp+38h
也就是说xsp+0是xsp+38-38即可
我们需要拦截第一个参数获取到地址,然后减去38就能获取到xsp也就是v19

春节46.png

[Python] 纯文本查看 复制代码
下面进行分析frida hook代码
import frida, sys
 
jscode = ''' 
 
var destAddr = '';  //定位xsp地址
  
function inline_hook() {
    var so_addr = Module.findBaseAddress("libnative-lib.so");//寻找基址
 //在ida中基址为0000000000000
//在动态调试时基址会变
//所以我们需要通过基址加上函数的偏移来定位一个函数

     
    if (so_addr) {//是否获取到基址,如果没有获取到里面的代码就不会执行
        console.log("so_addr:", so_addr);//打印so的地址
         
        var addr_b90 = so_addr.add(0xB90);//获取sub_b90的函数地址
        var sub_b90 = new NativeFunction(addr_b90 , 'int', ['pointer', 'int', 'pointer']);
//创建一个本地函数,类似于指针函数,给指针函数赋值函数的地址。
//然后进行调用,参数为_BYTE *password__, unsigned int password__length, char *threeStr
//返回值为unsigned __int64
//返回值对应'int',参数对应着['pointer', 'int','pointer']

//下面是一个拦截器,用于拦截函数的sub_b90的进入,与结束
        Interceptor.attach(sub_b90, { 
            onEnter: function(args) //进入函数时执行的代码
            {  
            destAddr = args[0];//把第一个参数赋值给destAddr 
            console.log('onEnter B90'); //打印'onEnter B90,用于确认函数是否进入

            },
            //在进入函数之后执行的语句            
onLeave:function(retval)
            { 
//retval为函数的返回值
            console.log('onLeave B90');//打印onLeave B90,用于确认函数是否离开
            } 
        }); 
 
      	
        var addr_b2c = so_addr.add(0xb2c);//0xb2c为偏移,也就是说拦截 b2c的地址

        console.log("The addr_b2c:", addr_b2c);//打印addr_b2c的地址
        Java.perform(function() {
            Interceptor.attach(addr_b2c, {//拦截b2c地址
                onEnter: function(args) { //arg为参数
                console.log("addr_b2c OnEnter :",  Memory.readByteArray(destAddr.sub(0x38),64) );
 // Memory.readByteArray(destAddr.sub(0x38),64)//读取xsp的内容,也就v19的值
//也就是base64编码的真码
     }
            })
        })
    } 
}
setImmediate(inline_hook)
   
   
'''

[Python] 纯文本查看 复制代码
//用于接收消息的函数
//第一个参数为需要打印的信息
def on_message(message, data):
    if message['type'] == 'send':
        print(" {0}".format(message['payload']))
    else:
        print(message)
Pass//跳过
#print(frida.enumerate_devices())
# 查找USB设备并附加到目标进程
device =  frida.get_remote_device()//调用frida的函数,获取设备
#pid = device.spawn(["com.live.xctv"])
  
#session = device.attach(pid)//
session =device.attach('cn.pojie52.cm01') #这里是要注入的apk包名
//这个包名可以通过frida-ps -U 来进行获取
# 在目标进程里创建脚本
script = session.create_script(jscode)
# 注册消息回调
script.on('message', on_message)
print(' Start attach')
# 加载创建好的javascript脚本
script.load()
# 读取系统输入
sys.stdin.read()

运行结果如下
dump1.png

下面的数据是左边的十六进制。
{0x35, 0x47, 0x68, 0x32, 0x2f, 0x79, 0x36, 0x50, 0x6f, 0x71, 0x32, 0x2f, 0x57, 0x49, 0x65, 0x4c, 0x4a, 0x66,0x6d, 0x68, 0x36, 0x79, 0x65, 0x73, 0x6e, 0x4b, 0x37, 0x6e, 0x64, 0x4b, 0x37, 0x6e, 0x64, 0x6e, 0x4a, 0x65, 0x57, 0x52, 0x45, 0x46, 0x6a, 0x52, 0x78, 0x38}
下面的数据是十六进制对应的ASCII码
5Gh2/y6Poq2/WIeLJfmh6yesnK7ndnJeWREFjRx8

有了这两个数据,我们就可以进行解密啦,
解密这个base64,然后对前面获得的异或key进行异或即可获得真码

解密代码如下:

[Python] 纯文本查看 复制代码
import base64//导入base64的包,因为我们要用到base64的解密函数

//xorkey是我们前面通过异或解密得出的数据
xorkey = [209, 90, 6, 144, 68, 230, 199, 229, 222, 40, 247, 242, 102, 145, 200, 133, 66, 223, 249, 224, 130, 1, 43, 59,
          56, 99, 55, 189, 46, 77]

//第一个参数为base64解密后的数据
//第二个参数为base64解密后的数据的长度
def sub_B90(data, l):
    ret = []
    for i in range(l):
        ret.append(data[i] ^ xorkey[i])//异或解密,把结果拼接到ret里面
    s = ''
    for i in ret://便利ret里面数据
        s += chr(i) //把ret的解密结果转换成字符,如何进行拼接。
    print(s)//打印拼接后的解密数据
    return ret//返回ret


def resv(data):
    data = base64.b64decode=1(data,)//解密base64数据

    t = sub_B90(data, len(data))//把解密后的base64数据传入sub_b90进行异或解密
    return (t)

data="5Gh2/y6Poq2/WIeLJfmh6yesnK7ndnJeWREFjRx8"//base64数据

resv(data)//调用这个函数进行解密base64数据与异或解密获得真吗

如下就是我们想要的真码了

春节49.png

输入真码后,提示我们恭喜你,验证正确

春节51.jpg

春节50.jpg


到此,详解完毕~~~




评分

参与人数 57威望 +1 HB +191 THX +23 收起 理由
花盗睡鼠 + 2 + 1 [吾爱汇编论坛52HB.COM]-吃水不忘打井人,给个评分懂感恩!
longge188 + 1 [吾爱汇编论坛52HB.COM]-吃水不忘打井人,给个评分懂感恩!
虚心学习 + 1 [吾爱汇编论坛52HB.COM]-感谢楼主热心分享,小小评分不成敬意!
创客者V2.0 + 1
自律回路 + 1
李卓吾 + 1
459121520 + 1
后学真 + 1
weiran324 + 1 [吾爱汇编论坛52HB.COM]-吃水不忘打井人,给个评分懂感恩!
极速菜 + 1
sjtkxy + 1
冷亦飞 + 1
飞刀梦想 + 1
xgbnapsua + 2
3429 + 1 [吾爱汇编论坛52HB.COM]-感谢楼主热心分享,小小评分不成敬意!
侠客行 + 1
我是好人 + 1 [吾爱汇编论坛52HB.COM]-学破解防破解,知进攻懂防守!
落雪玉 + 1
zm315857235 + 1 [吾爱汇编论坛52HB.COM]-学破解防破解,知进攻懂防守!
fjgh + 1 [吾爱汇编论坛52HB.COM]-学破解防破解,知进攻懂防守!
逆君 + 6 + 1 白云牛皮!!!
阿晋 + 1 [吾爱汇编论坛52HB.COM]-感谢楼主热心分享,小小评分不成敬意!
EMT + 1 + 1
zxjzzh + 1
口水鸡 + 1 [吾爱汇编论坛52HB.COM]-学破解防破解,知进攻懂防守!
豆芽菜 + 1 + 1
消逝的过去 + 1 [吾爱汇编论坛52HB.COM]-软件反汇编逆向分析,软件安全必不可少!
Shin + 2
liugu0hai + 1 [吾爱汇编论坛52HB.COM]-软件反汇编逆向分析,软件安全必不可少!
dawntx + 1
成丰羽 + 1 [吾爱汇编论坛52HB.COM]-感谢楼主热心分享,小小评分不成敬意!
逆向者 + 1
xiaoyudian4900 + 1 + 1
CraftDeadMRC + 1
hetao8003200 + 1 + 1 [快捷评语]--2021年,我们爱0爱1
firstcmm + 1 [快捷评语]--你将受到所有人的崇拜!
mengzhisuoliu + 1
woshuoxiang + 2 + 1 [快捷评语]--2021年,我们爱0爱1
豆0o0豆 + 1 + 1
小生俺怕怕 + 1
蓝色青菜 + 1
muker + 1
Klop1314 + 1 + 1 [快捷评语]--2021年,我们爱0爱1
beijita520 + 1 [快捷评语]--积极评分,从我做起。感谢分享!
janford + 1 + 1 [快捷评语]--吃水不忘打井人,给个评分懂感恩!
king51999 + 1 [快捷评语]--评分=感恩!简单却充满爱!感谢您的作品!
agan8888 + 1 [快捷评语]--2021年,我们爱0爱1
hnymsh + 1
temp + 1 + 1
lies + 1 [快捷评语]--你将受到所有人的崇拜!

查看全部评分

吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
ST手怎么白了 发表于 2021-11-1 15:29 | 显示全部楼层
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
一万八_ 发表于 2021-11-1 15:46 | 显示全部楼层

有一说一。不会安卓逆向的我,看的一愣一愣的
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
Shark恒 发表于 2021-11-1 17:14 | 显示全部楼层

真的很不错,之前发不过去,只是外链形式。这回都贴到了论坛,辛苦了,很细致!
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
edge 发表于 2021-11-9 15:17 | 显示全部楼层

膜拜大佬我来学习学习
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
tyt521 发表于 2021-11-10 21:14 | 显示全部楼层
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
gzhi521 发表于 2021-11-13 08:57 | 显示全部楼层

非常好的教程,感谢
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
Crack01 发表于 2021-11-14 13:16 | 显示全部楼层

多看贴子!!!
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
kaixinpj 发表于 2021-11-15 10:51 | 显示全部楼层

高手高手,你是高手。
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
dawntx 发表于 2021-11-15 18:12 | 显示全部楼层

看起来不错,就是对我小白来说有点深奥
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

警告:本站严惩灌水回复,尊重自己从尊重他人开始!

1层
2层
3层
4层
5层
6层
7层
8层
9层
10层

免责声明

吾爱汇编(www.52hb.com)所讨论的技术及相关工具仅限用于研究学习,皆在提高软件产品的安全性,严禁用于不良动机。任何个人、团体、组织不得将其用于非法目的,否则,一切后果自行承担。吾爱汇编不承担任何因为技术滥用所产生的连带责任。吾爱汇编内容源于网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑中彻底删除。如有侵权请邮件或微信与我们联系处理。

站长邮箱:SharkHeng@sina.com
站长QQ:1140549900


QQ|RSS|手机版|小黑屋|帮助|吾爱汇编 ( 京公网安备11011502005403号 , 京ICP备20003498号-6 )|网站地图

Powered by Discuz!

吾爱汇编 www.52hb.com

快速回复 返回顶部 返回列表