“ 前言:初学逆向 请多多指教”
学习到的内容
—
1、在java层,对容器类的对象进行hook来进行快速定位
2、ida的findcrypt插件对so层的算法快速识别
3、文章分析简单,就是记录了一次新的hook思路,没有新的套路,就当作练手了,有一个字段没有分析出来,之后知道了会补上的
算法逆向过程
—
APP登陆界面:
数据包(请求和相应)如下:
POST /api/v1/auth/login/sms HTTP/1.1Content-Type: application/jsonConnection: closeCharset: UTF-8User-Agent: Dalvik/2.1.0 (Linux; U; Android 8.1.0; Pixel Build/OPM1.171019.011)Host: api.taozuiredian.comAccept-Encoding: gzipContent-Length: 464{"code":"9500","device_id":"1f37638fd87cb9056801c785e040957f","channel":"aliapp","sign":"850da5bdfd60c42cc1dbc027884c85325e4145a8","getui_push_id":"6c6d6b7b7c1a7d6990cbcb17a27d55ee","nonce":"ecoxul1604389882104","mac":"AC:37:43:4F:22:9D","imei2":"null","device_name":"google Pixel","imei1":"359906070277673","system":"1","phone":"19817353426","app_ver":"53","device_udid":"6992562083e7f7b8ed1ed6e285c5dce9","from":"app","os_ver_code":"27","timestamp":"1604389882"}HTTP/1.1 200 OKServer: nginx/1.12.2Date: Tue, 03 Nov 2020 07:51:50 GMTContent-Type: application/jsonContent-Length: 187Connection: closeCache-Control: no-cache, privateAccess-Control-Allow-Origin: *Vary: Origin{"code":0,"success":true,"data":{"token":{"token":"03c24bI23Od8BcPkyEsgTxPjvGqMhRmUmEsAGSEjgR73m3s2H3BmHmCydn6Y8yqa","expired_at":1604476310,"refresh_expired_at":1606981910},"smfk":true}}
对java层直接进行hook,初步分析内容如下:
code:验证码
device_id:md5(imei1)
sign:不知道,但是可以看到签名值为40个字符,应该也就是sha1了
getui_push_id:不知道
nonce:随机6个字符 + timestamp
device_udid:md5(ffffffff-ec38-7ce6-0000-0000495241ed)
小提醒:ffffffff-ec38-7ce6-0000-0000495241ed这个值每个手机都是不同的,uuid
md5(imei1 + mac) =》 a59a6b16b9053a50c64805a5256bf113,不知道是啥
进行快速定位,这次用到方法是一个新的思路,来对java中容器类来进行hook,常用的都有HashMap类 ArrayList类 StringBuiler类
实现的代码:
var hashMap = Java.use("java.util.HashMap"); hashMap.put.implementation = function (key, value) { if (value == 'XXXX' || value == 'XXXX' || value == 'XXXX' || key == 'username' || key == 'user' || key == 'password' || key == 'pass' || key == 'sign') { send(key + " " + value); showStacks(); } return this.put(key, value); send("======================================"); }; var arrayList = Java.use("java.util.ArrayList"); arrayList.add.overload("java.lang.Object").implementation = function (value) { if (value == 'XXXX' || value == 'XXXX' || value == 'XXXX') { send(value); showStacks(); } return this.add(value); send("======================================"); }; var stringBuilder = Java.use("java.lang.StringBuilder"); stringBuilder.append.overload("java.lang.String").implementation = function (value) { if (value == '19817353426' || value == 'a12345678' || value == 'e10adc3949ba59abbe56e057f20f883e') { send(value); showStacks(); } return this.append(value); send("======================================"); };
通过分析函数调用栈来到sign的地方,然后进行了动态调试,感觉能动态调试的话比写代码方便多了
两个断点分别读取到的p0(v4)对应的值
VM> readvar v4 string"app_ver=53&captcha_id=&captcha_value=&channel=aliapp&device_id=75e908ea96987513c2a884591adb8a93&device_udid=b11785ed41cdef913e41b7b95796e7b4&from=app&mac=02:00:00:00:00:00&nonce=rxks7r1604403451122&os_ver_code=22&phone=19817353426&system=1×tamp=1604403451"VM> readvar v4 string"1fe0b02e4d0affbbde831618c73a065dafe80db7"
分析sign的生成
最终是加载了libtre.so文件中静态注册的导出函数sign
该函数为静态注册的函数,命名如下:
分析的注释如下:
这里可以使用ida中的findctypt插件,可以帮助我们快速定位,之前说了sign是40个字符的,那么这个插件也能通过特征码(算法初始化常量)来匹配出对应的算法
最后模拟sign测试:
1、将如下的数据进行base64加密
app_ver=53&captcha_id=&captcha_value=&channel=aliapp&device_id=75e908ea96987513c2a884591adb8a93&device_udid=b11785ed41cdef913e41b7b95796e7b4&from=app&mac=02:00:00:00:00:00&nonce=rxks7r1604403451122&os_ver_code=22&phone=19817353426&system=1×tamp=1604403451b2qKgtaW4,9z9D`Fmst?K5JZbLYOY]NP6ssGf2U~;zk9oCNgoytV!}wW7ia+`w9g
2、将base64编码的结果进行sha1加密
最后有一个字段没有分析出来,之后再补上吧,分析过程:
getui_push_id,这边可以从登陆按钮产生的调用栈来观察,结果如下:
D:\ATTACK\Android\Frida\myHook>D:/python38/python.exe d:/ATTACK/Android/Frida/myHook/frida-demo.py[*] java.lang.Throwable at com.maihan.tredian.net.MhRequestUtil.a(Native Method) at com.maihan.tredian.net.MhNetworkUtil$1.run(MhNetworkUtil.java:76) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:422) at java.util.concurrent.FutureTask.run(FutureTask.java:237) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587) at java.lang.Thread.run(Thread.java:818)
发现Util.g会先判断LocalValue.g的值是否存在,存在则将LocalValue作为getui_push_id的值
默认LocalValue.g是一个静态属性
com.maihan.tredian.util.LocalValue
通过wallbreaker插件(Objection插件)对这个对象结构进行打印观察,发现g已经被赋值了,所以这个应该是程序开启的时候就已经被初始化了
plugin wallbreaker classdump com.maihan.tredian.util.LocalValue
继续对该g属性进行交叉索引
来到如下的地方,Hook该方法的调用栈,代码实现如下:
function showStacks() { Java.perform(function() { send(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new())); //send(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new())); });}setImmediate(function(){ Java.perform(function () { var MhHttpEngine = Java.use("com.maihan.tredian.push.MGtPushReceiver"); MhHttpEngine.onReceiveClientId.implementation = function (a, b) { showStacks(); send(b); send("====================") } });});
会发现是从这个意图类中进行获取的clientId的值
最终只能看到这里,因为上一层就是系统的函数了无法查看,系统dispatchMessage给这个函数进行调用,这个函数是用来处理消息队列中的消息的,目前技术不行,也没研究深入...