一、前言
(本来已经放弃了,感谢kanxue、作者卓桐、小编的鼓励。使得能够在快结束时提交答案)
本题使用了一些android加固技术:包括
1)多种反调试
2)ollvm混淆
3)native函数多次动态注册
4) 简单的dex vmp虚拟机(advmp)
5) java层字符混淆
6) java部分函数插花指令
7)so 字符串加密(应该是ollvm实现的)
8)java字符串加密
4、将VMP大部分指令识别后,还原出加解密代码,发现输入竟然是随机的,因为其java层将输入的flag通过一些查表变换后使用了base64进行加密,但是这个base64加密的基准字符,使用了一个random随机生成的。当再次加密时这个基准又重新随机生成,所以2次输入相同的sn,base64的结果是不样的。此时整个人都不好了。以为掉了一个坑里面,这个vmp引擎是假的,再次分析程序,包括另外的一个libmyqtest.so,也没发现端倪,果断放弃。
5、作者在群里提示说
random的随机种子是固定的,我理解的是“对于正确的sn无论输入多少次,结果都应该是正确的“,看来作者的意图是第一次输入正确就算通过。再次开始分析。这里是造成多解的一个原因:第一次可以不正确,后面几次是可能正确的。
6、由于base64的基准是随机生成的,难免会有重复的,一般base64的基准字符是不重复的, 这样加密和解密才能对应。但是随机生成的基准字符有重复的造成加密和解密的结果是不一致的,因此对于重复的字符需要进行分别尝试,这也是可能造成多解的一个原因。
7、在java层对输入的flag使用了多种编码格式的转换,使得反推flag的时候花费了点时间。
二、流程分析
(一)java部分代码分析
1 java字符串解密
反编译后可以看到大部分字符串都是加密的,加密函数为:OooOO0OOli.d,为了快速定位到检测SN入口,需要将所有加密的字符串进行解密。使用C对dex文件中的加密字符串进行解密如下():
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
int
readUnsignedLeb128(unsigned
char
** pStream)
{
unsigned
char
* ptr = *pStream;
int
result = *(ptr++);
if
(result > 0x7f) {
int
cur = *(ptr++);
result = (result & 0x7f) | ((cur & 0x7f) << 7);
if
(cur > 0x7f) {
cur = *(ptr++);
result |= (cur & 0x7f) << 14;
if
(cur > 0x7f) {
cur = *(ptr++);
result |= (cur & 0x7f) << 21;
if
(cur > 0x7f) {
cur = *(ptr++);
result |= cur << 28;
}
}
}
}
*pStream = ptr;
return
result;
}
unsigned
char
* GetFileNameBuffer(unsigned
char
* fileName,
int
* size)
{
FILE
*fd;
unsigned
char
*pName;
int
sign = 0;
int
filesize;
if
(NULL == fileName)
{
return
NULL;
}
fd =
fopen
((
char
*)fileName,
"rb"
);
if
(NULL == fd)
{
return
NULL;
}
fseek
(fd, 0, SEEK_END);
filesize =
ftell
(fd);
pName = (unsigned
char
*)
malloc
(filesize + 100);
if
(NULL == pName)
{
fclose
(fd);
return
NULL;
}
memset
(pName, 0, filesize + 100);
fseek
(fd, 0, SEEK_SET);
fread
(pName, 1, filesize, fd);
fclose
(fd);
*size = filesize;
return
pName;
}
bool
WriteFileNameBuffer(
const
char
* fileName, unsigned
char
* buf,
int
size)
{
FILE
*fd;
if
(NULL == fileName)
{
return
false
;
}
unlink((
const
char
*)fileName);
fd =
fopen
((
char
*)fileName,
"wb"
);
if
(NULL == fd)
{
return
NULL;
}
fwrite
(buf, 1, (
size_t
)size, fd);
fclose
(fd);
return
true
;
}
char
dd1(
char
key)
{
if
(key <= 0x39 && key >= 0x30)
return
key - 0x30;
else
if
(key <=
'F'
&& key >=
'A'
)
{
return
key - 0x37;
}
else
{
return
-1;
}
}
bool
isnNeedDecryptString(
char
* arg8,unsigned
int
len)
{
for
(unsigned
int
i = 0; i < len; i++)
{
if
(dd1(arg8[i]) < 0)
{
return
false
;
}
}
return
true
;
}
bool
dexStringDecrypt(
char
* fileName,
char
* outFile)
{
int
size;
unsigned
char
* pbuf1 = GetFileNameBuffer((unsigned
char
*)
"fileName"
, &size);
if
(NULL == pbuf1)
return
false
;
unsigned
int
stringCnt = *(unsigned
int
*)(pbuf1 + 0x38);
unsigned
int
stringOffset = *(unsigned
int
*)(pbuf1 + 0x3c);
unsigned
int
* stringId = (unsigned
int
*)(pbuf1 + stringOffset);
for
(unsigned
int
i = 0; i < 2488; i++)
{
if
(i == 144 || i == 2145 || i == 2146 || i == 2147 || i == 2148 || i == 2308 || i == 2309 || i == 1900)
continue
;
unsigned
int
off = stringId[i];
unsigned
char
* pbuf = pbuf1 + off;
int
len = readUnsignedLeb128(&pbuf);
if
(len <3)
continue
;
if
(
false
== isnNeedDecryptString((
char
*)pbuf, len))
continue
;
char
* outString = decryptString((
char
*)pbuf, len);
if
(NULL == outString)
continue
;
printf
(
"i = %d, out = %s\n"
,i, outString);
memcpy
(pbuf, outString,
strlen
(outString));
}
WriteFileNameBuffer(outFile, (unsigned
char
*)pbuf1, size);
return
true
;
}
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
int
readUnsignedLeb128(unsigned
char
** pStream)
{
unsigned
char
* ptr = *pStream;
int
result = *(ptr++);
if
(result > 0x7f) {
int
cur = *(ptr++);
result = (result & 0x7f) | ((cur & 0x7f) << 7);
if
(cur > 0x7f) {
cur = *(ptr++);
result |= (cur & 0x7f) << 14;
if
(cur > 0x7f) {
cur = *(ptr++);
result |= (cur & 0x7f) << 21;
if
(cur > 0x7f) {
cur = *(ptr++);
result |= cur << 28;
}
}
}
}
*pStream = ptr;
return
result;
}
unsigned
char
* GetFileNameBuffer(unsigned
char
* fileName,
int
* size)
{
FILE
*fd;
unsigned
char
*pName;
int
sign = 0;
int
filesize;
if
(NULL == fileName)
{
return
NULL;
}
fd =
fopen
((
char
*)fileName,
"rb"
);
if
(NULL == fd)
{
return
NULL;
}
fseek
(fd, 0, SEEK_END);
filesize =
ftell
(fd);
pName = (unsigned
char
*)
malloc
(filesize + 100);
if
(NULL == pName)
{
fclose
(fd);
return
NULL;
}
memset
(pName, 0, filesize + 100);
fseek
(fd, 0, SEEK_SET);
fread
(pName, 1, filesize, fd);
fclose
(fd);
*size = filesize;
return
pName;
}
bool
WriteFileNameBuffer(
const
char
* fileName, unsigned
char
* buf,
int
size)
{
FILE
*fd;
if
(NULL == fileName)
{
return
false
;
}
unlink((
const
char
*)fileName);
fd =
fopen
((
char
*)fileName,
"wb"
);
if
(NULL == fd)
{
return
NULL;
}
fwrite
(buf, 1, (
size_t
)size, fd);
fclose
(fd);
return
true
;
}
char
dd1(
char
key)
{
if
(key <= 0x39 && key >= 0x30)
return
key - 0x30;
else
if
(key <=
'F'
&& key >=
'A'
)
{
return
key - 0x37;
}
else
{
return
-1;
}
}
bool
isnNeedDecryptString(
char
* arg8,unsigned
int
len)
{
for
(unsigned
int
i = 0; i < len; i++)
{
if
(dd1(arg8[i]) < 0)
{
return
false
;
}
}
return
true
;
}
bool
dexStringDecrypt(
char
* fileName,
char
* outFile)
{
int
size;
unsigned
char
* pbuf1 = GetFileNameBuffer((unsigned
char
*)
"fileName"
, &size);
if
(NULL == pbuf1)
return
false
;
unsigned
int
stringCnt = *(unsigned
int
*)(pbuf1 + 0x38);
unsigned
int
stringOffset = *(unsigned
int
*)(pbuf1 + 0x3c);
unsigned
int
* stringId = (unsigned
int
*)(pbuf1 + stringOffset);
for
(unsigned
int
i = 0; i < 2488; i++)
{
if
(i == 144 || i == 2145 || i == 2146 || i == 2147 || i == 2148 || i == 2308 || i == 2309 || i == 1900)
continue
;
unsigned
int
off = stringId[i];
unsigned
char
* pbuf = pbuf1 + off;
int
len = readUnsignedLeb128(&pbuf);
if
(len <3)
continue
;
if
(
false
== isnNeedDecryptString((
char
*)pbuf, len))
continue
;
char
* outString = decryptString((
char
*)pbuf, len);
if
(NULL == outString)
continue
;
printf
(
"i = %d, out = %s\n"
,i, outString);
memcpy
(pbuf, outString,
strlen
(outString));
}
WriteFileNameBuffer(outFile, (unsigned
char
*)pbuf1, size);
return
true
;
}
下面是部分结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
i = 117, out = No keylines defined
for
i = 193, out = Keyline index
i = 194, out = Keyframe
i = 195, out = Key error!
i = 297, out = Fragment no longer exists
for
key
i = 302, out = FLAG_REQUEST_FILTER_KEY_EVENTS
i = 476, out = CAPABILITY_CAN_FILTER_KEY_EVENTS
i = 482, out = Bad fragment at key
i = 681, out = The key is correct and the decryption begins!
i = 758, out = Result key can't be null
i = 933, out = keyframe
i = 934, out = key == null
i = 935, out = key == null || value == null
i = 941, out = intent_extra_data_key
i = 1149, out = android.arch.lifecycle.ViewModelProvider.DefaultKey:
i = 1182, out = android.support.groupKey
i = 1201, out = android.support.sortKey
i = 1241, out = action_key
i = 1397, out = resultKey
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
i = 117, out = No keylines defined
for
i = 193, out = Keyline index
i = 194, out = Keyframe
i = 195, out = Key error!
i = 297, out = Fragment no longer exists
for
key
i = 302, out = FLAG_REQUEST_FILTER_KEY_EVENTS
i = 476, out = CAPABILITY_CAN_FILTER_KEY_EVENTS
i = 482, out = Bad fragment at key
i = 681, out = The key is correct and the decryption begins!
i = 758, out = Result key can't be null
i = 933, out = keyframe
i = 934, out = key == null
i = 935, out = key == null || value == null
i = 941, out = intent_extra_data_key
i = 1149, out = android.arch.lifecycle.ViewModelProvider.DefaultKey:
i = 1182, out = android.support.groupKey
i = 1201, out = android.support.sortKey
i = 1241, out = action_key
i = 1397, out = resultKey
2 manifest分析
从manifest文件可知:
a)没有application
b)主activity为:android.support.v4.app.o000000o
c)注册了一个provider:android.support.v4.app.OO0OOOO0
因为
provider会在入口类走之前运行,因此第一个执行的类为
android.support.v4.app.OO0OOOO0,正其父类android.support.v4.app.O0OO00O的init函数中加载‘libmydvp.so’
1
System.loadLibrary(OooOO0OOli.d(
"mydvpB393F"
));
3、 android.support.v4.app.o000000o入口类
1)其init函数会加载 “libmyqtest.so
1
System.loadLibrary(OooOO0OOli.d(
"myqtestB2A433B"
));
2)后面的流程就比较复杂,而且字符混淆非常严重,可以从输入提示入手,即 "Key error!", 可以检测函数在类android.support.v4.app.O000000o中,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public
void
onClick(View arg3) {
String v1;
O0OOOOO v3;
if
(
this
.O000000o.O000oOo) {
v3 =
this
.O000000o.O0000oo0();
v1 = OooOO0OOli.d(
"The decryption has already started! Please don\'t touch it!083D1B0A2B6E101F2309083C0A4F2B205E683B4C1D201A0C276F593B6E"
);
}
else
if
(OO0o0.O00000Oo(o000000o.g_string2,
this
.O000000o.O0000O0o.getText().toString())) {
this
.O000000o.O000oOo =
true
;
Toast.makeText(
this
.O000000o.O0000oo0(), OooOO0OOli.d(
"The key is correct and the decryption begins!F3B27556F2B090A3D161F3B265F216F0E0C2806013C6E"
), 0).show();
this
.O000000o.mybutton.setText(OooOO0OOli.d(
"In decryptionD361C1D260001"
));
return
;
}
else
{
v3 =
this
.O000000o.O0000oo0();
v1 = OooOO0OOli.d(
"Key error!423D201E48"
);
}
Toast.makeText(((Context)v3), ((CharSequence)v1), 0).show();
}
可以看出OO0o0.O00000Oo(o000000o.g_string2, this.O000000o.O0000O0o.getText().toString())函数返回真即为正确。其中参数
this.O000000o.O0000O0o.getText().toString()为输入的flag。下面继续跟进函数“
OO0o0.O00000Oo
” 2)检测函数
OO0o0.O00000Oo
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
public
static
boolean O00000Oo(String randomString, String flag) {
byte[] key;
int
index = 0;
if
(TextUtils.isEmpty(((CharSequence)flag))) {
return
0;
}
int
v2 = 1;
if
((flag.startsWith(OooOO0OOli.d(
"flag{E2834"
))) && (flag.endsWith(OooOO0OOli.d(
"32"
)))) {
flag = flag.substring(flag.indexOf(123) + 1, flag.length() - 1);
}
flag.getBytes();
flag = OO0o0.O0000O0o(flag) +
""
;
System.out.println(flag);
if
(flag.startsWith(OooOO0OOli.d(
"62"
))) {
flag = flag.substring(1);
}
new
byte[0];
try
{
key = flag.getBytes(OooOO0OOli.d(
"utf-896277"
));
}
catch
(UnsupportedEncodingException v3) {
v3.printStackTrace();
}
StringBuilder v3_1 =
new
StringBuilder();
int
len = key.length;
while
(index < len) {
int
keyValue = key[index];
int
v6 = keyValue - 48;
if
(v6 > 9) {
v6 = keyValue - 65;
if
(v6 <= 25) {
v6 += 10;
}
else
if
(keyValue == 125) {
v6 = 62;
}
else
{
v6 += -61;
}
}
char
v5_1 = OO0o0.O0000OOo[v6];
v6 = v2 % 9;
if
(v6 == 0) {
v5_1 =
'+'
;
}
v3_1.append(v5_1);
if
(v6 == 0) {
keyValue = v2 / 9;
v3_1.append(flag.substring((keyValue - 1) * 9, keyValue * 9));
}
++v2;
++index;
}
return
OO0o0.O000000o(randomString, OO0o0.O00000Oo(v3_1.toString().getBytes()));
}
这部分流程比较简单
1)分析输入的sn是否是flag{xxx}格式的如果是就截取xxx。
2)调用OO0o0.O0000O0o(flag)函数。该函数实际上就是用输入的sn索引下面的表,然后转换成一个long型的字符串。
OO0o0.O0000O0o = new char[]{'d', 'c', '2', 'l', '{', '}', 'f', 'g', 'e', 'm', 'a', 'b', 'h', '0', '8', 'y'};
3)再次使用long型的字符串进行chartobyte后索引一个常量表:
OO0o0.O0000OOo = new char[]{'加', '载', '本', '件', '请', '卸', '要', '微', '软', '不', '可', '以', '来', '去', '安', 'a', '人', '7', '减', '好', 'l', '卓', '测', 'p', '试', 'p', '3', '7', '乘', '吗', 'b', '桐', 'c', 'e', '眼', 'q', '6', '4', '以', '为', '神', 'd', '无', 'f', '功', '圣', '名', '至', '己', '0', '何', '解', '忧', 'g', '唯', '有', '1', '杜', '2', '康', 'h', '}', '{'};
并且当时9的倍数是使用'+'字符代替,并且将原始的9个字符串拷贝到之后。
4) 调用
OO0o0.O00000Oo(v3_1.toString().getBytes())
这个函数实际上是个变形的base64。但是其基准字符是随机生成的,随机种子固定。部分代码如下:
1
2
3
4
5
6
7
8
9
10
11
for
(inputLen = 0; inputLen < OO0o0.seed.length; ++inputLen) {
try
{
OO0o0.seed[inputLen] = ((byte)(OO0o0.O0000OoO.nextInt() % 256));
}
catch
(Throwable v2_3) {
v2_3.printStackTrace();
}
catch
(NumberFormatException v2_1) {
v2_1.printStackTrace();
}
}
对应第一次的生成的基准如下:
1
2
3
4
5
6
unsigned
char
base64char[65] =
{ 0xa9, 0x06, 0xab, 0x48, 0x03, 0x8b, 0x08, 0xf0, 0xe5, 0x38, 0x29, 0xe9, 0x2b, 0x7e, 0x26, 0x57,
0x36, 0x75, 0xa2, 0x4d, 0x10, 0x35, 0x98, 0x3a, 0xd4, 0x63, 0x5b, 0xa3, 0x4c, 0x0e, 0xd7, 0x12,
0xdb, 0xbb, 0x1c, 0x4e, 0x9d, 0xbe, 0x1d, 0xcb, 0xcc, 0xcb, 0xb4, 0x63, 0x65, 0xcc, 0xe8, 0x46,
0x0b, 0xf4, 0x72, 0x2f, 0x2a, 0x13, 0x7d, 0xd8, 0x5e, 0x2b, 0xae, 0xb0, 0x16, 0x44, 0x63, 0xca,
0x74 };
1
System.loadLibrary(OooOO0OOli.d(
"mydvpB393F"
));
3、 android.support.v4.app.o000000o入口类
1)其init函数会加载 “libmyqtest.so
1
System.loadLibrary(OooOO0OOli.d(
"myqtestB2A433B"
));
2)后面的流程就比较复杂,而且字符混淆非常严重,可以从输入提示入手,即 "Key error!", 可以检测函数在类android.support.v4.app.O000000o中,如下:
1
System.loadLibrary(OooOO0OOli.d(
"myqtestB2A433B"
));
2)后面的流程就比较复杂,而且字符混淆非常严重,可以从输入提示入手,即 "Key error!", 可以检测函数在类android.support.v4.app.O000000o中,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public
void
onClick(View arg3) {
String v1;
O0OOOOO v3;
if
(
this
.O000000o.O000oOo) {
v3 =
this
.O000000o.O0000oo0();
v1 = OooOO0OOli.d(
"The decryption has already started! Please don\'t touch it!083D1B0A2B6E101F2309083C0A4F2B205E683B4C1D201A0C276F593B6E"
);
}
else
if
(OO0o0.O00000Oo(o000000o.g_string2,
this
.O000000o.O0000O0o.getText().toString())) {
this
.O000000o.O000oOo =
true
;
Toast.makeText(
this
.O000000o.O0000oo0(), OooOO0OOli.d(
"The key is correct and the decryption begins!F3B27556F2B090A3D161F3B265F216F0E0C2806013C6E"
), 0).show();
this
.O000000o.mybutton.setText(OooOO0OOli.d(
"In decryptionD361C1D260001"
));
return
;
}
else
{
v3 =
this
.O000000o.O0000oo0();
v1 = OooOO0OOli.d(
"Key error!423D201E48"
);
}
Toast.makeText(((Context)v3), ((CharSequence)v1), 0).show();
}
可以看出OO0o0.O00000Oo(o000000o.g_string2, this.O000000o.O0000O0o.getText().toString())函数返回真即为正确。其中参数
this.O000000o.O0000O0o.getText().toString()为输入的flag。下面继续跟进函数“
OO0o0.O00000Oo
” 2)检测函数
OO0o0.O00000Oo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public
void
onClick(View arg3) {
String v1;
O0OOOOO v3;
if
(
this
.O000000o.O000oOo) {
v3 =
this
.O000000o.O0000oo0();
v1 = OooOO0OOli.d(
"The decryption has already started! Please don\'t touch it!083D1B0A2B6E101F2309083C0A4F2B205E683B4C1D201A0C276F593B6E"
);
}
else
if
(OO0o0.O00000Oo(o000000o.g_string2,
this
.O000000o.O0000O0o.getText().toString())) {
this
.O000000o.O000oOo =
true
;
Toast.makeText(
this
.O000000o.O0000oo0(), OooOO0OOli.d(
"The key is correct and the decryption begins!F3B27556F2B090A3D161F3B265F216F0E0C2806013C6E"
), 0).show();
this
.O000000o.mybutton.setText(OooOO0OOli.d(
"In decryptionD361C1D260001"
));
return
;
}
else
{
v3 =
this
.O000000o.O0000oo0();
v1 = OooOO0OOli.d(
"Key error!423D201E48"
);
}
Toast.makeText(((Context)v3), ((CharSequence)v1), 0).show();
}
可以看出OO0o0.O00000Oo(o000000o.g_string2, this.O000000o.O0000O0o.getText().toString())函数返回真即为正确。其中参数
this.O000000o.O0000O0o.getText().toString()为输入的flag。下面继续跟进函数“
OO0o0.O00000Oo
” 2)检测函数
OO0o0.O00000Oo
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
public
static
boolean O00000Oo(String randomString, String flag) {
byte[] key;
int
index = 0;
if
(TextUtils.isEmpty(((CharSequence)flag))) {
return
0;
}
int
v2 = 1;
if
((flag.startsWith(OooOO0OOli.d(
"flag{E2834"
))) && (flag.endsWith(OooOO0OOli.d(
"32"
)))) {
flag = flag.substring(flag.indexOf(123) + 1, flag.length() - 1);
}
flag.getBytes();
flag = OO0o0.O0000O0o(flag) +
""
;
System.out.println(flag);
if
(flag.startsWith(OooOO0OOli.d(
"62"
))) {
flag = flag.substring(1);
}
new
byte[0];
try
{
key = flag.getBytes(OooOO0OOli.d(
"utf-896277"
));
}
catch
(UnsupportedEncodingException v3) {
v3.printStackTrace();
}
StringBuilder v3_1 =
new
StringBuilder();
int
len = key.length;
while
(index < len) {
int
keyValue = key[index];
int
v6 = keyValue - 48;
if
(v6 > 9) {
v6 = keyValue - 65;
if
(v6 <= 25) {
v6 += 10;
}
else
if
(keyValue == 125) {
v6 = 62;
}
else
{
v6 += -61;
}
}
char
v5_1 = OO0o0.O0000OOo[v6];
v6 = v2 % 9;
if
(v6 == 0) {
v5_1 =
'+'
;
}
v3_1.append(v5_1);
if
(v6 == 0) {
keyValue = v2 / 9;
v3_1.append(flag.substring((keyValue - 1) * 9, keyValue * 9));
}
++v2;
++index;
}
return
OO0o0.O000000o(randomString, OO0o0.O00000Oo(v3_1.toString().getBytes()));
}
这部分流程比较简单
1)分析输入的sn是否是flag{xxx}格式的如果是就截取xxx。
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
public
static
boolean O00000Oo(String randomString, String flag) {
byte[] key;
int
index = 0;
if
(TextUtils.isEmpty(((CharSequence)flag))) {
return
0;
}
int
v2 = 1;
if
((flag.startsWith(OooOO0OOli.d(
"flag{E2834"
))) && (flag.endsWith(OooOO0OOli.d(
"32"
)))) {
flag = flag.substring(flag.indexOf(123) + 1, flag.length() - 1);
}
flag.getBytes();
flag = OO0o0.O0000O0o(flag) +
""
;
System.out.println(flag);
if
(flag.startsWith(OooOO0OOli.d(
"62"
))) {
flag = flag.substring(1);
}
new
byte[0];
try
{
key = flag.getBytes(OooOO0OOli.d(
"utf-896277"
));
}
catch
(UnsupportedEncodingException v3) {
v3.printStackTrace();
}
StringBuilder v3_1 =
new
StringBuilder();
int
len = key.length;
while
(index < len) {
int
keyValue = key[index];
int
v6 = keyValue - 48;
if
(v6 > 9) {
v6 = keyValue - 65;
if
(v6 <= 25) {
v6 += 10;
}
else
if
(keyValue == 125) {
v6 = 62;
}
else
{
v6 += -61;
}
}
char
v5_1 = OO0o0.O0000OOo[v6];
v6 = v2 % 9;
if
(v6 == 0) {
v5_1 =
'+'
;
}
v3_1.append(v5_1);
if
(v6 == 0) {
keyValue = v2 / 9;
v3_1.append(flag.substring((keyValue - 1) * 9, keyValue * 9));
}
++v2;
++index;
}
return
OO0o0.O000000o(randomString, OO0o0.O00000Oo(v3_1.toString().getBytes()));
}
这部分流程比较简单
1)分析输入的sn是否是flag{xxx}格式的如果是就截取xxx。
2)调用OO0o0.O0000O0o(flag)函数。该函数实际上就是用输入的sn索引下面的表,然后转换成一个long型的字符串。
OO0o0.O0000O0o = new char[]{'d', 'c', '2', 'l', '{', '}', 'f', 'g', 'e', 'm', 'a', 'b', 'h', '0', '8', 'y'};
3)再次使用long型的字符串进行chartobyte后索引一个常量表:
OO0o0.O0000OOo = new char[]{'加', '载', '本', '件', '请', '卸', '要', '微', '软', '不', '可', '以', '来', '去', '安', 'a', '人', '7', '减', '好', 'l', '卓', '测', 'p', '试', 'p', '3', '7', '乘', '吗', 'b', '桐', 'c', 'e', '眼', 'q', '6', '4', '以', '为', '神', 'd', '无', 'f', '功', '圣', '名', '至', '己', '0', '何', '解', '忧', 'g', '唯', '有', '1', '杜', '2', '康', 'h', '}', '{'};
并且当时9的倍数是使用'+'字符代替,并且将原始的9个字符串拷贝到之后。
4) 调用
OO0o0.O00000Oo(v3_1.toString().getBytes())
这个函数实际上是个变形的base64。但是其基准字符是随机生成的,随机种子固定。部分代码如下:
1
2
3
4
5
6
7
8
9
10
11
for
(inputLen = 0; inputLen < OO0o0.seed.length; ++inputLen) {
try
{
OO0o0.seed[inputLen] = ((byte)(OO0o0.O0000OoO.nextInt() % 256));
}
catch
(Throwable v2_3) {
v2_3.printStackTrace();
}
catch
(NumberFormatException v2_1) {
v2_1.printStackTrace();
}
}
对应第一次的生成的基准如下:
1
2
3
4
5
6
unsigned
char
base64char[65] =
{ 0xa9, 0x06, 0xab, 0x48, 0x03, 0x8b, 0x08, 0xf0, 0xe5, 0x38, 0x29, 0xe9, 0x2b, 0x7e, 0x26, 0x57,
0x36, 0x75, 0xa2, 0x4d, 0x10, 0x35, 0x98, 0x3a, 0xd4, 0x63, 0x5b, 0xa3, 0x4c, 0x0e, 0xd7, 0x12,
0xdb, 0xbb, 0x1c, 0x4e, 0x9d, 0xbe, 0x1d, 0xcb, 0xcc, 0xcb, 0xb4, 0x63, 0x65, 0xcc, 0xe8, 0x46,
0x0b, 0xf4, 0x72, 0x2f, 0x2a, 0x13, 0x7d, 0xd8, 0x5e, 0x2b, 0xae, 0xb0, 0x16, 0x44, 0x63, 0xca,
0x74 };
1
2
3
4
5
6
7
8
9
10
11
for
(inputLen = 0; inputLen < OO0o0.seed.length; ++inputLen) {
try
{
OO0o0.seed[inputLen] = ((byte)(OO0o0.O0000OoO.nextInt() % 256));
}
catch
(Throwable v2_3) {
v2_3.printStackTrace();
}
catch
(NumberFormatException v2_1) {
v2_1.printStackTrace();
}
}
对应第一次的生成的基准如下:
1
2
3
4
5
6
unsigned
char
base64char[65] =
{ 0xa9, 0x06, 0xab, 0x48, 0x03, 0x8b, 0x08, 0xf0, 0xe5, 0x38, 0x29, 0xe9, 0x2b, 0x7e, 0x26, 0x57,
0x36, 0x75, 0xa2, 0x4d, 0x10, 0x35, 0x98, 0x3a, 0xd4, 0x63, 0x5b, 0xa3, 0x4c, 0x0e, 0xd7, 0x12,
0xdb, 0xbb, 0x1c, 0x4e, 0x9d, 0xbe, 0x1d, 0xcb, 0xcc, 0xcb, 0xb4, 0x63, 0x65, 0xcc, 0xe8, 0x46,
0x0b, 0xf4, 0x72, 0x2f, 0x2a, 0x13, 0x7d, 0xd8, 0x5e, 0x2b, 0xae, 0xb0, 0x16, 0x44, 0x63, 0xca,
0x74 };
1
2
3
4
5
6
unsigned
char
base64char[65] =
{ 0xa9, 0x06, 0xab, 0x48, 0x03, 0x8b, 0x08, 0xf0, 0xe5, 0x38, 0x29, 0xe9, 0x2b, 0x7e, 0x26, 0x57,
0x36, 0x75, 0xa2, 0x4d, 0x10, 0x35, 0x98, 0x3a, 0xd4, 0x63, 0x5b, 0xa3, 0x4c, 0x0e, 0xd7, 0x12,
0xdb, 0xbb, 0x1c, 0x4e, 0x9d, 0xbe, 0x1d, 0xcb, 0xcc, 0xcb, 0xb4, 0x63, 0x65, 0xcc, 0xe8, 0x46,
0x0b, 0xf4, 0x72, 0x2f, 0x2a, 0x13, 0x7d, 0xd8, 0x5e, 0x2b, 0xae, 0xb0, 0x16, 0x44, 0x63, 0xca,
0x74 };
其中最后一个字符为站位字符其数值为0x74,这里注意的是再次调用次base64函数后,这个基准数值将发生变化,重新随机生成。
其中最后一个字符为站位字符其数值为0x74,这里注意的是再次调用次base64函数后,这个基准数值将发生变化,重新随机生成。
5)调用OO0o0.O000000o(randomString, OO0o0.O00000Oo(v3_1.toString().getBytes()))
以一个随机产生的字符串(无用的),以及base64结果作为参数调用函数
OO0o0.O000000o。其返回如果为true则flag正确。
4、函数 : public static boolean O000000o(String paramString1, String paramString2)
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
public
static
boolean O000000o(String paramString1, String paramString2)
{
int
j;
try
{
paramString2 = paramString2.getBytes(OooOO0OOli.d(
"ISO-8859-1087A764158"
));
j = paramString2.length;
i = 0;
}
catch
(UnsupportedEncodingException paramString1)
{
paramString1.printStackTrace();
}
paramString1 =
new
OO0OOOO().OO0OOOO(paramString2, paramString1.getBytes());
paramString2 =
new
OO00OO();
paramString2.O000000o =
""
;
int
i = 0;
for
(;;)
{
if
(i < paramString1.length)
{
StringBuilder localStringBuilder =
new
StringBuilder();
localStringBuilder.append(paramString2.O000000o);
localStringBuilder.append(OO00OO.O00000Oo(paramString1[i]));
paramString2.O000000o = localStringBuilder.toString();
i += 1;
}
else
{
boolean
bool
=
"820e52333de3bcb42467f0a20564c145af5edbf2e923df33be21f0af159710c92cbc43f79f94ec930a7ae86021af5b3ae263369299de5436b85f297be08a032a28dc357391961ecc26931bfc97d67a5e74d8781fb4105b9afbe613a2041dd8c3"
.equals(paramString2.O000000o);
return
bool
;
}
}
while
(i < j)
{
int
k = paramString2[i];
i += 1;
}
}
从上面的函数可以知道,最后结果为“820e52333de3bcb42467f0a20564c145af5edbf2e923df33be21f0af159710c92cbc43f79f94ec930a7ae86021af5b3ae263369299de5436b85f297be08a032a28dc357391961ecc26931bfc97d67a5e74d8781fb4105b9afbe613a2041dd8c3”时flag正确。
函数开始是进行了编码转换后执行如下语句
paramString1 = new OO0OOOO().OO0OOOO(paramString2, paramString1.getBytes());
其是一个native函数,原型如下:
public native byte[] OO0OOOO(byte[] paramArrayOfByte1, byte[] paramArrayOfByte2);
这个native函数输入的是一个base64加密后的结果。将其返回的结果进行bytetochar转换后与上面的标定字符串比较相等即为正确。因此后续核心分析转为native函数
OO0OOOO。首先要定位到此函数。
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
public
static
boolean O000000o(String paramString1, String paramString2)
{
int
j;
try
{
paramString2 = paramString2.getBytes(OooOO0OOli.d(
"ISO-8859-1087A764158"
));
j = paramString2.length;
i = 0;
}
catch
(UnsupportedEncodingException paramString1)
{
paramString1.printStackTrace();
}
paramString1 =
new
OO0OOOO().OO0OOOO(paramString2, paramString1.getBytes());
paramString2 =
new
OO00OO();
paramString2.O000000o =
""
;
int
i = 0;
for
(;;)
{
if
(i < paramString1.length)
{
StringBuilder localStringBuilder =
new
StringBuilder();
localStringBuilder.append(paramString2.O000000o);
localStringBuilder.append(OO00OO.O00000Oo(paramString1[i]));
paramString2.O000000o = localStringBuilder.toString();
i += 1;
}
else
{
boolean
bool
=
"820e52333de3bcb42467f0a20564c145af5edbf2e923df33be21f0af159710c92cbc43f79f94ec930a7ae86021af5b3ae263369299de5436b85f297be08a032a28dc357391961ecc26931bfc97d67a5e74d8781fb4105b9afbe613a2041dd8c3"
.equals(paramString2.O000000o);
return
bool
;
}
}
while
(i < j)
{
int
k = paramString2[i];
i += 1;
}
}
从上面的函数可以知道,最后结果为“820e52333de3bcb42467f0a20564c145af5edbf2e923df33be21f0af159710c92cbc43f79f94ec930a7ae86021af5b3ae263369299de5436b85f297be08a032a28dc357391961ecc26931bfc97d67a5e74d8781fb4105b9afbe613a2041dd8c3”时flag正确。
函数开始是进行了编码转换后执行如下语句
paramString1 = new OO0OOOO().OO0OOOO(paramString2, paramString1.getBytes());
其是一个native函数,原型如下:
public native byte[] OO0OOOO(byte[] paramArrayOfByte1, byte[] paramArrayOfByte2);
这个native函数输入的是一个base64加密后的结果。将其返回的结果进行bytetochar转换后与上面的标定字符串比较相等即为正确。因此后续核心分析转为native函数
OO0OOOO。首先要定位到此函数。
1、初始化数组如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
init_array:000E0AD8 75 4A 01 00 DCD init_sshho+1
.init_array:000E0ADC 09 2A 02 00 DCD init_tracePidStringDecode+1
.init_array:000E0AE0 91 31 02 00 DCD init_nop+1
.init_array:000E0AE4 29 5B 07 00 DCD init_decryptString+1
.init_array:000E0AE8 E9 87 07 00 DCD init_decryptString2+1
.init_array:000E0AEC A1 5D 08 00 DCD init_decrypt3+1
.init_array:000E0AF0 F5 EF 08 00 DCD init_decrypt4+1
.init_array:000E0AF4 ED 04 09 00 DCD init_decrypt5+1
.init_array:000E0AF8 B9 05 09 00 DCD int_nop1+1
.init_array:000E0AFC E9 4A 09 00 DCD init_decrypt6+1
.init_array:000E0B00 C9 1B 01 00 DCD init_g_sspbahh1+1
.init_array:000E0B04 E9 AE 01 00 DCD init_antiDebug1_CreateThread_AndSet_g_antiDebugGlobal+1
.init_array:000E0B08 55 ED 01 00 DCD init_antiDebug2_fork+1
.init_array:000E0B0C CD 1B 01 00 DCD sub_11BCC+1
.init_array:000E0B10 A9 1C 01 00 DCD sub_11CA8+1
.init_array:000E0B14 85 1D 01 00 DCD sub_11D84+1
.init_array:000E0B18 C1 1D 01 00 DCD sub_11DC0+1
.init_array:000E0B1C D1 1D 01 00 DCD sub_11DD0+1
前面的几个函数都是解密字符串的,其中包括对native函数
OO0OOOO原型的解密。
2、反调试函数init_antiDebug1_CreateThread_AndSet_g_antiDebugGlobal(1AEE8)
由于此函数使用ollvm混淆,并且加入了一些垃圾代码使得F5失效,简单写个脚本去掉相关垃圾代码,F5即可成功。如下
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
#coding=utf-8
import
struct
from idaapi import *
from idc import *
from idautils import *
def patchNopCode(ea, endAddr, data, len):
while
ea < endAddr:
ea = find_binary(ea, SEARCH_DOWN| SEARCH_NEXT, data, radix=16)
if
BADADDR == ea:
break
patch_bytes(ea,
'\xC0\x46'
* len)
print
'ea = '
+ str(hex(ea))
ea = ea + len
def KillNop():
lajiData =
'2D E9 F0 47 BD E8 F0 47 13 E0 BD E8 F0 47 05 E0 00 F1 01 00 0A E0 1B 46 0E E0 10 E0 B1 B5 01 E0 12 46 01 E0 82 B0 FB E7 02 B0 F1 E7 A0 F1 01 00 F1 E7 2D E9 F0 47 E8 E7 BD E8 B1 40 ED E7 2D E9 F0 47 BD E8 F0 47'
nopData =
'\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46'
nopCode1 =
'B1 B5 82 B0 12 46 02 B0 00 F1 01 00 A0 F1 01 00 1B 46 BD E8 B1 40 01 F1 01 01 A1 F1 01 01'
lajiData2 =
'2D E9 F0 40 BD E8 F0 40 13 E0 BD E8 F0 47 05 E0 00 F1 01 00 0A E0 1B 46 14 E0 16 E0 B1 B5 01 E0 12 46 01 E0 82 B0 FB E7 02 B0 F1 E7 A0 F1 01 00 F1 E7 2D E9 F0 47 EF F3 00 84 10 B4 10 BC 84 F3 00 89 E2 E7 BD E8 B1 40 E7 E7 2D E9 F0 47 BD E8 F0 47 FF E7'
libBase = GetLibAddr(
'libmydvp.so'
)
startAddr = libBase
endAddr = libBase + 0x8916A
print
'startAddr= '
+ str(hex(startAddr))
print
'endAddr = '
+ str(hex(endAddr))
patchNopCode(startAddr, endAddr, lajiData, 35)
KillNop()
F5之后的代码也很多,将近2000行,只说下核心代码
1
2
3
4
5
6
7
pthread_create((pthread_t *)&g_antiDebug_thread, 0, (
void
*(*)(
void
*))antidebugThread1, 0);
pid = getpid();
sub_B7BB0(&v230);
buf_1 = std::operator|(16, 8);
sub_A83CC((
int
)&v249, buf_1);
sub_A37CC((
int
)&v250, pid);
sub_951B4(&v249, &v230);
1、函数开始创建了一个线程antidebugThread1(174CC )这个线程后面在说。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
init_array:000E0AD8 75 4A 01 00 DCD init_sshho+1
.init_array:000E0ADC 09 2A 02 00 DCD init_tracePidStringDecode+1
.init_array:000E0AE0 91 31 02 00 DCD init_nop+1
.init_array:000E0AE4 29 5B 07 00 DCD init_decryptString+1
.init_array:000E0AE8 E9 87 07 00 DCD init_decryptString2+1
.init_array:000E0AEC A1 5D 08 00 DCD init_decrypt3+1
.init_array:000E0AF0 F5 EF 08 00 DCD init_decrypt4+1
.init_array:000E0AF4 ED 04 09 00 DCD init_decrypt5+1
.init_array:000E0AF8 B9 05 09 00 DCD int_nop1+1
.init_array:000E0AFC E9 4A 09 00 DCD init_decrypt6+1
.init_array:000E0B00 C9 1B 01 00 DCD init_g_sspbahh1+1
.init_array:000E0B04 E9 AE 01 00 DCD init_antiDebug1_CreateThread_AndSet_g_antiDebugGlobal+1
.init_array:000E0B08 55 ED 01 00 DCD init_antiDebug2_fork+1
.init_array:000E0B0C CD 1B 01 00 DCD sub_11BCC+1
.init_array:000E0B10 A9 1C 01 00 DCD sub_11CA8+1
.init_array:000E0B14 85 1D 01 00 DCD sub_11D84+1
.init_array:000E0B18 C1 1D 01 00 DCD sub_11DC0+1
.init_array:000E0B1C D1 1D 01 00 DCD sub_11DD0+1
前面的几个函数都是解密字符串的,其中包括对native函数
OO0OOOO原型的解密。
2、反调试函数init_antiDebug1_CreateThread_AndSet_g_antiDebugGlobal(1AEE8)
由于此函数使用ollvm混淆,并且加入了一些垃圾代码使得F5失效,简单写个脚本去掉相关垃圾代码,F5即可成功。如下
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
#coding=utf-8
import
struct
from idaapi import *
from idc import *
from idautils import *
def patchNopCode(ea, endAddr, data, len):
while
ea < endAddr:
ea = find_binary(ea, SEARCH_DOWN| SEARCH_NEXT, data, radix=16)
if
BADADDR == ea:
break
patch_bytes(ea,
'\xC0\x46'
* len)
print
'ea = '
+ str(hex(ea))
ea = ea + len
def KillNop():
lajiData =
'2D E9 F0 47 BD E8 F0 47 13 E0 BD E8 F0 47 05 E0 00 F1 01 00 0A E0 1B 46 0E E0 10 E0 B1 B5 01 E0 12 46 01 E0 82 B0 FB E7 02 B0 F1 E7 A0 F1 01 00 F1 E7 2D E9 F0 47 E8 E7 BD E8 B1 40 ED E7 2D E9 F0 47 BD E8 F0 47'
nopData =
'\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46'
nopCode1 =
'B1 B5 82 B0 12 46 02 B0 00 F1 01 00 A0 F1 01 00 1B 46 BD E8 B1 40 01 F1 01 01 A1 F1 01 01'
lajiData2 =
'2D E9 F0 40 BD E8 F0 40 13 E0 BD E8 F0 47 05 E0 00 F1 01 00 0A E0 1B 46 14 E0 16 E0 B1 B5 01 E0 12 46 01 E0 82 B0 FB E7 02 B0 F1 E7 A0 F1 01 00 F1 E7 2D E9 F0 47 EF F3 00 84 10 B4 10 BC 84 F3 00 89 E2 E7 BD E8 B1 40 E7 E7 2D E9 F0 47 BD E8 F0 47 FF E7'
libBase = GetLibAddr(
'libmydvp.so'
)
startAddr = libBase
endAddr = libBase + 0x8916A
print
'startAddr= '
+ str(hex(startAddr))
print
'endAddr = '
+ str(hex(endAddr))
patchNopCode(startAddr, endAddr, lajiData, 35)
KillNop()
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
#coding=utf-8
import
struct
from idaapi import *
from idc import *
from idautils import *
def patchNopCode(ea, endAddr, data, len):
while
ea < endAddr:
ea = find_binary(ea, SEARCH_DOWN| SEARCH_NEXT, data, radix=16)
if
BADADDR == ea:
break
patch_bytes(ea,
'\xC0\x46'
* len)
print
'ea = '
+ str(hex(ea))
ea = ea + len
def KillNop():
lajiData =
'2D E9 F0 47 BD E8 F0 47 13 E0 BD E8 F0 47 05 E0 00 F1 01 00 0A E0 1B 46 0E E0 10 E0 B1 B5 01 E0 12 46 01 E0 82 B0 FB E7 02 B0 F1 E7 A0 F1 01 00 F1 E7 2D E9 F0 47 E8 E7 BD E8 B1 40 ED E7 2D E9 F0 47 BD E8 F0 47'
nopData =
'\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46'
nopCode1 =
'B1 B5 82 B0 12 46 02 B0 00 F1 01 00 A0 F1 01 00 1B 46 BD E8 B1 40 01 F1 01 01 A1 F1 01 01'
lajiData2 =
'2D E9 F0 40 BD E8 F0 40 13 E0 BD E8 F0 47 05 E0 00 F1 01 00 0A E0 1B 46 14 E0 16 E0 B1 B5 01 E0 12 46 01 E0 82 B0 FB E7 02 B0 F1 E7 A0 F1 01 00 F1 E7 2D E9 F0 47 EF F3 00 84 10 B4 10 BC 84 F3 00 89 E2 E7 BD E8 B1 40 E7 E7 2D E9 F0 47 BD E8 F0 47 FF E7'
libBase = GetLibAddr(
'libmydvp.so'
)
startAddr = libBase
endAddr = libBase + 0x8916A
print
'startAddr= '
+ str(hex(startAddr))
print
'endAddr = '
+ str(hex(endAddr))
patchNopCode(startAddr, endAddr, lajiData, 35)
KillNop()
F5之后的代码也很多,将近2000行,只说下核心代码
1
2
3
4
5
6
7
pthread_create((pthread_t *)&g_antiDebug_thread, 0, (
void
*(*)(
void
*))antidebugThread1, 0);
pid = getpid();
sub_B7BB0(&v230);
buf_1 = std::operator|(16, 8);
sub_A83CC((
int
)&v249, buf_1);
sub_A37CC((
int
)&v250, pid);
sub_951B4(&v249, &v230);
1
2
3
4
5
6
7
pthread_create((pthread_t *)&g_antiDebug_thread, 0, (
void
*(*)(
void
*))antidebugThread1, 0);
pid = getpid();
sub_B7BB0(&v230);
buf_1 = std::operator|(16, 8);
sub_A83CC((
int
)&v249, buf_1);
sub_A37CC((
int
)&v250, pid);
sub_951B4(&v249, &v230);
2、然后剩下的将近1800多行就是个tracepid反调试。
3、如果tracepid的值不为0 ,则执行如下代码:
1
2
3
4
5
6
if
( v161 != -942489079 )
break
;
LODWORD(v181) = fd;
HIDWORD(v181) = pid;
*(_QWORD *)&g_antiDebug_Global = v181;
v161 = -1048192996;
如果在调试状态下 全局变量g_antiDebug_Global(E3550)将被设置为文件句柄值,在
antidebugThread1会访问在这个值,实际上其应该是一个类似于jvm,env的指针,被设置成文件句柄后,会使程序访问其时发生异常崩溃。知道这些后过掉次函数反调试就比较容易了。(后面会有一个过掉所有反调试的脚步)
1
2
3
4
5
6
if
( v161 != -942489079 )
break
;
LODWORD(v181) = fd;
HIDWORD(v181) = pid;
*(_QWORD *)&g_antiDebug_Global = v181;
v161 = -1048192996;
如果在调试状态下 全局变量g_antiDebug_Global(E3550)将被设置为文件句柄值,在
antidebugThread1会访问在这个值,实际上其应该是一个类似于jvm,env的指针,被设置成文件句柄后,会使程序访问其时发生异常崩溃。知道这些后过掉次函数反调试就比较容易了。(后面会有一个过掉所有反调试的脚步)
3、反调试线程
antidebugThread1(174CC)
这个函数看上去依然很大(2000多行),起始代码也很简单,贴一下核心代码。
1)其会检索
g_antiDebug_Global(E3550)如果其值为0,则调用usleep随眠。
2)如果不为0,而是之前反调试的结果为文件句柄,将发生异常程序退出。
3)如果不为0,而是一个合法的值,执行如下代码:
nativeRegisterFlag = __PAIR__(realNativeFun, decryptString50((int)&v294));
(*(void (**)(void))(*(_DWORD *)((unsigned int)&dword_E3554 & ~dword_E3554 | dword_E3554 & ~(unsigned int)&dword_E3554)
+ 860))();
1)其会检索
g_antiDebug_Global(E3550)如果其值为0,则调用usleep随眠。
2)如果不为0,而是之前反调试的结果为文件句柄,将发生异常程序退出。
3)如果不为0,而是一个合法的值,执行如下代码:
nativeRegisterFlag = __PAIR__(realNativeFun, decryptString50((int)&v294));
(*(void (**)(void))(*(_DWORD *)((unsigned int)&dword_E3554 & ~dword_E3554 | dword_E3554 & ~(unsigned int)&dword_E3554)
+ 860))();
其中
realNativeFun(14A9C)实际上就是之前我们要寻找的native函数。但是由于
g_antiDebug_Global = 0,所以次部分代码没有执行。
4)剩下其他1800多号代码又是个tracepid反调试功能。与
init_antiDebug1_CreateThread_AndSet_g_antiDebugGlobal类似过掉方法也一样。
4、初始化函数init_antiDebug2_fork(1ED54)
核心代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
newthread = &v410;
haystack = (
char
*)&v409;
v417 = (
char
*)&v409;
v416 = (
char
*)&v406;
v413 = &v405;
v421 = &v404;
v418 = (
char
*)&v404;
needle = (
char
*)&v403;
v419 = &v402;
v426 = &v401;
v410 = pthread_self();
pipe(&g_pipe);
antidebugptraceFork();
v392 = pthread_create(newthread, 0, (
void
*(*)(
void
*))sub_1D404, 0);
v393 = &v411;
v394 = &v409;
v395 = &savedregs;
v396 = -1357197669;
v22 = getpid();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
newthread = &v410;
haystack = (
char
*)&v409;
v417 = (
char
*)&v409;
v416 = (
char
*)&v406;
v413 = &v405;
v421 = &v404;
v418 = (
char
*)&v404;
needle = (
char
*)&v403;
v419 = &v402;
v426 = &v401;
v410 = pthread_self();
pipe(&g_pipe);
antidebugptraceFork();
v392 = pthread_create(newthread, 0, (
void
*(*)(
void
*))sub_1D404, 0);
v393 = &v411;
v394 = &v409;
v395 = &savedregs;
v396 = -1357197669;
v22 = getpid();
1)创建线程sub_1D404(后面分析)
2) 创建一个管道,同时调用函数 antidebugptraceFork();
3)依然tracepid反调试,采用相同方法 bypass
4 antidebugptraceFork(1D580)
核心代码如下:
1
2
sprintf
((
char
*)&v125, v24, pid);
sonPid = fork();
1)fork一个子进程
1
2
sprintf
((
char
*)&v125, v24, pid);
sonPid = fork();
1)fork一个子进程
2)子进程调用 ptrace(0, 0, 0, 0, ststusBuf_2);
3)子进程读取tracepid,并将其值通过管道write给父进程:write(dword_E356C, &buf, 4u);
4)父进程流程比较简单,直接函数返回了。
5)父进程的线程
sub_1D404为读取管道,代码如下:
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
int
sub_1D404()
{
signed
int
v0;
int
v1;
__pid_t v2;
int
result;
int
v4;
int
v5;
v4 = -1;
close(dword_E356C);
read(g_pipe, &v4, 4u);
sleep(1u);
LABEL_3:
v0 = 1819773075;
while
( 1 )
{
v1 = v0 & 0x7FFFFFFF;
if
( (v0 & 0x7FFFFFFF) == 340946481 )
{
v4 = -1;
goto
LABEL_3;
}
if
( v1 == 1314826255 )
break
;
if
( v1 == 1819773075 )
{
read(g_pipe, &v4, 4u);
v0 = 1314826255;
if
( !v4 )
v0 = 340946481;
}
}
kill(g_sonPid, 9);
v2 = getpid();
kill(v2, 9);
result = _stack_chk_guard - v5;
if
( _stack_chk_guard == v5 )
result = 0;
return
result;
}
了解了以上反调试功能后:
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
int
sub_1D404()
{
signed
int
v0;
int
v1;
__pid_t v2;
int
result;
int
v4;
int
v5;
v4 = -1;
close(dword_E356C);
read(g_pipe, &v4, 4u);
sleep(1u);
LABEL_3:
v0 = 1819773075;
while
( 1 )
{
v1 = v0 & 0x7FFFFFFF;
if
( (v0 & 0x7FFFFFFF) == 340946481 )
{
v4 = -1;
goto
LABEL_3;
}
if
( v1 == 1314826255 )
break
;
if
( v1 == 1819773075 )
{
read(g_pipe, &v4, 4u);
v0 = 1314826255;
if
( !v4 )
v0 = 340946481;
}
}
kill(g_sonPid, 9);
v2 = getpid();
kill(v2, 9);
result = _stack_chk_guard - v5;
if
( _stack_chk_guard == v5 )
result = 0;
return
result;
}
了解了以上反调试功能后:
首先可以直接将父进程的线程
sub_1D404杀掉,让其直接返回。然后nop掉fork指令,并使其结果r0=0就可以。
至此,初始化函数就分析完了。
四、jni_onload函数
这个函数代码量相对较小,贴出来吧
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
int
__fastcall JNI_OnLoad(
int
a1)
{
char
v1;
char
v2;
unsigned
int
v3;
signed
int
v4;
signed
int
v5;
unsigned
int
v6;
int
v7;
unsigned
int
v8;
signed
int
v9;
signed
int
v10;
bool
v11;
signed
int
v12;
int
v13;
struct
globalInfo *v14;
int
v15;
signed
int
v16;
signed
int
v17;
signed
int
v18;
clock_t
v19;
signed
int
v20;
signed
int
v21;
struct
_JNIEnv **v22;
int
*v23;
struct
_JNIEnv **v24;
struct
_JNIEnv *v25;
clock_t
clockTime2;
int
size;
signed
int
v28;
int
result;
int
v30;
unsigned
int
v31;
int
*g_dword_E35501;
_DWORD *v33;
struct
_JavaVM *JVM;
char
v35;
char
v36;
struct
_JNIEnv **v37;
int
*v38;
char
v39;
clock_t
clockTime1;
int
v41;
v33 = &_stack_chk_guard;
v1 = 0;
if
( y_47 < 10 )
v1 = 1;
v36 = v1;
v2 = 0;
if
( !((~-x_46 * x_46 ^ 0xFFFFFFFE) & ~-x_46 * x_46) )
v2 = 1;
v35 = v2;
JVM = (
struct
_JavaVM *)a1;
v3 = (unsigned
int
)&g_antiDebug_Global & 0x1A04BD11;
g_dword_E35501 = &g_antiDebug_Global;
v31 = (~(unsigned
int
)&g_antiDebug_Global & 0xE5FB42EE | (unsigned
int
)&g_antiDebug_Global & 0x1A04BD11) ^ (a1 & 0x1A04BD11 | ~a1 & 0xE5FB42EE);
v4 = 1974858797;
while
( 1 )
{
while
( 1 )
{
while
( 1 )
{
while
( 1 )
{
v5 = v4;
v6 = v3;
if
( v4 <= 286463644 )
break
;
if
( v4 > 1387993772 )
{
if
( v4 > 1974858796 )
{
if
( v4 == 2007388415 )
{
v4 = 102368831;
}
else
if
( v4 == 1974858797 )
{
v4 = 1146459914;
if
( v36 )
v4 = -217046703;
if
( !v35 )
v4 = 1146459914;
if
( v35 != v36 )
v4 = -217046703;
}
}
else
if
( v4 == 1387993773 )
{
v3 = -1;
v4 = -1667146202;
}
else
if
( v4 == 1813294739 )
{
v16 = 0;
if
( y_47 < 10 )
v16 = 1;
v17 = 0;
if
( (~(x_46 * (x_46 - 1)) | 0xFFFFFFFE) == -1 )
v17 = 1;
v11 = v16 == v17;
v18 = 407986737;
if
( !v11 )
v18 = -1082095904;
v4 = v18;
if
( (~(x_46 * (x_46 - 1)) | 0xFFFFFFFE) == -1 )
v4 = -1082095904;
if
( y_47 >= 10 )
v4 = v18;
}
}
else
if
( v4 <= 719945599 )
{
if
( v4 == 286463645 )
{
v4 = 0xEFC89CB9;
if
( v39 )
v4 = 0x52BB1AAD;
}
else
if
( v4 == 407986737 )
{
v4 = -1082095904;
}
}
else
{
v3 = 65540;
v4 = -1667146202;
if
( v5 != 719945600 )
{
if
( v5 != 1146459914 )
{
v28 = 795111493;
goto
LABEL_92;
}
v30 = 0;
getENV(JVM);
v3 = v6;
v4 = -217046703;
}
}
}
if
( v4 <= -770792315 )
break
;
if
( v4 > -123417836 )
{
if
( v4 == 0xF8A4CB15 )
{
size = readSSSAndGetSize(g_apkPath, (
int
)algn_E353C);
v4 = -1557264936;
if
( !size )
v4 = 1813294739;
g_sssSize = size;
v3 = v6;
}
else
if
( v4 == 102368831 )
{
v19 =
clock
();
v4 = 0x2AE97F80;
if
( (
double
)(
signed
int
)(v19 - clockTime1) / 1000000.0 > 5.0 )
v4 = 0xD20EA486;
v3 = v6;
}
}
else
if
( v4 == -272065351 )
{
clockTime1 =
clock
();
callnativeRegister(*v37);
v22 = v37;
v23 = g_dword_E35501;
*g_dword_E35501 = v31;
v24 = v37;
v23[1] = (unsigned
int
)(v23 + 1) & ~(unsigned
int
)*v22 | (unsigned
int
)*v22 & ~(unsigned
int
)(v23 + 1);
v25 = *v24;
g_apkPath = (
int
)GetApkPath();
clockTime2 =
clock
();
v4 = 0xF8A4CB15;
if
( (
double
)(
signed
int
)(clockTime2 - clockTime1) / 1000000.0 > 2.0 )
v4 = 0x2F647045;
v3 = v6;
}
else
if
( v4 == -217046703 )
{
v37 = (
struct
_JNIEnv **)&v30;
v38 = &v30;
v30 = 0;
v7 = getENV(JVM);
v8 = (x_46 * ~-x_46 ^ 0xFFFFFFFE) & x_46 * ~-x_46;
v9 = 0;
if
( !v8 )
v9 = 1;
v10 = 0;
if
( y_47 < 10 )
v10 = 1;
v11 = v10 == v9;
v12 = 1146459914;
if
( !v11 )
v12 = 286463645;
v4 = v12;
if
( !v8 )
v4 = 286463645;
if
( v7 )
LOBYTE(v7) = 1;
v39 = v7;
if
( y_47 >= 10 )
v4 = v12;
v3 = v6;
}
else
{
v28 = -770792314;
LABEL_92:
v11 = v5 == v28;
v3 = v6;
v4 = v5;
if
( v11 )
abort
();
}
}
if
( v4 <= -1509585911 )
break
;
v4 = 102368831;
if
( v5 != -1509585910 )
{
v4 = v5;
if
( v5 == -1082095904 )
{
v4 = 407986737;
if
( !(x_46 * (x_46 - 1) & (x_46 * (x_46 - 1) ^ 0xFFFFFFFE)) )
v4 = -1509585910;
if
( y_47 >= 10 )
v4 = 407986737;
v20 = 0;
if
( !(x_46 * (x_46 - 1) & (x_46 * (x_46 - 1) ^ 0xFFFFFFFE)) )
v20 = 1;
v21 = 0;
if
( y_47 < 10 )
v21 = 1;
if
( v21 != v20 )
v4 = -1509585910;
v3 = v6;
}
}
}
if
( v4 == -1667146202 )
break
;
if
( v4 == 2737702360 )
{
v13 = operator
new
(0x84u);
v14 = (
struct
globalInfo *)sub_85FB0(v13);
g_globalInfo = v14;
v15 = initSSSGlobal((
int
*)v14, *(
int
*)algn_E353C, g_sssSize);
v4 = 2007388415;
if
( v15 )
v4 = 102368831;
v3 = v6;
}
}
result = *v33 - v41;
if
( *v33 == v41 )
result = v3;
return
result;
}
1、第一部分
1) 获得env:etENV(JVM);
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
int
__fastcall JNI_OnLoad(
int
a1)
{
char
v1;
char
v2;
unsigned
int
v3;
signed
int
v4;
signed
int
v5;
unsigned
int
v6;
int
v7;
unsigned
int
v8;
signed
int
v9;
signed
int
v10;
bool
v11;
signed
int
v12;
int
v13;
struct
globalInfo *v14;
int
v15;
signed
int
v16;
signed
int
v17;
signed
int
v18;
clock_t
v19;
signed
int
v20;
signed
int
v21;
struct
_JNIEnv **v22;
int
*v23;
struct
_JNIEnv **v24;
struct
_JNIEnv *v25;
clock_t
clockTime2;
int
size;
signed
int
v28;
int
result;
int
v30;
unsigned
int
v31;
int
*g_dword_E35501;
_DWORD *v33;
struct
_JavaVM *JVM;
char
v35;
char
v36;
struct
_JNIEnv **v37;
int
*v38;
char
v39;
clock_t
clockTime1;
int
v41;
v33 = &_stack_chk_guard;
v1 = 0;
if
( y_47 < 10 )
v1 = 1;
v36 = v1;
v2 = 0;
if
( !((~-x_46 * x_46 ^ 0xFFFFFFFE) & ~-x_46 * x_46) )
v2 = 1;
v35 = v2;
JVM = (
struct
_JavaVM *)a1;
v3 = (unsigned
int
)&g_antiDebug_Global & 0x1A04BD11;
g_dword_E35501 = &g_antiDebug_Global;
v31 = (~(unsigned
int
)&g_antiDebug_Global & 0xE5FB42EE | (unsigned
int
)&g_antiDebug_Global & 0x1A04BD11) ^ (a1 & 0x1A04BD11 | ~a1 & 0xE5FB42EE);
v4 = 1974858797;
while
( 1 )
{
while
( 1 )
{
while
( 1 )
{
while
( 1 )
{
v5 = v4;
v6 = v3;
if
( v4 <= 286463644 )
break
;
if
( v4 > 1387993772 )
{
if
( v4 > 1974858796 )
{
if
( v4 == 2007388415 )
{
v4 = 102368831;
}
else
if
( v4 == 1974858797 )
{
v4 = 1146459914;
if
( v36 )
v4 = -217046703;
if
( !v35 )
v4 = 1146459914;
if
( v35 != v36 )
v4 = -217046703;
}
}
else
if
( v4 == 1387993773 )
{
v3 = -1;
v4 = -1667146202;
}
else
if
( v4 == 1813294739 )
{
v16 = 0;
if
( y_47 < 10 )
v16 = 1;
v17 = 0;
if
( (~(x_46 * (x_46 - 1)) | 0xFFFFFFFE) == -1 )
v17 = 1;
v11 = v16 == v17;
v18 = 407986737;
if
( !v11 )
v18 = -1082095904;
v4 = v18;
if
( (~(x_46 * (x_46 - 1)) | 0xFFFFFFFE) == -1 )
v4 = -1082095904;
if
( y_47 >= 10 )
v4 = v18;
}
}
else
if
( v4 <= 719945599 )
{
if
( v4 == 286463645 )
{
v4 = 0xEFC89CB9;
if
( v39 )
v4 = 0x52BB1AAD;
}
else
if
( v4 == 407986737 )
{
v4 = -1082095904;
}
}
else
{
v3 = 65540;
v4 = -1667146202;
if
( v5 != 719945600 )
{
if
( v5 != 1146459914 )
{
v28 = 795111493;
goto
LABEL_92;
}
v30 = 0;
getENV(JVM);
v3 = v6;
v4 = -217046703;
}
}
}
if
( v4 <= -770792315 )
break
;
if
( v4 > -123417836 )
{
if
( v4 == 0xF8A4CB15 )
{
size = readSSSAndGetSize(g_apkPath, (
int
)algn_E353C);
v4 = -1557264936;
if
( !size )
v4 = 1813294739;
g_sssSize = size;
v3 = v6;
}
else
if
( v4 == 102368831 )
{
v19 =
clock
();
v4 = 0x2AE97F80;
if
( (
double
)(
signed
int
)(v19 - clockTime1) / 1000000.0 > 5.0 )
v4 = 0xD20EA486;
v3 = v6;
}
}
else
if
( v4 == -272065351 )
{
clockTime1 =
clock
();
callnativeRegister(*v37);
v22 = v37;
v23 = g_dword_E35501;
*g_dword_E35501 = v31;
v24 = v37;
v23[1] = (unsigned
int
)(v23 + 1) & ~(unsigned
int
)*v22 | (unsigned
int
)*v22 & ~(unsigned
int
)(v23 + 1);
v25 = *v24;
g_apkPath = (
int
)GetApkPath();
clockTime2 =
clock
();
v4 = 0xF8A4CB15;
if
( (
double
)(
signed
int
)(clockTime2 - clockTime1) / 1000000.0 > 2.0 )
v4 = 0x2F647045;
v3 = v6;
}
else
if
( v4 == -217046703 )
{
v37 = (
struct
_JNIEnv **)&v30;
v38 = &v30;
v30 = 0;
v7 = getENV(JVM);
v8 = (x_46 * ~-x_46 ^ 0xFFFFFFFE) & x_46 * ~-x_46;
v9 = 0;
if
( !v8 )
v9 = 1;
v10 = 0;
if
( y_47 < 10 )
v10 = 1;
v11 = v10 == v9;
v12 = 1146459914;
if
( !v11 )
v12 = 286463645;
v4 = v12;
if
( !v8 )
v4 = 286463645;
if
( v7 )
LOBYTE(v7) = 1;
v39 = v7;
if
( y_47 >= 10 )
v4 = v12;
v3 = v6;
}
else
{
v28 = -770792314;
LABEL_92:
v11 = v5 == v28;
v3 = v6;
v4 = v5;
if
( v11 )
abort
();
}
}
if
( v4 <= -1509585911 )
break
;
v4 = 102368831;
if
( v5 != -1509585910 )
{
v4 = v5;
if
( v5 == -1082095904 )
{
v4 = 407986737;
if
( !(x_46 * (x_46 - 1) & (x_46 * (x_46 - 1) ^ 0xFFFFFFFE)) )
v4 = -1509585910;
if
( y_47 >= 10 )
v4 = 407986737;
v20 = 0;
if
( !(x_46 * (x_46 - 1) & (x_46 * (x_46 - 1) ^ 0xFFFFFFFE)) )
v20 = 1;
v21 = 0;
if
( y_47 < 10 )
v21 = 1;
if
( v21 != v20 )
v4 = -1509585910;
v3 = v6;
}
}
}
if
( v4 == -1667146202 )
break
;
if
( v4 == 2737702360 )
{
v13 = operator
new
(0x84u);
v14 = (
struct
globalInfo *)sub_85FB0(v13);
g_globalInfo = v14;
v15 = initSSSGlobal((
int
*)v14, *(
int
*)algn_E353C, g_sssSize);
v4 = 2007388415;
if
( v15 )
v4 = 102368831;
v3 = v6;
}
}
result = *v33 - v41;
if
( *v33 == v41 )
result = v3;
return
result;
}
1、第一部分
1) 获得env:etENV(JVM);
2
)
获得当前clockTime1 = clock();
3
)
调用函数
callnativeRegister(*v37);
4
)
调用g_apkPath = (int)GetApkPath();
5
)
调用clockTime2 = clock();
6
)
执行
clockTime2-
clockTime1
貌似有个 时间反调试,为了方便,直接修改clock函数返回值即可。
2、
callnativeRegister函数(16CFC )
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
unsigned
int
__fastcall callnativeRegister(
struct
_JNIEnv *env)
{
char
v1;
char
v2;
unsigned
int
result;
signed
int
v4;
signed
int
v5;
unsigned
int
v6;
signed
int
v7;
signed
int
v8;
bool
v9;
signed
int
v10;
struct
_JNIEnv *env1;
unsigned
__int8
v12;
char
v13;
unsigned
__int8
v14;
env1 = env;
v1 = 0;
v2 = 0;
if
( y_45 < 10 )
v2 = 1;
v13 = v2;
result = (~((x_44 - 1) * x_44) | 0xFFFFFFFE) + 1;
if
( (~((x_44 - 1) * x_44) | 0xFFFFFFFE) == -1 )
v1 = 1;
v12 = v1;
v4 = -1230185785;
do
{
while
( 1 )
{
while
( 1 )
{
while
( v4 <= 1146544159 )
{
switch
( v4 )
{
case
-1230185785:
result = v12;
v5 = 626878466;
if
( v12 != (unsigned
__int8
)v13 )
v5 = 1146544160;
v4 = v5;
if
( v13 )
v4 = 1146544160;
if
( !v12 )
v4 = v5;
break
;
case
-299872326:
result = v14;
v4 = 1603580720;
if
( v14 )
v4 = 1673631727;
break
;
case
626878466:
result = nativeRegister(env1);
v4 = 1146544160;
break
;
}
}
if
( v4 != 1146544160 )
break
;
result = nativeRegister(env1);
v6 = (x_44 * ~-x_44 ^ 0xFFFFFFFE) & x_44 * ~-x_44;
v7 = 0;
if
( !v6 )
v7 = 1;
v8 = 0;
if
( y_45 < 10 )
v8 = 1;
v9 = v8 == v7;
v10 = 626878466;
if
( !v9 )
v10 = -299872326;
v4 = v10;
if
( !v6 )
v4 = -299872326;
v14 = result;
if
( y_45 >= 10 )
v4 = v10;
}
if
( v4 != 1603580720 )
break
;
v4 = 1673631727;
}
}
while
( v4 != 1673631727 );
return
result;
}
看出是一个native注册函数,其又调用152C0进行真正的nativeregister,其部分代码如下:
1
2
3
v73 = NewGlobalRef(&env1->functions, v107);
dword_E3558 = (unsigned
int
)&dword_E3558 & ~v73 | v73 & ~(unsigned
int
)&dword_E3558;
RegisterNatives(&env1->functions, v107, v106, 1);
其实这个函数是个虚假的native函数。这里就不分析了。
3、第二部分
创建一个结构体 ,bin进行初始化
1
2
3
4
v13 = operator
new
(0x84u);
v14 = (
struct
globalInfo *)sub_85FB0(v13);
g_globalInfo = v14;
v15 = initSSSGlobal((
int
*)v14, *(
int
*)algn_E353C, g_sssSize);
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
unsigned
int
__fastcall callnativeRegister(
struct
_JNIEnv *env)
{
char
v1;
char
v2;
unsigned
int
result;
signed
int
v4;
signed
int
v5;
unsigned
int
v6;
signed
int
v7;
signed
int
v8;
bool
v9;
signed
int
v10;
struct
_JNIEnv *env1;
unsigned
__int8
v12;
char
v13;
unsigned
__int8
v14;
env1 = env;
v1 = 0;
v2 = 0;
if
( y_45 < 10 )
v2 = 1;
v13 = v2;
result = (~((x_44 - 1) * x_44) | 0xFFFFFFFE) + 1;
if
( (~((x_44 - 1) * x_44) | 0xFFFFFFFE) == -1 )
v1 = 1;
v12 = v1;
v4 = -1230185785;
do
{
while
( 1 )
{
while
( 1 )
{
while
( v4 <= 1146544159 )
{
switch
( v4 )
{
case
-1230185785:
result = v12;
v5 = 626878466;
if
( v12 != (unsigned
__int8
)v13 )
v5 = 1146544160;
v4 = v5;
if
( v13 )
v4 = 1146544160;
if
( !v12 )
v4 = v5;
break
;
case
-299872326:
result = v14;
v4 = 1603580720;
if
( v14 )
v4 = 1673631727;
break
;
case
626878466:
result = nativeRegister(env1);
v4 = 1146544160;
break
;
}
}
if
( v4 != 1146544160 )
break
;
result = nativeRegister(env1);
v6 = (x_44 * ~-x_44 ^ 0xFFFFFFFE) & x_44 * ~-x_44;
v7 = 0;
if
( !v6 )
v7 = 1;
v8 = 0;
if
( y_45 < 10 )
v8 = 1;
v9 = v8 == v7;
v10 = 626878466;
if
( !v9 )
v10 = -299872326;
v4 = v10;
if
( !v6 )
v4 = -299872326;
v14 = result;
if
( y_45 >= 10 )
v4 = v10;
}
if
( v4 != 1603580720 )
break
;
v4 = 1673631727;
}
}
while
( v4 != 1673631727 );
return
result;
}
看出是一个native注册函数,其又调用152C0进行真正的nativeregister,其部分代码如下:
[注意]看雪招聘,专注安全领域的专业人才平台!
最后于 2019-9-24 20:26
被oooAooo编辑
,原因: