unidbg 算法还原术 · 某民宿 app 篇 · 下卷

本文由 简悦 SimpRead 转码, 原文地址 mp.weixin.qq.com

int __fastcall bodyEncrypt(JNIEnv *a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9, int a10)
{
  JNIEnv *v10; // r4
  int v11; // r6
  int v12; // r5
  int v13; // r11
  char *v14; // r9
  int v15; // ST14_4
  int v16; // r6
  int v17; // r10
  size_t v18; // r0
  int v19; // r8
  void *v20; // r5
  int v21; // r4
  int result; // r0
  int v23; // [sp+1Ch] [bp-24h]
  int v24; // [sp+20h] [bp-20h]
  v10 = a1;
  v11 = a9;
  v12 = a3;
  v13 = a7;
  if ( !a9 )
    v11 = ((*a1)->NewStringUTF)(a1, &unk_13ECB);
  if ( !a7 )
    v13 = ((*v10)->NewStringUTF)(v10, &unk_13ECB);
  if ( !v12 )
    v12 = ((*v10)->NewStringUTF)(v10, &unk_13ECB);
  v14 = ((*v10)->GetStringUTFChars)(v10, v12, 0);
  v15 = v11;
  v16 = ((*v10)->GetStringUTFChars)(v10, v11, 0);
  v17 = ((*v10)->GetStringUTFChars)(v10, v13, 0);
  v23 = 0;
  v18 = strlen(v14);
  v19 = j_tj_crypt(v14, v18, a5, a6, v17, a8, v16, a10, &v23);
  ((*v10)->ReleaseStringUTFChars)(v10, v12, v14);
  ((*v10)->ReleaseStringUTFChars)(v10, v13, v17);
  ((*v10)->ReleaseStringUTFChars)(v10, v15, v16);
  if ( v19 )
  {
    v20 = j_tjtxtutf8(v19, v23, 10);
    v21 = ((*v10)->NewStringUTF)(v10, v20);
    free(v20);
  }
  else
  {
    v21 = ((*v10)->NewStringUTF)(v10, &unk_13ECB);
  }
   result = _stack_chk_guard - v24;
  if ( _stack_chk_guard == v24 )
    result = v21;
  return result;
}

流程如下

v19 = j_tj_crypt(v14, v18, a5, a6, v17, a8, v16, a10, &v23);
v20 = j_tjtxtutf8(v19, v23, 10);
v21 = ((*v10)->NewStringUTF)(v10, v20);
result = v21;
return result;

v19 是经过加密函数 j_tj_crypt  加密后生成的 bytes,再经过 j_tjtxtutf8(上篇知道第三个参数为 10 的时候是标准的 base64)base64 编码

可以打个断点验证下

j_tjtxtutf8:

0x38FC 下个断点

打印 r0 的值,长度是 0x31

mr0 0x31

blr 添加函数结束后断点

c 跳到下个断点

逆向之友验证

https://gchq.github.io/CyberChef

没毛病,接下来看 j_tj_crypt 是怎么生成 v19 的

进到 tj_crypt 函数

看到熟悉的 key 了

然后这些 “1”,“2”,“3” 是代表加密模式

stmcmp 是 c 函数

也就是根据第一个参数的值才决定用哪种加密

我们可以在 unidbg 上修改第一个参数的值,可以看到三种加密的结果是不一样的

    public void get_bodyencrypt() throws FileNotFoundException {
        List<Object> list = new ArrayList<>();
        DvmClass Native=vm.resolveClass("com/tujia/gundam/Gundam");
        DvmObject<?> jclass =Native.newObject(null);
        String arg0_str = "1";
        Object arg0 = vm.addLocalObject(new StringObject(vm, arg0_str));
        long arg1 = 1630808396L;
        String arg2_str = "YM0A2TMAIEWA3xMAMM1xTjQEUYGD0wZAYAh32AdgQATDzAZNZA33WAEIZAzzhAMMNAzyTDAkZMzTizYOYN11TTgMRcDThyNMZO44GTIAVQGDi5OONN2wWGMMUVWj1iMNOYxxGmUU";
        Object arg2 = vm.addLocalObject(new StringObject(vm, arg2_str));
        int arg3 = arg2_str.getBytes(StandardCharsets.UTF_8).length;
        String arg4_str = "{\"code\":null,\"parameter\":{\"activityTask\":{},\"defa";//"{\"code\":null,\"parameter\":{\"activityTask\":{},\"defaultKeyword\":\"\",\"abTest\":{\"AppSearchHouseList\":\"B\",\"listabtest8\":\"B\"},\"returnNavigations\":true,\"returnFilterConditions\":true,\"specialKeyType\":0,\"returnGeoConditions\":true,\"abTests\":{\"T_login_831\":{\"s\":true,\"v\":\"A\"},\"searchhuojiatujia\":{\"s\":true,\"v\":\"D\"},\"listfilter_227\":{\"s\":true,\"v\":\"D\"},\"T_renshu_292\":{\"s\":true,\"v\":\"D\"},\"Tlisttest_45664\":{\"s\":true,\"v\":\"C\"},\"Tlist_168\":{\"s\":true,\"v\":\"C\"},\"T_LIST27620\":{\"s\":false,\"v\":\"A\"}},\"pageSize\":10,\"excludeUnitIdSet\":null,\"historyConditions\":[],\"searchKeyword\":\"\",\"url\":\"\",\"isDirectSearch\":false,\"sceneCondition\":null,\"returnAllConditions\":true,\"searchId\":null,\"pageIndex\":0,\"onlyReturnTotalCount\":false,\"conditions\":[{\"type\":2,\"value\":\"2021-09-05\"},{\"type\":3,\"value\":\"2021-09-06\"},{\"label\":\"大理州\",\"type\":1,\"value\":\"36\"}]},\"client\":{\"abTest\":{},\"abTests\":{},\"adTest\":{\"m1\":\"Color OS V5.2.1\",\"m2\":\"ade40419318f085ff21b4776f2eef21f\",\"m3\":\"armeabi-v7a\",\"m4\":\"armeabi\",\"m5\":\"100\",\"m6\":\"2\",\"m7\":\"5\"},\"api_level\":260,\"appFP\":\"qA/Ch2zqjORBz90YV34sUZpcFXFV6vzhmAISdTjYAFeqMBTtMUukzQFXqkDokr+sMau0bWClwjtk36nbrVBWVrjmrPTCkXFIraNHdgRVW/QT6g4eLWuM3hhP8qsWgGnrErk2KA+GFxr/OBRMYfV4l0v+TYUDZ5k4bUCUawafdLY5b3aC02SuOrqjW3jjrXiB/dt6ErjrDv44vY4Y8/1r5Z6ut/2BmcErxM37MniKpW6EZc8F4CjJ9S1KRTtEPJ2Kkd2Sd8602jqdgtssJ6QKXyx2+qsKvybydVe+zSTXQGn/T86A6uW0oC+mJHwOLnP8HKN0q2Fu3rTcKZ+Prbs/dcBHaWJi1C1tHZFza2O+1gUQTgvg+Kq57BvE6IjEhveT\",\"appId\":\"com.tujia.hotel\",\"appVersion\":\"260_260\",\"appVersionUpdate\":\"rtag-20210803-183436-zhengyuan\",\"batteryStatus\":\"full\",\"buildTag\":\"rtag-20210803-183436-zhengyuan\",\"buildVersion\":\"8.38.0\",\"ccid\":\"51742042410923060391\",\"channelCode\":\"qq\",\"crnVersion\":\"254\",\"devModel\":\"OPPO R11st\",\"devToken\":\"\",\"devType\":2,\"dtt\":\"\",\"electricity\":\"100\",\"flutterPkgId\":\"277\",\"gps\":null,\"kaTest\":{\"k1\":\"2_1_2\",\"k2\":\"sdm660\",\"k3\":\"ubuntu-16\",\"k4\":\"R11st_11_A.43_200402\",\"k5\":\"OPPO/R11st/R11s:8.1.0/OPM1.171019.011/1577198226:user/release-keys\",\"k6\":\"R11st\",\"k7\":\"OPM1.171019.011\"},\"latitude\":\"23.105714\",\"locale\":\"zh-CN\",\"longitude\":\"113.470271\",\"networkType\":\"1\",\"osVersion\":\"8.1.0\",\"platform\":\"1\",\"salt\":\"ZMmADTBAMIzAzxMAYMkxWjVEFY2TkwNMZAh2WAdAFATzmAZMYA3wTAEckAzT2AMMMAz52DAIZMzThzYMMN1xWTgYMcDD0yNNMO41zTIQUQGDx5OOZN2wDGMMEVWjziMNZYxxDmUA\",\"screenInfo\":\"\",\"sessionId\":\"a3e57f1c-03a8-3bc4-8709-fb36e7698ae4_1630844995950\",\"tId\":\"21090509553913313246\",\"tbTest\":{\"j1\":\"11d890e2\",\"j2\":\"R11s\",\"j3\":\"OPPO R11st\",\"j4\":\"OPPO\",\"j5\":\"OPPO\",\"j6\":\"unknown\",\"j7\":\"qcom\",\"j8\":\"2.1.0  (ART)\"},\"traceid\":\"1630845462612_1630845462444_1630845358749\",\"uID\":\"a3e57f1c-03a8-3bc4-8709-fb36e7698ae4\",\"version\":\"260\",\"wifi\":null,\"wifimac\":\"i5ZQ9aI14FDr9VMJ/ECIg7KAOE+Xev1/CrFoa53WLbE=\"},\"psid\":\"76938405-a463-464a-9c49-752022daf516\",\"type\":null,\"user\":null,\"usid\":null}";
        Object arg4 = vm.addLocalObject(new StringObject(vm, arg4_str));
        int arg5 = arg4_str.getBytes(StandardCharsets.UTF_8).length;
        list.add(vm.getJNIEnv());
        list.add(vm.addLocalObject(jclass));
        list.add(arg0);
        list.add(arg1);
        list.add(arg2);
        list.add(arg3);
        list.add(arg4);
        list.add(arg5);
        Number number = module.callFunction(emulator, 0x380c+1, list.toArray())[0];
        String result = vm.getObject(number.intValue()).getValue().toString();
        System.out.println("result: " + result);
    }

当 String arg0_str = “1”

结果为

2uEIVLrtvmHQ72DYG1+fFcCSo5obEd62UMKP+O7zFRx+K6u3FhLY0f3gYdZds/BMRA==

当 String arg0_str = “2”

结果为

NO3dVW0Fr4yk5Xxoc64Nn903e6sSZK3Uif1TB8dHmVVlXXqzMuteseCb4hfd1/WUQFyHPmxIIRM86pCkymjlgQ==

当 String arg0_str = “3”

结果为

HAT5BvN81B8PjW69r1sqCvf0cwN/Pey9XoAWBnkuGYEeVpqWTDBuyei8kaZ00XTJig/SKP4qThHwuGgWmlr4fg==

开始逐一击破

加密模式 1

strncmp “1” 和 v10 比较,v10=1 所以相等返回 0,!0 则是 true

所以跳到 LABEL_17

有三个函数,分别是 sub_3880、sub_302C、j_CCCrypt

显而易见,加密逻辑在 j_CCCrypt 函数里,而且因为最后 return 的 v11=v22,v22 就是放解密结果的地方

hook CCCrypt:

    public void hook_CCCrypt(){
        IHookZz hookZz = HookZz.getInstance(emulator); // 加载HookZz
        hookZz.wrap(module.base + 0x4480 + 1, new WrapCallback<HookZzArm32RegisterContext>() {
            @Override
            // 方法执行前
            public void preCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
                int arg0 = ctx.getIntArg(0);
                int arg1 = ctx.getIntArg(1);
                int arg2 = ctx.getIntArg(2);
                Pointer arg3 = ctx.getPointerArg(3);
                byte[] arg3_ = arg3.getByteArray(0, ctx.getIntArg(4));
                int arg4 = ctx.getIntArg(4);
                int arg5 = ctx.getIntArg(5);
                String arg6 = ctx.getPointerArg(6).getString(0);
                int arg7 = ctx.getIntArg(7);
                Pointer arg8 = ctx.getPointerArg(8);
                int arg9 = ctx.getIntArg(9);
                System.out.println("arg0:"+arg0);
                System.out.println("arg1:"+arg1);
                System.out.println("arg2:"+arg2);
                Inspector.inspect(arg3_, "arg3");
                System.out.println("arg4:"+arg4);
                System.out.println("arg5:"+arg5);
                System.out.println("arg6:"+arg6);
                System.out.println("arg7:"+arg7);
                ctx.push(arg8);
                ctx.push(arg9);
            };
            @Override
            // 方法执行后
            public void postCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
                int length=ctx.pop();
                Pointer output = ctx.pop();
                byte[] outputhex = output.getByteArray(0, length);
                Inspector.inspect(outputhex, "CCCrypt ret");
            }
        });
        hookZz.disable_arm_arm64_b_branch();
    }

hook 结果

大胆一点猜,arg3(v4) 就是密钥,是 sub_302C 函数生成的

arg6(a7) 就是明文,是什么加密还不知道,反正 99% 是对称加密

这时候是先看密钥怎么生成的还是先找出是什么加密算法呢?

都可以,我喜欢倒着推,所以先看 CCCrypt,双击进入函数

显而易见  j_CCCryptorCreate 是加密的初始化函数

双击进去 

int __fastcall CCCryptorCreate(int a1, unsigned int a2, void *a3, int a4, int a5, int a6, _DWORD *a7)
{
  unsigned int v7; // r5
  void **v8; // r8
  int v9; // r9
  void *v10; // r11
  int v11; // r10
  int v12; // r4
  size_t v14; // r4
  _BYTE *v15; // r0
  int v16; // r0
  void *v17; // [sp+10h] [bp-28h]
  size_t v18; // [sp+14h] [bp-24h]
  int v19; // [sp+18h] [bp-20h]
  v11 = a1;
  v12 = 0xFFFFEF34;
  if ( a7 )
  {
    v7 = a2;
    if ( a2 <= 5 )
    {
      v9 = a4;
      v10 = a3;
      v8 = off_1EB14[a2];
      v12 = (*v8)(a1);
      if ( !v12 )
        goto LABEL_6;
    }
  }
  while ( _stack_chk_guard != v19 )
  {
LABEL_6:
    v17 = v10;
    v14 = v18 + 20;
    v18 = v14;
    v15 = malloc(v14);
    if ( v15 )
    {
      v10 = v15;
      *(v15 + 1) = v14;
      *(v15 + 2) = v11;
      *(v15 + 3) = v7;
      *(v15 + 4) = v8;
      *v15 = 1;
      v16 = (v8[1])(v15 + 20, v11, v7, v17, v9, a5, a6);
      if ( v16 )
      {
        v12 = v16;
        free(v10);
      }
      else
      {
        v12 = 0;
        *a7 = v10;
      }
    }
    else
    {
      v12 = -4302;
    }
  }
  return v12;
}

这里伪代码看着有点懵?看上去好像没函数? 但是这里不对劲?

这里 v8[1] 应该是个函数地址?后边是参数?

那 v8[1] 的地址是多少呢?

按 tab,转成汇编再看看

.text:00004274                 EXPORT CCCryptorCreate
.text:00004274 CCCryptorCreate                         ; CODE XREF: j_CCCryptorCreate+8↑j
.text:00004274                                         ; DATA XREF: LOAD:00000450↑o ...
.text:00004274
.text:00004274 anonymous_0     = -0x34
.text:00004274 var_28          = -0x28
.text:00004274 var_24          = -0x24
.text:00004274 var_20          = -0x20
.text:00004274 anonymous_1     =  8
.text:00004274 arg_8           =  0x10
.text:00004274
.text:00004274 ; __unwind {
.text:00004274                 PUSH            {R4-R7,LR}
.text:00004276                 ADD             R7, SP, #0xC
.text:00004278                 PUSH.W          {R1-R11}
.text:0000427C                 MOV             R10, R0
.text:0000427E                 LDR             R0, =(__stack_chk_guard_ptr - 0x4284)
.text:00004280                 ADD             R0, PC  ; __stack_chk_guard_ptr
.text:00004282                 LDR             R6, [R0] ; __stack_chk_guard
.text:00004284                 LDR             R0, [R6]
.text:00004286                 STR             R0, [SP,#0x38+var_20]
.text:00004288                 LDR             R0, =0xFFFFEF32
.text:0000428A                 ADDS            R4, R0, #2
.text:0000428C                 LDR             R0, [R7,#arg_8]
.text:0000428E                 CBZ             R0, loc_42B2
.text:00004290                 MOV             R5, R1
.text:00004292                 CMP             R1, #5
.text:00004294                 BHI             loc_42B2
.text:00004296                 LDR             R0, =(off_1EB14 - 0x42A2) ; r0=0x1EB14 - 0x42A2 = 0x1A872
.text:00004298                 MOV             R9, R3
.text:0000429A                 MOV             R11, R2
.text:0000429C                 ADD             R2, SP, #0x38+var_24
.text:0000429E                 ADD             R0, PC  ; off_1EB14
.text:000042A0                 MOV             R1, R5
.text:000042A2                 LDR.W           R8, [R0,R5,LSL#2]
.text:000042A6                 MOV             R0, R10
.text:000042A8                 LDR.W           R3, [R8]
.text:000042AC                 BLX             R3
.text:000042AE                 MOV             R4, R0
.text:000042B0                 CBZ             R0, loc_42C8
.text:000042B2
.text:000042B2 loc_42B2                                ; CODE XREF: CCCryptorCreate+1A↑j
.text:000042B2                                         ; CCCryptorCreate+20↑j ...
.text:000042B2                 LDR             R0, [R6]
.text:000042B4                 LDR             R1, [SP,#0x38+var_20]
.text:000042B6                 SUBS            R0, R0, R1
.text:000042B8                 ITTTT EQ
.text:000042BA                 MOVEQ           R0, R4
.text:000042BC                 ADDEQ           SP, SP, #0x1C
.text:000042BE                 POPEQ.W         {R8-R11}
.text:000042C2                 POPEQ           {R4-R7,PC}
.text:000042C4                 BLX             __stack_chk_fail
.text:000042C8 ; ---------------------------------------------------------------------------
.text:000042C8
.text:000042C8 loc_42C8                                ; CODE XREF: CCCryptorCreate+3C↑j
.text:000042C8                 LDR             R0, [SP,#0x38+var_24]
.text:000042CA                 STR.W           R11, [SP,#0x38+var_28]
.text:000042CE                 ADD.W           R4, R0, #0x14
.text:000042D2                 STR             R4, [SP,#0x38+var_24]
.text:000042D4                 MOV             R0, R4  ; size
.text:000042D6                 BLX             malloc
.text:000042DA                 CBZ             R0, loc_4312
.text:000042DC                 MOV             R11, R0
.text:000042DE                 LDRD.W          R1, R0, [R7,#8]
.text:000042E2                 MOVS            R2, #1
.text:000042E4                 STRD.W          R4, R10, [R11,#4]
.text:000042E8                 STRD.W          R5, R8, [R11,#0xC]
.text:000042EC                 STRB.W          R2, [R11]
.text:000042F0                 MOV             R2, R5
.text:000042F2                 LDR.W           R4, [R8,#4]
.text:000042F6                 LDR             R3, [SP,#0x38+var_28]
.text:000042F8                 STRD.W          R9, R1, [SP]
.text:000042FC                 MOV             R1, R10
.text:000042FE                 STR             R0, [SP,#0x38+anonymous_0+4]
.text:00004300                 ADD.W           R0, R11, #0x14
.text:00004304                 BLX             R4
.text:00004306                 CBZ             R0, loc_4316
.text:00004308                 MOV             R4, R0
.text:0000430A                 MOV             R0, R11 ; ptr
.text:0000430C                 BLX             free
.text:00004310                 B               loc_42B2
.text:00004312 ; ---------------------------------------------------------------------------

按 tab 键,可以看到对应的汇编是这行,

汇编: BLX  R4

指令BLX 
指令的格式为:BLX 目标地址BLX 
指令从ARM 指令集跳转到指令中所指定的目标地址,
并将处理器的工作状态有ARM 状态切换到Thumb 状态。

显而易见,这里是动态跳转的,也就是说 R4 的地址是计算出来的,不是写死的,所以我们得知道 R4 是多少;

这里有两种方法获得 R4 的值:

第一种方法很简单,用 unidbg 或 frida inline hook 0x4304,看 r4 寄存器的值不就行了嘛?

第二种方法就是静态分析,手动计算出来不就行了嘛?

先挑战一下第二种方法看看能出来不?

R4 哪来的?

.text:000042F2                 LDR.W           R4, [R8,#4]
即:R4 = [R8 + 0x4]  []代表取R8+R4的指针

R8 哪来的?

.text:000042A2                 LDR.W           R8, [R0,R5,LSL#2]
即:R8 = [R0 + (R5<<2)] ,[]代表取R0+(R5<<2)的指针

R0 哪来的?

.text:0000429E                 ADD             R0, PC  ; off_1EB14
即:R0 = R0+PC   这里ida已经算出R0=0x1EB14

R5 哪来的?

.text:00004290                 MOV             R5, R1
R5=R1 也就是函数的第二个参数,最后追到tj_crypt函数里的v25=v17 = 4
    if ( !strncmp("1", v10, n) )
    {
      v16 = v9;
      v17 = 4;
LABEL_15:
      v25 = v17;
      v9 = 0;
      goto LABEL_17;
    }

流程:

.text:00004290                 MOV             R5, R1
.text:0000429E                 ADD             R0, PC  ; off_1EB14
.text:000042A2                 LDR.W           R8, [R0,R5,LSL#2]
.text:000042F2                 LDR.W           R4, [R8,#4]

R5 = 4

R0 = 0x1EB14

R8 = [R0+(4«2)] = [0x1EB24]

R4 = [R8+4]

[0x1EB24] 表示取 0x1EB24 的指针,也就是指向的地址,怎么看

Ida 中按 g 输入 0x1EB24,回车

双击 ccRC4Callouts

0x1EC3C 就是 0x1EB24 的指针

所以 R8 = 0x1EC3C

最后 R4 = [R8+0x4] = [0x1EC3C+4] = [0x1EC40]

[0x1EC40] 表示取 0x1EC40 的指针,就是 0x4E89

第二种获取 R4 地址得方法很简单

hook 0x4304

debugger.addBreakPoint(module.base+0x4304);

笑死,直接就出来了,还静态分析个毛

所以 BLX R4 最终等价于 BLX 0x4E89

ida 中按 g 输入 0x4E89 跳转,也可以双击 sub_4E88

咦,显而易见这是 rc4 算法初始化密钥的函数

Hook 这个函数就能拿到 rc4 的密钥

打个断点

debugger.addBreakPoint(module.base+0xC244);

查看 r2 的值

密钥是:f4c7fc5d61bdff1914c383b340958ef1

没毛病吧!就是上面说的 V9

然后最终加密的函数呢?

回到 CCCrypt 函数

v12 = (*(*(v21 + 16) + 12))(v21 + 20);

有经验了,这一行一看跟上面差不多嘛,看看汇编

BLX R4

R4 地址又是算出来的,直接用 unidbg hook 拿到地址

debugger.addBreakPoint(module.base+0x453A);

运行

BLX R4 等价于 BLX 0x4ea5 

ida 中按 g 输入 0x4ea5  跳转

二话不说 hook 这个函数

    public void hook_CC_RC4(){
        //rc4密钥
        IHookZz hookZz = HookZz.getInstance(emulator); // 加载HookZz
        hookZz.wrap(module.base + 0xBEC8 + 1, new WrapCallback<HookZzArm32RegisterContext>() {
            @Override
            // 方法执行前
            public void preCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
                Pointer arg0 = ctx.getPointerArg(3);
                int arg1 = ctx.getIntArg(1);
                Pointer arg2 = ctx.getPointerArg(2);
                System.out.println("arg1:"+arg1);
                System.out.println("arg2:"+arg2.getString(0));
                ctx.push(arg0);
                ctx.push(arg1);
            };
            @Override
            // 方法执行后
            public void postCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
                int length=ctx.pop();
              //  System.out.println(length);
                Pointer output = ctx.pop();
                byte[] outputhex = output.getByteArray(0, length);
                Inspector.inspect(outputhex, "CC_RC4 ret");
            }
        });
        hookZz.disable_arm_arm64_b_branch();
    }

hook 结果:

看来前面猜的都没错嘛,

现在就用逆向之友看看这个 rc4 是不是标准的 rc4 了

没毛病

现在明文知道了,也就是 bodyencrypt 的 第五个参数,

加密方式也知道了,对称算法 rc4

最后就要解决密钥是怎么生成的

rc4 key

回到 tj_crypt,LABEL_17

显而易见!

我们要进入 sub_302 的身体里去探索

char *__fastcall sub_302C(int a1, int a2, int a3, int a4)
{
  int v4; // r8
  char *result; // r0
  int v6; // r4
  int v7; // r9
  char *v8; // r6
  v4 = a1;
  result = 0;
  if ( a2 )
  {
    v6 = a4;
    if ( a4 )
    {
      v7 = a3;
      v8 = malloc(0x15u);
      result = 0;
      *(v8 + 13) = 0;
      *v8 = 0LL;
      *(v8 + 1) = 0LL;
      *(v8 + 17) = 0;
      if ( v8 )
      {
        j_CCHmac(0, v7, v6, v4);
        sub_33E4(v8, 20);
        result = v8;
      }
    }
  }
  return result;
}

先 hook 一下,看看参数和结果

    public void hook_sub_302C(){
        IHookZz hookZz = HookZz.getInstance(emulator); // 加载HookZz
        hookZz.wrap(module.base + 0x302C + 1, new WrapCallback<HookZzArm32RegisterContext>() {
            @Override
            // 方法执行前
            public void preCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
                Pointer arg0 = ctx.getPointerArg(0);
                int arg1 = ctx.getIntArg(1);
                int arg3 = ctx.getIntArg(3);
                Pointer arg2 = ctx.getPointerArg(2);
                System.out.println("arg0:"+arg0.getString(0));
                System.out.println("arg1:"+arg1);
                System.out.println("arg2:"+arg2.getString(0));
                System.out.println("arg3:"+arg3);
            };
            @Override
            // 方法执行后
            public void postCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
                Pointer output = ctx.getR0Pointer();
                byte[] outputhex = output.getByteArray(0, 128);
                Inspector.inspect(outputhex, "sub_302C ret");
            }
        });
        hookZz.disable_arm_arm64_b_branch();
    }

显而易见是个 hmac 哈希编码

hook 结果:

arg0 是明文,可以发现是由 arg2_str+arg1 拼接而来

哪个函数拼接的?

arg2 是密钥,结果是长度 20 个字节(00 00 00 00 是结束标志),可以盲猜是 hmacsha1,然后去逆向之友看看是不是 hmacsha1

咦,不对呢,但是可以发现 hmacsha1 的结果出现在这里

j_CCHmac 下面还有个 sub_33E4(v8, 20)

应该是 sub_33E4 搞得鬼

hook hook 看

这次没错了

如果猜不出是 hmacsha1,那就进入 j_CCHmac、CCHmac

int __fastcall CCHmac(int a1, int a2, int a3, int a4, int a5, int a6)
{
  int v6; // r11
  int v7; // r4
  char v9; // [sp+4h] [bp-194h]
  int v10; // [sp+Ch] [bp-18Ch]
  void (__fastcall *v11)(int *, int, int); // [sp+160h] [bp-38h]
  int v12; // [sp+184h] [bp-14h]
  int v13; // [sp+188h] [bp-10h]
  v13 = v6;
  v7 = a4;
  j_CCHmacInit(&v9, a1, a2, a3);
  v11(&v10, v7, a5);
  j_CCHmacFinal(&v9, a6);
  return _stack_chk_guard - v12;
}

显而易见,又是个 init、update、Final 流程,在中卷已经说了,忘了的回去看看

进入 j_CCHmacInit、CCHmacInit

看到有个控制流,根据 a2 的值来决定走哪个分支

这里 a2 是 0

所以进到这里

可以看到初始化常量

回到上面

这里的 v11 又是动态跳转,要看的自己 hook 下,不在细说

再回到上面,解决这个函数

进入 sub_33E4 函数

const char *__fastcall sub_33E4(const char *result, unsigned int a2)
{
  unsigned int v2; // r6
  const char *v3; // r9
  _BYTE *v4; // r4
  int v5; // r2
  int v6; // r3
  signed int v7; // r2
  const char *v8; // r0
  const char *v9; // r1
  int v10; // r3
  char v11; // t1
  char v12; // t1
  int v13; // r2
  _BYTE *v14; // r0
  int v15; // r1
  _BYTE *v16; // r3
  char v17; // r5
  if ( a2 >= 2 )
  {
    v2 = a2;
    v3 = result;
    result = strlen(result);
    if ( result >= v2 )
    {
      v4 = malloc(v2 + 1);
      _aeabi_memclr(v4, v2 + 1, v5, v6);
      v7 = 1;
      v8 = &v3[v2 - 1];
      v9 = v3;
      while ( 1 )
      {
        v10 = &v4[v7];
        if ( v9 >= v8 )
          break;
        v11 = *v9++;
        *(v10 - 1) = v11;
        v12 = *v8--;
        v4[v7] = v12;
        v7 += 2;
      }
      if ( v8 == v9 )
        *(v10 - 1) = *v9;
      v13 = v2 - 1;
      v14 = v3 + 1;
      v15 = 0;
      while ( 1 )
      {
        v16 = &v4[v15];
        if ( &v4[v15] >= &v4[v13] )
          break;
        v17 = v4[v13--];
        *(v14 - 1) = v17;
        ++v15;
        *v14 = *v16;
        v14 += 2;
      }
      if ( v13 == v15 )
        *(v14 - 1) = v4[v15];
      result = j_free(v4);
    }
  }
  return result;
}

就是对 hmacsha1 编码后的 byte 移位

看着 ida 反编译的伪代码不好翻译,可以伪代码为主,汇编为辅助去还原;

也可以汇编为主,伪代码和 unidbg 动态调试为辅助去还原;

都不会的还有个更简单的,只需要多搞几组数据对比下,找出规律自己实现就行了;

放出伪代码为主,汇编辅助的代码

import hashlib
import hmac
from hexdump import hexdump
def CCHmac(key:str,input_str:str)->bytes:
    hmacsha1_ret = hmac.new(key.encode(), input_str.encode(), hashlib.sha1).digest()
    return hmacsha1_ret
def sub_33E4(hmacsha1_ret:bytes,size=20)->bytes:
    v4 = [0 for i in range(size)]
    v5 = 1
    v6 = size
    v7 = 0
    while (1):
        if (v7 >= v6):
            break
        v9 = hmacsha1_ret[v7]
        v7 += 1
        v4[v5 - 1] = v9
        v6 -= 1
        v10 = v6
        v4[v5] = hmacsha1_ret[v10]
        v5 += 2
    if v6 == v7:
        hmacsha1_ret = v4
    v11 = size
    v13 = 0
    v12 = 1
    v4 = [0 for i in range(size)]
    while (1):
        if (v13 >= v11):
            break
        v11 -= 1
        v10 = v11
        v4[v12 - 1] = hmacsha1_ret[v10]
        v9 = hmacsha1_ret[v13]
        v13 += 1
        v4[v12] = v9
        v12 += 2
    return bytes(v4)[:16]
if __name__ == '__main__':
    input_str = "YM0A2TMAIEWA3xMAMM1xTjQEUYGD0wZAYAh32AdgQATDzAZNZA33WAEIZAzzhAMMNAzyTDAkZMzTizYOYN11TTgMRcDThyNMZO44GTIAVQGDi5OONN2wWGMMUVWj1iMNOYxxGmUU1630808396"
    Hmackey = "dGp******dG8K"
    v9 = CCHmac(Hmackey,input_str)
    key = sub_33E4(v9,20)
    hexdump(key)

 rc4 自己写

加密模式 2

其实后面两种加密模式流程都差不多,简单说一下吧。。文章已经太长了

这里改成 2

然后根据上面模式 1 分析的可以知道这个 v25 就是 R5(就忘了?划上去看)

R5=v25=v17=0

R8, [R0,R5,LSL#2]

即:R8=[R0+(0«2)]=[0x1EB14]

同理可得 0x1EB14 的指针是 0x1EB2C

R4=[R8+4]

最后 R4=[R8+0x4]=[0x1EB2C+4] = [0x1EB30]

[0x1EB30] 表示取 0x1EB30 的指针,就是 0x4829

不信的话用 unidbg 打个断点

还是这个地方

debugger.addBreakPoint(module.base+0x4304);

废话不多说,直接 g 0x4829 跳过去

signed int __fastcall sub_4828(int a1, int a2, int a3, char a4, int a5, unsigned int a6, int a7)
{
  int v7; // r5
  char v8; // r8
  int v9; // r6
  char *v10; // r0
  signed int v11; // r4
  int *v12; // r6
  char v13; // r3
  int v14; // r12
  v7 = a1;
  v8 = a4;
  v9 = a2;
  v10 = sub_4DE0(a3);
  v11 = -4300;
  if ( a5 && v10 && *((_DWORD *)v10 + 2) <= a6 && *((_DWORD *)v10 + 3) >= a6 )
  {
    *(_DWORD *)v7 = v10;
    if ( v9 == 1 )
    {
      v12 = (int *)(v10 + 40);
      *(_DWORD *)(v7 + 4) = *((_DWORD *)v10 + 8);
      v13 = 0;
    }
    else
    {
      if ( v9 )
        return v11;
      v12 = (int *)(v10 + 36);
      *(_DWORD *)(v7 + 4) = *((_DWORD *)v10 + 7);
      v13 = 1;
    }
    v14 = *v12;
    *(_BYTE *)(v7 + 49) = v8 & 1;
    *(_BYTE *)(v7 + 48) = v13;
    *(_DWORD *)(v7 + 8) = v14;
    if ( v8 & 2 )
    {
      *(_WORD *)(v7 + 50) = 0;
    }
    else
    {
      *(_BYTE *)(v7 + 50) = 1;
      if ( v10[16] )
        *(_BYTE *)(v7 + 51) = 0;
      else
        *(_BYTE *)(v7 + 51) = 1;
    }
    *(_DWORD *)(v7 + 28) = 0;
    if ( !(*((int (__fastcall **)(int))v10 + 5))(v7 + 52) )
    {
      if ( *(_BYTE *)(v7 + 50) )
        sub_4E0C((unsigned __int8 *)v7, (int *)a7);
      v11 = 0;
    }
  }
  return v11;
}

这函数里只有 sub_4DE0 跟 sub_4E0C 两个函数,sub_4DE0 很短没什么信息

那肯定就再 sub_4E0C 函数里了

进去

int __fastcall sub_4E0C(unsigned __int8 *a1, int *a2)
{
  int *v2; // r3
  JNIEnv *v3; // r2
  int v4; // r1
  int v5; // r2
  int v7; // [sp+0h] [bp-30h]
  __int64 v8; // [sp+8h] [bp-28h]
  __int64 v9; // [sp+10h] [bp-20h]
  __int64 v10; // [sp+18h] [bp-18h]
  int v11; // [sp+24h] [bp-Ch]
  v2 = a2;
  v3 = *a1;
  if ( *(*a1 + 16) )
  {
    if ( !a2 )
    {
      v2 = &v7;
      *&v7 = 0LL;
      v8 = 0LL;
      v9 = 0LL;
      v10 = 0LL;
    }
    (v3[6])(a1 + 52, a1[48], v2);
  }
  else
  {
    v4 = (a1 + 12);
    if ( !a1[48] )
      v4 = (a1 + 32);
    v5 = *(v3 + 4);
    if ( v2 )
      _aeabi_memmove(v4, v2, v5);
    else
      _aeabi_memclr(v4, v5, v5, 0);
  }
  return _stack_chk_guard - v11;
}

(v3[6])(a1 + 52, a1[48], v2);

这一行很熟悉了吧,看汇编

直接 hook,静态分析个毛,早 hook 早发表

debugger.addBreakPoint(module.base+0x4E40);

g 0x11a51

显而易见,这是 aes 算法,设置 iv 的地方

hook

debugger.addBreakPoint(module.base+0x11A50);

打印 iv

那 aes 的 key 是哪里初始化的?一般是先初始化 key 再 iv,前面是不是漏掉了什么关键的地方? 回去看看

噢这里还有这 BLX

hook 之

ida g 0x119e9

unidbg b 0x119e9 

c

进去

hook 之

b0xE398 

c

打印 r0

可以看到这个 key 跟 rc4 的 key 是一样的

加密方式:aes_cbc
明文:{"code":null,"parameter":{"activityTask":{},"defa
aes_key:f4c7fc5d61bdff1914c383b340958ef1
aes_iv:00000000000000000000000000000000

逆向之友验证

没毛病

还想知道 aes_encrypt 在哪的

hook 这里

拿到 R4 地址

再 hook 这里 

拿到 R5 地址

就是了

加密模式 3

跟模式 2 一样是 aes_cbc, 只是 iv 不一样,所以只说 iv 的生成

改成 3

看样子就是比模式二多走了一个 sub_302c,

public void hook_sub_302C(){
        IHookZz hookZz = HookZz.getInstance(emulator); // 加载HookZz
        hookZz.wrap(module.base + 0x302C + 1, new WrapCallback<HookZzArm32RegisterContext>() {
            @Override
            // 方法执行前
            public void preCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
                Pointer arg0 = ctx.getPointerArg(0);
                int arg1 = ctx.getIntArg(1);
                int arg3 = ctx.getIntArg(3);
                Pointer arg2 = ctx.getPointerArg(2);
                System.out.println("arg0:"+arg0.getString(0));
                System.out.println("arg1:"+arg1);
                System.out.println("arg2:"+arg2.getString(0));
                System.out.println("arg3:"+arg3);
            };
            @Override
            // 方法执行后
            public void postCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
                Pointer output = ctx.getR0Pointer();
                byte[] outputhex = output.getByteArray(0, 128);
                Inspector.inspect(outputhex, "sub_302C ret");
            }
        });
        hookZz.disable_arm_arm64_b_branch();
    }

然后后边的流程就跟模式二就没区别了,就不说了

hook aes_cc_set_iv

debugger.addBreakPoint(module.base+0x11A50);

可以看到这个 iv 就是第一次 sub_302c 生成的值

这个 iv 和 key 的生成方式都是一样的,只是明文少了个时间戳而已

加密方式:aes_cbc
明文:{"code":null,"parameter":{"activityTask":{},"defa
aes_key:f4c7fc5d61bdff1914c383b340958ef1
aes_iv:7b53e03485ddbbcf07bd9698718abc8a

逆向之友验证

这些都是标准的算法,遇到魔改算法的时候,就需要对算法的原理非常熟悉了,这时就需要看这个男人的密码学专栏

完结!

饮茶去了

上一页
下一页