App逆向百例|12|某电商App Sign分析

简介: App逆向百例|12|某电商App Sign分析

观前提示:

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

样本:aHR0cHM6Ly9wYW4uYmFpZHUuY29tL3MvMUNtbzZHU1lOUmM4a2l4YkVsT3Rkc1E/cHdkPWxpbm4=

0x1 抓包&定位

打开样本App 直接抓包

本次的受害参数为sign

打开jadx定位到sign的加密位置

加载的so为libjdbitmapkit.so

有了这些信息 上frida hook入参和结果

打开frida 启动样本App发现 App卡死闪退

可能有frida检测 那我们就使用葫芦娃大佬的魔改frida

github: https://github.com/hluwa/strongR-frida-android

继续启动frida 发现还是崩溃

猜测可能还有其他检测方式

根据网上文章给出的frida检测点进行多次尝试

经过多次试错后最后可知 样本App对frida的默认端口27042进行检测

那就需要让frida运行在非默认端口

/data/local/tmp/hluda-server-15.1.17-android-arm64 -l 127.0.0.1:8080

然后映射端口

adb forward tcp:8080 tcp:8080

最后启动frida

frida -H 127.0.0.1:8080

成功运行frida且不闪退后就可以对样本进行hook操作了

function hook_getSignFromJni() {
    Java.perform(function () {
        var Class = Java.use('com.xxxx.common.utils.BitmapkitUtils');
        var Method = "getSignFromJni"
        Class[Method].overload('android.content.Context', 'java.lang.String', 'java.lang.String', 'java.lang.String', 'java.lang.String', 'java.lang.String').implementation = function () {
            var result = this[Method]['apply'](this, arguments);
            console.log('----------------------');
            console.log('arg1:' + arguments[0]);
            console.log('arg2:' + arguments[1]);
            console.log('arg2:' + arguments[2]);
            console.log('arg2:' + arguments[3]);
            console.log('arg2:' + arguments[4]);
            console.log('arg2:' + arguments[5]);
            console.log('result:' + result);
            console.log('----------------------');
            return result;
        }
    })
}
setImmediate(hook_getSignFromJni)

通过hook 拿到了多个结果 选取其中一个进行固定分析

arg1:com.jingdong.app.mall.JDApp@f0dac2f
arg2:hotWords
arg2:{"originHotWord":"0","pageFrom":"1"}
arg2:3036c83c3c4b25a2
arg2:android
arg2:10.2.0
result:st=1664463515894&sign=7ce2045026643e68ea1639c5e291127f&sv=122

0x2 Unidbg黑盒调用

复制粘贴造个轮子运行

根据报错信息开始补环境

vm.resolveClass("android/app/Activity");

getpackagemanager所以需要在前面的基础上再完善一点环境

vm.resolveClass("android/app/Activity",vm.resolveClass("android/content/ContextWrapper", vm.resolveClass("android/content/Context"))).newObject(null);

ApplicationInfo也就是apk的存放位置 可以在RE文件管理器/data/app中找到对应的位置

new StringObject(vm,"/data/app/com.xxxx.app.mall-cd4VeJ0b5yxrR0Zb-io_MA=/base.apk");

unZip 看传入参数是什么

传入了仨参数

arg1->"/data/app/com.xxxx.app.mall-cd4VeJ0b5yxrR0Zb-io_MA=/base.apk"
arg2->"META-INF/"
arg3->".RSA"

根据参数打开APK中中的META-INF搜索RSA结尾的文件

根据这个文件名补即可

new ByteArray(vm,vm.unzip("META-INF/xxxx.RSA"));

vm.resolveClass("sun/security/pkcs/PKCS7").newObject(new PKCS7((byte[]) varArg.getObjectArg(0).getValue()));

PKCS7 pkcs7 = (PKCS7) dvmObject.getValue();  
X509Certificate[] certificates = pkcs7.getCertificates();  
return ProxyDvmObject.createObject(vm,certificates);

这里的objectToBytes是java层的一个方法 直接去复制粘贴就行

new ByteArray(vm,objectToBytes(varArg.getObjectArg(0).getValue()));

补到这里 恭喜你完成初始化了

主动调用getSignFromJni

public void getSignFromJni(){
    ArrayList<Object> args = new ArrayList<>(10);
    args.add(vm.getJNIEnv());
    args.add(0);
    args.add(vm.addLocalObject(vm.resolveClass("android/content/Context").newObject(null)));
    args.add(vm.addLocalObject(new StringObject(vm,"hotWords")));
    args.add(vm.addLocalObject(new StringObject(vm,"{\"originHotWord\":\"0\",\"pageFrom\":\"1\"}")));
    args.add(vm.addLocalObject(new StringObject(vm,"3036c83c3c4b25a2")));
    args.add(vm.addLocalObject(new StringObject(vm,"android")));
    args.add(vm.addLocalObject(new StringObject(vm,"10.2.0")));
    Number number = module.callFunction(emulator, 0x28b5, args.toArray());
    System.out.println(vm.getObject(number.intValue()).getValue().toString());
}

然后接着报错接着补环境

vm.resolveClass("java/lang/StringBuffer").newObject(new StringBuffer());

StringBuffer stringBuffer = (StringBuffer) dvmObject.getValue();
return vm.resolveClass("java/lang/StringBuffer").newObject(stringBuffer.append(vaList.getObjectArg(0).getValue()));

Integer integer = new Integer(vaList.getIntArg(0));  
return vm.resolveClass("java/lang/Integer").newObject(integer);

Integer integer = (Integer) dvmObject.getValue();  
return vm.resolveClass("java/lang/Integer").newObject(integer.toString());

StringBuffer stringBuffer = (StringBuffer) dvmObject.getValue();
return new StringObject(vm,stringBuffer.toString());

恭喜你 补到这里就能出结果了

小声bb 写到这里就1k字了还没开始看算法

多次调用sv变动 st变动 sign也变动

0x3 算法分析

IDA打开直接搜索

看到直接是静态注册的

双击跳转过去

这一段全是拼接操作

st生成位置

sv生成位置

固定随机项

idea双击shift搜gettimeofday

改为固定的时间戳

固定了时间 不同的sv算出来的sign结果也不一致

观察代码 sv是通过lrand48生成的

固定lrand48

HookZz instance = HookZz.getInstance(emulator);
instance.wrap(module.findSymbolByName("lrand48"), new WrapCallback<HookZzArm32RegisterContext>() {
    int count=0;
    @Override
    public void preCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
    }
    @Override
    public void postCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
        count+=1;
        if(count==2){
            ctx.setR0(0x1);
        }
        if(count==1){
            ctx.setR0(0x1);
        }
    }
});

就是这俩处位置结果变动使sign的结果随之改变

改了这些就可以为所欲为了

前面拼接了一堆东西的结果传入sub_126AC

先放着继续往下看

sub_126AC传回的结果放入了sub_18B8

进入sub_18B8

看到这里似乎是一个Base64的码表

sub_18B8可能就是一个Base64方法

sub_18B8的结果由v66传出并且传入sub_227C

进入sub_227C

看到似曾相识的东西 这样看可能不是很明显 手动分割一下

  • 0xEFCDAB89
  • 0x67452301
  • 0x10325476
  • 0x98BADCFE

这不就是MD5

从魔数上看 似乎并没有魔改

验证一下前面的Base64和MD5猜想

拿到sub_126AC的结果进行Base64再MD5

验证成功 那重心就在sub_126AC

回到sub_126AC

这里有3个case分别代表三个算法

而算法的走向是由v24决定

v24是由sv2和sv3决定的

根据伪代码逻辑可以翻译为

unk_17440 = [0, 1, 2]
sv1 = 1
sv2 = 1
sv3 = 1
print(unk_17440[(sv2 + sv3) % 3])

根据结果得到下表

sv1 sv2 sv3 sv case
1 0 0 100 0
1 0 1 101 1
1 0 2 102 2
1 1 0 110 1
1 1 1 111 2
1 1 2 112 0
1 2 0 120 2
1 2 1 121 0
1 2 2 122 1

往下看这里先是内存拷贝了一个值然后根据v12 * 40的值进行偏移 其实实现的就是一个数组取值的操作这里根据伪代码可以翻译为

v11 = ['44e715a6e322ccb7d028f7a42fa55040', '7d0069660c9b5d32074facf37c3738a1', '80306f4370b39fd5630ad0529f77adb6']
v13 = v11[v12]

接下来的重点就是在

  • case 1->sub_10E18
  • case 2->sub_10DE4
  • case 0->sub_10E4C

前面手动固定了lrand48 sv为111

所以走的是case 2

进入sub_10DE4 只有三个方法

算法在sub_12ECC中 双击进来

根据hook入参得到以下结果

a2->80306f4370b39fd5630ad0529f77adb6
a3->0x1
a4->functionId=hotWords&body={"originHotWord":"0","pageFrom":"1"}&uuid=3036c83c3c4b25a2&client=android&clientVersion=10.2.0&st=1664689004670&sv=111
a5->0x8f

由入参可知a4为拼接后的明文 a5是a4的长度

所以这个if是必定成立的 else后面的那一块可以忽略不看

这一段主要在计算v21

这里的a3对应的是sv1 而sv1固定为1 所以同理 不用理else部分

这里就是sub_12ECC的核心计算位置

其中

v16 = &v21[7] + (v15 & 0xF);
v18 =*(v16 - 20);

从汇编中可知R0=(SP+0x20-0x14)+(R3&0xf)

所以这段实现的操作是v21[v15&0xf]

v21结果为SP+0xC

也就是前面小端结果

往下看

v17 = v15++ & 7;
result = ((v18 ^ *a4 ^ *(a2 + v17)) + v18);

此处为取值进行异或操作

LOBYTE(v18) = v18 ^ result;
*a4++ = v18;
*(a4 - 1) = v18 ^ *(a2 + v17);

将异或后的结果取低位然后再与a2[v17]进行运算最后算出结果

整个循环翻译成py简简单单没有难点

v15 = 0
v21 = [0x37, 0x92, 0x44, 0x68, 0xA5, 0x3D, 0xCC, 0x7F, 0xBB, 0x0F, 0xD9, 0x88, 0xEE, 0x9A, 0xE9, 0x5A]
while v15 != a5:
    v18 = v21[v15 % 16]
    v17 = v15 & 7
    result = (v18 ^ a4[v15] ^ a2[v17]) + v18
    v18 = v18 ^ result % 256
    a4[v15] = v18 ^ a2[v17]
    v15 += 1

运算后的结果Base64再MD5即为sign值

这是简单的case 2

接下来将前面固定lrand48的返回值改为0x2 使得sv为122

当sv为122时 走case 1

进入sub_10E18 和前面一样进来就是三个方法

但是不同的是出现了一个nullsub_1 那就分析不了吗?

并不然 从前面的分析结合这里可知 sub_125F0可能为初始化 sub_12510可能为计算核心方法 那nullsub_1就可能是释放 所以并不需要理睬nullsub_1

进入sub_12510

入参和前面基本一致 a2变为7d0069660c9b5d32074facf37c3738a1

这里循环每次取8个字节进入sub_10EA4计算 一共循环a5 >> 3

进入sub_10EA4 955行代码 有点多 不过大部分都能直接复制

这里一堆与操作的目的就是将传入的8位分别和0x80 0x40 0x20 0x10 8 4 2 1与操作扩展至64位

接着就是一堆赋值 直接Ctrl+C Ctrl+V

这里为遍历a2然后进行判断走不同分支

其中出现了一些goto操作

Python本身是没有的 但是可以依靠一个库goto-statement来实现

pip install goto-statement

https://www.w3cschool.cn/article/3069641.html

这里就是将前面计算好并且重新赋值后的64位循环4次计算 每次取16位

每次循环更改2位 循环4次一共8位

实现了goto 其他操作只需要复制粘贴复制粘贴并稍微改改就能实现 反正全靠肝

回到上层 这里一共循环0x8f >> 3 = 17 但是似乎还有部分明文并没有参与计算 而最后得出的结果显示 全部都参与了计算

hook验证猜想

确实 只循环了17次 后面还会有&sv=122没有参与计算 但是从最终结果来看 确实是计算到了 那是哪里计算了呢?

直接traceWrite

emulator.traceWrite(0x4021c080L,0x4021c080L+10L);

跳转0xfbd0

这里就是赋值位置 但是 这里居然有3509行 这谁顶得住

先不管 看一下sub_E7FC的交叉引用

只一个 跳转过来

(a4 & 7) - 1可知

这。。这段不就是根据未参与计算的明文的长度走不同的方法 而且每个方法都有上千行 留给有肝的人还原吧

将之前还原好的做个验证没问题

接下来将前面固定lrand48的返回值改为0x0 使得sv为100

当sv为100时 走case 0

进入sub_10E4C 一样的三个方法

进入sub_12580 看到核心方法是一样的sub_10EA4 不过16变成了32

其余的和前面分析case2的一致

到此整一个流程就基本走完了 最后再拼接成st=xxx&sign=xxx&sv=xxx即可


-恭喜你 看完了这篇又水又长的东西-

感谢各位大佬观看感谢大佬们的文章分享 如有错误 还请海涵共同进步 带带弟弟

相关文章
|
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