app逆向之安卓native层安全逆向分析(三):ida使用+unidbg补环境+算法还原

前言

继续跟着龙哥的unidbg学习:SO逆向入门实战教程三:V2-Sign_unidbg context_白龙~的博客-CSDN博客

还是那句,我会借鉴龙哥的文章,以一个初学者的角度,加上自己的理解,把内容丰富一下,尽量做到不在龙哥的基础上画蛇添足,哈哈。感谢观看的朋友

分析

 

打开app,抓包,发现有个sign

 

这个sign就是今天的重点了,jadx打开apk,可以,没有加壳,一搜,发现很快就搜到这些了,而且也不多

 

问题不大,用objcetion 把这几个都hook了,看看是走的哪里,没搞多久,就看到这里,入参和返回值,感觉就是这里了

 

 

因为这个返回值的格式,根抓包看到的格式基本一致

 

还有一个,我们看看请求的加密和解密,objection:

 

感觉objection不是太好看这个结果,那么这里改用frida看看吧,虽然objection 可以改源码,然后让结果可读,但是这里感觉不是有太大的必要,直接用frida就行了。

 

 

 

 

就是这里了,直接就解密了。

 

ida查看

打开ida看看这个so:

 

卧槽,这个so文件有点硬啊,看看,jni_onload:

 

 卧槽,确实硬啊,这就尴尬了。那直接上unidbg吧

unidbg调试

1.搭架子

先把main跑起来

 

package com.xiaochuankeji;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.AbstractJni;
import com.github.unidbg.linux.android.dvm.DalvikModule;
import com.github.unidbg.linux.android.dvm.VM;
import com.github.unidbg.memory.Memory;
import com.weibo.international;

import java.io.File;

public class tieba extends AbstractJni {
    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;
    tieba() {
        // 创建模拟器实例,进程名建议依照实际进程名填写,可以规避针对进程名的校验
        emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("cn.xiaochuankeji.tieba").build();
        // 获取模拟器的内存操作接口
        final Memory memory = emulator.getMemory();
        // 设置系统类库解析
        memory.setLibraryResolver(new AndroidResolver(23));
        // 创建Android虚拟机,传入APK,Unidbg可以替我们做部分签名校验的工作
        vm = emulator.createDalvikVM(new File("unidbg-android\\src\\test\\java\\cn\\xiaochuankeji\\right573.apk"));
        // 加载目标SO
        DalvikModule dm = vm.loadLibrary(new File("unidbg-android\\src\\test\\java\\com\\xiaochuankeji\\libnet_crypto.so"), true); // 加载so到虚拟内存
        //获取本SO模块的句柄,后续需要用它
        module = dm.getModule();
        vm.setJni(this); // 设置JNI
        vm.setVerbose(true); // 打印日志
        dm.callJNI_OnLoad(emulator); // 调用JNI OnLoad
    }

    public static void main(String[] args) {
        tieba test = new tieba();
        System.out.println(test);
    }
}

  

运行,看起来没啥问题

 

根据龙哥的文章,说如果这里设置为false:

 

结果就会乱码。原因是这个so样本,做了【字符串的混淆或加密】 ,以此来对抗分析人员,但字符串总是要解密的

 

这个解密一般发生在Init array节或者JNI OnLoad中,又或者是该字符串使用前的任何一个时机,而本例呢,就发生在Init array节中,Shift+F7快捷键查看节区验证这一点

 

Init array节内有大量函数,解密就发生在其中。当我们使用Unidbg模拟执行时,如果加载SO时配置为不执行Init相关函数,这导致整个SO中的字符串都没有被解密,自然输出就是一团”乱码“。

 

由此还可以衍生出一个小话题——如果样本中的字符串被加密了,如何还原?使得分析者可以愉快的用IDA静态分析?

  • 从内存中Dump出解密后的SO或者字符串(可以用Frida/IDA 脚本/ adb 等),将结果回填或者说修复本身SO。
  • 使用Unicorn或基于Unicorn的模拟执行工具(Unidbg、ExAndroidNativeemu等)运行SO,dump解密后的虚拟内存,回填修复SO

 

 

2.执行native_init 

为啥要执行native_init,看jadx,他注册so的时候,同时执行了这个方法,那么我们要完全的模拟安卓环境,那就也要先执行这个方法,再执行核心的方法

 

再看看,刚才的main函数执行打印的日志,刚好,已经有了native的方法地址 

 

 

用ida看看是thumb还是arm,看这个应该是thumb

 

执行下这个方法:

 

卧槽,这个啥情况,意思是没有这个方法,那不急,不加1看看

 

现在好像可以了,这个就尴尬了,跟咱们之前说的规则对不上啊,这就奇怪了。

根据大佬的解答,我们这里拿的地址是unidbg打印的地址,而不是像之前在ida看到的地址,unidbg的地址是真实的地址,如果是arm的话,就会自动+1

再仔细看看这个,好像是报错:

 

它提示,意思是不支持这个方法,可以的,终于到了补环境的环节

3.unidbg补环境

点进第一个报错的地方看看:

 确实有补了很多java环境,但是差我们这个,那我们就自己补了,怎么补,关于补法,有两种实践方法都很有道理

  • 全部在用户类中补,防止项目迁移或者Unidbg更新带来什么问题,这样做代码的移植性比较好。
  • 样本的自定义JAVA方法在用户类中补,通用的方法在AbstractJNI中补,这样做的好处是,之后运行的项目如果调用通用方法,就不用做重复的修补工作

 

那么这里我就直接在自己定义的地方补了,防止对unidbg源码修改过多,没啥意义。再看,这个报错,它要的是一个context,那么给他一个context就行。context怎么来,在第二篇有讲过了:

vm.resolveClass("android/content/Context").newObject(null)

 

再运行看看,卧槽,内存不够了,这么猛的吗

 

没事,查阅资料:

unidbg/SignalTest.java at 56c1397c639716817896766df9bbce9c3a0a15b9 · zhkl0228/unidbg (github.com)

加一行这个就行:

 

 

再次运行,现在好像没报错了:

 

4.执行sign方法

 

解析来开始执行加密方法sign了:

 

 

 

点进去,

 

照着补了

 

 

 

运行,又有新的环境缺失:

 

捋一下完整的流程,在com/izuiyou/common/base/BaseApplication中调用getAppContext方法,获得一个Context上下文,然后getClass获取它的类,最后查看它的类名。类名就是这一系列操作的最终目的,我们前面几步都只浅浅的补了一下,只能说类型给对了,别的都没给。但只要最后的类名给它返回正确的字符串,就没问题。

用objection  的wallerbreaker 看看:

 

 

看这个,说明class就是这个AppController了,补完接着跑:

 

它要一个文件的地址,补完接着跑,

 

它要绝对路径,其实还是上面那个,接着补,新的报错:

 

查看这个是否在调试,这里,按正常的逻辑,肯定是给false

 

又有新的报错,继续补:

 

卧槽,好像终于出结果了!!!!,不容易啊。

为了保险起见,还是验证下,跟hook到的值,是否能保持一致

5.验证值

先用frida 重新hook下这个方法:

 

本来想把这个bytearray转成字符串看看的,试了以下几种方法,都不行:

console.log('sign arg2===》', ByteString.of(arg2).utf8())
console.log('sign arg2===》', ByteString.of(arg2).toString())
console.log('sign arg2===》', hex2str(ByteString.of(arg2).hex()))
console.log('sign arg2===》', jbhexdump(arg2))
console.log('sign arg2===》', Java.use("java.lang.String").$new(Java.array('byte', arg2)))
console.log(Java.use("java.lang.String").$new(arg2))

 

那没法了,直接复制这个byte array吧

 

unidbg里稍微调一下:

 

 

 

 牛逼,结果对上了。舒服

 

完整代码:

package com.xiaochuankeji;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.linux.android.dvm.api.AssetManager;
import com.github.unidbg.linux.android.dvm.array.ByteArray;
import com.github.unidbg.memory.Memory;
import com.weibo.international;

import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

public class tieba extends AbstractJni {
    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;

    tieba() {
        // 创建模拟器实例,进程名建议依照实际进程名填写,可以规避针对进程名的校验
        emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("cn.xiaochuankeji.tieba").build();
        // 获取模拟器的内存操作接口
        final Memory memory = emulator.getMemory();
        emulator.getSyscallHandler().setEnableThreadDispatcher(true);
        // 设置系统类库解析
        memory.setLibraryResolver(new AndroidResolver(23));
        // 创建Android虚拟机,传入APK,Unidbg可以替我们做部分签名校验的工作
        vm = emulator.createDalvikVM(new File("unidbg-android\\src\\test\\java\\com\\xiaochuankeji\\right573.apk"));
        // 加载目标SO
        DalvikModule dm = vm.loadLibrary(new File("unidbg-android\\src\\test\\java\\com\\xiaochuankeji\\libnet_crypto.so"), true); // 加载so到虚拟内存
        //获取本SO模块的句柄,后续需要用它
        module = dm.getModule();
        vm.setJni(this); // 设置JNI
        vm.setVerbose(true); // 打印日志
        dm.callJNI_OnLoad(emulator); // 调用JNI OnLoad
    }

    public static void main(String[] args) {
        tieba test = new tieba();
//        System.out.println(test);
        test.native_init();
        test.sign();
    }

    public void native_init() {
        List<Object> list = new ArrayList<>(10);
        list.add(vm.getJNIEnv()); // arg1,env
        list.add(0); // arg2,jobject
        module.callFunction(emulator, 0x4a069, list.toArray());
    }

    @Override
    public DvmObject<?> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
        switch (signature) {
            case "com/izuiyou/common/base/BaseApplication->getAppContext()Landroid/content/Context;":
                return vm.resolveClass("android/content/Context").newObject(null);
        }
        return super.callStaticObjectMethodV(vm, dvmClass, signature, vaList);
    }

    @Override
    public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
        switch (signature) {
            case "android/content/Context->getClass()Ljava/lang/Class;":
                return dvmObject.getObjectType();
            case "java/lang/Class->getSimpleName()Ljava/lang/String;":
                return new StringObject(vm, "AppController");
            case "android/content/Context->getFilesDir()Ljava/io/File;":
            case "java/lang/String->getAbsolutePath()Ljava/lang/String;":
                return new StringObject(vm, "/data/user/0/cn.xiaochuankeji.tieba/files");
        }
        return super.callObjectMethodV(vm, dvmObject, signature, vaList);
    }

    @Override
    public boolean callStaticBooleanMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
        switch (signature) {
            case "android/os/Debug->isDebuggerConnected()Z":
                return false;
        }
        return super.callStaticBooleanMethodV(vm, dvmClass, signature, vaList);
    }
    @Override
    public int callStaticIntMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
        switch (signature) {
            case "android/os/Process->myPid()I":
                return emulator.getPid();
        }
        return super.callStaticIntMethodV(vm, dvmClass, signature, vaList);
    }

    public String sign() {
        List<Object> list = new ArrayList<>(10);
        list.add(vm.getJNIEnv()); // arg1,env
        list.add(0); // arg2,jobject
        list.add(vm.addLocalObject(new StringObject(vm, "https://api.izuiyou.com/index/recommend"))); // arg3
        byte[] ss = new byte[]{-61,8,81,112,-113,62,93,124,-101,59,-104,72,103,-122,-91,69,-101,86,18,29,118,-53,-110,-34,-35,100,9,114,-24,-56,84,20,-62,117,-95,76,90,-53,66,85,-128,-104,126,114,-91,13,111,11,121,12,77,68,101,52,-60,25,61,-30,38,-59,118,31,110,9,109,-87,119,83,-12,48,-111,41,116,-114,103,98,43,-12,-84,122,-9,-91,28,-26,-79,-45,-74,-52,6,56,63,-32,108,106,3,-23,-67,56,35,-100,91,-42,-22,-68,-112,-83,-79,39,49,92,112,-111,100,103,-127,16,-112,-10,-12,83,46,-108,-121,-54,-16,26,82,-102,83,97,-115,-2,118,-103,-52,-46,-118,-120,9,63,59,77,31,9,-14,68,69,-125,-16,105,-88,103,-80,61,78,115,112,-4,10,101,-21,53,39,-78,57,92,56,-77,20,-17,79,-76,-82,-12,17,-4,2,84,115,-71,20,115,-58,-105,116,-102,-103,-43,-105,96,95,-35,-53,-35,-56,-7,104,7,72,20,-113,-109,37,117,107,64,37,41,-107,70,10,-12,-127,-37,-24,74,21,66,-82,57,-7,76,97,-37,-19,-18,4,60,-49,-15,120,-24,12,-23,3,-63,-55,-12,-70,65,-16,117,9,-90,35,-3,110,-86,49,-97,24,85,60,38,16,-36,-64,24,117,-55,106,9,67,-5,-115,-74,20,-83,32,5,18,54,17,93,83,-18,-19,56,78,87,80,57,46,-105,10,49,-109,97,4,-104,48,69,73,119,45,9,-57,74,-44,3,-97,-101,57,124,44,-17,-54,66,-94,32,-64,-46,-30,87,-104,17,-68,-85,-69,36,46,-42,23,16,0,-117,-72,117,-20,-96,3,-115,104,-11,-26,-35,-32,127,24,37,21,77,16,66,-75,124,101,42,117,118,89,26,-86,62,45,-106,-111,6,-74,-83,13,0,32,-120,-94,-56,44,0,-12,-51,58,105,1,-74,8,105,65,22,-98,112,-90,-66,-13,-126,-96,-122,-113,-97,-85,-100,-126,123,47,-89,109,14,93,15,35,-61,46,76,40,63,119,52,87,-19,-28,41,-11,68,17,69,-101,21,-60,-73,2,-72,-97,-95,-98,-20,-97,65,-8,-29,-4,78,-118,25,-31,-16,-105,90,91,38,-100,77,24,34,-28,-23,81,74,44,-78,68,-62,115,125,-109,-76,-97,88,51,-13,100,-38,110,110,-51,17,51,5,91,-24,10,-102,40,60,-27,106,71,97,-125,37,-75,40,40,-43,101,-8,41,-124,-8,93,-18,-128,8,-50,-128,-128,-99,-23,97,-25,-118,-25,-23,24,-125,-47,-91,108,-66,4,73,19,31,-34,62,-78,122,-81,5,41,11,66,-26,-116,81,107,-14,-106,-110,35,78,-9,-88,20,-41,-111,-37,39,-104,82,71,-36,-116,-47,29,24,-107,24,32,-7,-90,-52,124,-94,-107,57,-7,117,38,-99,-37,50,-126,-68,45,-32,-25,-118,-69,20,55,-24,40,17,-116,111,-36,-15,109,102,37,-7,-118,63,88,52,46,58,-91,111,33,-23,102,116,-77,-100,102,-124,8,7,92,-84,-33,-26,54,-4,75,56,100,-5,-59,-66,51,127,-126,-31,39,-20,-81,72,106,64,-71,8,88,9,-18,-50,-118,-109,31,-55,11,89,0,-49,-69,-54,-118,14,-78,109,36,95,-6,68,21,58,-86,-103,55,94,4,-23,-75,106,-60,-20,72,3,-66,54,78,12,-9,-70,-13,106,-112,-51,100,62,-101,28,-63,8,-120,-67,24,-10,50,41,114,-41,42,-108,-19,-116,-62,-45,17,112,81,91,18,-108,-113,108,108,-88,-18,57,-40,117,-64,38,65,36,-24,-90,53,-61,-37,-45,-70,-14,-41,109,3,-59,-7,-101,-87,84,-39,25,85,-91,-111,17,35,77,-57,75,-105,-97,65,-37,-103,-112,-62,51,-42,40,59,70,79,50,-83,63,-23,-6,58,29,-5,-3,-33,-29,-91,-59,-36,55,34,24,74,38,-72,99,125,84,-124,60,97,-111,47,40,-29,-85,-76,-119,-89,106,-1,38,17,-17,-123,94,-104,-85,-10,13,53,110,103,100,123,-21,-14,107,11,45,-47,-22,-40,7,-102,26,59,98,120,-28,-27,123,55,97,18,23,-111,109,-127,-14,27,88,-19,-93,59,-118,-118,114,-17,-55,-99,86,-87,-73,-80,17,71,-32,-37,-50,53,29,-112,-81,-124,89,11,83,41,-41,24,-75,6,27,54,-121,12,-19,110,-73,35,-111,51,-118,115,30,106,-56,32,99,39,-75,67,-104,-77,-124,71,7,-36,-38,75,1,-8,-96,-44,99,115,-7,28,-128,38,39,-74,-43,-24,-35};
        ByteArray inbarr = new ByteArray(vm, ss);
        list.add(vm.addGlobalObject(inbarr)); //arg4
        Number number = module.callFunction(emulator, 0x4a28d, list.toArray());
        String result = vm.getObject(number.intValue()).getValue().toString();
        return result;
    }
}

 

 

 

python调用

 

有没有想过,把这个项目,打包成jar后,给py调用呢,这样不就可以直接模拟调用了?有点意思对吧,上一章是直接给java调用,好像还没有给python调用过

先改一下这两个文件的路径,然后打包

 

打包流程就省略了,前面有

 

 

用python调用,记得把apk和so也放到这个py所在的路径下,因为打包的时候写的相对路径,谁调用,谁就是主程序,就要把这两个文件放到主程序文件所在同级目录:

 

这里这个test.py就是我的文件

 

# coding:utf-8

import os
import subprocess
import jpype
import time


def query_by_java_jar(jar_path, param=None):
    if param:
        execute = "java -jar {} '{}'".format(jar_path, param)
    else:
        execute = "java -jar {}".format(jar_path)
    print(execute)
    output = subprocess.Popen(execute, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    res = output.stdout.readlines()
    print('result', res)
    return res


if __name__ == '__main__':
    query_by_java_jar(r"E:\work\myproject\unidbg\out\artifacts\unidbg_jar\unidbg.jar")

  

运行结果:

 

我试了下那个jpype,感觉各种问题,还是这个subproess方便点,直接出结果 

 

算法还原

用findhash,一顿搜索

 

然后找到这个函数

 

鼠标直接点过去:

 

按tab键

 

光标放上去,然后键盘按【h】,它就变成这样了

 

把这4个都这么操作下:

 

又由,先看看md5的源码:

 

import binascii

SV = [0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf,
      0x4787c62a, 0xa8304613, 0xfd469501, 0x698098d8, 0x8b44f7af,
      0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e,
      0x49b40821, 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
      0xd62f105d, 0x2441453, 0xd8a1e681, 0xe7d3fbc8, 0x21e1cde6,
      0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8,
      0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681, 0x6d9d6122,
      0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
      0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x4881d05, 0xd9d4d039,
      0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, 0xf4292244, 0x432aff97,
      0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d,
      0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
      0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391]


# 根据ascil编码把字符转成对应的二进制
def binvalue(val, bitsize):
    binval = bin(val)[2:] if isinstance(val, int) else bin(ord(val))[2:]
    if len(binval) > bitsize:
        raise ("binary value larger than the expected size")
    while len(binval) < bitsize:
        binval = "0" + binval
    return binval


def string_to_bit_array(text):
    array = list()
    for char in text:
        binval = binvalue(char, 8)
        array.extend([int(x) for x in list(binval)])
    return array


# 循环左移
def leftCircularShift(k, bits):
    bits = bits % 32
    k = k % (2 ** 32)
    upper = (k << bits) % (2 ** 32)
    result = upper | (k >> (32 - (bits)))
    return (result)


# 分块
def blockDivide(block, chunks):
    result = []
    size = len(block) // chunks
    for i in range(0, chunks):
        result.append(int.from_bytes(block[i * size:(i + 1) * size], byteorder="little"))
    return result


# F函数作用于“比特位”上
# if x then y else z
def F(X, Y, Z):
    compute = ((X & Y) | ((~X) & Z))
    return compute


# if z then x else y
def G(X, Y, Z):
    return ((X & Z) | (Y & (~Z)))


# if X = Y then Z else ~Z
def H(X, Y, Z):
    return (X ^ Y ^ Z)


def I(X, Y, Z):
    return (Y ^ (X | (~Z)))


# 四个F函数
def FF(a, b, c, d, M, s, t):
    result = b + leftCircularShift((a + F(b, c, d) + M + t), s)
    return (result)


def GG(a, b, c, d, M, s, t):
    result = b + leftCircularShift((a + G(b, c, d) + M + t), s)
    return (result)


def HH(a, b, c, d, M, s, t):
    result = b + leftCircularShift((a + H(b, c, d) + M + t), s)
    return (result)


def II(a, b, c, d, M, s, t):
    result = b + leftCircularShift((a + I(b, c, d) + M + t), s)
    return (result)


# 数据转换
def fmt8(num):
    bighex = "{0:08x}".format(num)
    binver = binascii.unhexlify(bighex)
    result = "{0:08x}".format(int.from_bytes(binver, byteorder='little'))
    return (result)


# 计算比特长度
def bitlen(bitstring):
    return len(bitstring) * 8


def md5sum(msg):
    # 计算比特长度,如果内容过长,64个比特放不下。就取低64bit。
    msgLen = bitlen(msg) % (2 ** 64)
    # 先填充一个0x80,其实是先填充一个1,后面跟对应个数的0,因为一个明文的编码至少需要8比特,所以直接填充 0b10000000即0x80
    msg = msg + b'\x80'  # 0x80 = 1000 0000
    # 似乎各种编码,即使是一个字母,都至少得1个字节,即8bit才能表示,所以不会出现原文55bit,pad1就满足的情况?可是不对呀,要是二进制文件呢?
    # 填充0到满足要求为止。
    zeroPad = (448 - (msgLen + 8) % 512) % 512
    zeroPad //= 8
    msg = msg + b'\x00' * zeroPad + msgLen.to_bytes(8, byteorder='little')
    # 计算循环轮数,512个为一轮
    msgLen = bitlen(msg)
    iterations = msgLen // 512
    # 初始化变量
    # 算法魔改的第一个点,也是最明显的点
    A = 0x67452301
    B = 0xefcdab89
    C = 0x98badcfe
    D = 0x10325476

    # MD5的主体就是对abcd进行n次的迭代,所以得有个初始值,可以随便选,也可以用默认的魔数,这个改起来毫无风险,所以大家爱魔改它,甚至改这个都不算魔改。
    # main loop
    for i in range(0, iterations):
        a = A
        b = B
        c = C
        d = D
        block = msg[i * 64:(i + 1) * 64]
        # 明文的处理,顺便调整了一下端序
        M = blockDivide(block, 16)
        # Rounds
        a = FF(a, b, c, d, M[0], 7, SV[0])
        d = FF(d, a, b, c, M[1], 12, SV[1])
        c = FF(c, d, a, b, M[2], 17, SV[2])
        b = FF(b, c, d, a, M[3], 22, SV[3])
        a = FF(a, b, c, d, M[4], 7, SV[4])
        d = FF(d, a, b, c, M[5], 12, SV[5])
        c = FF(c, d, a, b, M[6], 17, SV[6])
        b = FF(b, c, d, a, M[7], 22, SV[7])
        a = FF(a, b, c, d, M[8], 7, SV[8])
        d = FF(d, a, b, c, M[9], 12, SV[9])
        c = FF(c, d, a, b, M[10], 17, SV[10])
        b = FF(b, c, d, a, M[11], 22, SV[11])
        a = FF(a, b, c, d, M[12], 7, SV[12])
        d = FF(d, a, b, c, M[13], 12, SV[13])
        c = FF(c, d, a, b, M[14], 17, SV[14])
        b = FF(b, c, d, a, M[15], 22, SV[15])

        a = GG(a, b, c, d, M[1], 5, SV[16])
        d = GG(d, a, b, c, M[6], 9, SV[17])
        c = GG(c, d, a, b, M[11], 14, SV[18])
        b = GG(b, c, d, a, M[0], 20, SV[19])
        a = GG(a, b, c, d, M[5], 5, SV[20])
        d = GG(d, a, b, c, M[10], 9, SV[21])
        c = GG(c, d, a, b, M[15], 14, SV[22])
        b = GG(b, c, d, a, M[4], 20, SV[23])
        a = GG(a, b, c, d, M[9], 5, SV[24])
        d = GG(d, a, b, c, M[14], 9, SV[25])
        c = GG(c, d, a, b, M[3], 14, SV[26])
        b = GG(b, c, d, a, M[8], 20, SV[27])
        a = GG(a, b, c, d, M[13], 5, SV[28])
        d = GG(d, a, b, c, M[2], 9, SV[29])
        c = GG(c, d, a, b, M[7], 14, SV[30])
        b = GG(b, c, d, a, M[12], 20, SV[31])

        a = HH(a, b, c, d, M[5], 4, SV[32])
        d = HH(d, a, b, c, M[8], 11, SV[33])
        c = HH(c, d, a, b, M[11], 16, SV[34])
        b = HH(b, c, d, a, M[14], 23, SV[35])
        a = HH(a, b, c, d, M[1], 4, SV[36])
        d = HH(d, a, b, c, M[4], 11, SV[37])
        c = HH(c, d, a, b, M[7], 16, SV[38])
        b = HH(b, c, d, a, M[10], 23, SV[39])
        a = HH(a, b, c, d, M[13], 4, SV[40])
        d = HH(d, a, b, c, M[0], 11, SV[41])
        c = HH(c, d, a, b, M[3], 16, SV[42])
        b = HH(b, c, d, a, M[6], 23, SV[43])
        a = HH(a, b, c, d, M[9], 4, SV[44])
        d = HH(d, a, b, c, M[12], 11, SV[45])
        c = HH(c, d, a, b, M[15], 16, SV[46])
        b = HH(b, c, d, a, M[2], 23, SV[47])

        a = II(a, b, c, d, M[0], 6, SV[48])
        d = II(d, a, b, c, M[7], 10, SV[49])
        c = II(c, d, a, b, M[14], 15, SV[50])
        b = II(b, c, d, a, M[5], 21, SV[51])
        a = II(a, b, c, d, M[12], 6, SV[52])
        d = II(d, a, b, c, M[3], 10, SV[53])
        c = II(c, d, a, b, M[10], 15, SV[54])
        b = II(b, c, d, a, M[1], 21, SV[55])
        a = II(a, b, c, d, M[8], 6, SV[56])
        d = II(d, a, b, c, M[15], 10, SV[57])
        c = II(c, d, a, b, M[6], 15, SV[58])
        b = II(b, c, d, a, M[13], 21, SV[59])
        a = II(a, b, c, d, M[4], 6, SV[60])
        d = II(d, a, b, c, M[11], 10, SV[61])
        c = II(c, d, a, b, M[2], 15, SV[62])
        b = II(b, c, d, a, M[9], 21, SV[63])
        A = (A + a) % (2 ** 32)
        B = (B + b) % (2 ** 32)
        C = (C + c) % (2 ** 32)
        D = (D + d) % (2 ** 32)
    result = fmt8(A) + fmt8(B) + fmt8(C) + fmt8(D)
    return result


if __name__ == "__main__":
    data = str("https://api.izuiyou.com/index/recommend").encode("UTF-8")
    print("plainText: ", data)
    print("result: ", md5sum(data))

  

 

 

 

 

 

对比在线cyberchef:

https://ctf.mzy0.com/CyberChef3

 

 

对比下那四个iv;

 

 

看样子,就是改了4个iv:

 

 

ok的,这个md5的算法,搞定

 

知识点总结

1.看so的调用逻辑,如果有loadlibrary的同时有调用某个方法,unidbg模拟执行的时候也要先调用这个方法

2.如果加载so文件的时候,给定的第二个参数是false,加上so文件有字符串加密和混淆的话就会乱码,所以这里最好给为true,ida里,shift+F7,可以看

3.补环境的时候,要根据报错,最核心的部分进行补环境,同时也要结合实际的执行逻辑

4.构造context,用vm.resolveClass("android/content/Context").newObject(null

5.dvmObject.getObjectType()可以获取当前类的class

6.emulator.getPid(),获取pid

7.6种 frida里打印byte[]的方法

console.log('sign arg2===》', ByteString.of(arg2).utf8())
console.log('sign arg2===》', ByteString.of(arg2).toString())
console.log('sign arg2===》', hex2str(ByteString.of(arg2).hex()))
console.log('sign arg2===》', jbhexdump(arg2))
console.log('sign arg2===》', Java.use("java.lang.String").$new(Java.array('byte', arg2)))
console.log(Java.use("java.lang.String").$new(arg2))

8.ida,光标放到上面,按【h】,可以转为16进制

9.unidbg打印的地址是真实的地址,如果是arm的话,就会自动+1

posted @ 2023-04-25 11:35  Eeyhan  阅读(1013)  评论(0编辑  收藏  举报