最近一直在研究百度壳,发现网上这方面的资料非常少。所以我把自己做的发出来跟大家分享,共同学习进步。
下面开始:
一、init_array
我们发现init_array中存在多个函数地址,JNI_Onload为加密状态
动态调试在init_array上下断跟着进入一个大循环。发现他在此处对so进行了抹头操作。
之后遇到反调试崩溃退出。后来发现反调试检测了以下字段:
android_server
gdbserver
gdb
TracePid:
/proc/self/task/%s/status
isDebuggerConnected等。。
二、bypass
编写了一个loader程序调用该so中的JNI_Onload函数bypass壳代码和反调试。
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
|
#include <stdio.h>
#include <string.h>
#include <dlfcn.h>
#include <jni.h>
int
main()
{
JavaVM* vm;
JNIEnv* env;
jint res;
JavaVMInitArgs vm_args;
JavaVMOption options[1];
options[0].optionString =
"-Djava.class.path=."
;
vm_args.version=0x00010002;
vm_args.options=options;
vm_args.nOptions =1;
vm_args.ignoreUnrecognized=JNI_TRUE;
printf
(
"[+] dlopen libdvm.so\n"
);
void
*handle = dlopen(
"/system/lib/libdvm.so"
, RTLD_LAZY);
//RTLD_LAZY RTLD_NOW
if
(!handle){
printf
(
"[-] dlopen libdvm.so failed!!\n"
);
return
0;
}
//这里我先创建一个java虚拟机。因为JNI_ONload函数参数第一个参数为JavaVM。
typedef
int
(*JNI_CreateJavaVM_Type)(JavaVM**, JNIEnv**,
void
*);
JNI_CreateJavaVM_Type JNI_CreateJavaVM_Func = (JNI_CreateJavaVM_Type)dlsym(handle,
"JNI_CreateJavaVM"
);
if
(!JNI_CreateJavaVM_Func){
printf
(
"[-] dlsym failed\n"
);
return
0;
}
res=JNI_CreateJavaVM_Func(&vm,&env,&vm_args)
void
* si=dlopen(
"/data/local/tmp/libbaiduprotect.so"
,RTLD_LAZY);
if
(si == NULL){
printf
(
"[-] dlopen err!\n"
);
return
0;
}
typedef
jint (*FUN)(JavaVM* vm,
void
* res);
FUN func_onload=(FUN)dlsym(si,
"JNI_OnLoad"
);
if
(func_onload==NULL)
//我将断点下在了这里可以正好获取到JNI_Onload的函数地址。
return
0;
func_onload(vm,NULL);
return
0;
}
|
此时R0为dlsym返回的JNI_Onload地址.我们跳转过去,按C将字节码转成代码,发现此时JNI_Onload已经解密。
这时候我们将so从内存中dump出来。将原来的so头部和section修复回去。代码的解密已经完成。
3、字符串解密
这时候我们发现so的字符串还是加密状态,每个字符串对应一个解密函数。
经过分析发现。解密算法是一样的,只是异或的偏移地址不一样。
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
|
_BYTE *__fastcall sub_17A28(_BYTE *a1)
{
_BYTE *v1;
// r4@1
int
v2;
// r0@1
int
v3;
// r1@2
unsigned
int
v4;
// r5@3
int
v5;
// r3@3
int
v6;
// r7@3
_BYTE *v7;
// r4@4
int
v8;
// r6@4
_BYTE *v9;
// r5@4
unsigned
int
v10;
// r2@5
signed
int
v11;
// r0@5
unsigned
int
v12;
// r1@7
int
v13;
// r3@7
int
v14;
// r7@7
int
v15;
// r0@9
int
v16;
// r2@9
_BYTE *v18;
// [sp+4h] [bp-1Ch]@3
v1 = a1;
v2 = (
int
)(a1 - 1);
do
v3 = *(_BYTE *)(v2++ + 1);
while
( v3 );
v4 = (unsigned
int
)(v2 - (_DWORD)v1) >> 1;
v18 = j_j_malloc(v4 + 1);
v5 = 0;
v18[v4] = 0;
v6 = *v1;
if
( *v1 )
{
v7 = v1 + 2;
v8 = 0;
v9 = v18;
do
{
v10 = v7[~v5];
v11 = 208;
if
( v10 >= 0x3A )
v11 = 201;
v12 = (unsigned
__int8
)v6;
v13 = 16 * v6;
v14 = 16 * v6 + 144;
if
( v12 >= 0x3A )
v13 = v14;
v15 = v13 + v11 + v10;
v5 = 0;
v16 = 0;
if
( v8 != 8 )
v16 = v8;
*v9 = byte_5FB25[v16] ^ v15;
//此处byte数组值不同。
v8 = v16 + 1;
++v9;
v6 = *v7;
v7 += 2;
}
while
( v6 );
}
return
v18;
}
|
分析完解密算法后,写了个ida py解密脚本。由于初次写ida脚本。借鉴了一些网上的代码。写的很糟糕。
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
|
import
re
def
getDispatchAddress(ea):
ea
=
FindText(ea, SEARCH_DOWN |SEARCH_NEXT | SEARCH_REGEX,
0
,
0
,
"BL sub"
)
#Message("Find in %x\n" % ea)
if
ea
=
=
BADADDR:
#Message("Cann't find the Dispatch address")
address
=
BADADDR
else
:
address
=
GetOperandValue(ea,
0
)
#Message("Dispatch address is %x\n" % address)
return
address
def
getFunctionInstructions(addr):
Instructions
=
[]
DispatchBeginAddress
=
getDispatchAddress(addr)
if
DispatchBeginAddress
=
=
BADADDR:
Message(
"Cann't find the Function Instructions List"
)
return
None
DispatchEndAddress
=
GetFunctionAttr(DispatchBeginAddress,FUNCATTR_END)
i
=
DispatchBeginAddress
while
True
:
#Instructions.append(GetDisasm(i))
Instructions.append(i)
tmp
=
i
+
ItemSize(i)
if
tmp < DispatchEndAddress:
i
=
i
+
ItemSize(i)
else
:
break
address
=
i
return
Instructions
def
getaddr(addr):
for
x
in
XrefsTo(addr,flags
=
0
):
#print hex(x.frm)
num
=
0
got
=
0
for
i
in
getFunctionInstructions(x.frm):
mnem
=
GetMnem(i)
if
"LDR"
in
mnem :
if
num
=
=
5
:
#print hex(i)
op
=
GetOperandValue(i,
1
)
b3
=
Byte(op
+
3
)<<
24
b2
=
Byte(op
+
2
)<<
16
b1
=
Byte(op
+
1
)<<
8
b0
=
Byte(op
+
0
)
got
=
b0
+
b1
+
b2
+
b3
+
(i
+
6
)
if
num
=
=
6
:
#print hex(i)
op
=
GetOperandValue(i,
1
)
b3
=
Byte(op
+
3
)<<
24
b2
=
Byte(op
+
2
)<<
16
b1
=
Byte(op
+
1
)<<
8
b0
=
Byte(op
+
0
)
tmp
=
b0
+
b1
+
b2
+
b3
off
=
got
+
tmp
#print hex(off&0xffffffff)
return
off&
0xffffffff
break
num
=
num
+
1
break
def
decrypt1(addr,
str
):
len1
=
len
(
str
)>>
1
out
=
''
for
i
in
range
(
0
,len1):
v1
=
208
v2
=
16
*
ord
(
str
[
2
*
i])
if
ord
(
str
[
2
*
i
+
1
])>
=
0x3a
:
v1
=
201
if
ord
(
str
[
2
*
i])>
=
0x3a
:
v2
=
16
*
ord
(
str
[
2
*
i])
+
144
v3
=
(v1
+
v2
+
ord
(
str
[
2
*
i
+
1
]))&
0xff
out
=
out
+
chr
(Byte(addr
+
i
%
8
)^v3)
print
out
def
decrypt(addr1,
str
):
#这里就是主要的解密
addr
=
getaddr(addr1)
if
addr
=
=
None
:
return
''
len1
=
len
(
str
)>>
1
out
=
''
for
i
in
range
(
0
,len1):
v1
=
208
v2
=
16
*
ord
(
str
[
2
*
i])
if
ord
(
str
[
2
*
i
+
1
])>
=
0x3a
:
v1
=
201
if
ord
(
str
[
2
*
i])>
=
0x3a
:
v2
=
16
*
ord
(
str
[
2
*
i])
+
144
v3
=
(v1
+
v2
+
ord
(
str
[
2
*
i
+
1
]))&
0xff
out
=
out
+
chr
(Byte(addr
+
i
%
8
)^v3)
print
out
#print hex(addr1)
MakeComm(addr1,out)
for
x
in
XrefsTo(addr1,flags
=
0
):
MakeComm(x.frm,out)
def
GetAllString(addr):
str_1
=
GetString(addr,
-
1
,ASCSTR_C)
len_1
=
len
(str_1)
if
str_1 !
=
''
and
addr<
0x5f6c8
:
if
re.search(
"^[0-9A-Z]+$"
,str_1):
#print addr,str_1
decrypt(addr,str_1)
return
GetAllString(addr
+
len_1
+
1
)
else
:
decrypt(addr,str_1)
addr
=
0x5d8e8
GetAllString(addr)
|
运行之后可以看到字符串已解密成功。
之后就可以继续分析so对dex的保护过程,我目前还未分析完成。大家有兴趣的可以一起分析。分析好后我会再发一篇文章。
libbaiduprotect.so我放到附件了