【Android安全-零基础算法还原01以及使用python和JS还原C++部分细节】此文章归类为:Android安全。
你能从本文中学到apk中的so层简单算法还原以及使用js和python还原C代码的部分细节
题目链接
链接:
提取码:1234
使用jadx
打开algorithmbase_10.apk
使用Frida
获取先生成的随机字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
// 定义一个名为hook_js的JavaScript函数
function
hook_js(){
// 使用Java.perform()函数来执行JavaScript代码
Java.perform(
function
(){
// 使用Java.use()函数来获取Java类com.kanxue.algorithmbase.MainActivity
var
m_randomAscii = Java.use(
"com.kanxue.algorithmbase.MainActivity"
)
// 检查获取的类对象是否存在
if
(m_randomAscii!=undefined){
console.log(
"开始Hook"
);
// 打印开始Hook的消息
// 重写函数encodeFromJni_11的实现
m_randomAscii.encodeFromJni_11.implementation =
function
(input){
// 获取输入参数input
var
res =
this
.encodeFromJni_11(input);
// 打印输入参数和返回值
console.log(
"input:==>"
+input);
console.log(
"res:==>"
+res);
// 返回结果
return
res;
}
}
})
}
function
main(){
hook_js();
}
setImmediate(main);
|
传入的随机字符串和字符串在Native层加密后的结果如下
1
2
|
input
:
=
=
> meUx3DppB
%
Gj]
-
2J
res:
=
=
> LCllTadbMHYZ0kNnDitri5
=
=
|
我们进入Native
层查看加密后的结果
使用unzip
命令解压apk
获取so
文件
unzip algorithmbase_10.apk -d fileso10
使用Ida
打开so
文件,在Export
表输入JNI
查看加密函数的位置
使用快捷键Y
和N
修改传入的参数名称,以便我们方便我们后续分析
返回值是v13
我们从下往上回溯来到 sub_EE38(v9, v10, v11)
函数
使用Frida
对sub_EE38
函数进行hook
,目的是为了查看传入的参数和传出的参数
脚本如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
function
hook_js(){
Java.perform(
function
(){
var
m_randomAscii = Java.use(
"com.kanxue.algorithmbase.MainActivity"
)
//获取MainActivity类
if
(m_randomAscii!=undefined){
console.log(
"开始Hook"
);
m_randomAscii.encodeFromJni_11.implementation =
function
(input){
//找到encodeFromJni_11方法,并拦截调用
var
res =
this
.encodeFromJni_11(input);
//调用原始方法
console.log(
"input:==>"
+input);
//打印输入参数
console.log(
"res:==>"
+res);
//打印返回结果
return
res;
//返回结果
}
}
})
}
function
hook_native(){
Java.perform(
function
(){
//找到基地址
var
base_address = Module.getBaseAddress(
"libnative-lib.so"
)
//获取libnative-lib.so的基地址
var
sub_EE38 = base_address.add(0xEE38);
//计算函数偏移地址
//开启拦截器
Interceptor.attach(sub_EE38,{
//进入函数
onEnter:
function
(args){
this
.arg0 = args[0];
//保存第一个参数
console.log(
"======>onENter<=========="
);
console.log(
"第一个参数未处理前===>"
+args[0].readCString());
//打印第一个参数内容
console.log(
"第二个参数未处理前===>"
+args[1].readCString());
//打印第二个参数内容
console.log(
"第三个参数未处理前===>"
+args[2]);
//打印第三个参数内容
},
onLeave:
function
(nresult){
console.log(
"======>onLeave<=========="
);
console.log(
"第一个参数处理后======>"
+
this
.arg0.readCString());
//打印处理后的第一个参数
}
})
})
}
function
main(){
hook_js();
hook_native();
}
setImmediate(main);
|
点击app按钮,Hook的代码如下所示
我们获取到如下参数
sub_EE38(_BYTE *a1, __int64 a2, int a3) | 第一个参数 | 第二个参数 | 第三个参数 |
---|---|---|---|
未处理前 | "" | meUx3DppB%Gj]-2J | 0x10 |
处理后后 | LCllTadbMHYZ0kNnDitri5== |
1
2
3
4
5
6
7
|
第一个参数未处理前
=
=
=
>
第二个参数未处理前
=
=
=
>meUx3DppB
%
Gj]
-
2J
第三个参数未处理前
=
=
=
>
0x10
=
=
=
=
=
=
>onLeave<
=
=
=
=
=
=
=
=
=
=
第一个参数处理后
=
=
=
=
=
=
>LCllTadbMHYZ0kNnDitri5
=
=
input
:
=
=
>meUx3DppB
%
Gj]
-
2J
res:
=
=
>LCllTadbMHYZ0kNnDitri5
=
=
|
也就是说 sub_EE38
就是我们找的加密call
我们不妨进入sub_EE38
查看加密细节
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
|
__int64
__fastcall sub_EE38(_BYTE *a1,
__int64
a2,
int
a3)
{
__int64
v3;
// x9,循环计数器
_BYTE *v4;
// x12,用于存储结果的指针
unsigned
__int8
*v5;
// x10,用于指向输入数据的指针
unsigned
__int64
v6;
// x13,临时变量
_BYTE *v7;
// x10,下一个结果的存储位置
__int64
v8;
// x11,临时变量
char
v9;
// w8,临时变量
__int64
v10;
// x9,临时变量
__int64
result;
// x0,函数返回值
if
(a3 - 2 < 1)
// 当数据长度小于等于2时
{
LODWORD(v3) = 0;
v7 = a1;
if
(a3 <= 0)
goto
LABEL_11;
}
else
{
v3 = 0LL;
v4 = a1;
do
{
v5 = (unsigned
__int8
*)(a2 + v3);
// 从输入数据中取出三个字节进行处理
v6 = *(unsigned
__int8
*)(a2 + v3);
v3 += 3LL;
*v4 = aAyzabfghz0cmbd[v6 >> 2];
// 取出第一个字节的前6位对应的字符
v4[1] = aAyzabfghz0cmbd[(16 * (unsigned
int
)*v5) & 0x30LL | ((unsigned
__int64
)v5[1] >> 4)];
// 取出第二个字节的前4位和第一个字节的后2位所对应的字符
v4[2] = aAyzabfghz0cmbd[(4 * (unsigned
int
)v5[1]) & 0x3CLL | ((unsigned
__int64
)v5[2] >> 6)];
// 取出第三个字节的前2位和第二个字节的后4位所对应的字符
LOBYTE(v6) = aAyzabfghz0cmbd[v5[2] & 0x3F];
// 取出第三个字节的后6位对应的字符
v7 = v4 + 4;
// 指向下一个结果的存储位置
v4[3] = v6;
// 存储刚才取出的字符
v4 += 4;
// 指向下一个处理位置
}
while
(v3 < a3 - 2);
// 处理到倒数第三个字节为止
if
((
int
)v3 >= a3)
// 如果处理到倒数第二个字节或最后一个字节
goto
LABEL_11;
}
*v7 = aAyzabfghz0cmbd[(unsigned
__int64
)*(unsigned
__int8
*)(a2 + (unsigned
int
)v3) >> 2];
// 取出最后一个字节的前6位对应的字符
v8 = (16 * (unsigned
int
)*(unsigned
__int8
*)(a2 + (unsigned
int
)v3)) & 0x30LL;
// 取出最后一个字节的前4位
if
((_DWORD)v3 == a3 - 1)
// 如果只有最后一个字节
{
v7[1] = aAyzabfghz0cmbd[v8];
// 存储最后一个字节的前4位对应的字符
v9 = 61;
// 存储'='字符
}
else
{
v10 = a2 + (unsigned
int
)v3;
// 最后两个字节的指针
v7[1] = aAyzabfghz0cmbd[v8 | ((unsigned
__int64
)*(unsigned
__int8
*)(v10 + 1) >> 4)];
// 存储最后一个字节的前4位和倒数第二个字节的后2位对应的字符
v9 = aAyzabfghz0cmbd[(4 * (unsigned
int
)*(unsigned
__int8
*)(v10 + 1)) & 0x3CLL];
// 存储倒数第二个字节的后4位对应的字符
}
v7[2] = v9;
// 存储倒数第二个字节的后4位或'='字符
v7[3] = 61;
// 存储'='字符
v7 += 4;
// 指向下一个结果的存储位置
LABEL_11:
result = (unsigned
int
)((_DWORD)v7 - (_DWORD)a1 + 1);
// 计算结果的长度
*v7 = 0;
// 结果字符串结尾添加NULL字符
return
result;
// 返回结果长度
}
|
根据call,还原加密算法
用C代码还原加密算法如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
|
// 01.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <memory>
#include <Windows.h>
#include <minwindef.h>
#include <rpcndr.h>
using
namespace
std;
unsigned
char
* sub_EE38(
char
* strResult,
long
long
inPut,
signed
int
nCnt)
{
long
long
v3 = 0;
unsigned
char
* v4 =
new
unsigned
char
[100];
unsigned
char
* v5 = nullptr;
unsigned
long
long
v6 = 0;
char
aAyzabfghz0cmbd[] =
"AYZabFGHz0cmBdefghijklCDE1KLMNTU56789+/VWXnopq23IJrstuvwOPQRSxy4"
;
long
long
result = 0;
unsigned
long
long
v8 = 0;
char
v9;
unsigned
char
* v7;
long
long
v10;
//记录v4初始
unsigned
char
* vCount = v4;
do
{
*v4 = 0;
v5 = (unsigned
char
*)(inPut + v3);
v6 = *(unsigned
char
*)(inPut + v3);
v3 += 3;
*v4 = aAyzabfghz0cmbd[v6 >> 2];
v4[1] = aAyzabfghz0cmbd[(16 * (unsigned
int
)*v5) & 0x30 | ((unsigned
long
long
)v5[1] >> 4)];
v4[2] = aAyzabfghz0cmbd[(4 * (unsigned
int
)v5[1]) & 0x3C | ((unsigned
long
long
)v5[2] >> 6)];
unsigned
char
lowByte = aAyzabfghz0cmbd[v5[2] & 0x3F];
v6 = (v6 & ~(0xFFull)) | lowByte;
v7 = v4 + 4;
v4[3] = v6;
v4 += 4;
}
while
(v3 < nCnt - 2);
if
((
int
)v3 >= nCnt)
goto
LABEL_11;
*v7 = aAyzabfghz0cmbd[(unsigned
long
long
) * (unsigned
char
*)(inPut + (unsigned
int
)v3) >> 2];
v8 = (16 * (unsigned
int
)*(unsigned
__int8
*)(inPut + (unsigned
int
)v3)) & 0x30LL;
if
(v3 == nCnt - 1)
{
v7[1] = aAyzabfghz0cmbd[v8];
v9 = 61;
}
else
{
v10 = inPut + (unsigned
int
)v3;
v7[1] = aAyzabfghz0cmbd[v8 | ((unsigned
long
long
) * (unsigned
char
*)(v10 + 1) >> 4)];
v9 = aAyzabfghz0cmbd[(4 * (unsigned
int
)*(unsigned
char
*)(v10 + 1)) & 0x3CLL];
}
v7[2] = v9;
v7[3] = 61;
v7 += 4;
std::cout << (unsigned
char
*)vCount;
return
(unsigned
char
*)vCount;
LABEL_11:
result = (unsigned
int
)((
DWORD
)v7 - (
DWORD
)strResult + 1);
*v7 = 0;
delete
[] v4;
std::cout << result;
return
(unsigned
char
*)result;
}
int
main()
{
char
str[] =
""
;
unsigned
char
* str2= sub_EE38(str, (
__int64
)
"meUx3DppB%Gj]-2J"
, 0x10);
std::cout << str2;
}
|
还原得到加密结果
1
|
LCllTadbMHYZ0kNnDitri5
=
=
|
使用JS还原加密算法如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
function
stringToUint8Array(str){
var
arr = [];
for
(
var
i=0;i<str.length;i++)
{
arr.push(str.charCodeAt(i));
}
var
outputbytes =
new
Uint8Array(arr);
return
outputbytes;
}
function
sub_EE38(str,input,nCnt){
var
input_bytes = stringToUint8Array(input);
var
aAyzabfghz0cmbd =
"AYZabFGHz0cmBdefghijklCDE1KLMNTU56789+/VWXnopq23IJrstuvwOPQRSxy4"
;
var
result =0;
var
v4 =
""
;
var
v7=0;
var
v3 =0;
var
v6 =0
var
v8 =0
var
v9 =
""
while
(v3 <nCnt-2){
var
v5 = input_bytes[v3];
var
v5_1 = input_bytes[v3+1]
var
v5_2 = input_bytes[v3+2]
v6 = input_bytes[v3];
v3 +=3;
v4 += aAyzabfghz0cmbd.charAt(v6 >>2)
v4 += aAyzabfghz0cmbd.charAt(((16*v5)& 0x30) | v5_1 >>4);
v4 += aAyzabfghz0cmbd.charAt((4*v5_1)&0x3C |v5_2 >>6 );
v6 = aAyzabfghz0cmbd[v5_2 & 0x3F]
v4 += v6;
}
v4 +=aAyzabfghz0cmbd[(input_bytes[v3]) >> 2];
v8 = 16 * input_bytes[v3] & 0x30
if
(v3 >= nCnt -1){
v4 += aAyzabfghz0cmbd[v8]
v9 =
"="
}
else
{
v4 += aAyzabfghz0cmbd[v8 |(v4[v3+1] >>4)]
v9 = aAyzabfghz0cmbd[(4*v4[v3+1] & 0x3C)]
}
v4 += v9
v4 +=
"="
return
v4
}
console.log(sub_EE38(
""
,
"meUx3DppB%Gj]-2J"
,0x10))
|
使用Python还原算法如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
def
sub_EE38(strResult,
inPut
,nCnt):
v3
=
0
v4
=
bytes(
inPut
,
"utf-8"
)
aAyzabfghz0cmbd
=
"AYZabFGHz0cmBdefghijklCDE1KLMNTU56789+/VWXnopq23IJrstuvwOPQRSxy4"
v6
=
0
result
=
0
v8
=
0
output
=
""
v7
=
0
while
True
:
v5
=
v4[v3]
v5_1
=
v4[v3
+
1
]
v5_2
=
v4[v3
+
2
]
v6
=
v4[v3]
v3
+
=
3
output
+
=
aAyzabfghz0cmbd[v6>>
2
]
output
+
=
aAyzabfghz0cmbd[(
16
*
v5) &
0x30
| (v5_1 >>
4
)]
output
+
=
aAyzabfghz0cmbd[(
4
*
v5_1)&
0x3C
| (v5_2>>
6
)]
v6
=
aAyzabfghz0cmbd[v5_2&
0x3F
]
output
+
=
v6
v7
+
=
4
if
v3>
=
nCnt
-
2
:
break
output
+
=
aAyzabfghz0cmbd[v4[v3] >>
2
]
v8
=
16
*
v4[v3] &
0x30
if
(v3 >
=
nCnt
-
1
):
output
+
=
aAyzabfghz0cmbd[v8]
v9
=
'='
else
:
output
+
=
aAyzabfghz0cmbd[v8 | (v4[v3
+
1
]>>
4
)]
v9
=
aAyzabfghz0cmbd[(
4
*
v4[v3
+
1
]&
0x3C
)]
output
+
=
v9
output
+
=
'='
return
output
# Press the green button in the gutter to run the script.
if
__name__
=
=
'__main__'
:
print
(sub_EE38("
","
meUx3DppB
%
Gj]
-
2J
",
0x10
))
|
algorithmbase_12.apk
使用apkInfo打开发现是32位程序
分析如图,先生成随机字符串(我们可以通过Hook
encodeFromJni_12
函数来查看生成的随机字符串的值),然后把生成的随机字符串丢到encodeFromJni_12
里面处理
我们先Hook一下encodeFromJni_12
函数,查看加密前的字符串和加密后的字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
// 定义一个名为hook_js的函数
function
hook_js() {
// 使用Java.perform方法执行函数体
Java.perform(
function
() {
// 定义变量enrandomcode并使用Java.use方法获取com.kanxue.algorithmbase.MainActivity类
var
enrandomcode = Java.use(
"com.kanxue.algorithmbase.MainActivity"
)
// 如果enrandomcode已定义
if
(enrandomcode != undefined) {
// 打印开始JAVA层Hook
console.log(
"开始JAVA层Hook"
);
// 重写encodeFromJni_12方法的实现
enrandomcode.encodeFromJni_12.implementation =
function
(intput) {
// 调用原始的encodeFromJni_12方法,并将结果赋值给变量res
var
res =
this
.encodeFromJni_12(intput);
// 打印传入参数的值
console.log(
"传入参数是====>"
+ intput);
// 打印加密后的参数的值
console.log(
"加密后的参数是====>"
+ res);
// 返回加密后的结果
return
res;
}
}
})
}
|
结果如下
1
2
|
传入参数是
=
=
=
=
>xVjHx
-
D&nji8i
*
rBZ)j
加密后的参数是
=
=
=
=
>`!^fZFrxI^q~[
+
\~p<yT
#p~F==
|
加密的结果看起来很像是Base64
,我们用64编码后的结果对比一下
不是64编码,但是结果很像。猜测可能是换了码表或并非完全的base64
加密
打开IDA
,来到加密函数Java_com_kanxue_algorithmbase_MainActivity_encodeFromJni_112(int a1, int a2, int a3, char *a4)
里面 进行分析
从下往上回溯,定位到sub_8B04
函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
|
// 定义一个名为sub_8B04的函数,参数为a1、a2和a3,返回值为_BYTE类型的指针
_BYTE *__fastcall sub_8B04(
int
a1,
int
a2,
int
a3)
{
int
v3;
// r12
int
v6;
// r6
int
v7;
// r5
_BYTE *v8;
// r3
int
v9;
// r2
unsigned
int
v10;
// r0
char
v11;
// r4
int
v12;
// r6
char
v13;
// r1
// 计算v3的值
v3 = a3 - 2;
v6 = 0;
v7 = 0;
// 进入while循环,直到v7 >= v3
while
( 1 )
{
// 获取a1+v6处的_BYTE类型指针
v8 = (_BYTE *)(a1 + v6);
if
( v7 >= v3 )
break
;
// 计算v9的值
v9 = a2 + v7;
// v6加上4
v6 += 4;
// 将aAyzpq23ijrtffg[*(unsigned __int8 *)(a2 + v7) >> 2]的值赋给v8指向的地址
*v8 = aAyzpq23ijrtffg[*(unsigned
__int8
*)(a2 + v7) >> 2];
// 计算v10的值
v10 = *(unsigned
__int8
*)(a2 + v7 + 1);
// 获取a2+v7处的值
v11 = *(_BYTE *)(a2 + v7);
// v7加上3
v7 += 3;
// 将aAyzpq23ijrtffg[(v10 >> 4) & 0xFFFFFFCF | (16 * (v11 & 3))]的值赋给v8指向的地址+1处
v8[1] = aAyzpq23ijrtffg[(v10 >> 4) & 0xFFFFFFCF | (16 * (v11 & 3))];
// 将aAyzpq23ijrtffg[(*(unsigned __int8 *)(v9 + 2) >> 6) & 0xFFFFFFC3 | (4 * (*(_BYTE *)(v9 + 1) & 0xF))]的值赋给v8指向的地址+2处
v8[2] = aAyzpq23ijrtffg[(*(unsigned
__int8
*)(v9 + 2) >> 6) & 0xFFFFFFC3 | (4 * (*(_BYTE *)(v9 + 1) & 0xF))];
// 将*(_BYTE *)(v9 + 2) & 0x3F的值赋给v8指向的地址+3处
v8[3] = aAyzpq23ijrtffg[*(_BYTE *)(v9 + 2) & 0x3F];
}
// 如果v7 < a3,执行以下代码
if
( v7 < a3 )
{
// 将aAyzpq23ijrtffg[*(unsigned __int8 *)(a2 + v7) >> 2]的值赋给v8指向的地址
*v8 = aAyzpq23ijrtffg[*(unsigned
__int8
*)(a2 + v7) >> 2];
// 计算v12的值
v12 = (16 * *(unsigned
__int8
*)(a2 + v7)) & 0x30;
// 如果a3 - 1 == v7,执行以下代码
if
( a3 - 1 == v7 )
{
v13 = 61;
// 将aAyzpq23ijrtffg[v12]的值赋给v8指向的地址+1处
v8[1] = aAyzpq23ijrtffg[v12];
}
else
{
// 将aAyzpq23ijrtffg[v12 | (*(unsigned __int8 *)(a2 + v7 + 1) >> 4)]的值赋给v8指向的地址+1处
v8[1] = aAyzpq23ijrtffg[v12 | (*(unsigned
__int8
*)(a2 + v7 + 1) >> 4)];
// 将aAyzpq23ijrtffg[4 * (*(_BYTE *)(a2 + v7 + 1) & 0xF)]的值赋给v13
v13 = aAyzpq23ijrtffg[4 * (*(_BYTE *)(a2 + v7 + 1) & 0xF)];
}
// 将61的值赋给v8指向的地址+3处
v8[3] = 61;
// 将v13的值赋给v8指向的地址+2处
v8[2] = v13;
// v8加上4
v8 += 4;
}
// 将0赋给v8指向的地址
*v8 = 0;
// 返回&v8[-a1 + 1]的值
return
&v8[-a1 + 1];
}
|
进行hook
这里有个坑,就是在使用拦截器对sub_8B04
位置进行定位,如果使用var sub_8B04 = base_address.add(0x8B04);
拦截出错就加一
这样才能进入函数里面
1
|
var
sub_8B04 = base_address.add(0x8B04+1);
|
hook代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
function
hook_native(){
Java.perform(
function
(){
var
base_address = Module.getBaseAddress(
"libnative-lib.so"
);
if
(base_address !=undefined){
console.log(
"开始native层hook"
);
//定位拦截位置
var
sub_8B04 = base_address.add(0x8B04+1);
console.log(
"sub_8b04:"
,sub_8B04)
if
(sub_8B04!=undefined){
console.log(
"进入native层0x8B04"
);
}
else
{
console.log(
"拦截器加载失败"
);
}
//使用拦截器
Interceptor.attach(sub_8B04,{
//开始进入函数,传入了三个参数依次打印
onEnter:
function
(args){
//保存参数
this
.arg0 = args[0];
this
.arg1 = args[1];
this
.arg2 = args[2];
console.log(
"========>ENTER<========"
);
console.log(
"sub_8B04未初始化时第一个参数是===>"
+
this
.arg0.readCString());
console.log(
"sub_8B04未初始化时第二个参数是===>"
+
this
.arg1.readCString());
console.log(
"sub_8B04未初始化时第三个参数是===>"
+
this
.arg2.readCString());
},
onLeave:
function
(result){
console.log(
"========>LEAVE<========"
);
console.log(
"sub_8B04已经初始化时第一个参数是===>"
+
this
.arg0.readCString());
}
}
)
}
})
}
|
hook显示的结果如下
1
2
3
4
|
sub_8B04未初始化时第一个参数是
=
=
=
>
sub_8B04未初始化时第二个参数是
=
=
=
>xVjHx
-
D&nji8i
*
rBZ)j
sub_8B04未初始化时第三个参数是
=
=
=
>
0x13
(传入参数的字符串大小)
sub_8B04已经初始化时第一个参数是
=
=
=
>`!^fZFrxI^q~[
+
\~p<yT
#p~F==
|
使用IDA
对libnative-lib.so
进行的时候会发现 aAyzpq23ijrtffg
字符串和我们之前记录的字符串(aAyzpq23ijrtffg DCB "AYZpq23IJrTFfghijklCDE1KLMmBdestU5678GHz0cuvwabN9+/VWXnoOPQRSxy4",0
)并不相同,结合前面的思考,换了码表可能性更大
我们开始hook
aAyzpq23ijrtffg
码表(0x1B000
),这个有个hook
技巧: 复制使用readCString
打印字符串 可能会造成数据丢失,所以我们使用hexdump
(16进制)显示码表的内容会更精确
hook代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
function
hook_native_sub_8B04(){
Java.perform(
function
(){
var
base_address = Module.getBaseAddress(
"libnative-lib.so"
);
if
(base_address !=undefined){
console.log(
"开始native层hook"
);
//定位拦截位置
var
sub_8B04 = base_address.add(0x8B04+1);
if
(sub_8B04){
console.log(
"进入native层0x8B04"
);
}
else
{
console.log(
"拦截器加载失败"
);
}
//使用拦截器
Interceptor.attach(sub_8B04,{
//开始进入函数,传入了三个参数依次打印
onEnter:
function
(args){
//保存第一个参数
this
.arg0 = args[0];
this
.arg1 = args[1];
this
.arg2 = args[2];
console.log(
"========>ENTER<========"
);
// console.log("sub_8B04未初始化时第一个参数是===>"+hexdump(this.arg0,{length:32,header:false}));
// console.log("sub_8B04未初始化时第一个参数是===>"+hexdump(this.arg1,{length:32,header:false}));
//console.log("sub_8B04未初始化时第一个参数是===>"+hexdump(this.arg2,{length:32,header:false}));
var
hook_aAyzpq23ijrtffg = base_address.add(0x1B000);
if
(hook_aAyzpq23ijrtffg!=undefined){console.log(
"aAyzpq23ijrtffg进入8B04的形状=====>"
+hexdump(hook_aAyzpq23ijrtffg))};
},
onLeave:
function
(result){
console.log(
"========>LEAVE<========"
);
console.log(
"sub_8B04第一个参数处理后======>"
+hexdump(
this
.arg0));
//打印处理后的第一个参数
//console.log("sub_8B04得到的结果是====>"+result.readCString());
// console.log("sub_8B04处理后第一个参数是===>"+hexdump(this.arg0,{length:32,header:false}));
console.log(
"sub_8B04处理后时第一个参数是===>"
+result);
// console.log("sub_8B04处理后时第一个参数是===>"+hexdump(this.arg2,{length:32,header:false}));
}
})
}
})
}
|
码表果然发生改变
看看是不是只是简单换了码表,其他地方有没有进行加密
是的,和猜测的结果完全一致!
再来看看码表是在哪个位置发生改变的,在sub_8ABC
找到码表发生变化的位置,并且发现传参(a1)就是加密前的字符串被int强转
完整的hook代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
|
function
hook_js(){
Java.perform(
function
(){
var
enrandomcode = Java.use(
"com.kanxue.algorithmbase.MainActivity"
)
//如果定义好了
if
(enrandomcode !=undefined)
{
console.log(
"开始JAVA层Hook"
);
enrandomcode.encodeFromJni_12.implementation =
function
(intput){
var
res =
this
.encodeFromJni_12(intput);
console.log(
"传入参数是====>"
+intput);
console.log(
"加密后的参数是====>"
+res);
return
res;
}
}
})
}
function
hook_native(){
Java.perform(
function
(){
var
base_address = Module.getBaseAddress(
"libnative-lib.so"
);
if
(base_address !=undefined){
console.log(
"开始native层hook"
);
//定位拦截位置
var
sub_8B04 = base_address.add(0x8B04+1);
console.log(
"sub_8b04:"
,sub_8B04)
if
(sub_8B04){
console.log(
"进入native层0x8B04"
);
}
else
{
console.log(
"拦截器加载失败"
);
}
//使用拦截器
Interceptor.attach(sub_8B04,{
//开始进入函数,传入了三个参数依次打印
onEnter:
function
(args){
//保存第一个参数
this
.arg0 = args[0];
this
.arg1 = args[1];
this
.arg2 = args[2];
console.log(
"========>ENTER<========"
);
// console.log("sub_8B04未初始化时第一个参数是===>"+this.arg0.readCString());
// console.log("sub_8B04未初始化时第二个参数是===>"+this.arg1.readCString());
//console.log("sub_8B04未初始化时第三个参数是===>"+hexdump(this.arg2,{length:64,header:false}));
var
hook_aAyzpq23ijrtffg = base_address.add(0x1B000);
if
(hook_aAyzpq23ijrtffg!=undefined){console.log(
"aAyzpq23ijrtffg进入8B04的形状=====>"
+hook_aAyzpq23ijrtffg.readCString().length())};
},
onLeave:
function
(result){
console.log(
"========>LEAVE<========"
);
// console.log("sub_8B04已经初始化时第一个参数是===>"+hexdump(this.arg0,{length:64,header:false}));
// console.log("sub_8B04已经初始化时第二个参数是===>"+hexdump(this.arg1,{length:64,header:false}));
//console.log("sub_8B04已经初始化时第三个参数是===>"+hexdump(this.arg2,{length:64,header:false}));
}
}
)
}
})
}
function
hookencodeFromJni(){
Java.perform(
function
(){
var
base_address = Module.getBaseAddress(
"libnative-lib.so"
);
var
base_address_method = Module.findExportByName(
"libnative-lib.so"
,
"Java_com_kanxue_algorithmbase_MainActivity_encodeFromJni_112"
)
if
(base_address_method!=undefined)
{
console.log(
"=======>进入初始界面进行Hook<======="
);
//使用拦截器
Interceptor.attach(base_address_method,{
onEnter:
function
(args){
//打印我们想要的param1和param2
// console.log("args[0]===>"+args[0]);
// console.log("args[1]===>"+args[1]);
// console.log("args[2]===>"+args[2]);
// console.log("args[3]===>"+args[3]);
var
hook_aAyzpq23ijrtffghijklmn = base_address.add(0x1B000+1);
console.log(
"aAyzpq23ijrtffghijklmn在导出函数里面=====>"
+hook_aAyzpq23ijrtffghijklmn.readCString());
},
onLeave:
function
(nreval){
nreval.replace(0);
console.log(nreval);
}
})
}
})
}
function
hook_native_sub_8B04(){
Java.perform(
function
(){
var
base_address = Module.getBaseAddress(
"libnative-lib.so"
);
if
(base_address !=undefined){
console.log(
"开始native层hook"
);
//定位拦截位置
var
sub_8B04 = base_address.add(0x8B04+1);
if
(sub_8B04){
console.log(
"进入native层0x8B04"
);
}
else
{
console.log(
"拦截器加载失败"
);
}
//使用拦截器
Interceptor.attach(sub_8B04,{
//开始进入函数,传入了三个参数依次打印
onEnter:
function
(args){
//保存第一个参数
this
.arg0 = args[0];
this
.arg1 = args[1];
this
.arg2 = args[2];
console.log(
"========>ENTER<========"
);
// console.log("sub_8B04未初始化时第一个参数是===>"+hexdump(this.arg0,{length:32,header:false}));
// console.log("sub_8B04未初始化时第一个参数是===>"+hexdump(this.arg1,{length:32,header:false}));
//console.log("sub_8B04未初始化时第一个参数是===>"+hexdump(this.arg2,{length:32,header:false}));
var
hook_aAyzpq23ijrtffg = base_address.add(0x1B000);
if
(hook_aAyzpq23ijrtffg!=undefined){console.log(
"aAyzpq23ijrtffg进入8B04的形状=====>"
+hexdump(hook_aAyzpq23ijrtffg))};
},
onLeave:
function
(result){
console.log(
"========>LEAVE<========"
);
console.log(
"sub_8B04第一个参数处理后======>"
+hexdump(
this
.arg0));
//打印处理后的第一个参数
//console.log("sub_8B04得到的结果是====>"+result.readCString());
// console.log("sub_8B04处理后第一个参数是===>"+hexdump(this.arg0,{length:32,header:false}));
console.log(
"sub_8B04处理后时第一个参数是===>"
+result);
// console.log("sub_8B04处理后时第一个参数是===>"+hexdump(this.arg2,{length:32,header:false}));
}
})
}
})
}
function
main(){
hook_js();
hook_native_sub_8B04()
hook_native();
hookencodeFromJni()
}
setImmediate(main);
|
使用C++还原加密流程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
|
#include <iostream>
#include <memory>
#include <Windows.h>
#include <minwindef.h>
#include <rpcndr.h>
#include <intsafe.h>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#define _DWORD DWORD
using
namespace
std;
DWORD
v16[2] = { 1, 2 };
// 将数组的所有元素初始化为0
string Hex2Ascii(string input) {
std::stringstream ss(input);
std::string token;
std::vector<
char
> characters;
while
(std::getline(ss, token,
' '
)) {
int
ascii = std::stoi(token, nullptr, 16);
char
c =
static_cast
<
char
>(ascii);
characters.push_back(c);
}
std::string result(characters.begin(), characters.end());
return
result;
}
unsigned
char
* sub_8B04(
int
a1,
int
a2,
int
a3)
{
int
v3 =0;
// r12
int
v6 =0;
// r6
int
v7 =0;
// r5
unsigned
char
* v8 =
new
unsigned
char
[100];
// r3
unsigned
char
* address = v8;
//保存v8的首地址
memset
(v8,
'\0'
, 100);
// 将 v8 的所有元素都设置为 '1'
int
v9 =0;
// r2
unsigned
int
v10 =0;
// r0
char
v11 =
'\0'
;
// r4
int
v12 =0;
// r6
char
v13 =
'\0'
;
// r1
std::string input =
"52 4a 49 63 62 21 20 5a 59 61 47 55 75 74 7b 7a 79 78 7f 50 57 56 22 58 5f 5e 7e 51 77 76 60 67 46 26 25 24 2b 54 5b 69 23 70 66 65 64 72 71 5d 2a 38 3c 45 44 4b 7d 7c 5c 43 42 41 40 6b 6a 27"
;
string aAyzpq23ijrtffg = Hex2Ascii(input);
v3 = a3 - 2;
v6 = 0;
v7 = 0;
while
(1)
{
v8 = (unsigned
__int8
*)(address + v6);
if
(v7 >= v3)
break
;
v9 = a2 + v7;
//开始出现a2(传进来的参数)
v6 += 4;
*v8 = aAyzpq23ijrtffg[*(unsigned
__int8
*)(a2 + v7) >> 2];
v10 = *(unsigned
__int8
*)(a2 + v7 + 1);
v11 = *(unsigned
char
*)(a2 + v7);
v7 += 3;
v8[1] = aAyzpq23ijrtffg[(v10 >> 4) & 0xFFFFFFCF | (16 * (v11 & 3))];
v8[2] = aAyzpq23ijrtffg[(*(unsigned
__int8
*)(v9 + 2) >> 6) & 0xFFFFFFC3 | (4 * (*(unsigned
char
*)(v9 + 1) & 0xF))];
v8[3] = aAyzpq23ijrtffg[*(unsigned
char
*)(v9 + 2) & 0x3F];
}
if
(v7 < a3)
{
*v8 = aAyzpq23ijrtffg[*(unsigned
__int8
*)(a2 + v7) >> 2];
v12 = (16 * *(unsigned
__int8
*)(a2 + v7)) & 0x30;
if
(a3 - 1 == v7)
{
v13 = 61;
v8[1] = aAyzpq23ijrtffg[v12];
}
else
{
v8[1] = aAyzpq23ijrtffg[v12 | (*(unsigned
__int8
*)(a2 + v7 + 1) >> 4)];
v13 = aAyzpq23ijrtffg[4 * (*(unsigned
__int8
*)(a2 + v7 + 1) & 0xF)];
}
v8[2] = v13;
v8[3] = 61;
v8 += 4;
}
*v8 = 0;
return
&v8[-a1 + 1];
}
__int64
sub_882C(
__int64
result)
{
int
i;
// r2
result &= 0xFFFFFFFFFFFFFFFFULL;
// 将result的低64位保留,高64位设置为0
for
(i = 0; i != 3; ++i)
*(_DWORD*)(result + 4 * i) = 0;
return
result;
}
DWORD
* sub_8740(
__int64
a1)
{
int
v1=0;
// r4
v1 = a1;
*(_DWORD*)a1 = 0;
*(_DWORD*)(a1 + 4) = 0;
*(_DWORD*)(a1 + 8) = 0;
sub_882C(a1);
return
(_DWORD*)v1;
}
int
main()
{
char
str1[] =
"xVjHx-D&nji8i*rBZ)j"
;
string str(str1);
int
n = str.length();
int
param2 = 0;
char
* v17=nullptr;
v17 = (
char
*)param2;
unsigned
char
* str2 = sub_8B04(
int
(
""
), (
int
)str1, n);
}
|
使用JS对加密算法进行还原
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
|
function
Hex2Ascii(string){
var
hexArray = string.split(
" "
);
// 拆分字符串为数组
var
asciiArray = hexArray.map(
function
(hex) {
var
decimal = parseInt(hex, 16);
// 将16进制数转换为10进制数
return
String.fromCharCode(decimal);
// 将10进制数转换为对应的ASCII字符
});
var
result = asciiArray.join(
""
);
// 将字符数组合并为一个字符串
return
result;
}
function
stringToUint8Array(str){
var
arr = [];
for
(
var
i=0;i<str.length;i++)
{
arr.push(str.charCodeAt(i));
}
var
outputbytes =
new
Uint8Array(arr);
return
outputbytes;
}
function
sub_8B04(str,len){
var
string =
"52 4a 49 63 62 21 20 5a 59 61 47 55 75 74 7b 7a 79 78 7f 50 57 56 22 58 5f 5e 7e 51 77 76 60 67 46 26 25 24 2b 54 5b 69 23 70 66 65 64 72 71 5d 2a 38 3c 45 44 4b 7d 7c 5c 43 42 41 40 6b 6a 27"
var
aAyzpq23ijrtffg = Hex2Ascii(string);
//生成码表
var
v8 =
""
;
var
v3 = len-2;
var
v6 =0;
var
v7=0;
var
v9=0;
var
v12 =0;
var
a2 = stringToUint8Array(str);
while
(1){
if
(v7>=v3){
break
;
}
v9 = a2[v7];
var
v9_1 = a2[v7+1];
var
v9_2 =a2[v7+2];
v6 +=4;
v8 += aAyzpq23ijrtffg.charAt(a2[v7] >>2 );
var
v10 = a2[v7+1];
var
v11 = a2[v7];
v7 +=3;
v8 += aAyzpq23ijrtffg.charAt((v10>>4)&0xFFFFFFCF |(16* (v11 &3)));
v8 += aAyzpq23ijrtffg.charAt(((v9_2) >>6)& 0xFFFFFFC3 | 4*((v9_1)&0xF));
v8 += aAyzpq23ijrtffg[(v9_2)&0x3F]
}
if
(v7 <len){
v8+=aAyzpq23ijrtffg[a2[v7]];
v12 =(16 * a2[v7])&0x03;
if
(len -1 ==v7){
var
v13 = 61;
v8 += aAyzpq23ijrtffg[v12];
}
else
{
v8 +=aAyzpq23ijrtffg[v12 | (a2[v7+1]) >>4];
v13 = aAyzpq23ijrtffg[4*a2[v7+1] & 0xF]
}
v8 += String.fromCharCode(v13);
v8 += String.fromCharCode(61);
return
v8;
}
}
var
str =
"xVjHx-D&nji8i*rBZ)j"
;
console.log(sub_8B04(str,str.length))
|
使用python还原加密流程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
def
Hex2Ascii(string):
hex_list
=
string.split()
# 分割字符串生成包含十六进制数的列表
ascii_list
=
[]
for
hex_str
in
hex_list:
decimal
=
int
(hex_str,
16
)
# 将十六进制数转换为十进制数
ascii_char
=
chr
(decimal)
# 将十进制数转换为ASCII字符
ascii_list.append(ascii_char)
result
=
''.join(ascii_list)
# 将ASCII字符列表合并为一个字符串
return
result
def
sub_8B04(
str
,
len
):
string
=
"52 4a 49 63 62 21 20 5a 59 61 47 55 75 74 7b 7a 79 78 7f 50 57 56 22 58 5f 5e 7e 51 77 76 60 67 46 26 25 24 2b 54 5b 69 23 70 66 65 64 72 71 5d 2a 38 3c 45 44 4b 7d 7c 5c 43 42 41 40 6b 6a 27"
# 生成码表
aAyzpq23ijrtffg
=
Hex2Ascii(string)
v8
=
""
v3
=
len
-
2
v6
=
0
v7
=
0
v9
=
0
a2
=
bytes(
str
,
"utf-8"
)
while
True
:
if
v7>
=
v3:
break
v9
=
a2[v7]
v9_1
=
a2[v7
+
1
]
v9_2
=
a2[v7
+
2
]
v6
+
=
4
v8
+
=
aAyzpq23ijrtffg[a2[v7] >>
2
]
v10
=
a2[v7
+
1
]
v11
=
a2[v7]
v7
+
=
3
v8
+
=
aAyzpq23ijrtffg[(v10>>
4
)&
0xFFFFFFCF
|(
16
*
(v11 &
3
))]
v8
+
=
aAyzpq23ijrtffg[((v9_2) >>
6
)&
0xFFFFFFC3
|
4
*
((v9_1)&
0xF
)]
v8
+
=
aAyzpq23ijrtffg[(v9_2)&
0x3F
]
if
v7<
len
:
v8
+
=
aAyzpq23ijrtffg[a2[v7] >>
2
]
v12
=
(
16
*
a2[v7]) &
0x30
if
(
len
-
1
=
=
v7):
v13
=
61
v8
+
=
aAyzpq23ijrtffg[v12]
else
:
v8
+
=
aAyzpq23ijrtffg[v12 | (a2[v7
+
1
]) >>
4
]
v13
=
aAyzpq23ijrtffg[
4
*
a2[v7
+
1
] &
0xF
]
v8
+
=
chr
(v13)
v8
+
=
chr
(
61
)
return
v8
if
__name__
=
=
'__main__'
:
str1
=
"xVjHx-D&nji8i*rBZ)j"
result
=
sub_8B04(str1,
len
(str1))
print
(result)
|
algorithmbase_13.apk
使用JADX
打开apk
文件
通过调用MainActivity类中的encodeFromJni_12方法,将randomAscii字符串进行加密,并将加密后的结果存储在encodeFromJni_12字符串中。然后,使用Log.e方法将randomAscii字符串和加密后的结果一起打印出来,以便进行调试和分析。
使用IDA
打开,按照以前的思路继续从下往上分析
按照第二题的思路,来到sub_8B04
发现和上一题加密没啥区别就只是多了和参数a3
的异或
使用Frida Hook
一下传入参数和传出参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
|
function
hook_js(){
Java.perform(
function
(){
var
enrandomcode = Java.use(
"com.kanxue.algorithmbase.MainActivity"
)
//如果定义好了
if
(enrandomcode !=undefined)
{
console.log(
"开始JAVA层Hook\r\n"
);
enrandomcode.encodeFromJni_13.implementation =
function
(intput){
var
res =
this
.encodeFromJni_13(intput);
console.log(
"\n传入参数是====>"
+intput);
console.log(
"加密后的参数是====>"
+res);
return
res;
}
}
})
}
function
hook_native(){
Java.perform(
function
(){
var
base_address = Module.getBaseAddress(
"libnative-lib.so"
);
if
(base_address!=undefined)
{
console.log(
"获取到基地址"
);
var
sub_8B04=base_address.add(0x8B04+1);
//开启拦截器
Interceptor.attach(sub_8B04,{
onEnter:
function
(args){
this
.arg0 = args[0];
this
.arg1 = args[1];
this
.arg2 = args[2];
console.log(
"sub_80B4传进来的第一个参数是===>"
+
this
.arg0.readCString());
console.log(
"sub_80B4传进来的第二个参数是===>"
+
this
.arg1.readCString());
//console.log("sub_80B4传进来的第三个参数是===>"+this.arg2.readCString());
var
sub_1B000 = base_address.add(0x1B000);
if
(sub_1B000!=undefined){
console.log(
"aAyzpq23ijrtffg===>"
+ hexdump(sub_1B000));
}
},onLeave:
function
(nresult){
console.log(
"sub_80B4对传入参数处理后的结果是===>"
+nresult);
console.log(
"sub_80B4对传入参数处理后的结果是===>"
+
this
.arg0.readCString());
}
})
}
})
}
function
main(){
hook_js();
hook_native();
}
setImmediate(main);
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
[LGE Nexus
5X
::com.kanxue.algorithmbase]
-
> 开始JAVA层Hook
获取到基地址
sub_80B4传进来的第一个参数是
=
=
=
>
sub_80B4传进来的第二个参数是
=
=
=
>cPN~zk)|Kpehw7zZn;PU6Gg_kj
*
[Z
aAyzpq23ijrtffg
=
=
=
>
0
1
2
3
4
5
6
7
8
9
A B C D E F
0123456789ABCDEF
ca86a000
5c
44
47
6d
6c
2f
2e
54
57
6f
49
5b
7b
7a
75
74
\DGml
/
.TWoI[{zut
ca86a010
77
76
71
5e
59
58
2c
56
51
50
70
5f
79
78
6e
69
wvq^YX,VQPp_yxni
ca86a020
48
28
2b
2a
25
5a
55
67
2d
7e
68
6b
6a
7c
7f
53
H(
+
*
%
ZUg
-
~hkj|.S
ca86a030
24
36
32
4b
4a
45
73
72
52
4d
4c
4f
4e
65
64
29
$
62KJEsrRMLONed
)
ca86a040
00
00
00
00
fd
91
85
ca d9
92
85
ca b8
47
86
ca .............G..
ca86a050
78
a2
86
ca
00
00
00
00
00
00
00
00
00
00
00
00
x...............
ca86a060
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
................
ca86a070
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
................
ca86a080
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
................
ca86a090
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
................
ca86a0a0
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
................
ca86a0b0
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
................
ca86a0c0
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
................
ca86a0d0
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
................
ca86a0e0
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
................
ca86a0f0
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
................
sub_80B4对传入参数处理后的结果是
=
=
=
>
0x29
sub_80B4对传入参数处理后的结果是
=
=
=
>LEYutgckTV
+
[d.E
-
eKeL1UOOD
/
Dskseims0h,E
-
=
|
hook的结果更加验证了我们的思路
C++代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
|
#include <iostream>
#include <memory>
#include <Windows.h>
#include <minwindef.h>
#include <rpcndr.h>
#include <intsafe.h>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#define _DWORD DWORD
using
namespace
std;
DWORD
v16[2] = { 1, 2 };
// 将数组的所有元素初始化为0
string Hex2Ascii(string input) {
std::stringstream ss(input);
std::string token;
std::vector<
char
> characters;
while
(std::getline(ss, token,
' '
)) {
int
ascii = std::stoi(token, nullptr, 16);
char
c =
static_cast
<
char
>(ascii);
characters.push_back(c);
}
std::string result(characters.begin(), characters.end());
return
result;
}
unsigned
char
* sub_8B04(
int
a1,
int
a2,
int
a3)
{
int
v3 = 0;
// r12
int
v6 = 0;
// r6
int
v7 = 0;
// r5
unsigned
char
* v8 =
new
unsigned
char
[100];
// r3
unsigned
char
* address = v8;
//保存v8的首地址
memset
(v8,
'\0'
, 100);
// 将 v8 的所有元素都设置为 '1'
int
v9 = 0;
// r2
unsigned
int
v10 = 0;
// r0
char
v11 =
'\0'
;
// r4
int
v12 = 0;
// r6
char
v13 =
'\0'
;
// r1
std::string input =
"5c 44 47 6d 6c 2f 2e 54 57 6f 49 5b 7b 7a 75 74 77 76 71 5e 59 58 2c 56 51 50 70 5f 79 78 6e 69 48 28 2b 2a 25 5a 55 67 2d 7e 68 6b 6a 7c 7f 53 24 36 32 4b 4a 45 73 72 52 4d 4c 4f 4e 65 64 29"
;
string aAyzpq23ijrtffg = Hex2Ascii(input);
v3 = a3 - 2;
v6 = 0;
v7 = 0;
while
(1)
{
v8 = (unsigned
__int8
*)(address + v6);
if
(v7 >= v3)
break
;
v9 = a2 + v7;
//开始出现a2(传进来的参数)
v6 += 4;
*v8 = aAyzpq23ijrtffg[*(unsigned
__int8
*)(a2 + v7) >> 2] ^a3;
v10 = *(unsigned
__int8
*)(a2 + v7 + 1);
v11 = *(unsigned
char
*)(a2 + v7);
v7 += 3;
v8[1] = aAyzpq23ijrtffg[(v10 >> 4) & 0xFFFFFFCF | (16 * (v11 & 3))];
v8[2] = aAyzpq23ijrtffg[(*(unsigned
__int8
*)(v9 + 2) >> 6) & 0xFFFFFFC3 | (4 * (*(unsigned
char
*)(v9 + 1) & 0xF))] ^ a3;
v8[3] = aAyzpq23ijrtffg[*(unsigned
char
*)(v9 + 2) & 0x3F];
}
if
(v7 < a3)
{
*v8 = aAyzpq23ijrtffg[*(unsigned
__int8
*)(a2 + v7) >> 2];
v12 = (16 * *(unsigned
__int8
*)(a2 + v7)) & 0x30;
if
(a3 - 1 == v7)
{
v13 = 61;
v8[1] = aAyzpq23ijrtffg[v12];
}
else
{
v8[1] = aAyzpq23ijrtffg[v12 | (*(unsigned
__int8
*)(a2 + v7 + 1) >> 4)];
v13 = aAyzpq23ijrtffg[4 * (*(unsigned
__int8
*)(a2 + v7 + 1) & 0xF)];
}
v8[2] = v13;
v8[3] = 61;
v8 += 4;
}
*v8 = 0;
return
&v8[-a1 + 1];
}
__int64
sub_882C(
__int64
result)
{
int
i;
// r2
result &= 0xFFFFFFFFFFFFFFFFULL;
// 将result的低64位保留,高64位设置为0
for
(i = 0; i != 3; ++i)
*(_DWORD*)(result + 4 * i) = 0;
return
result;
}
DWORD
* sub_8740(
__int64
a1)
{
int
v1 = 0;
// r4
v1 = a1;
*(_DWORD*)a1 = 0;
*(_DWORD*)(a1 + 4) = 0;
*(_DWORD*)(a1 + 8) = 0;
sub_882C(a1);
return
(_DWORD*)v1;
}
int
main()
{
char
str1[] =
"cPN~zk)|Kpehw7zZn;PU6Gg_kj*[Z"
;
string str(str1);
int
n = str.length();
int
param2 = 0;
char
* v17 = nullptr;
v17 = (
char
*)param2;
unsigned
char
* str2 = sub_8B04(
int
(
""
), (
int
)str1, n);
std::cout << str2 << std::endl;
}
|
JS代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
|
function
Hex2Ascii(string){
var
hexArray = string.split(
" "
);
// 拆分字符串为数组
var
asciiArray = hexArray.map(
function
(hex) {
var
decimal = parseInt(hex, 16);
// 将16进制数转换为10进制数
return
String.fromCharCode(decimal);
// 将10进制数转换为对应的ASCII字符
});
var
result = asciiArray.join(
""
);
// 将字符数组合并为一个字符串
return
result;
}
function
stringToUint8Array(str){
var
arr = [];
for
(
var
i=0;i<str.length;i++)
{
arr.push(str.charCodeAt(i));
}
var
outputbytes =
new
Uint8Array(arr);
return
outputbytes;
}
function
sub_8B04(str,len){
var
string =
"5c 44 47 6d 6c 2f 2e 54 57 6f 49 5b 7b 7a 75 74 77 76 71 5e 59 58 2c 56 51 50 70 5f 79 78 6e 69 48 28 2b 2a 25 5a 55 67 2d 7e 68 6b 6a 7c 7f 53 24 36 32 4b 4a 45 73 72 52 4d 4c 4f 4e 65 64 29"
;
var
aAyzpq23ijrtffg = Hex2Ascii(string);
//生成码表
var
v8 =
""
;
var
v3 = len-2;
var
v6 =0;
var
v7=0;
var
v9=0;
var
v12 =0;
var
a2 = stringToUint8Array(str);
var
a3 = len;
while
(1){
if
(v7>=v3){
break
;
}
v9 = a2[v7];
var
v9_1 = a2[v7+1];
var
v9_2 =a2[v7+2];
v6 +=4;
v8 += String.fromCharCode(aAyzpq23ijrtffg.charCodeAt(a2[v7] >>2 ) ^ a3);
var
v10 = a2[v7+1];
var
v11 = a2[v7];
v7 +=3;
v8 += aAyzpq23ijrtffg.charAt((v10>>4)&0xFFFFFFCF |(16* (v11 &3))) ;
v8 += String.fromCharCode(aAyzpq23ijrtffg.charCodeAt(((v9_2) >>6)& 0xFFFFFFC3 | 4*((v9_1)&0xF)) ^ a3);
v8 += aAyzpq23ijrtffg[(v9_2)&0x3F]
}
if
(v7 <a3){
v8+=aAyzpq23ijrtffg[a2[v7] >>2];
v12 =(16 * a2[v7])& 0x30;
if
(len -1 ==v7){
var
v13 = 61;
v8 += aAyzpq23ijrtffg[v12];
}
else
{
v8 +=aAyzpq23ijrtffg[v12 | (a2[v7+1]) >>4];
v13 = aAyzpq23ijrtffg[4*(a2[v7+1] & 0xF)]
}
v8 += v13;
v8 += String.fromCharCode(61);
return
v8;
}
}
var
str =
"cPN~zk)|Kpehw7zZn;PU6Gg_kj*[Z"
;
console.log(sub_8B04(str,str.length))
|
python代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
def
Hex2Ascii(string):
hex_list
=
string.split()
# 分割字符串生成包含十六进制数的列表
ascii_list
=
[]
for
hex_str
in
hex_list:
decimal
=
int
(hex_str,
16
)
# 将十六进制数转换为十进制数
ascii_char
=
chr
(decimal)
# 将十进制数转换为ASCII字符
ascii_list.append(ascii_char)
result
=
''.join(ascii_list)
# 将ASCII字符列表合并为一个字符串
return
result
def
sub_8B04(
str
,
len
):
string
=
"5c 44 47 6d 6c 2f 2e 54 57 6f 49 5b 7b 7a 75 74 77 76 71 5e 59 58 2c 56 51 50 70 5f 79 78 6e 69 48 28 2b 2a 25 5a 55 67 2d 7e 68 6b 6a 7c 7f 53 24 36 32 4b 4a 45 73 72 52 4d 4c 4f 4e 65 64 29"
;
# 生成码表
aAyzpq23ijrtffg
=
Hex2Ascii(string)
v8
=
""
v3
=
len
-
2
v6
=
0
v7
=
0
v9
=
0
a2
=
bytes(
str
,
"utf-8"
)
a3
=
len
while
True
:
if
v7>
=
v3:
break
v9
=
a2[v7]
v9_1
=
a2[v7
+
1
]
v9_2
=
a2[v7
+
2
]
v6
+
=
4
v8
+
=
chr
(
ord
(aAyzpq23ijrtffg[a2[v7] >>
2
]) ^ a3)
v10
=
a2[v7
+
1
]
v11
=
a2[v7]
v7
+
=
3
v8
+
=
aAyzpq23ijrtffg[(v10>>
4
)&
0xFFFFFFCF
|(
16
*
(v11 &
3
))]
v8
+
=
chr
(
ord
(aAyzpq23ijrtffg[((v9_2) >>
6
)&
0xFFFFFFC3
|
4
*
((v9_1)&
0xF
)])^ a3)
v8
+
=
aAyzpq23ijrtffg[(v9_2)&
0x3F
]
if
v7<a3:
v8
+
=
aAyzpq23ijrtffg[a2[v7] >>
2
]
v12
=
(
16
*
a2[v7]) &
0x30
if
(a3
-
1
=
=
v7):
v13
=
61
v8
+
=
aAyzpq23ijrtffg[v12]
else
:
v8
+
=
aAyzpq23ijrtffg[v12 | (a2[v7
+
1
]) >>
4
]
v13
=
aAyzpq23ijrtffg[
4
*
(a2[v7
+
1
] &
0xF
)]
v8
+
=
v13
v8
+
=
chr
(
61
)
return
v8
if
__name__
=
=
'__main__'
:
str1
=
"cPN~zk)|Kpehw7zZn;PU6Gg_kj*[Z"
result
=
sub_8B04(str1,
len
(str1))
print
(result)
|
C++的函数传参是如果char数组,当使用JS
对其进行还原时候,为了方便后面字符串型参数在JS
中计算字符串偏移,最好把字符串转为8位无符号整型数组Uint8Array(对应是C++
中的(unsigned char*
))
如下
1
2
3
4
5
6
7
8
9
10
|
function
stringToUint8Array(str){
var
arr = [];
for
(
var
i=0;i<str.length;i++)
{
arr.push(str.charCodeAt(i));
}
var
outputbytes =
new
Uint8Array(arr);
return
outputbytes;
}
|
这么看不太直观。拿刚才的第三题举例
C++中
1
2
3
4
5
6
7
|
char
str1[] =
"cPN~zk)|Kpehw7zZn;PU6Gg_kj*[Z"
;
string str(str1);
int
n = str.length();
int
param2 = 0;
char
* v17 = nullptr;
v17 = (
char
*)param2;
unsigned
char
* str2 = sub_8B04(
int
(
""
), (
int
)str1, n);
|
这里面str1是char数组被强转为整型当成传参进入的sub_8B04
函数
但在JS
中,我们进入sub_8B04
就是传参str是字符串,不需要变整型
为了方便后面的字节偏移计算,我们再把字符串str 转为8位无符号整型数组
1
|
var
a2 = stringToUint8Array(str);
|
1
2
3
4
5
|
unsigned
char
* sub_8B04(
int
a1,
int
a2,
int
a3)
{
int
v7 = 0;
// r5
v9 = a2 + v7;
//开始出现a2(传进来的参数)
}
|
在JS中还原C++函数sub_8B04里面的参数V9思路:
因为a2
原先是被int强转char数组。v9是(char*
)a2数组偏移v7个字节后的参数
在前面的步骤中,a2在JS中已经被我们设置为8位无符号整型数组,所以v9=a2[v7]
1
2
3
4
|
function
sub_8B04(str,len){
var
a2 = stringToUint8Array(str);
v9 = a2[v7];
}
|
根据这个思路
使用JS还原一下C++代码中* (unsigned __int8*)(v9 + 2) 和 * (unsigned __int8*)(v9 + 2) 代码如下
1
2
|
var
v9_1 = a2[v7+1];
var
v9_2 =a2[v7+2];
|
由于JS语言的特性,不能直接把字符和数字进行直接的运算,所以要把字符转为Unicode
编码与数字进行运算后,再把得到的结果重新转为字符
需要用js的charat
charCodeAt
fromCharCode
三个函数进行转化这里面贴一下介绍
charAt
方法用于返回指定索引位置的字符。索引位置从0开始计数。语法:
string.charAt(index)
示例:
Copy
123var
str =
"Hello World"
;
console.log(str.charAt(0));
// 输出 "H"
console.log(str.charAt(6));
// 输出 "W"
charCodeAt
方法返回指定索引位置的字符的Unicode编码。索引位置从0开始计数。语法:
string.charCodeAt(index)
示例:
123var
str =
"Hello World"
;
console.log(str.charCodeAt(0));
// 输出 72
console.log(str.charCodeAt(6));
// 输出 87
fromCharCode
方法从Unicode编码创建一个字符串。语法:
String.fromCharCode(number1, number2, ... , numberX)
示例:
1console.log(String.fromCharCode(72, 101, 108, 108, 111));
// 输出 "Hello"
现在分析一下前面第三题贴的C代码
1
|
*v8 = aAyzpq23ijrtffg[*(unsigned
__int8
*)(a2 + v7) >> 2] ^a3;
|
这段C代码的含义是将变量a2和v7的和作为地址,取出该地址处的一个字节数据,将其右移两位后转换为无符号8位整数,然后使用这个值作为索引,从数组aAyzpq23ijrtffg中取出对应位置的值,与变量a3进行异或操作,并将结果赋给变量v8。
在JS中aAyzpq23ijrtffg[*(unsigned __int8*)(a2 + v7) >> 2]
这个字符要和a3进行异或,a3是整型不能和字符异或,所以我们要把先把字符转为Unicode码再和a3进行异或运算,然后转为字符串如下
1
|
v8 += String.fromCharCode(aAyzpq23ijrtffg.charCodeAt(a2[v7] >>2 ) ^ a3);
|
C++的函数传参是如果char数组,当使用python
对其进行还原时候,为了方便后面字符串型参数在python
中计算字符串偏移,最好把字符串转为bytes数组(对应是C++
中的(unsigned char*
))
如下
1
2
|
def
sub_8B04(
str
,
len
):
a2
=
bytes(
str
,
"utf-8"
)
|
1
2
3
4
5
|
unsigned
char
* sub_8B04(
int
a1,
int
a2,
int
a3)
{
int
v7 = 0;
// r5
v9 = a2 + v7;
//开始出现a2(传进来的参数)
}
|
在python中还原C++函数sub_8B04里面的参数V9思路:
因为a2
原先是被int强转char数组。v9是(char*
)a2数组偏移v7个字节后的参数
在前面的步骤中,a2在python中已经被我们设置为bytes
数组,所以v9=a2[v7]
1
|
v9
=
a2[v7];
|
根据这个思路
使用python还原一下C++代码中* (unsigned __int8*)(v9 + 2) 和 * (unsigned __int8*)(v9 + 2) 代码如下
1
2
|
v9_1
=
a2[v7
+
1
]
v9_2
=
a2[v7
+
2
]
|
由于python语言的特性,不能直接把字符和数字进行直接的运算,所以要把字符转为Unicode
编码与数字进行运算后,再把得到的结果重新转为字符
chr和ord是Python中的内置函数,用于字符和对应的Unicode编码之间的转换。
chr函数接受一个整数参数,返回对应的字符。例如:
123(
chr
(
65
))
# 输出A
(
chr
(
97
))
# 输出a
(
chr
(
8364
))
# 输出€
ord函数接受一个字符参数,返回对应的Unicode编码。例如:
123(
ord
(
'A'
))
# 输出65
(
ord
(
'a'
))
# 输出97
(
ord
(
'€'
))
# 输出8364
通过chr和ord函数,我们可以方便地在字符和对应的Unicode编码之间进行转换。
现在分析一下前面第三题贴的C代码
1
|
*v8 = aAyzpq23ijrtffg[*(unsigned
__int8
*)(a2 + v7) >> 2] ^a3;
|
这段C代码的含义是将变量a2和v7的和作为地址,取出该地址处的一个字节数据,将其右移两位后转换为无符号8位整数,然后使用这个值作为索引,从数组aAyzpq23ijrtffg中取出对应位置的值,与变量a3进行异或操作,并将结果赋给变量v8。
在python中aAyzpq23ijrtffg[*(unsigned __int8*)(a2 + v7) >> 2]
这个字符要和a3进行异或,a3是整型不能和字符异或,所以我们要把先把字符转为Unicode码再和a3进行异或运算,然后转为字符串如下
1
|
v8 += chr(ord(aAyzpq23ijrtffg[a2[v7] >>2]) ^ a3)
|
更多【Android安全-零基础算法还原01以及使用python和JS还原C++部分细节】相关视频教程:www.yxfzedu.com