App逆向百例|06|某App mfsig分析

本文涉及的产品
密钥管理服务KMS,1000个密钥,100个凭据,1个月
简介: App逆向百例|06|某App mfsig分析

观前提示:

本文章仅供学习交流,切勿用于非法通途,如有侵犯贵司请及时联系删除

样本:aHR0cHM6Ly9wYW4uYmFpZHUuY29tL3MvMUFrMms4M1BjTlRjLVRSbWJDQWlRRkE/cHdkPTgyd3Y=

0x1 准备工作

  • 下载并安装样本App
  • 脱壳
  • 跳转至加密位置

0x2 Unidbg实现

  • 初始化

通过使用Frida hooknativeSignnativeInit发现在加载完so后会调用一次nativeInit 接着才是正常调用nativeSign方法 所以在unidbg中也需要主动初始化

  • 补环境

复制粘贴好基础框架后运行

报了下面的错误

INFO [com.github.unidbg.linux.AndroidElfLoader] (AndroidElfLoader:464) - libsign.so load dependency libandroid.so failed
exit status=0

按照报错给出的信息就是缺少libandroid.so这个东东 那我们给上就好了

new AndroidModule(emulator,vm).register(memory);

继续执行 发现已经不提示缺少libandroid.so了 但还是退出了 也没给出具体原因

那我们改改src/test/resources/log4j.properties

将里面的INFO改成DEBUG 接着再次运行

能够看到里面读取了/proc/self/maps这个文件

虽然unidbg会自动补上一个maps 但是这个maps和真机环境上的maps差距甚远 所以我们还得从真机上copy一个正确的maps文件 并且重定向到我们自己的maps上

@Override
public FileResult resolve(Emulator emulator, String pathname, int oflags) {
    System.out.println(pathname);
    if (pathname.equals("/proc/self/maps")){
        return  FileResult.success(new SimpleFileIO(oflags,new File("unidbg-android/src/test/java/com/meiriyouxian/maps"),pathname));
    }
    return null;
}

此时此刻 这个so文件已经能正常加载了

  • 主动调用

由之前hook的结果可知需要先初始化 所以在unidbg中同样需要进行初始化

并且由java层代码可知传入了俩个参数

private static native int nativeInit(Context context, String str);

unidbg主动调用

public void CallnativeInit(){
    List<Object> args = new ArrayList<>(10);
    args.add(vm.getJNIEnv());
    args.add(0);
    DvmObject<?> context = vm.resolveClass("android/content/Context").newObject(null);
    args.add(vm.addLocalObject(context));
    args.add(vm.addLocalObject(new StringObject(vm,"01000002")));
    module.callFunction(emulator,0x38bb4+1,args.toArray());
}

此时出现报错

补上即可

@Override
public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
    switch (signature){
        case "android/content/Context->getAssets()Landroid/content/res/AssetManager;":{
            return new AssetManager(vm,signature);
        }
    }
    return super.callObjectMethodV(vm, dvmObject, signature, vaList);
}

初始化完后根据java代码实现一下nativeSign

private static native String nativeSign(Context context, long j, byte[] bArr);

unidbg主动调用

public void CallnativeSign(){
    List<Object> args = new ArrayList<>(10);
    args.add(vm.getJNIEnv());
    args.add(0);
    DvmObject<?> context = vm.resolveClass("android/content/Context").newObject(null);
    args.add(vm.addLocalObject(context));
    args.add(1648631301174L);
    byte[] input = "1234567890".getBytes(StandardCharsets.UTF_8);
    args.add(vm.addLocalObject(new ByteArray(vm,input)));
    Number number = module.callFunction(emulator, 0x38bf4 + 1, args.toArray());
    System.out.printf(vm.getObject(number.intValue()).getValue().toString());
}

看看效果 这个时候已经能够正常出值了 并且多次主动调用发现返回值没有发生改变

0x3 算法还原

使用IDA Pro打开so文件发现so文件被加壳 这里使用yang神的dump_so脚本

https://github.com/lasting-yang/frida_dump

IDA Pro打开dump出来的so文件 进来后可以在导出表中找到nativeSignnativeInit都是静态注册的

直接打开nativeSign进行分析

从伪代码的逻辑上看 最终指向了sub_36FC0 而且返回值为v18

这里用unidbg来hook辅助分析so还原算法

emulator.attach().addBreakPoint(module.base+0x36FC0+1);

hook住后 记住mr0的地址 输入blr下断 接着跳到下一个断点

输入刚刚mr0的地址

看到00 70 2E 40 也就是存放数据的地址 通过读取验证确定就是返回的加密值了

traceWrite

emulator.traceWrite(0x402e7000L, 0x402e7000L + 32L);

通过hook可知在0x37f64存在写操作

上ida跳转过去 突然看到了熟悉的东西 这不就是base64吗

双击一下 发现 这码表好像不对 可以确定是改了码表的base64

先hook一下sub_37F3C看看输入和输出结果 输入值

输出值

上CyberChef验证 可以见到结果一模一样

接着找找是谁调用了sub_37F3C

跳转过来后看到v7a2有关

继续找是谁调用了sub_37E5C

跳过来看到a2对应v116

v116sub_2F8F6有关  所以hook一下sub_2F8F6这里注意一下方法一共被调用了3次 后2次才是我们需要的 第2次输出值和之前base64传入值对上了 但是还差了点

第3次输出值就和之前base64传入值一模一样了

看一下传入值 第2次

第3次

和输出值对比了一下可以发现 就是取我们传入的时间戳1648631301174分成了俩半1648631301174中间插入了第2次的传入值

接着找找这段值的由来

还是使用traceWrite

emulator.traceWrite(0x402a10f0L, 0x402a10f0L + 32L);

得到结果 跳转过去0x36488

先hooksub_363DC的传入值

emulator.attach().addBreakPoint(module.base+0x363DC+1);

a2

a3

知道了传入值后看看代码流程

根据代码不难看出 主要操作就是循环累加

for ( i = 0; i != v11; ++i )
      {
        sub_815D8(i, v28);
        v14 = v3 + 1;
        if ( *v3 << 31 )
          v14 = *(v3 + 2);
        v15 = v14[v13];                         
        sub_815D8(i, v27);
        v17 = v26 + 1;
        if ( *v26 << 31 )
          v17 = *(v3 + 5);
        v18 = v31;
        v19 = *(a3 + 2);
        v20 = v17[v16];                         
        if ( (v29 & 1) == 0 )
          v18 = &v29 + 1;
        if ( (*a3 & 1) == 0 )
          v19 = a3 + 1;
        v18[i] = v20 + v15 + v19[i];
      }

v19就是a3 v20是v17[v16]对应a2+12+1 v15是v14[v13]对应a2+1

而v16和v13在ida中并没有正确识别到 那就继续hook看是什么情况

先在0x3647C处下个断点看v13

r0=0xbfffee85(-1073746299) r1=0x0
ldrb.w fp, [r0, r1]" [0xbfffee85] => 0x31 //1
r0=0xbfffee85(-1073746299) r1=0x1
ldrb.w fp, [r0, r1]" [0xbfffee86] => 0x31 //1
r0=0xbfffee85(-1073746299) r1=0x2
ldrb.w fp, [r0, r1]" [0xbfffee87] => 0x37 //7
r0=0xbfffee85(-1073746299) r1=0x3
ldrb.w fp, [r0, r1]" [0xbfffee87] => 0x34 //4
r0=0xbfffee85(-1073746299) r1=0x4
ldrb.w fp, [r0, r1]" [0xbfffee85] => 0x31 //1
r0=0xbfffee85(-1073746299) r1=0x5
ldrb.w fp, [r0, r1]" [0xbfffee86] => 0x31 //1
r0=0xbfffee85(-1073746299) r1=0x6
ldrb.w fp, [r0, r1]" [0xbfffee87] => 0x37 //7
r0=0xbfffee85(-1073746299) r1=0x7
ldrb.w fp, [r0, r1]" [0xbfffee87] => 0x34 //4
.......

然后在0x364A4处下个断点看v16

r0=0xbfffee91(-1073746287) r1=0x0
0x400364a4:*"ldrb r0, [r0, r1]" [0xbfffee91] => 0x41 //A
r0=0xbfffee91(-1073746287) r1=0x1
0x400364a4:*"ldrb r0, [r0, r1]" [0xbfffee91] => 0x42 //B
r0=0xbfffee91(-1073746287) r1=0x2
0x400364a4:*"ldrb r0, [r0, r1]" [0xbfffee91] => 0x43 //C
r0=0xbfffee91(-1073746287) r1=0x3
0x400364a4:*"ldrb r0, [r0, r1]" [0xbfffee91] => 0x44 //D
r0=0xbfffee91(-1073746287) r1=0x4
0x400364a4:*"ldrb r0, [r0, r1]" [0xbfffee91] => 0x45 //E
r0=0xbfffee91(-1073746287) r1=0x5
0x400364a4:*"ldrb r0, [r0, r1]" [0xbfffee91] => 0x46 //F
r0=0xbfffee91(-1073746287) r1=0x6
0x400364a4:*"ldrb r0, [r0, r1]" [0xbfffee91] => 0x47 //G
r0=0xbfffee91(-1073746287) r1=0x7
0x400364a4:*"ldrb r0, [r0, r1]" [0xbfffee91] => 0x47 //H
r0=0xbfffee91(-1073746287) r1=0x8
0x400364a4:*"ldrb r0, [r0, r1]" [0xbfffee91] => 0x41 //A
r0=0xbfffee91(-1073746287) r1=0x9
0x400364a4:*"ldrb r0, [r0, r1]" [0xbfffee91] => 0x42 //B
.......

能看到v13和v16其实就是i

相当于1174ABCDEFGH在循环往复取值

所以这段代码主要的作用就加加加

用python还原就是

def sub_363DC(v14,v19):
    v17="ABCDEFGH".encode()
    result=[]
    for i in range(len(v19)):
        result.append(v17[i%8]+v14[i%4]+v19[i])
    return bytes(result).hex()

查找谁调用了sub_363DC

找找v162的由来

跳转过来后看到下面的报错信息出现了protobuf

带着猜想 复制数据测试 发现能成功反序列化

1: 16777218
2: 1
3: "29FE8A6E707C1697A7DB626B9880CC8DF6BD9AFA6C72BDE90F4CA76D39EA3A77"
6: 1

其中1就是0x1000002 3为未知字符串 2和6不变都是1 验证成功就可以开始写protoc文件了

syntax = "proto3";
message meiriyouxian{
    uint64 i1=1;
    uint64 i2=2;
    string s3=3;
    uint64 i6=6;
}

写完生成python文件就可以在python里面引用生成我们需要的结果

生成结果和hook结果能够对上 现在需要找出里面s3的来源

hooksub_348B4

r0=0x402a1000 r1=0xbffff070 r2=0x1000002 r3=0x1 r4=0xbfffefc8 r5=0x1000002 r6=0x1

看看v144

所以v144也就是我们自己定义的s3

查找一下v144的引用

跳转过来 没发现什么特别的地方 先hook了再说

sub_2E5A4有2处调用 第2次调用才是我们需要的

此时能看到 值已经生成了 所以我们还得找v178的来源

此时重点来到sub_37D8C 进去观摩观摩 每个方法都看看瞧瞧

sub_37D8C->sub_2FB14->sub_367F6->sub_36558

里面发现了v28 = dword_9E030[v14++];

眼尖的朋友们应该发现这是什么东西了吧 不认识不要紧 随便复制一个去百度搜搜看

结果很明显 就是一个sha256的k表 那这就是一个sha256算法吗?

不一定 也有可能是hmac sha256

继续分析 回到sub_37D8C找v201的引用位置 (这里我把v201改成了sha)

这里可以看到上面俩处引用

先看看 sub_37D80(&sha, a3, a4)

通过对比sha256的c代码发现sub_37D80就是SHA256update

接着看sub_37D5C(&sha, v13)

进入sub_37D5C中的sub_2FA30方法发现0x360x5C

这不就是HMAC SHA256中的INNER_PAD和OUTER_PAD吗


HMAC-SHA256 C实现

https://blog.csdn.net/miniphoenix/article/details/110135164


确定完是HMAC SHA256 那就是找key以及查看是否加盐了

hooksub_37D5C

emulator.attach().addBreakPoint(module.base+0x37D5C+1);


所以key就是PwwGKgCqZAc2PPb31TLnnqPNVFAAdq/X

hooksub_37D80

emulator.attach().addBreakPoint(module.base+0x37D80+1);

结果显示没有加盐之类的操作

知道明文,加密方式和KEY后在CyberChef验证结果

结果一模一样

0x4 mfsig加密流程总结

  • HMAC SHA256
  • protobuf
  • sub_363DC
  • base64
  • 头部拼接mfsn


感谢各位大佬观看

感谢大佬们的文章分享

如有错误 还请海涵

共同进步


[完]

相关文章
|
8天前
|
开发框架 监控 .NET
【Azure App Service】部署在App Service上的.NET应用内存消耗不能超过2GB的情况分析
x64 dotnet runtime is not installed on the app service by default. Since we had the app service running in x64, it was proxying the request to a 32 bit dotnet process which was throwing an OutOfMemoryException with requests >100MB. It worked on the IaaS servers because we had the x64 runtime install
|
2月前
|
安全
【Azure App Service】App service无法使用的情况分析
App Service集成子网后,如果子网网段中的剩余IP地址非常少的情况下,会在App Service实例升级时( 先加入新实例,然后在移除老实例 )。新加入的实例不能被分配到正确的内网IP地址,无法成功的访问内网资源。 解决方法就是为App Service增加子网地址, 最少需要/26 子网网段地址。
|
3月前
【Azure Function App】本地运行的Function发布到Azure上无法运行的错误分析
【Azure Function App】本地运行的Function发布到Azure上无法运行的错误分析
|
3月前
|
开发框架 缓存 .NET
【App Service】在Azure App Service中分析.NET应用程序的性能的好帮手(Review Stack Traces)
【App Service】在Azure App Service中分析.NET应用程序的性能的好帮手(Review Stack Traces)
|
3月前
|
C# 开发工具
【Azure 应用服务】Azure Function App使用SendGrid发送邮件遇见异常消息The operation was canceled,分析源码渐入最源端
【Azure 应用服务】Azure Function App使用SendGrid发送邮件遇见异常消息The operation was canceled,分析源码渐入最源端
|
3月前
|
网络协议 NoSQL 网络安全
【Azure 应用服务】由Web App“无法连接数据库”而逐步分析到解析内网地址的办法(SQL和Redis开启private endpoint,只能通过内网访问,无法从公网访问的情况下)
【Azure 应用服务】由Web App“无法连接数据库”而逐步分析到解析内网地址的办法(SQL和Redis开启private endpoint,只能通过内网访问,无法从公网访问的情况下)
|
5月前
|
移动开发 小程序 开发工具
微信支付的类型分析(JSAPI+APP+H5+NATIVE+付款码+合单)
微信支付的类型分析(JSAPI+APP+H5+NATIVE+付款码+合单)
537 1
|
5月前
|
数据采集 JSON 算法
使用Python爬取华为市场APP应用进行分析
这个网站也是作者最近接触到的一个APP应用市场类网站。讲实话,还是蛮适合新手朋友去动手学习的。毕竟爬虫领域要想进步,还是需要多实战、多分析!该网站中的一些小细节也是能够锻炼分析能力的,也有反爬虫处理。甚至是下载APP的话在Web端是无法拿到APK下载的直链,需要去APP端接口数据获取
|
6月前
|
数据采集 小程序 网络安全
云擎技术---分析工信部APP备案的“传闻”
APP备案并非新事物,自2005年起已有非经营性互联网信息服务备案制度。备案针对的是网站主办者,而非用户,不涉及个人用户网络访问。网络接入服务提供者包括ISP和IDC,不限于三大运营商。通知要求不为未备案网站提供接入,但不影响国外软件使用。个人开发者不能涉及经营性内容,备案审核时长1-20个工作日。境内服务器和国内应用商店需备案,境外则无需。手机厂商不会开启白名单制,仅实行黑名单制。APP备案与民营经济发展壮大意见不冲突,工信部有权颁布相关规定。该政策不存在逐步试探底线情况,所有解读均有法律依据。
125 3
云擎技术---分析工信部APP备案的“传闻”
|
6月前
|
移动开发 JavaScript 前端开发
APP的HTML5页面经过运营商网络被植入手机管家问题及分析,解决方案见新文章
APP的HTML5页面经过运营商网络被植入手机管家问题及分析,解决方案见新文章
58 0