可以看到其中有个请求头
X-App-Token
,这就是验证,至于
X-App-Device
这玩意儿应该是获取你手机信息的,不管它,先看看软件源代码,找到请求方法
1、jeb分析
没加固,好像也没混淆,舒服
搜索关键字:
X-App-Token
很明显找到了我们要的东西了,(jeb3.0按tab键反编译)
这个 X-App-Token 是变量 v2_1,v2_1是,一个
AuthUtils
类里的
getAS
方法返回的
跟进可以发现这是一个native方法,lib是native-lib
到这里就没法用jeb分析了,我们先看看参数2 deviceId 是个什么玩意儿
可以看到是一个
SystemUtils
类里的
getDeviceID
方法返回的,传入一个context参数
我们去app的application hook这个方法
我找到一个代码量最少的方法,这样可以帮助我们不破坏原逻辑的情况下hook
hook的代码很简单
Java.perform(function() {
var CoolMarket = Java.use('com.coolapk.market.CoolMarketApplication');
CoolMarket.onLog.implementation = function() {
var deviceId = Java.use('com.coolapk.market.util.SystemUtils').getDeviceId(this);
console.log('Device Id: ', deviceId);
var app_token = Java.use('com.coolapk.market.util.AuthUtils').getAS(this, deviceId);
console.log('App Token: ', app_token);
console.log('----------');
return 1;
复制代码
拿到 deviceId 后分析so
ida分析
解压apk拿到
native-lib.so
,用ida打开
我们已知方法名和参数个数,那么就先搜索方法名
在
Function Window
按 option+t 搜索
getAS
,可以看到,毛都没有
那我们就到
IDA View
里搜索,快捷键一样
找到了这个,参数是两个,但这个不是方法,没法 F5 反编译
不瞒你们,这个DCB是个什么玩意儿我也不知道.
但是我在
Function Window
瞎翻想找一些我能看得懂的方法名时看到了这个
我一看,这个方法好像和 getAS 有点关系就顺手 F5 了
简单看了看里面的代码,我估计我要找的是这个方法,为什么是这个方法而不是其他的
我分析了
X-App-Token
这个验证的组成,它长这样:
f2c29a109fde487e9350d3e6b881036a8513efac-09ea-3709-b214-95b366f1a1850x5d024391
我之前就获取到了我的 Device Id,我无意间看到了我的 device id就在里面,然后我把它拆分成了这样:
f2c29a109fde487e9350d3e6b881036a
8513efac-09ea-3709-b214-95b366f1a185
0x5d024391
第一项很明显是md5密文,第二部分就是device id,最后是一个十六进制,不知道什么玩意儿,但我在那个
getAuthString
代码里看到了这段
这是一个字符串拼接的过程,其中
v82
是md5密文,也是字符串的头部,后面接着是
v43
(device id)、字符串0x、最后是
hex_time
(这是我改了后的命名),所以我就能确定这个方法就是我想要的;
上面说的也就是接下来要分析的,其中那个十六进制的东西就是时间戳,我们只需要分析出md5是怎么来的就行了,我们知道md5是
v61
,
v61
的加密代码在这
加密的内容是v58,v58是一个经过base64编码后的变量
我懒得去看它是什么了,我直接hook了md5加密类,有三个方法,一个是md5(应该是构造方法把)、update、finalize,hook 代码如下
var
JNI_LOAD_POINTER
=
Module
.
getExportByName
(
'libnative-lib.so'
,
'JNI_OnLoad'
);
var
BASE_ADDR
=
parseInt
(
JNI_LOAD_POINTER
) -
parseInt
(
'0x31A04'
);
Java
.
perform
(
function
(
) {
var
hookpointer =
'0x'
+
parseInt
(
BASE_ADDR
+
parseInt
(
'0x32168'
)).
toString
(
16
)
var
pointer =
new
NativePointer
(hookpointer)
console
.
log
(
'[MD5::MD5] hook pointer: '
, pointer)
var
arg0, arg1, arg2, arg3
Interceptor
.
attach
(pointer, {
onEnter
:
function
(
args
) {
arg0 = args[
0
]
arg1 = args[
1
]
console
.
log
(
'\n'
)
console
.
log
(
'=====> [MD5::MD5] -> [方法调用前]'
)
console
.
log
(
'参数1: {0} => {1}'
.
format
(arg0,
Memory
.
readCString
(arg0)))
console
.
log
(
'参数2: {0} => {1}'
.
format
(arg1,
Memory
.
readCString
(arg1)))
console
.
log
(
'\n'
)
onLeave
:
function
(
retval
) {
console
.
log
(
'\n'
)
console
.
log
(
'=====> [MD5::MD5] -> [方法调用后]:'
)
console
.
log
(
'返回值: '
, retval)
console
.
log
(
'参数1: {0} => {1}'
.
format
(arg0,
Memory
.
readCString
(arg0)))
console
.
log
(
'参数2: {0} => {1}'
.
format
(arg1,
Memory
.
readCString
(arg1)))
console
.
log
(
'\n'
)
Java
.
perform
(
function
(
) {
var
hookpointer =
'0x'
+
parseInt
(
BASE_ADDR
+
parseInt
(
'0x329AC'
)).
toString
(
16
)
var
pointer =
new
NativePointer
(hookpointer)
console
.
log
(
'[MD5::update] hook pointer: '
, pointer)
var
arg0, arg1, arg2, arg3
Interceptor
.
attach
(pointer, {
onEnter
:
function
(
args
) {
arg0 = args[
0
]
arg1 = args[
1
]
arg2 = args[
2
]
console
.
log
(
'\n'
)
console
.
log
(
'=====> [MD5::update] -> [方法调用前]'
)
console
.
log
(
'参数1: {0} => {1}'
.
format
(arg0,
Memory
.
readCString
(arg0)))
console
.
log
(
'参数2: {0} => {1}'
.
format
(arg1,
Memory
.
readCString
(arg1)))
console
.
log
(
'参数3: {0} => {1}'
.
format
(arg2,
Memory
.
readCString
(arg2)))
console
.
log
(
'\n'
)
onLeave
:
function
(
retval
) {
console
.
log
(
'\n'
)
console
.
log
(
'=====> [MD5::update] -> [方法调用后]:'
)
console
.
log
(
'返回值: '
, retval)
console
.
log
(
'参数1: {0} => {1}'
.
format
(arg0,
Memory
.
readCString
(arg0)))
console
.
log
(
'参数2: {0} => {1}'
.
format
(arg1,
Memory
.
readCString
(arg1)))
console
.
log
(
'参数3: {0} => {1}'
.
format
(arg2,
Memory
.
readCString
(arg2)))
console
.
log
(
'\n'
)
Java
.
perform
(
function
(
) {
var
hookpointer =
'0x'
+
parseInt
(
BASE_ADDR
+
parseInt
(
'0x321C4'
)).
toString
(
16
)
var
pointer =
new
NativePointer
(hookpointer)
console
.
log
(
'[MD5::finalize] hook pointer: '
, pointer)
var
arg0, arg1, arg2, arg3
Interceptor
.
attach
(pointer, {
onEnter
:
function
(
args
) {
arg0 = args[
0
]
arg1 = args[
1
]
arg2 = args[
2
]
arg3 = args[
3
]
console
.
log
(
'\n'
)
console
.
log
(
'=====> [MD5::finalize] -> [方法调用前]'
)
console
.
log
(
'参数1: {0} => {1}'
.
format
(arg0,
Memory
.
readCString
(arg0)))
console
.
log
(
'参数2: {0} => {1}'
.
format
(arg1,
Memory
.
readCString
(arg1)))
console
.
log
(
'参数3: {0} => {1}'
.
format
(arg2,
Memory
.
readCString
(arg2)))
console
.
log
(
'参数4: {0} => {1}'
.
format
(arg3,
Memory
.
readCString
(arg3)))
console
.
log
(
'\n'
)
onLeave
:
function
(
retval
) {
console
.
log
(
'\n'
)
console
.
log
(
'=====> [MD5::finalize] -> [方法调用后]:'
)
console
.
log
(
'返回值: '
, retval)
console
.
log
(
'参数1: {0} => {1}'
.
format
(arg0,
Memory
.
readCString
(arg0)))
console
.
log
(
'参数2: {0} => {1}'
.
format
(arg1,
Memory
.
readCString
(arg1)))
console
.
log
(
'参数3: {0} => {1}'
.
format
(arg2,
Memory
.
readCString
(arg2)))
console
.
log
(
'参数4: {0} => {1}'
.
format
(arg3,
Memory
.
readCString
(arg3)))
console
.
log
(
'\n'
)
复制代码
运行后得到了那个base64编码过的内容:
dG9rZW46Ly9jb20uY29vbGFway5tYXJrZXQvYzY3ZWY1OTQzNzg0ZDA5NzUwZGNmYmIzMTAyMGYwYWI/MzgyMzIxNWQ5MWQyOWQ5ODg3ZWJjMDVmMGQ3ZmQzMGQkODUxM2VmYWMtMDllYS0zNzA5LWIyMTQtOTViMzY2ZjFhMTg1JmNvbS5jb29sYXBrLm1hcmtldA==
经过解码后:
token://com.coolapk.market/c67ef5943784d09750dcfbb31020f0ab?3823215d91d29d9887ebc05f0d7fd30d$8513efac-09ea-3709-b214-95b366f1a185&com.coolapk.market
在我看到这段代码后
上面解码后的内容可以拆分为:
token://com.coolapk.market/c67ef5943784d09750dcfbb31020f0ab?
3823215d91d29d9887ebc05f0d7fd30d
8513efac-09ea-3709-b214-95b366f1a185
com.coolapk.market
据我分析,只需要的到第二部分的md5加密的来历就行了,继续分析,找到了加密的地方
根据这图的画线,可以明确的知道这md5就是时间戳,在我hook的输出中也可以看到这个就是时间戳
至于 token://com.coolapk.market/c67ef5943784d09750dcfbb31020f0ab? 这个是不变的
token://com.coolapk.market/c67ef5943784d09750dcfbb31020f0ab?
+ md5加密后的时间戳 + $ +
device id
+ & +
com.coolapk.market
(包名),将其md5加密后得到
第一部分
token的来历就是:第一部分 +
deivce id
+ 0x + 十六进制转换后的时间戳
简单的测试代码:
import requests
import time
import hashlib
import base64
DEVICE_ID = "8513efac-09ea-3709-b214-95b366f1a185"
def get_app_token():
t = int(time.time())
hex_t = hex(t)
# 时间戳加密
md5_t = hashlib.md5(str(t).encode('utf-8')).hexdigest()
# 不知道什么鬼字符串拼接
a = 'token://com.coolapk.market/c67ef5943784d09750dcfbb31020f0ab?{}${}&com.coolapk.market' \
.format(md5_t, DEVICE_ID)
# 不知道什么鬼字符串拼接 后的字符串再次加密
md5_a = hashlib.md5(base64.b64encode(a.encode('utf-8'))).hexdigest()
token = '{}{}{}'.format(md5_a, DEVICE_ID, hex_t)
print(token)
return token
def request():
url = "https://api.coolapk.com/v6/main/indexV8?page=1"
headers = {
"User-Agent": "Dalvik/2.1.0 (Linux; U; Android 9; MI 8 SE MIUI/9.5.9) (#Build; Xiaomi; MI 8 SE; PKQ1.181121.001; 9) +CoolMarket/9.2.2-1905301"
headers = {
"User-Agent": "Dalvik/2.1.0 (Linux; U; Android 9; MI 8 SE MIUI/9.5.9) (#Build; Xiaomi; MI 8 SE; PKQ1.181121.001; 9) +CoolMarket/9.2.2-1905301",
"X-App-Id": "com.coolapk.market",
"X-Requested-With": "XMLHttpRequest",
"X-Sdk-Int": "28",
"X-Sdk-Locale": "zh-CN",
"X-Api-Version": "9",
"X-App-Version": "9.2.2",
"X-App-Code": "1903501",
"X-App-Device": "QRTBCOgkUTgsTat9WYphFI7kWbvFWaYByO1YjOCdjOxAjOxEkOFJjODlDI7ATNxMjM5MTOxcjMwAjN0AyOxEjNwgDNxITM2kDMzcTOgsTZzkTZlJ2MwUDNhJ2MyYzM",
"Host": "api.coolapk.com",
"X-Dark-Mode": "0",
"X-App-Token": get_app_token(),
resp = requests.get(url, headers=headers)
print(resp.text)
if __name__ == '__main__':
request()
复制代码
最后