前言
样例来源于3W班9月份第三题,考察还原ollvm混淆后的算法。
思路
对于经ollvm混淆的算法,动态一步步调试,或者静态分析,都是一个比较大的工程。
通用的思路就是trace汇编指令及寄存器变化,追踪核心的计算式,从而还原算法。尤其对于非标准算法,非常有效。
我这里直接用ida trace关键函数起始地址,然后分析log日志,从而还原出算法。
解题
静态分析apk
jadx静态分析apk
java层代码比较简单
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
|
进程名: com.kanxue.ollvm_ndk_9
package com.kanxue.ollvm_ndk;
import
android.os.Bundle;
import
android.util.Log;
import
android.view.View;
import
android.widget.Button;
import
android.widget.TextView;
import
androidx.appcompat.app.AppCompatActivity;
import
com.kanxue.ollvm_ndk_9.R;
import
org.apache.commons.lang3.RandomStringUtils;
public
class
MainActivity extends AppCompatActivity {
public static native String UUIDCheckSum(String
str
);
static {
System.loadLibrary(
"native-lib"
);
}
/
*
access modifiers changed
from
: protected
*
/
public void onCreate(Bundle bundle) {
super
.onCreate(bundle);
setContentView((
int
) R.layout.activity_main);
final TextView textView
=
(TextView) findViewById(R.
id
.sample_text);
((Button) findViewById(R.
id
.button)).setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
String randomAlphanumeric
=
RandomStringUtils.randomAlphanumeric(
36
);
String UUIDCheckSum
=
MainActivity.UUIDCheckSum(randomAlphanumeric);
textView.setText(UUIDCheckSum);
Log.e(
"kanxue"
,
"input: "
+
randomAlphanumeric
+
" output: "
+
UUIDCheckSum);
}
});
}
}
|
从上面获取实用信息:
1>libnative-lib.so和native方法。
2>adb logcat -s "kanxue",查看输入输出结果。
ida静态分析so
解压apk, ida64打开libnative-lib.so,这里在exports中搜索java,看看是否有UUIDCheckSum方法,定位方法的start和end的偏移地址:
1
2
3
4
5
|
.text:
000000000000FF30
Java_com_kanxue_ollvm_1ndk_MainActivity_UUIDCheckSum
..略
.text:
00000000000101CC
RET
|
IDA trace log
修改ida的trace脚本,将start_ea和end_ea修改上面分析so的UUIDCheckSum的偏移起始地址:
1
2
|
start_ea
=
(module.base
+
0xFF30
)
end_ea
=
[((module.base
+
0x101CC
))]
|
ida attach com.kanxue.ollvm_ndk_9,trace保存日志记录,具体步骤略。
分析log
日志的样例输入输出:
1
2
|
input
: OBjr9WRO8BUNXhcB0hsGn6sgqa8y63XDvjIr
output: hybFqNvkiOSHePfdkgOOeN5D_inJbR9K_k0Ts3qMkijRoQ9v
|
input算法分析1
直接用010Editor打开日志文件,从UUIDCheckSum入参开始,逐步追踪输入的str使用过程。具体跟踪:
Java_com_kanxue_ollvm_1ndk_MainActivity_UUIDCheckSum输入参数: 其中X2为string
根据日志:X2=0000007FF45F9998
---> (*_env)->GetStringUTFChars(_env, _uuid, 0LL);
libart.so:_ZN3art8CheckJNI17GetStringUTFCharsEP7_JNIEnvP8_jstringPh
得到:jstring转换为chars的数据存入内存地址:
X0=00000078A9094350
经过:libc.so:strdup
X0=00000078A9094380
--->继续分析input数据在哪里处理
全局搜索:[X0,
观测结果,得出如下:
<1> input[0x17]=input[0x18]
<2>
000000789DD8FE14 LDRSW X22, [SP,#0x18]
000000789DD8FE18 LDRB W20, [X0,X22]
000000789DD8FE24. EOR W24, W20, #1
000000789DD8FE28 STRB W24, [X0,X22]
这里:input下标X22定位位置:000000789DD8FE14
全局搜索:000000789DD8FE14
除了下标:0x8 0xD 0xE 0x12 0x18 0x22 0x23
其他的input[k]都是如上的计算方式。
<3> 0x8 0xD等分析
全局搜索[X0, 结果的第823行,发现:
unk_789DD8FE4C LDRSW X20, [SP,#0x18] X20=0000000000000008
000000789DD8FE50 STRB W7, [X0,X20]
追溯W7=0x2D
最终:0x8 0xD 0x12 0x18被赋予固定0x2D
而0xE:
unk_789DD8FE40 LDRSW X20, [SP,#0x18] X20=000000000000000E
000000789DD8FE44 STRB W6, [X0,X20]
//MOV W6, #0x34 X6=0000000000000034
input[0xE]=0x34
<4>0x22 和0x23分析
根据:
000000789DD8FF10 STRB W8, [X0,#0x23]
000000789DD8FF18 STRB W8, [X0,#0x22]
反推出[X0,#0x22]和[X0, #0x23]的数据来源。
以上处理过程的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
|
int
len
=
input
.length();
int
t_23
=
0xFF
;
int
t_22
=
0x0
;
for
(
int
i
=
0
; i<
len
-
2
; i
+
+
){
if
(i
=
=
0x8
|| i
=
=
0xD
|| i
=
=
0x12
|| i
=
=
0x18
){
input
[i]
=
0x2D
;
continue
;
}
if
( i
=
=
0xE
){
input
[i]
=
0x34
;
continue
;
}
if
(i
=
=
0x17
){
input
[i]
=
input
[i
+
1
];
}
t_23
=
t_23 ^
input
[i];
t_22
=
t_22
+
input
[i];
/
/
计算式
input
[i]
=
input
[i] ^
1
;
}
t_22
=
t_22
-
(t_22 &
0xFFFFFFF0
);
t_23
=
t_23 &
0xF
;
input
[
0x22
]
=
key_xmmword_37060[t_22];
input
[
0x23
]
=
key_xmmword_37060[t_23];
std::cout <<
input
<< std::endl;
|
上述分析出:
input经sub_FCB4(_uuid_cpy, len); 处理后的样例结果:
1
|
NCks8VSN
-
CTOY
-
4C1i
-
Fo7rp
-
`
9x72YEwkba
|
算法分析2
继续分析X0=00000078A9094380
000043B5 libc.so:memcpy PRFM #0, [X1]
000043B5 X0=00000078A9094530 X1=00000078A9094380 X2=0000000000000024
经过memcpy, X0=00000078A9094530
--->继续追踪:00000078A9094530地址
在:3543行发现libc.so memcpy
000043B5 libc.so:memcpy PRFM #0, [X1]
000043B5 X0=00000078A9094560 X1=00000078A9094530
X0= 00000078A9094560
--->继续追踪:00000078A9094560
3567: 000000789DD8FAA0 LDR X9, [SP,#0x20] X9=00000078A9094560
...
3664:
MOV X21, X0 X21=00000078A9094560
这里发现X21一直存入的是:00000078A9094560
定位处理数据地方:
这里发现X21一直存入的是:00000078A9094560
定位处理数据地方:
1
2
3
4
|
000043B5
libnative_lib.so:
000000789DD8F11C
LDRB W8, [X21,X24] X8
=
000000000000004E
;debug input_new[
0x0
]
=
0x4E
X21
=
00000078A9094560
000043B5
libnative_lib.so:
000000789DD8F120
LSR X8, X8,
000043B5
libnative_lib.so:
000000789DD8F124
LDRB W1, [X23,X8] X1
=
0000000000000068
;debug X23[
0x13
]
=
0x68
result[
0x0
]
=
0x68
/
/
X23
=
X23
=
000000789DDB7010
对应的是:stru_37010
|
分析发现如下规律:
X23的下标共有4个计算方式:
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
|
分析出下标计算式
<
1
>
libnative_lib.so:unk_789DD8F11C LDRB W8, [X21,X24] X8
=
0000000000000043
libnative_lib.so:
000000789DD8F120
LSR X8, X8,
<
2
>
libnative_lib.so:
000000789DD8F130
LDRB W8, [X21,X24] X8
=
0000000000000043
libnative_lib.so:
000000789DD8F134
MOV W24, W22 X24
=
000000000000000A
libnative_lib.so:
000000789DD8F138
CMP
X24, X20 C
=
0
Z
=
0
N
=
1
libnative_lib.so:
000000789DD8F13C
UBFIZ X8, X8,
libnative_lib.so:
000000789DD8F140
B.CS unk_789DD8F198
libnative_lib.so:
000000789DD8F144
LDRB W9, [X21,X24] X9
=
0000000000000054
libnative_lib.so:
000000789DD8F148
ORR X8, X8, X9,LSR
libnative_lib.so:
000000789DD8F14C
LDRB W1, [X23,X8] X1
=
0000000000000050
<
3
>
libnative_lib.so:
000000789DD8F158
LDRB W8, [X21,X24] X8
=
0000000000000054
libnative_lib.so:
000000789DD8F15C
ADD W24, W22,
libnative_lib.so:
000000789DD8F160
CMP
X24, X20 C
=
0
Z
=
0
N
=
1
libnative_lib.so:
000000789DD8F164
UBFIZ X8, X8,
libnative_lib.so:
000000789DD8F168
B.CS unk_789DD8F1C0
libnative_lib.so:
000000789DD8F16C
LDRB W9, [X21,X24] X9
=
000000000000004F
libnative_lib.so:
000000789DD8F170
ORR X8, X8, X9,LSR
libnative_lib.so:
000000789DD8F174
LDRB W1, [X23,X8] X1
=
0000000000000066
;debug result[
0xE
]
=
0x66
<
4
> libnative_lib.so:
000000789DD8F180
LDRB W8, [X21,X24] X8
=
000000000000004F
libnative_lib.so:
000000789DD8F184
AND X8, X8,
libnative_lib.so:
000000789DD8F188
LDRB W1, [X23,X8] X1
=
0000000000000064
|
发现相关参数input_new[i]的规律,后续都是根据这4个计算式循环。
注意:
1>UBFIZ X8, X8, #2, #4 这种汇编指令,不知道咋整,后面直接抄袭ida反汇编的代码计算式的。
2>X23 :stru_37010 后面直接用frida dump的内存数据。
这里直接X2入参追踪的,其实可以方便一点,从input_new入手或者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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
int
main() {
std::string key_xmmword_37060
=
"0123456789abcdef"
;
std::string key_stru_37010
=
"0123456789-_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
;
std::string
input
=
"5XH1S4C0qs1ofLGzvX7gTCwWOUqy2fxPUqc0"
;
int
len
=
input
.length();
int
t_23
=
0xFF
;
int
t_22
=
0x0
;
for
(
int
i
=
0
; i<
len
-
2
; i
+
+
){
if
(i
=
=
0x8
|| i
=
=
0xD
|| i
=
=
0x12
|| i
=
=
0x18
){
input
[i]
=
0x2D
;
continue
;
}
if
( i
=
=
0xE
){
input
[i]
=
0x34
;
continue
;
}
if
(i
=
=
0x17
){
input
[i]
=
input
[i
+
1
];
}
t_23
=
t_23 ^
input
[i];
t_22
=
t_22
+
input
[i];
input
[i]
=
input
[i] ^
1
;
}
t_22
=
t_22
-
(t_22 &
0xFFFFFFF0
);
t_23
=
t_23 &
0xF
;
input
[
0x22
]
=
key_xmmword_37060[t_22];
input
[
0x23
]
=
key_xmmword_37060[t_23];
std::cout <<
input
<< std::endl;
int
k
=
48
;
int
i
=
0
;
std::string result;
result.resize(k);
for
(
int
j
=
0
; j < k; j
+
+
){
int
k_index;
int
sk
=
j
%
4
;
if
(j!
=
0
&& sk
=
=
0
)
i
=
i
+
3
;
if
(sk
=
=
0
){
k_index
=
(
input
[i] >>
0x2
) &
0xFF
;
}
else
if
(sk
=
=
1
){
/
/
UBFIZ X8, X8,
int
v11
=
(
input
[i] &
0x3
)
*
16
;
/
/
ORR X8, X8, X9,LSR
k_index
=
v11 | (
input
[i
+
1
] >>
0x4
);
}
else
if
(sk
=
=
2
){
/
/
UBFIZ X8, X8,
int
v13
=
(
input
[i
+
1
] &
0xF
)
*
4
;
k_index
=
v13 | (
input
[i
+
2
] >>
0x6
) ;
}
else
if
(sk
=
=
3
){
k_index
=
input
[i
+
2
] &
0x3F
;
}
result[j]
=
key_stru_37010[k_index];
}
std::cout<<
"result="
<< result<<std::endl;
return
0
;
}
|
验证代码:
1
2
3
4
5
6
7
|
kanxue :
input
: YJTRQNVwIJYZnQgNGhkGOweU4qogKhd2dfeR
output: k4HjiP1djRmHgPvppMOOhOnD_incrAeP_l1InyDDnhbznQeM
result
=
k4HjiP1djRmHgPvppMOOhOnD_incrAeP_l1InyDDnhbznQeM
kanxue :
input
: KHuwjWk1vYiB7c9COhIx9d6n2qiIKCdXkot2
output: gyzOrAHkox0Hk6v3bwOOeyTD_lySnhqN_l1Cg4D2njzEpxiS
result
=
gyzOrAHkox0Hk6v3bwOOeyTD_lySnhqN_l1Cg4D2njzEpxiS
|