闲来无事,随便下载了个免费小说软件,对其中的登录时的参数进行了分析
在登录发送验证码时使用Charles抓包
根据关键词在Java层搜索
查找用例,定位到函数
可以看到将一些参数存入ArrayMap
中传入j.addSign()
函数中,这里的addSign
是我自己手动修改的名字
传入的Key
值对照数据包中的参数也都能够对的上
继续跟进addSign
函数中
又添加了一个时间戳键值对后,经过getSortedParamStr
后传给hash
得到sign
值
getSortedParamStr
将键值对按键进行排序后,转成字符串返回。不同键值对之间用&
连接,键与值之间用=
连接
比如,对如下键值对进行转换
Security
的hash
函数调用了JNISecurity
的hash2
函数,参数有Signature
的SHA1WithRSA
、KeyFactory
的RSA
,字符串转成的字节
JNISecurity
里的hash2
是一个native
函数,可以看到类里加载了UiControl
库,大概率就在libUiControl.so
里
解压后在lib
文件夹里找到libUiControl.so
先在Function
搜索hash2
,不出所料没有结果,所以这是动态注册的函数
那么应该分析JNI_OnLoad
函数
由GetEnv
得知参数v26
类型为JNIEnv*
,另外看下面有调用固定常数偏移函数大概率都是JNIEnv*
参数,重设类型后就能分析出JNI
函数了
往下分析,在sub_7A5A8
函数中找到了调用了RegisterNatives
函数,可能是我们要找的hash
函数
由RegisterNatives
的定义可知,
第一个参数clazz
是注册的函数所在的类
第二个参数methods
是JNINativeMethod
类型,里面包含了函数名、函数签名以及函数指针
不过这里的各个参数当前都是空的数据,需要先经过sub_78F54
解密才能得到原本的数据。第二个参数是我们的结果
sub_78F54
函数的逻辑也比较简单,主要运算的部分也就只有中间这一句而已
v9
是第一个参数,结合具体值分析可以得到是取三位一组然后转成十进制数,和v12
异或
v12
是字符串"8080"
后面的v8 - (v10 & 0xFFFFFFFC)
其实就相当于v8&3
,只取了最后两位
可以得到一个简单的解密脚本
输出结果发现果然是我们想要找到的hash2
函数
重命名参数后,可以知道sub_877EC
是hash
函数的函数指针
sub_87324
是hash2
函数的函数指针
另外有个坑点,一开始分析的是arm64
的so文件,可以看到反编译的结果不是很好分析,头脑有点迷糊没有对上哪个函数指针对应哪个函数
后面换了32位的so文件才发现反编译效果好的太多了,不仅methods
数组各个参数排列的很整齐,甚至hash2
函数名的符号表都还在:cry:
让我想到了之前打的一个比赛中,两个不同架构的so文件,arm
的反编译有问题,反而x86
反编译的结果非常清晰
点进来同样发现调用了指针加偏移的函数,估计也是JNI
函数,不过还是跟进分析一下sub_78EEC
函数
sub_78EEC
也是这样的调用函数,惯性的改类型成JNIEnv*
发现得到的是FindClass
函数,显然不太对
FindClass
显然得不到JNIEnv*
类型的变量
交叉引用一下,发现在JNI_OnLoad
里调用了这个变量,才发现原来是JavaVM*
类型
改成JavaVM*
就得到了GetEnv
函数
将sub_87324
里的各个env变量修正后,JNI
函数就都能正常显示了,
将其中的几个字符串写在了旁边的注释中
函数逻辑也比较清晰了
比较常见的JNI层调用Java算法的过程
翻译成Java代码,一个比较常规的签名算法
type
是hash2
的第一个参数,传入的是固定的2
,因此这里使用的私钥是off_3BFC50+1=unk_2F5477
长度为0x279
hash2
签名后再Base64
一下即为sign
参数值
使用python
还原算法后验证,同时实现了发包请求
可以看到计算得到的sign
值和抓包得到的sign
值是一致的
同时返回包也是成功
手机上也是成功收到了短信
同样地该APP在登陆时的sign
参数也是类似的逻辑
将之前的代码添加了登录的功能,实现了从获取验证码到登录的过程
arraymap
=
{
"flag"
:
"1"
,
"channelId"
:
"1240202"
,
"imei"
:
"____7548"
,
"device"
:
"Nexus 5X"
,
}
channelId
=
1240202
&device
=
Nxus
5X
&flag
=
1
&imei
=
___7548
arraymap
=
{
"flag"
:
"1"
,
"channelId"
:
"1240202"
,
"imei"
:
"____7548"
,
"device"
:
"Nexus 5X"
,
}
channelId
=
1240202
&device
=
Nxus
5X
&flag
=
1
&imei
=
___7548
jint RegisterNatives(jclass clazz, const JNINativeMethod
*
methods,jint nMethods)
typedef struct {
const char
*
name;
const char
*
signature;
void
*
fnPtr;
} JNINativeMethod;
jint RegisterNatives(jclass clazz, const JNINativeMethod
*
methods,jint nMethods)
typedef struct {
const char
*
name;
const char
*
signature;
void
*
fnPtr;
} JNINativeMethod;
def
decrypt(start,size
=
0
):
if
size
=
=
0
:
size
=
get_item_end(start)
-
start
data
=
get_bytes(start,size)
key
=
[
56
,
48
]
out
=
[]
for
i
in
range
(
0
,
len
(data),
3
):
tmp
=
data[i:i
+
3
].decode()
if
tmp.startswith(
'\x00'
):
return
out
t
=
int
(tmp)
tmp
=
t^key[i
/
/
3
%
2
]
out.append(
chr
(tmp))
s
=
''.join(out)
return
s
addrs
=
[
0x2F5008
,
0x2F5084
,
0x2F5130
,
0x2F513D
,
0x2F51EC
]
for
i
in
range
(
len
(addrs)):
if
i !
=
len
(addrs)
-
1
:
size
=
addrs[i
+
1
]
-
addrs[i]
else
:
size
=
0x10
s
=
decrypt(addrs[i])
print
(
hex
(addrs[i]),''.join(s))
def
decrypt(start,size
=
0
):
if
size
=
=
0
:
size
=
get_item_end(start)
-
start
data
=
get_bytes(start,size)
key
=
[
56
,
48
]
out
=
[]
for
i
in
range
(
0
,
len
(data),
3
):
tmp
=
data[i:i
+
3
].decode()
if
tmp.startswith(
'\x00'
):
return
out
t
=
int
(tmp)
tmp
=
t^key[i
/
/
3
%
2
]
out.append(
chr
(tmp))
s
=
''.join(out)
return
s
addrs
=
[
0x2F5008
,
0x2F5084
,
0x2F5130
,
0x2F513D
,
0x2F51EC
]
for
i
in
range
(
len
(addrs)):
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2022-12-8 18:46
被si1enceZ编辑
,原因: