前言

本文仅供学习探讨之用,如果侵犯您的权益请联系我删除。

上文

距离上一篇文章已经过了半年了,最近也是刚好在学逆向相关的内容,就拿以前的工程来练手。

静态分析

还是接着上文,寻找hkey计算方式

Jadx生成的Java代码

可以看到,生成hkey的过程中调用了Native函数encode,这个函数在libnative-lib.so

IDA生成的encode伪代码

解压出so文件,拖进IDA,可以看到伪代码,可以看到一共有5个参数,但是在Java代码中是3个参数,前2个参数是不需要的。

尝试直接Hook,但是只能得到看不懂的结果,应该都是字符串地址。

Hook encode函数(代码参见上文)

我们换个思路,从return开始分析

int __fastcall encode(int a1, int a2, int a3, int a4, int a5)
{
  const char *v5; // r10
  int v6; // r4
  int v7; // r6
  int v8; // r8
  const char *v9; // r11
  int v10; // r0
  bool v11; // zf
  size_t v12; // r5
  char *v13; // r5
  char *v14; // r0
  size_t v15; // r0
  char *v16; // r0
  int v17; // r5
  int result; // r0
  int v19; // [sp+0h] [bp-30h]
  int *v20; // [sp+4h] [bp-2Ch]
  int v21; // [sp+8h] [bp-28h]
  int v22; // [sp+Ch] [bp-24h]
  v19 = a4;
  v6 = a1;    // v6 是第一个参数
  v7 = a4;
  if ( j_check_signature(a1) == 1 )    // 看名字应该是验证签名的方法
  {
    v8 = 0;
    v9 = (const char *)(*(int (__fastcall **)(int, int, _DWORD))(*(_DWORD *)v6 + 676))(v6, v7, 0);
    v10 = (*(int (__fastcall **)(int, int, _DWORD))(*(_DWORD *)v6 + 676))(v6, a5, 0);
    v11 = v9 == 0;
    if ( v9 )
    {
      v5 = (const char *)v10;
      v11 = v10 == 0;
    }
    if ( !v11 )
    {
      v21 = a5;
      v12 = strlen(v9);
      v20 = &v19;
      v13 = (char *)&v19 - ((strlen(v5) + v12 + 21) & 0xFFFFFFF8);
      v14 = strcpy(v13, v9);
      v15 = strlen(v14);
      _aeabi_memcpy(&v13[v15], "/bfhdkud_time=", 15);
      v16 = strcat(v13, v5);
      v17 = j_MDString(v16);    // v16 被赋值
      (*(void (__fastcall **)(int, int, const char *))(*(_DWORD *)v6 + 680))(v6, v7, v9);
      (*(void (__fastcall **)(int, int, const char *))(*(_DWORD *)v6 + 680))(v6, v21, v5);
      v8 = j_charToJstring(v6, v17);    // v8 被赋值,值与v6,v17有关
    }
    if ( _stack_chk_guard == v22 )
      return v8;    // 返回值是 v8
  }    //正常逻辑应该走不到这里
  result = _stack_chk_guard - v22;
  if ( _stack_chk_guard == v22 )
    result = j_j_charToJstring(v6, UNSIGNATURE[0]);
  return result;
}

不难看出,返回值跟j_charToJstringj_MDString有关,j_大概是一个修饰符,真实函数应该是charToJstringMDString

找到目标,接下来上Hook。

动态分析

接下来使用 Frida 进行分析,Frida 安装与使用可以参考:

Python 加载器

保存为run.py

import sys
import frida
process_name = 'com.max.xiaoheihe'
# 发送信息回调函数
def on_message(message, data):
    if message['type'] == 'send':
        print(f"[*] {message['payload']}")
    else:
        print(message)
if __name__ == '__main__':
    try:
        device = frida.get_usb_device()
    except:
        device = frida.get_remote_device()
    if not device:
        print("* 连接到Frida Server失败")
    else:
        process = device.attach(process_name)
        # 加载JS脚本
        js = open('hook.js', encoding='utf-8').read()
        script = process.create_script(js)
        script.on('message', on_message)
        script.load()
        # 读取返回输入
        input()
        script.unload()

Hook代码

保存为hook.js

console.log("脚本载入成功");
Java.perform(function () {
    var charToJstringAddr = Module.findExportByName("libnative-lib.so", "charToJstring");
    console.log(charToJstringAddr);
    if (charToJstringAddr != null) {
        Interceptor.attach(charToJstringAddr, {
            onEnter: function (args) {
                var arg0 = args[0];
                var arg1 = args[1];
                console.log('charToJstring - Enter');
                console.log(arg0, Memory.readCString(arg0));
                console.log(arg1, Memory.readCString(arg1));
            },
            onLeave: function (retval) {
                //retval函数返回值
                console.log('charToJstring - Leave');
                console.log(retval.toString());
                console.log('======');
            }
        });
    }
    var MDStringAddr = Module.findExportByName("libnative-lib.so", "MDString");
    console.log(MDStringAddr);
    if (MDStringAddr != null) {
        Interceptor.attach(MDStringAddr, {
            onEnter: function (args) {
                var arg0 = args[0];
                var arg1 = args[1];
                console.log('MDString - Enter');
                console.log(arg0, Memory.readCString(arg0));
                console.log(arg1, Memory.readCString(arg1));
            },
            onLeave: function (retval) {
                console.log('MDString - Leave');
                console.log(retval.toString());
                console.log('======');
            }
        });
    }
})

Hook结果

不难看出,MDString的输入是特定的拼接字符串,MDString的输出是charToJstring的输入,而且是一个32位哈希值,推测是MD5

MD5测试

试了一下,果真是MD5

所以encode的逻辑是把url的路径部分,拼上/bfhdkud_time=,再加上秒级时间戳,然后返回整个字符串的MD5哈希

Python 实现

稍微整理了一下Java代码:

String path = get_url_path(str);
String time = (System.currentTimeMillis() / 1000) + "";
if (path.endsWith("/")) {
    path = path.substring(0, i.length() - 1);  //去除path结尾的"/"
}
String hkey = get_MD5(NDKTools.encode(HeyBoxApplication.f(), path, time).replaceAll("a", "app").replaceAll("0", "app")).substring(0, 10);

接下来换成Python实现

import time
import hashlib
from urllib.parse import urlparse
def gen_hkey(url: str,t:int) -> str:
    def url_to_path(url: str) -> str:
        path = urlparse(url).path
        if path and path[-1] == '/':
            path = path[:-1]
        return(path)
    def get_md5(data: str):
        md5 = hashlib.md5()
        md5.update(data.encode('utf-8'))
        result = md5.hexdigest()
        return(result)
    h = f'{url_to_path(url)}/bfhdkud_time={t}'
    h = get_md5(h)
    h = h.replace('a', 'app')
    h = h.replace('0', 'app')
    h = get_md5(h)
    h = h[:10]
    return(h)
t=time.time()
h=gen_hkey('https://api.xiaoheihe.cn/bbs/app/feeds/news',t)
print('time: ',t)
print('hkey: ',h)

成功得到一模一样的hkey

后续

最后修改:2020 年 08 月 20 日
Null