App逆向百例|09|某App hkey分析还原

简介: App逆向百例|09|某App hkey分析还原

观前提示:

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



样本:aHR0cHM6Ly9wYW4uYmFpZHUuY29tL3MvMU1LbEhzbFRoTU8zeHFCODhVbER1NVE/cHdkPWxpbm4=

0x1 抓包

  • 有证书验证

使用objection过掉证书验证后可正常抓包

本次受害参数为hkey

0x2 找加密位置

使用jadx-gui打开 发现并没有加壳

直接搜索hkey也没有看到有用的地方

那是因为真实的字符串处已经被处理过了

通过替换字符最终组成hkey

最终定位到

传入的参数分别为

  • 上下文
  • url
  • 时间戳
  • 随机32位字符串

0x3 Unidbg黑盒调用

先复制粘贴好框架运行 发现并不需要补环境可以直接运行

看到encode方法是动态注册的

主动调用

public void call_encode(){
        List<Object> args = new ArrayList<>(3);
        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,"/bbs/app/api/search/hot_words/")));
        args.add(vm.addLocalObject(new StringObject(vm,"123456")));
        args.add(vm.addLocalObject(new StringObject(vm,"GkaZmuMPhIxUBiIjH8P8kmnuA9vcsKU7")));
        Number number=module.callFunction(emulator,0x3369,args.toArray());
        System.out.println(vm.getObject(number.intValue()).getValue().toString());
    }

主动调用发现也不用补环境即可出结果

0x4 算法还原

libnative-lib.so放到大姐姐看看代码

跳转0x3369 先从伪代码分析

进入sub_1B28 可以知道这里的目的就是签名验证

回到上层继续看 根据if条件判断v15是否小于1 从前面知道传入的v15的长度是32 所以走到如下分支

v13就是传入的32位随机字符串

*v13++就相当于从前往后取字符

经过计算和判断 最终叠加到v17上

这里的v17其实是指代v47

其中v19-97这里 如果结果是负数 py算出来的结果是能直接是负的 但是so算出来的却是正的 所以在代码中还需要做个判断

所以需要把代码改为20 < 26 and v20 > 0 这个是在调试过程中才发现的一个坑

分析完后就可以直接翻译

v15 = len(v11)
v16 = 0
i = 0
v47 = "23456789BCDFGHJKMNPQRTVWXY"
while (v15):
    v19 = ord(v11[i])
    v18 = v19
    v20 = v19 - 97
    if (v19 - 48) < 10 :
        v16+=1
    if v20 < 26 and v20 > 0:
        v18 -= 32
    v15-=1
    v47 += chr(v18)
    i+=1

上面的代码执行完后v47会叠加延长到58位

并且根据计算得出v16的值

这里的atoi就是将字符串转化为十六进制 然后再加上前面计算的v16

然后是一些赋值和申请内存的操作

进入sub_1CF8(url1, url_len, v24)

进来之后是一堆看不懂的操作 那之前造好的Unidbg就可以上线了

目前是对sub_1DBC这里有疑问 看汇编对应的是LR

直接在0x1D48下断

断下来后 直接m+地址将地址上的数据输出出来

可以看到LR其实是一个码表 并非sub_1DBC 所以这里的操作有可能就是BASE64

验证一下 在0x1CF8下断 mr0为url

mr2对应地址为0x401d2000

记住这个地址 先下个blr然后c放行

接着输入m0x401d2000即可看到方法执行完输出的mr2的内容

验证成功

继续看sub_1E80(v41, v24, &v42, v26, 8u)

进来之后一眼看到0x5c0x36

懂的都懂吧 不懂也没关系 继续看sub_1F68

所以啥都告诉你了 这就是一个hmac-sha1

直接hook0x1E80

mr1为KEY 也就是前面BASE64后的url

mr2为明文 也就是加上v16后的十六进制时间戳并往前填充00到16个字节

对比验证结果一致

继续看

hmac-sha1的结果保存在v41中 而代码中又取出v41中19后的位置 也就是取出53

接着进行计算v41[19] & 0xF取低位3

然后从v41中取出索引3的值 所以难道是取出8E?

其实并不是 转到汇编这里

.mytext:0000354A LDR R0, [R5,R0]

可以看到伪代码的翻译并没有太大的问题 但是要注意的是LDR的作用

所以正确的是

后面的bswap32的作用是32位寄存器内的字节次序变反 因为读取顺序是从低往高的 所以需要次序变反

我们翻译的话可以直接是0x8EA583AA 后面的0xFFFFFF7F也需要同时变成0x7FFFFFFF

继续往后走就是一段循环操作 生成5位的字符串

直接翻译即可 走到这里 已经完成了前面五个字符的生成 还差后面俩个数字

这里取出了五个字符中的后四个进到了sub_1900计算

进入sub_1900能看到多次出现sub_1888

进入sub_1888能看到就是一些计算操作

那就直接翻译下来就行了吧

def sub_1888(a1):
    v1 = 2 * a1 ^ 0x1B
    if (a1 & 0x80) == 0:
        v1 = 2 * a1
    v2 = 2 * v1  ^ 0x1B
    if (v1 & 0x80) == 0:
        v2 = 2 * v1
    v3 = v2 ^ v1
    v4 = 2 * v3 ^ 0x1B
    if (v3 & 0x80) == 0:
        v4 = 2 * v3
    v5 = 2 * v4 % 256 ^ 0x1B
    if (v4 & 0x80) == 0:
        v5 = 2 * v4
    return v5 ^ v4

对比一下hook结果传出0x8c也就是140和我们的结果相差甚远

那就直接tracecode吧 选定0x1888-0x18CE

通过观察汇编 原来是这里的uxtb指令的位置开始出错了

网上的解释是

UXTB

每个32位整型都有4个字节,该命令主要将4个字节的其中一个字节提取出来,然后转成一个新的32位整型

所以我们要改的话也要根据汇编的实际情况来修改代码0x160 % 256 => 0x60

def sub_1888(a1):
    v1 = 2 * a1 % 256 ^ 0x1B
    if (a1 & 0x80) == 0:
        v1 = 2 * a1 % 256
    v2 = 2 * v1 % 256 ^ 0x1B
    if (v1 & 0x80) == 0:
        v2 = 2 * v1  % 256
    v3 = v2 ^ v1
    v4 = 2 * v3 % 256 ^ 0x1B
    if (v3 & 0x80) == 0:
        v4 = 2 * v3  % 256
    v5 = 2 * v4 % 256 ^ 0x1B
    if (v4 & 0x80) == 0:
        v5 = 2 * v4 % 256
    return v5 ^ v4

修改后验证成功

回到sub_1900

还是老规矩先tracecode先留着备用

传入参数为58 00 00 00 58 00 00 00 4A 00 00 00 54 00 00 00

传出结果为78 00 00 00 5B 00 00 00 D9 00 00 00 FA 00 00 00

跟前面分析的sub_1888一样 需要注意处理uxtb 其他大部分可以照着伪代码翻译过去 但是还是有几处错误

sub_1888(*a1)中的*a1指代58

LDRD.W R2,R4,[R10,#8]中R10为a1 意思是 将a1偏移+8后存入R2 将a1+8+4后存入R4 所以后续的v84AHIDWORD(v8)SHIDWORD(v8)54

其他基本照着翻译下来即可 翻译完执行的结果为[120, 91, 35, 0]

对比hook的结果可知0x78=>120 0x5B=>91 0xD9=>217 0xFA=>250

所以问题还是出在了2和3里面

a1[2] = v4 ^ v24 ^ v22 ^ v8 ^ v26 ^ v13 ^ v14 ^ v25;
a1[3] = v14 ^ HIDWORD(v8) ^ v22 ^ v8 ^ v10 ^ v17 ^ v27 ^ v18;

直接看tracecode 从0x1A22开始看

v4为0xb0 v24为0x58 v8为0xb0 对比到这里问题就出现了

回到汇编v8指R2 所以根据汇编 R2正确的指代是v15

所以整个sub_1900翻译完是

def sub_1900(a1):
    v2 = a1[0]
    v3 = sub_1888(a1[0])
    v27 = v3
    v4 = 2 * v2 % 256 ^ 0x1B
    v24 = a1[1]
    if (v2 & 0x80) == 0:
        v4 = 2 * v2 % 256
    v5 = v3 ^ v2
    v6 = 2 * v4 % 256 ^ 0x1B
    if (v4 & 0x80) == 0:
        v6 = 2 * v4 % 256
    v7 = v6
    v26 = v6
    v23 = sub_1888(a1[1])
    v8 = a1[2]
    v9 = v7 ^ v5 ^ v23 ^ a1[3]
    v10 = 2 * v8 % 256 ^ 0x1B
    if (v8 & 0x80) == 0:
        v10 = 2 * v8 % 256
    v11 = 2 * v8 % 256 ^ 0x1B
    v22 = a1[2]
    if (v8 & 0x8000000000) == 0:
        v11 = 2 * a1[3] % 256
    v12 = 2 * v10 % 256 ^ 0x1B
    if (v10 & 0x80) == 0:
        v12 = 2 * v10 % 256
    v25 = v12
    v21 = v12 ^ v9 ^ v10 ^ v11
    v13 = sub_1888(a1[2])
    v14 = sub_1888(a1[3])
    a1[0] = v21
    v15 = 2 * v24 % 256 ^ 0x1B
    if (v24 & 0x80) == 0:
        v15 = 2 * v24 % 256
    v16 = v24 ^ v2 ^ v4 ^ v23 ^ v13 ^ v11
    v17 = 2 * v15 % 256 ^ 0x1B
    v18 = 2 * v11 % 256 ^ 0x1B
    if (v15 & 0x80) == 0:
        v17 = 2 * v15 % 256
    v19 = v16 ^ v17
    if (v11 & 0x80) == 0:
        v18 = 2 * v11 % 256
    a1[1] = v19 ^ v18
    a1[2] = v4 ^ v24 ^ v22 ^ v15 ^ v26 ^ v13 ^ v14 ^ v25
    a1[3] = v14 ^ a1[3] ^ v22 ^ v15 ^ v10 ^ v17 ^ v27 ^ v18
    return a1

运行结果为[120, 91, 217, 250] 一模一样

回到上层继续看

这里就是直接将返回的结果求和然后%100即拿到最终的俩位数验证一下

到此 所有算法都已经明了啦




感谢各位大佬观看

感谢大佬们的文章分享

如有错误 还请海涵

共同进步


[完]

相关文章
|
11天前
|
开发框架 监控 .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+付款码+合单)
542 1
|
5月前
|
数据采集 JSON 算法
使用Python爬取华为市场APP应用进行分析
这个网站也是作者最近接触到的一个APP应用市场类网站。讲实话,还是蛮适合新手朋友去动手学习的。毕竟爬虫领域要想进步,还是需要多实战、多分析!该网站中的一些小细节也是能够锻炼分析能力的,也有反爬虫处理。甚至是下载APP的话在Web端是无法拿到APK下载的直链,需要去APP端接口数据获取
|
6月前
|
数据采集 小程序 网络安全
云擎技术---分析工信部APP备案的“传闻”
APP备案并非新事物,自2005年起已有非经营性互联网信息服务备案制度。备案针对的是网站主办者,而非用户,不涉及个人用户网络访问。网络接入服务提供者包括ISP和IDC,不限于三大运营商。通知要求不为未备案网站提供接入,但不影响国外软件使用。个人开发者不能涉及经营性内容,备案审核时长1-20个工作日。境内服务器和国内应用商店需备案,境外则无需。手机厂商不会开启白名单制,仅实行黑名单制。APP备案与民营经济发展壮大意见不冲突,工信部有权颁布相关规定。该政策不存在逐步试探底线情况,所有解读均有法律依据。
126 3
云擎技术---分析工信部APP备案的“传闻”
|
6月前
|
移动开发 JavaScript 前端开发
APP的HTML5页面经过运营商网络被植入手机管家问题及分析,解决方案见新文章
APP的HTML5页面经过运营商网络被植入手机管家问题及分析,解决方案见新文章
58 0

热门文章

最新文章

下一篇
无影云桌面