日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

逆向so_记一次APP的so层算法逆向(七)

發(fā)布時(shí)間:2023/12/4 编程问答 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 逆向so_记一次APP的so层算法逆向(七) 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

?前言:初學(xué)逆向 請(qǐng)多多指教 好累 感覺每天這樣肝 人有點(diǎn)受不了了...

????學(xué)習(xí)到的內(nèi)容

1、新學(xué)習(xí)到IDA的一些分析時(shí)候的小技巧

2、算法還原代碼實(shí)現(xiàn)的練習(xí)(有個(gè)參數(shù)沒有分析出來,后面知道了會(huì)補(bǔ)上的)

3、在Frida中使用命令行調(diào)試的方便方法

分析過程

APP登陸界面:

請(qǐng)求包:

POST /api/adult/check_guest HTTP/1.1Content-Type: application/x-www-form-urlencodedUser-Agent: Dalvik/2.1.0 (Linux; U; Android 8.1.0; Pixel Build/OPM1.171019.011)Host: service.kuyangsh.cnConnection: Keep-AliveAccept-Encoding: gzipContent-Length: 1108a=6ih0KN8TFL5%2FQT%2FN3JY63ZovsWQyeIxHjBLHp1GGwjUNYVaGJLC%2FYZenRFKbeqsgIoI4rD1atROr%0Ajkl1p7eobNMARMel19oiGkl5hRD72vOn9zyNbERMe8Cj3b24Ru4wc2mWbbnamKVKPepkaa2mqpJl%0Akp4%2Fa5udSz0UbDR6cTwLCRWeKb60H%2Fir4vzZv1OfwQF%2FXJQsuBmxH2F6wp9CJkk9WYchx4LQU%2FS3%0AjQfpQY2iZWwsmHAiyZGVsfZIgXTvhJygpT8vH268Py5JspYZoho0RRrx4BjUfs5boif6rEMpd5PC%0AiZTLhIPovbPpoZQJC7d%2BWPFtwjPT7Ljyzgz5QxtctnZqa4qMMkfFwAIeA7lj2wJZeZBD%2F%2BoU5R45%0AMEFK6OMrMXB1M%2BC4dBt7Rd384SYhe%2BAEp0gKNHGkrpxWImFcPAaalajyijs6V4Wl4res9CWuEE5W%0Ayj5ehtmPc6uZGuo5ns6THfDwnho8BiFOnH0QoqJEeyVdTsCzOiMwfBJJldB7qbsTfUlLbSnpq4tf%0AurPbVMVKQbk4ui1XgDH5v%2FptUHirNK0IHT%2BBms8wQ%2BSX3BcMLKFiWI0OBAjUydqcJpIi0sPSWpfh%0A2k1nmMlvPnljfc7P12iF8nFHoFWRYQPVie46K%2Bhd4%2FttkyrZ4Gy3WM6zWdmnED3h6CCgZ4rEe0DB%0AN5Dj8lJJbsOAE%2FxWcYDguyj8WkUET3yLB%2FBZQx%2BsOn9otWzjwROdhfi6V7OObXZ5XoGUIDffKaFu%0ADnvTinsAh%2FvjcSDVq%2BHyD%2FzUeRceMf6uQruBhHRzikSb2Oz0Zfxld7rqmWYZ8aIBe1DMRJuXecB%2F%0AAsBu4VVuVVfQf4hlCpVNmKnX6huuMkHtCptCLaD0pkmuY7X7OEfCsudtFIco%2F7gXaQ5aXgfCs7GJ%0AzAMxfpCRm4vnF0kc8nQ4OWlexOV5t65k%2B4eDt8wY91%2BIFHcq%2BIwZPR3e41oeKriHlbsPdocNOkeg%0AeyUw%2FXlAY97IpZA%3D%0A

返回包:

{ "ret": 0, "msg": "沒有錯(cuò)誤", "data": { "needs_check": "0", "needs_bind": "0", "needs_guest_bind": 0, "user_is_guest": -1 }}

這里定位很簡(jiǎn)單,直接全局搜索關(guān)鍵詞:"a" 就可以了,因?yàn)榭梢钥吹絇OST數(shù)據(jù)包中只有一個(gè)字段就是a,但是這里需要帶上雙引號(hào),來到如下

可以看到最關(guān)鍵的加密代碼就是:String a = m3385a((Map) hashMap, initFromXML.getAppKey());

initFromXML.getAppKey則正常返回一個(gè)定值

跟進(jìn)m3385a函數(shù)可以發(fā)現(xiàn),定義如下:

public static String m3385a(Map<String, String> map, String str) { if (C1074a.f2394c || TivicloudController.f2343a) { map.put("testing", "1"); } try { JSONObject jSONObject = new JSONObject(map); Debug.m3346d("params : " + jSONObject.toString()); String encode = URLEncoder.encode(jSONObject.toString(), "UTF-8"); String encryptString = EncryptUtil.encryptString(encode + str); JSONObject jSONObject2 = new JSONObject(); try { jSONObject2.put("sign", encryptString); jSONObject2.put(C0882di.C0883a.DATA, encode); } catch (JSONException e) { Debug.m3354w((Exception) e); } return new String(Base64.encode(EncryptUtil.nativeAES(jSONObject2.toString()), 0), "UTF-8"); } catch (UnsupportedEncodingException e2) { Debug.m3354w((Exception) e2); return null; } }

注意:這里發(fā)現(xiàn)JSONObject是處于org.json.JSONObject,系統(tǒng)自帶的庫中的類,這里也能作為一種hook的方法定位

這里可以直接對(duì)m3385a這個(gè)函數(shù)進(jìn)行觀察,流程就是將傳入的數(shù)據(jù)先進(jìn)行一個(gè)url編碼,然后拼接initFromXML.getAppKey作為參數(shù),調(diào)用EncryptUtil.encryptString

繼續(xù)跟到encryptString中去,代碼如下,那么也就需要進(jìn)libgavesec.so中的nativeEncrypt函數(shù)進(jìn)行分析

這里遇到了一個(gè)問題,比如下面的兩個(gè)函數(shù)和注入代碼,一個(gè)函數(shù)會(huì)調(diào)用native中的函數(shù),但是如果我hook?encryptString這個(gè)函數(shù),返回值寫的是調(diào)用native層的nativeEncrypt函數(shù),這樣的寫法就會(huì)導(dǎo)致程序結(jié)束,具體原因不知道

public static String encryptString(String str) { return nativeEncrypt(str); } private static native String nativeEncrypt(String str); Java.perform(function () { var EncryptUtil = Java.use("com.tivicloud.utils.EncryptUtil"); EncryptUtil.encryptString.implementation = function (a) { send("EncryptUtil.encryptString args[0]: " + a); var result = this.nativeEncrypt(a); return result; } });

因?yàn)樯厦婺菢訉懢蛯?dǎo)致程序結(jié)束,所以這里就直接hook?nativeEncrypt,驚奇的發(fā)現(xiàn)這樣子就不會(huì)導(dǎo)致程序結(jié)束了

setImmediate(function(){ Java.perform(function () { var EncryptUtil = Java.use("com.tivicloud.utils.EncryptUtil"); EncryptUtil.nativeEncrypt.implementation = function (a) { send("EncryptUtil.nativeEncrypt args[0]: " + a); var result = this.nativeEncrypt(a); return result; } });});

這里可以對(duì)EncryptUtil.encryptString(encode + str),java層進(jìn)行一次hook來獲取對(duì)應(yīng)的參數(shù)先

[*] EncryptUtil.encryptString args[0]: {"mobile_operator":"","app_versioncode":"4","app_version":"2.1.0.22277","connection_type":"WIFI","os_version":"8.1.0","version_code":"4","version_name":"2.1.0.22277","os_lang":"zh","sdk_version":"3.1.4","package_name":"com.lm.lm","imei":"359906070277673","os_name":"Android","lang":"zh","udid":"10e1ef4509a2340eb0276bb99d186506","nudid":"155c2b70-fc97-4005-abc7-6d6c4329ed23","app_id":"10054","channel_id":"10006","tdid":"39fb282a761c17e80c069332fc6a2ffa9"}fff18b83431fa3a83b9de80c1e413bde

那么的initFromXML.getAppKey值就為fff18b83431fa3a83b9de80c1e413bde

然后接著繼續(xù)跟native層進(jìn)行分析,將libgavesec.so拖入到IDA中,并且找到對(duì)應(yīng)的函數(shù)

1、在通過導(dǎo)出library庫之后,有些jni的函數(shù)無法識(shí)別參數(shù),你可以直接右鍵該函數(shù)選擇Force call type來進(jìn)行重新分析,一般都可以成功識(shí)別參數(shù)

2、有時(shí)候ida中的強(qiáng)制轉(zhuǎn)換類型太多,可以右鍵選擇Show casts來隱藏強(qiáng)制轉(zhuǎn)換,然后進(jìn)行分析

主要進(jìn)行了encrypt函數(shù)的調(diào)用

接著就是來到加密函數(shù)encrypt中進(jìn)行分析

先是進(jìn)行一次sha1加密

所以這里要hook兩個(gè)地方

encrypt:Base + 0x1C8C ,獲取第一個(gè)參數(shù)

SHA1::Result:Base + 0x2AA4,獲取該函數(shù)調(diào)用完之后的第二個(gè)參數(shù)的結(jié)果

這里分享的frida的調(diào)試方法,通過命令行注入js腳本進(jìn)入到frida的命令行中進(jìn)行操作

frida -RF -l hooktest.js

1、通過主動(dòng)調(diào)用獲取對(duì)應(yīng)的類

2、調(diào)用類對(duì)應(yīng)的靜態(tài)/非靜態(tài)方法調(diào)試數(shù)據(jù)的時(shí)候會(huì)很方便,代碼如下:

function data_test() { Java.perform(function () { var EncryptUtil = Java.use("com.tivicloud.utils.EncryptUtil"); var res = EncryptUtil.nativeEncrypt('%7B%22mobile_operator%22%3A%22%22%2C%22app_versioncode%22%3A%224%22%2C%22app_version%22%3A%222.1.0.22277%22%2C%22connection_type%22%3A%22WIFI%22%2C%22os_version%22%3A%228.1.0%22%2C%22source%22%3A%22SDK%22%2C%22user_id%22%3A%2216073535%22%2C%22os_lang%22%3A%22zh%22%2C%22sdk_version%22%3A%223.1.4%22%2C%22imei%22%3A%22359906070277673%22%2C%22os_name%22%3A%22Android%22%2C%22login_token%22%3A%2285700bcdf4a41164ab7406e8445479ed%22%2C%22lang%22%3A%22zh%22%2C%22udid%22%3A%2210e1ef4509a2340eb0276bb99d186506%22%2C%22nudid%22%3A%22155c2b70-fc97-4005-abc7-6d6c4329ed23%22%2C%22app_id%22%3A%2210054%22%2C%22channel_id%22%3A%2210006%22%2C%22tdid%22%3A%2239fb282a761c17e80c069332fc6a2ffa9%22%7Dfff18b83431fa3a83b9de80c1e413bde'); console.log(res); });}

接著上面的分析,打印了sha1加密過后的數(shù)據(jù)v12變量和最終的Sign值(這里也就是nativeEncrypt的返回值),你會(huì)發(fā)現(xiàn)結(jié)果是不一樣的

接著你會(huì)看到下面的循環(huán)的操作,所以sha1加密的結(jié)果應(yīng)該被二次修改了,這里有兩個(gè)大循環(huán)的操作

首先第一段循環(huán):

SHA1::Result((SHA1 *)&v13, (unsigned int *)v12);// sha1加密的結(jié)果v4 = 0LL; // long long v5 = 7;do // 進(jìn)行循環(huán)操作 { v6 = *(_DWORD *)&v12[v4]; // 0X0C v7 = &dest_cstr_1[v5]; while ( 1 ) { v8 = v6 & 0xF; // 0X0C v6 >>= 4; // 0X00 *v7-- = hexDigits[v8]; // if ( !(v5 & 7) ) break; --v5; } *(_DWORD *)&v12[v4] = v6; // 賦值操作 v4 += 4LL; v5 += 15; } while ( v4 != 20 );

你會(huì)發(fā)現(xiàn)hexDigits是data段的一段數(shù)據(jù):

最后分析,其實(shí)就是將其原封不動(dòng)的轉(zhuǎn)換為16進(jìn)制的字符,然后一起拼接起來,分析注釋如下:

然后就是第二段循環(huán)了,循環(huán)次數(shù)也可以看出來會(huì)對(duì)每個(gè)16進(jìn)制字符進(jìn)行處理,循環(huán)次數(shù)為40次

hook了char2hexInt處理過后的數(shù)據(jù)會(huì)發(fā)現(xiàn),char2hexInt這個(gè)函數(shù)才是二次處理的函數(shù)

do { v10 = char2hexInt(dest_cstr_1[v9]); dest_cstr_1[v9] = hexDigits[(signed int)((unsigned __int64)char2hexInt(a211034f8af4e6b[v9]) ^ v10)]; //異或的值為v10 ++v9; }

還需要char2hexInt的參數(shù)來觀察,該地址為0x1B34,HOOK結(jié)果如下

其實(shí)就是兩個(gè)返回值進(jìn)行異或處理,比如0x2^0x2就為0,最后還會(huì)通過hexDigits數(shù)組轉(zhuǎn)成對(duì)應(yīng)的16進(jìn)制在內(nèi)存中保存為0x30,因?yàn)樽址?'0' 對(duì)應(yīng)的ASCII 十六進(jìn)制就是 0x30 是相等的!

最終的sign值分析的注釋:

sign這里也分析完了,還有個(gè)整體加密的分析,發(fā)現(xiàn)整體會(huì)進(jìn)行一次Base64.encode,但是nativeAES可以跟進(jìn)去觀察

發(fā)現(xiàn)該函數(shù)依舊是native層的函數(shù),所以這里繼續(xù)去看分析

來到如下進(jìn)行分析,看到名字就知道是AES算法加密了,這里的話用findcrypt插件通過特征碼也可以進(jìn)行識(shí)別

然后接著就是先對(duì)傳入的數(shù)據(jù)進(jìn)行字符串復(fù)制

然后就是AES對(duì)象和密鑰的初始化,這里的AES需要hook,來確認(rèn)v19的值,還有該函數(shù)最終的返回值dest_array_bytes的地址來進(jìn)行打印

AES::AES:0x3274,Cipher:0x397C,然后hook到的數(shù)據(jù)如下,一個(gè)是AES密鑰,最終要進(jìn)行AES字符串加密的字符串

最終hook的代碼:

function hook_test() { Java.perform(function () { var EncryptUtil = Java.use("com.tivicloud.utils.EncryptUtil"); EncryptUtil.nativeEncrypt.implementation = function (a) { console.log("=============================") // console.log("EncryptUtil.nativeEncrypt args[0] 被加密的字符串: ", a); var result = this.nativeEncrypt(a); console.log("Sign的結(jié)果:", result); return result; } var b64 = Java.use("android.util.Base64"); var str = Java.use("java.lang.String"); EncryptUtil.nativeAES.implementation = function (a) { console.log("=============================") // console.log("EncryptUtil.nativeEncrypt args[0] 被加密的字符串: ", a); var result = this.nativeAES(a); console.log("base64數(shù)據(jù)", str.$new(b64.encode(result, 0))); return result; } var libgavesec = Module.findBaseAddress("libgavesec.so"); // encrypt Interceptor.attach(libgavesec.add(0x1C8C), { onEnter: function (args) { console.log("encrypt args[0] 被加密的字符串: ", Memory.readCString(args[0])); this.args1 = args[1]; }, onLeave: function (retVal) { console.log("encrypt args[1] 處理過后的數(shù)據(jù):", hexdump(this.args1, { offset: 0, length: 32, header: true, ansi: false })); } }) // SHA1::Result Interceptor.attach(libgavesec.add(0x2AA4), { onEnter: function (args) { // console.log("SHA1::Result args[1] 加密前的數(shù)據(jù)", hexdump(args[1], { // offset: 0, // length: 64, // header: true, // ansi: false // })); this.args1 = args[1]; }, onLeave: function (retVal) { console.log("SHA1::Result args[1] 加密后的數(shù)據(jù)", hexdump(this.args1, { offset: 0, length: 32, header: true, ansi: false })); } }) // char2hexInt Interceptor.attach(libgavesec.add(0x1B34), { onEnter: function (args) { // console.log("char2hexInt 參數(shù):", args[0]); this.args1 = args[1]; }, onLeave: function (retVal) { // console.log("char2hexInt 返回值:", retVal); } }) // AES::AES Interceptor.attach(libgavesec.add(0x3274), { onEnter: function (args) { console.log("AES密鑰:", hexdump(args[1], { offset: 0, length: 32, header: true, ansi: false })); }, onLeave: function (retVal) { } }) // AES::Cipher Interceptor.attach(libgavesec.add(0x397C), { onEnter: function (args) { console.log("AES::Cipher args[1]:", Memory.readCString(args[1])); this.args1 = args[1]; }, onLeave: function (retVal) { } }); });};setImmediate(function () { Java.perform(function () { hook_test(); });});

簡(jiǎn)單的加密代碼的實(shí)現(xiàn):

from Crypto.Cipher import AESfrom urllib import parsefrom binascii import b2a_hex, a2b_hex, b2a_base64import hashlib"""aes加密算法ECB模式"""class Aes128_(object): def __init__(self): self.key = b"14ca829f017c0357" self.mode = AES.MODE_ECB def add_to_16(self, text): if len(text.encode('utf-8')) % 16: add = 16 - len(text.encode('utf-8')) % 16 else: add = 0 text = text + ("\0"*add) # 明文 + \00填充 return text.encode('utf-8') def encrypt(self, text): text = self.add_to_16(text) cryptos = AES.new(self.key, self.mode) cipher_text = cryptos.encrypt(text) # return b2a_hex(cipher_text) return b2a_base64(cipher_text) def decrypto(self, text): cryptor = AES.new(self.key, self.mode) plain_text = cryptor.decrypt(a2b_hex(text)) return bytes.decode(plain_text).rstrip('\0')def getSign(data): sign = '' a211034f8af4e6b = '211034f8af4e6b9546c19ae13ed099553319b6c3' for i in range(40): print(hex(int(data[i], 16) ^ int(a211034f8af4e6b[i], 16))) sign += str(hex(int(data[i], 16) ^ int(a211034f8af4e6b[i], 16)))[2:3] return signif __name__ == "__main__": data = bytes(parse.quote('{"mobile_operator":"","app_versioncode":"4","app_version":"2.1.0.22277","connection_type":"WIFI","os_version":"8.1.0","source":"SDK","user_id":"16073535","os_lang":"zh","sdk_version":"3.1.4","imei":"359906070277673","os_name":"Android","login_token":"9f6037c931db9a9cfcbb991966fad614","lang":"zh","udid":"10e1ef4509a2340eb0276bb99d186506","nudid":"155c2b70-fc97-4005-abc7-6d6c4329ed23","app_id":"10054","channel_id":"10006","tdid":"39fb282a761c17e80c069332fc6a2ffa9"}fff18b83431fa3a83b9de80c1e413bde'), encoding="utf-8") sha1 = hashlib.sha1(data) sha1_data = sha1.hexdigest() sign = getSign(sha1_data) aes_data = str(Aes128_().encrypt('{"sign":"'+sign+'","data": "' + parse.quote('{"mobile_operator":"","app_versioncode":"4","app_version":"2.1.0.22277","connection_type":"WIFI","os_version":"8.1.0","source":"SDK","user_id":"16073535","os_lang":"zh","sdk_version":"3.1.4","imei":"359906070277673","os_name":"Android","login_token":"9f6037c931db9a9cfcbb991966fad614","lang":"zh","udid":"10e1ef4509a2340eb0276bb99d186506","nudid":"155c2b70-fc97-4005-abc7-6d6c4329ed23","app_id":"10054","channel_id":"10006","tdid":"39fb282a761c17e80c069332fc6a2ffa9"}"}')), encoding='utf8').replace("\n", "") print(aes_data)

這個(gè)其實(shí)不是最終的代碼,因?yàn)閘ogin_token沒有分析出來,感覺有點(diǎn)難,我繼續(xù)試試,可以的話再寫一篇!

總結(jié)

以上是生活随笔為你收集整理的逆向so_记一次APP的so层算法逆向(七)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。