本文主要是介绍安卓逆向百例十-币coin,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
typora-root-url: ./pic
安卓逆向百例十-币coin
现在售价依旧是99¥,计划更新100案例,平均一个案例1块钱,要什么自行车!
案例起源:
有位老哥 经过炒币失败后想做一个这种查询接口的app,但是苦于他的资金比较紧缺,就给我几百块,但是架不住苦苦哀求,我就做了,顺便当个案例。
版本: 币coin 4.0.3
包名: com.temperaturecoin
1.抓包分析
url是https://i.bicoin.com.cn/firmOffer/getUserAccountInfoBySecretNew?salt=8&sign=CDGCCEJCPCIGGGLICMVVBQOIEJTNNUQQN&time=1724126970032&userId=894919
位置在:
其中我们今天要去逆向的是 sign的生成,以及data的解密。
2.关键词定位
搜索
查找用例:
进入方法内 我们可以看的 sign是 经过了AESUtil.s
其中我们可以可以得到 :
time 就是时间戳 ,salt是一个 随机的值 sign 是 time + salt + "getUserAccountInfoBySecretNew"
接下来跳转到AESUtil.s 方法里面 跳转到这个位置:
hook验证一下入参:
查看一下加载的so文件是 ns:
打开ida 反编译 libns.so 搜索java我们发现是静态注册的 并且发现了 Java_com_bcoin_ns_S_s这个字眼:
进去看看喽!顺便把fastcall Java_com_bcoin_ns_S_s(int64 a1 的a1改成 JNIEnv *
jstring __fastcall Java_com_bcoin_ns_S_s(JNIEnv *a1, __int64 a2, __int64 a3)
{const char *v5; // x20const char *v6; // x21jstring result; // x0int v8; // w10__int64 (__fastcall *v9)(); // x10char v10; // w24int v11; // w9char *v12; // x20size_t v13; // w0char *v14; // x21unsigned __int64 v15; // x10char *v16; // x11unsigned __int64 v17; // x9char *v18; // x10char v19; // t1_OWORD *v20; // x12__int128 *v21; // x13unsigned __int64 v22; // x14__int128 v23; // q0__int128 v24; // q1_QWORD v25[2]; // [xsp+0h] [xbp-40h] BYREF
v25[1] = *(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);v5 = (*a1)->GetStringUTFChars(a1, a3, 0LL);v6 = (*a1)->GetStringUTFChars(a1, token, 0LL);(*a1)->ReleaseStringUTFChars(a1, a3, v5);(*a1)->ReleaseStringUTFChars(a1, token, v6);result = 0LL;if ( v5 && v6 ){v8 = idxMethod % 3;if ( idxMethod % 3 ){if ( v8 == 2 ){v9 = jointMd5;v10 = 100;}else if ( v8 == 1 ){v9 = axor;v10 = 67;}else{v9 = chainXor;v10 = 97;}}else{v9 = cxor;v10 = 98;}if ( idxMethod == 2147483646 )v11 = 0;elsev11 = idxMethod + 1;idxMethod = v11;v12 = (v9)(v5, v6);__android_log_print(3, "bc", "pre payload: %s", v12);if ( v12 ){v13 = strlen(v12);v14 = v25 - ((v13 + 1 + 15LL) & 0x1FFFFFFF0LL);*v14 = v10;if ( v13 >= 1 ){if ( v13 > 0x1FuLL && (v14 + 1 >= &v12[v13] || v12 >= &v14[v13 + 1]) ){v15 = v13 - (v13 & 0x1F);v20 = v14 + 17;v21 = (v12 + 16);v22 = v15;do{v23 = *(v21 - 1);v24 = *v21;v22 -= 32LL;v21 += 2;*(v20 - 1) = v23;*v20 = v24;v20 += 2;}while ( v22 );if ( (v13 & 0x1F) == 0 )goto LABEL_21;}else{v15 = 0LL;}v16 = &v12[v15];v17 = v13 - v15;v18 = &v14[v15 + 1];do{v19 = *v16++;--v17;*v18++ = v19;}while ( v17 );}
LABEL_21:v14[v13 + 1] = 0;free(v12);return (*a1)->NewStringUTF(a1, v14);}return (*a1)->NewStringUTF(a1, "");}return result;
}
眼尖的朋友已经看见了 这里有个 __android_log_print 我们可以使用adb 来监控他的输出
在设备上运行日志记录工具 logcat
,这是 Android 提供的一个工具,用于实时查看系统和应用程序的日志。
在终端中执行以下命令启动 logcat
:
adb logcat
这会显示设备上的所有日志信息。如果你想要过滤特定标签(例如 "bc"
),可以使用:
adb logcat -s bc:D
这里的 -s
选项指定了过滤的标签,D
表示只显示 DEBUG
级别及以上的日志。
logcat:524b5a6860bad1a0bbe0712e48a5debb
抓包:d524b5a6860bad1a0bbe0712e48a5debb
我们从中可以发现 其实就是 结果 加了一个d 对吧,但是我们通过观察 logcat 和 抓包会发现有的时候是大写 有的时候是小写而且规律也不一样。他其实和这个有关系
这段代码根据 idxMethod
的值选择一个函数指针,并根据不同的条件设置函数的参数和值。接着调用这个函数,并将结果保存到 v12
中。代码的目的是根据不同的条件选择合适的处理函数,并在每次调用后更新 idxMethod
的值。
代码解释:
result = 0LL; // 初始化一个 long long 类型的变量 result 为 0
if (v5 && v6) // 如果 v5 和 v6 都不为 0(即它们都有效)
{v8 = idxMethod % 3; // 计算 idxMethod 除以 3 的余数,并将结果赋值给 v8
if (idxMethod % 3) // 如果 idxMethod 除以 3 的余数不为 0{if (v8 == 2) // 如果余数是 2{v9 = jointMd5; // 将 v9 设置为 jointMd5(假设 jointMd5 是一个函数指针)v10 = 100; // 将 v10 设置为 100}else if (v8 == 1) // 如果余数是 1{v9 = axor; // 将 v9 设置为 axor(假设 axor 是一个函数指针)v10 = 67; // 将 v10 设置为 67}else // 如果余数是 0{v9 = chainXor; // 将 v9 设置为 chainXor(假设 chainXor 是一个函数指针)v10 = 97; // 将 v10 设置为 97}}else // 如果 idxMethod 除以 3 的余数为 0{v9 = cxor; // 将 v9 设置为 cxor(假设 cxor 是一个函数指针)v10 = 98; // 将 v10 设置为 98}
if (idxMethod == 2147483646) // 如果 idxMethod 的值为 2147483646v11 = 0; // 将 v11 设置为 0elsev11 = idxMethod + 1; // 否则,将 v11 设置为 idxMethod + 1
idxMethod = v11; // 更新 idxMethod 的值为 v11
v12 = (v9)(v5, v6); // 调用 v9 指向的函数,传入 v5 和 v6 作为参数,并将结果赋值给 v12
}
我们这里可以直接看他走 jointMd5 的位置 然后 直接查看入参不就行了 ,然后固定参数去请求。这样就不用还原 chainXor 和 cxor 函数了。
我们查看joinmd5这个函数 映入眼帘的是 这两个
v8 = strcpy(v6, s); strcat(v8, a1);
所以我直接去hook joinMd5 这个函数:
args1来源token:
抓包和adb 记录的:
所以就是 MD5(token + times + salt + 'getUserAccountInfoBySecretNew') 这个已经验证过了是标准的md5
str = '2c06cef65865431546fdb751f255508b'+str(times)+"6"+'getUserAccountInfoBySecretNew'
# print(str)
sign = 'd'+calculate_md5(str)
所以我们请求的时候 固定salt 就行了
具体代码放在文章最后...
3.返回值解密
一般来说 同一个数据包的加密的位置在都在一块,所以我们就顺藤摸瓜,就找到
hook验证:
去so层看看 :
一进去我们就看到 AES 128 pkcs5 的字眼:
进来后发现:
好家伙 Key = getKey(); IV = getIV(); 这么明显吗?那就不怪我了
上frida hook: hook到key 和 iv了
hook代码如下:
// key
var addr = Module.findBaseAddress('libns.so');
var KEY = addr.add(0x10144);
console.log(KEY);
Interceptor.attach(KEY,{onEnter:function (args){console.log("---------key 进入了-----------")
},onLeave:function(retval) {console.log("--------------------")console.log('KEY 返回值:',hexdump(retval,{length:16}))}
})
// IV
var addr = Module.findBaseAddress('libns.so');
var IV = addr.add(0x10144);
console.log(IV);
Interceptor.attach(IV,{onEnter:function (args){console.log("---------IV 进入了-----------")
},onLeave:function(retval) {console.log("--------------------")console.log('IV 返回值:',hexdump(retval,{length:16}))}
})
至此我们整个流程就已经完成了。
4.python代码还原
import time
from loguru import logger
import requests
import hashlib
from Crypto.Cipher import AES
import base64
import requests
def unpad(data):"""去除填充"""pad_length = data[-1]return data[:-pad_length]
def decrypt_aes_cbc(ciphertext_base64, key, iv):# key = bytes(key_text, 'utf-8')# iv = bytes(iv_text, 'utf-8')ciphertext = base64.b64decode(ciphertext_base64)
cipher = AES.new(key, AES.MODE_CBC, iv)plaintext = cipher.decrypt(ciphertext)plaintext = unpad(plaintext).decode('utf-8')return plaintext
def calculate_md5(data):# 创建一个MD5哈希对象md5_hash = hashlib.md5()# 更新哈希对象md5_hash.update(data.encode('utf-8'))# 返回MD5哈希的十六进制表示return md5_hash.hexdigest()
headers = {xxx
}
times = int(time.time() * 1000)
# 17236209670982
# 1723621530537
# 1723621708604
# print(times)
str = 'token'+str(times)+"6"+'getUserAccountInfoBySecretNew'
# print(str)
sign = 'd'+calculate_md5(str)
logger.info("sign签名{}".format(sign))
# print(sign)
headers['Sign'] = sign
headers['Time'] = f'{times}'
url = "https://i.bicoin.com.cn/firmOffer/getUserAccountInfoBySecretNew"
params = {"salt": "6","sign": f"{sign}","time": f"{times}","userId": "894919"
}
response = requests.get(url, headers=headers, params=params).json()
print(response)
data = response['data']
key_hex = '8971483f9910300bdffee864cb135f34'
iv_hex = '8971483f9910300bdffee864cb135f34'
data = decrypt_aes_cbc(data, bytes.fromhex(key_hex), bytes.fromhex(iv_hex))
print(data)
#2c06cef65865431546fdb751f255508b17236221106396getUserAccountInfoBySecretNew
#2c06cef65865431546fdb751f255508b17236215305376getUserAccountInfoBySecretNew
交流群:
这篇关于安卓逆向百例十-币coin的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!