0x00 前言 小伙伴给我发来一道android题,涉及花指令,记录一下去除花指令和算法还原的过程。 样本强网杯 flower.apk 0x01 花指令花指令是企图隐藏掉不想被逆向工程的代码块(或其它功能)的一种方法,在真实代码中插入一些垃圾代码的同时还保证原有程序的正确执行,而程序无法很好地反编译, 难以理解程序内容,达到混淆视听的效果。
主要目的加大静态分析难度 1、不可执行的花指令运行是不执行的汇编指令,会对反汇编造成影响,影响静态分析(如ida无法f5) 2、可执行的花指令0x02 算法还原1、样本分析(1) java层
Android记去花指令还原算法
(2) native层1
2
3
4
5
6
7
8
9
10
11
12
13
14
| int __fastcall Java_com_a_flower_MainActivity_check(int a1, int a2, int a3)
{
int v3; // r5
int v4; // r4
char *v5; // r6
int v6; // r8
v3 = a1;
v4 = a3;
v5 = (*(*a1 + 676))(a1, a3, 0);
v6 = sub_F34(v5);
(*(*v3 + 680))(v3, v4, v5);
return v6;
}
| sub_F341
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| int __fastcall sub_F34(char *s)
{
char *v1; // r8
size_t v2; // r0
int v3; // r4
v1 = s;
v2 = strlen(s);
if ( v2 <= 0x80 )
{
if ( (signed int)v2 >= 1 )
{
v3 = (unsigned __int8)*v1 << 8;
sub_F8E((int)&dword_0 + 3);
}
sub_FD0((int)&dword_0 + 3);
}
return 1;
}
| 修复 sub_F8E
根据ida的提示sub_F8E应该跳转的位置是 0xFA4 = 0xF98 + 0xC
patch脚本 1
2
3
4
5
6
7
8
9
10
11
12
13
| def put_unconditional_branch(source, destination):
offset = (destination - source - 4) >> 1
if offset > 2097151 or offset < -2097152:
raise RuntimeError("Invalid offset")
if offset > 1023 or offset < -1024:
instruction1 = 0xf000 | ((offset >> 11) & 0x7ff)
instruction2 = 0xb800 | (offset & 0x7ff)
PatchWord(source, instruction1)
PatchWord(source + 2, instruction2)
else:
instruction = 0xe000 | (offset & 0x7ff)
PatchWord(source, instruction)
put_unconditional_branch(0xF98, 0xFA4)
|
path之后要重建函数(神奇的P键)
Android记去花指令还原算法
修复前
Android记去花指令还原算法
Android记去花指令还原算法
Android记去花指令还原算法
修复后
Android记去花指令还原算法
Android记去花指令还原算法
Android记去花指令还原算法
使用如上方法对sub_FD0可以进行函数重建,让ida识别成功后可以使用F5. 2、去花指令动态调试可以发现不可以执行的花指令
Android记去花指令还原算法
经过分析可以将可以执行的花指令识别出来 1
2
3
4
5
6
7
8
9
10
11
| 8F 46 7D 46 00 DF 8F BD BF 46 70 47 02 BC 8E BC 08 46 11 46 1A 46 3B 46 80 BC
8F 46 FF 46 00 DF 8F BD BF 46 70 47 04 BC 8E BC 08 46 11 46 1A 46 3B 46 80 BC
8F 46 7B 46 00 DF 8F BD BF 46 70 47 80 BC 8E BC 08 46 11 46 1A 46 3B 46 80 BC
8F 46 79 46 00 DF 8F BD BF 46 70 47 04 BC 8E BC 08 46 11 46 1A 46 3B 46 80 BC
8F 46 7e 46 00 DF 8F BD BF 46 70 47 08 BC 8E BC 08 46 11 46 1A 46 3B 46 80 BC
8F 46 FE 46 00 DF 8F BD BF 46 70 47 04 BC 8E BC 08 46 11 46 1A 46 3B 46 80 BC
|
将以上替换成 1
| 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
去花指令之前
Android记去花指令还原算法
去花指令之后(函数重建之后ida识别成功)
Android记去花指令还原算法
去花指令之前伪代码 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| int __fastcall sub_F34(char *s)
{
char *v1; // r8
size_t v2; // r0
int v3; // r4
v1 = s;
v2 = strlen(s);
if ( v2 <= 0x80 )
{
if ( (signed int)v2 >= 1 )
{
v3 = (unsigned __int8)*v1 << 8;
sub_F8E();
}
sub_FD0();
}
return 1;
}
|
去花指令之后伪代码 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
| // write access to const memory has been detected, the output may be wrong!
int __fastcall sub_F34(char *s)
{
char *v1; // r8
_DWORD *v2; // r10
size_t v3; // r0
int v4; // r9
unsigned int v5; // r3
int v6; // r0
int v7; // r1
char *v8; // r2
int v9; // r4
signed int v10; // r3
int v11; // r6
int v12; // r10
unsigned int v13; // r6
int v14; // r3
void *v15; // r11
char *i; // r0
unsigned int v17; // r1
int v18; // r10
char *v19; // r2
int v20; // t1
int *v21; // r2
int v22; // lr
int v23; // r1
unsigned __int8 *v24; // r8
int v25; // r12
int j; // r4
int k; // r4
int v28; // r9
int v29; // r5
int v30; // r1
int v31; // r4
char v32; // r5
int v33; // r1
int l; // r0
int v35; // r0
char v36; // r2
int v37; // r4
unsigned __int8 v38; // r3
int result; // r0
int v40; // [sp+0h] [bp-11Ch]
int *v41; // [sp+4h] [bp-118h]
int v42; // [sp+8h] [bp-114h]
__int64 *v43; // [sp+Ch] [bp-110h]
int *v44; // [sp+10h] [bp-10Ch]
int v45; // [sp+14h] [bp-108h]
int *v46; // [sp+18h] [bp-104h]
int v47; // [sp+1Ch] [bp-100h]
__int64 *v48; // [sp+20h] [bp-FCh]
char *v49; // [sp+28h] [bp-F4h]
int v50; // [sp+2Ch] [bp-F0h]
int *v51; // [sp+30h] [bp-ECh]
int v52; // [sp+34h] [bp-E8h]
__int64 *v53; // [sp+38h] [bp-E4h]
unsigned int v54; // [sp+3Ch] [bp-E0h]
int v55; // [sp+40h] [bp-DCh]
int v56; // [sp+44h] [bp-D8h]
char *v57; // [sp+48h] [bp-D4h]
unsigned int v58; // [sp+4Ch] [bp-D0h]
__int64 *v59; // [sp+50h] [bp-CCh]
int *v60; // [sp+54h] [bp-C8h]
void *v61; // [sp+58h] [bp-C4h]
int v62; // [sp+5Ch] [bp-C0h]
char *v63; // [sp+60h] [bp-BCh]
int v64; // [sp+64h] [bp-B8h]
__int64 *v65; // [sp+68h] [bp-B4h]
int v66; // [sp+74h] [bp-A8h]
int v67; // [sp+98h] [bp-84h]
__int64 savedregs; // [sp+11Ch] [bp+0h]
v1 = s;
v2 = &_stack_chk_guard;
v3 = strlen(s);
if ( v3 <= 0x80 )
{
v4 = v3;
v5 = 0;
v6 = 4129;
v7 = v4;
v8 = v1;
while ( v7 >= 1 )
{
v9 = v5 ^ (*v8 << 8);
v10 = 8;
while ( v10 )
{
v11 = v6 ^ 2 * v9;
if ( !(v9 & 0x8000) )
v11 = 2 * v9;
v61 = v6;
v62 = v7;
v63 = v8;
v64 = v10;
v65 = &savedregs;
v8 = &loc_F8E;
v60 = (&dword_0 + 3);
v6 = 4 * (&dword_0 + 3);
v7 = 4 * (&dword_0 + 3) + 3992;
--v10;
v9 = v11;
}
--v7;
v5 = v9;
++v8;
}
v61 = &_stack_chk_guard;
v55 = 128;
v56 = v7;
v57 = v8;
v58 = v5;
v59 = &savedregs;
v54 = v5;
v12 = v5 & 0xF;
v13 = (v5 >> 8) & 0x3F;
v15 = malloc(4 * (&dword_0 + 3));
for ( i = 0; v13 != i; ++i )
i[v15] = *v1;
v17 = v13 + v4;
v18 = v12 + 16;
v19 = v1;
while ( v13 < v17 )
{
v20 = *v19++;
v14 = v20;
*(v15 + v13++) = v20;
}
while ( v13 <= 0x7F )
*(v15 + v13++) = v1[v4 - 1];
v21 = &v56;
v57 = 0x88776655;
v22 = 0;
v23 = 0x44332211;
v24 = v15;
v56 = 0x44332211;
while ( v22 != 16 )
{
v25 = 8 * v22;
for ( j = 0; j != 8; ++j )
{
v49 = i;
v50 = v23;
v51 = v21;
v52 = v14;
v53 = &savedregs;
i = &dword_0 + 3;
v21 = &loc_1076;
v23 = v24[j] ^ *(&loc_1076 + j);
*(&loc_1076 + j) = v23;
}
for ( k = 0; k != 8; ++k )
*(v21 + k) ^= byte_2B98[k];
v28 = 0;
while ( v28 != v18 )
{
v14 = HIBYTE(v51);
v29 = 0;
v30 = HIBYTE(v51);
while ( v29 + 6 >= 0 )
{
v44 = (v21 + v29);
v45 = v30;
v46 = v21;
v47 = HIBYTE(v51);
v48 = &savedregs;
v21 = &loc_10E2;
--v29;
byte_9[1] = (v30 & 0xF0) + (byte_9[0] & 0xF);
v30 = byte_9[0];
}
v31 = 0;
v32 = v46 & 0xF0;
LOBYTE(v46) = (HIBYTE(v51) & 0xF) + (v46 & 0xF0);
while ( v31 != 7 )
{
v33 = v21 + v31;
v40 = v21 + v31;
v41 = v21;
v42 = v14;
v43 = &savedregs;
v21 = &loc_1140;
*(&loc_1140 + v31) = (*(&loc_1140 + v31) & 0xF) + (*(v33 + 1) & 0xF0);
++v31;
}
++v28;
HIBYTE(v42) = HIBYTE(v42) & 0xF | v32;
}
v23 = v42;
v24 += 8;
*(v15 + v25) = v41;
i = v15 + v25;
++v22;
*(v15 + v25 + 4) = v42;
}
_aeabi_memcpy8(&v43, &unk_2BA0, 128);
v2 = v40;
for ( l = 0; ; l = v35 + 1 )
{
if ( l == 128 )
{
free(v15);
result = 0;
goto LABEL_43;
}
v35 = 4 * (&dword_0 + 3);
v36 = *(&v43 + 4 * (&dword_0 + 3));
v37 = *(v15 + 4 * (&dword_0 + 3));
v38 = v36 ^ (4 * (&dword_0 + 3) - 30);
*(&v43 + 4 * (&dword_0 + 3)) = v38;
if ( v37 != v38 )
break;
*(&v43 + v35) = v36;
}
free(v15);
}
result = 1;
LABEL_43:
if ( *v2 == v66 )
JUMPOUT(__CS__, v67);
return result;
}
| 3、算法还原(1) 动态调试根据伪代码结合动态调试进行算法还原
Android记去花指令还原算法
这里需要动态调试获取值
Android记去花指令还原算法
调试的时候断点参考 loc_11B0 loc_10AA (2) 加密算法加密过程 1
2
3
4
5
6
7
8
9
10
11
12
| data1 = [0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]
data2 = [0x77, 0x23, 0x9D, 0xAC, 0x13, 0x27, 0xCF, 0xFE]
data3 = [0xA8, 0xAF, 0x56, 0x98, 0x88, 0xEF, 0x40, 0x06, 0xFD, 0xAE, 0xE9, 0x9E, 0xB9, 0xEA, 0xAD, 0x52,
0xCC, 0xAB, 0x04, 0xCA, 0xEC, 0xEB, 0x12, 0x54, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00,
0xA8, 0xAF, 0x56, 0x98, 0x88, 0xEF, 0x40, 0x06, 0xFD, 0xAE, 0xE9, 0x9E, 0xB9, 0xEA, 0xAD, 0x52,
0xCB, 0xAA, 0x19, 0xE5, 0xEC, 0xEB, 0x12, 0x5E, 0xA0, 0xA6, 0xA8, 0xF3, 0x8B, 0xD6, 0xCD, 0x6F,
0xCB, 0xC0, 0x4F, 0xC1, 0xE0, 0xDA, 0x74, 0x00, 0x91, 0xBE, 0xC6, 0x83, 0xD0, 0xA6, 0x8D, 0x2E,
0xBE, 0xFC, 0x3F, 0xAD, 0x9B, 0xE0, 0x26, 0x52, 0xF5, 0xBA, 0x94, 0xD1, 0xB4, 0xA2, 0xDF, 0x7C,
0xDA, 0xF8, 0x6D, 0xFF, 0xFF, 0xE4, 0x74, 0x00, 0x91, 0xBE, 0xC6, 0x83, 0xD0, 0xA6, 0x8D, 0x2E,
0xBE, 0xFC, 0x3F, 0xAD, 0x9B, 0xE0, 0x26, 0x52, 0xF5, 0xBA, 0x94, 0xD1, 0xB4, 0xA2, 0xDF, 0x7C]
|
1、输入字符串
2、按首字符填充长度为首字符10进制长度 + 字符串长度 + 尾字符填充 = 128长度
3、循环128位字符 先与data1和data2进行异或,再循环 28x16(1-7 6-0) 次 进行高/低四位保留(0xf0 0xf 与运算),最后异或0x88
4、结果与data3进行比较 (3) 算法还原加密 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
| int sub_F34(char *s){
char *v1; // r8
char *v2;
size_t v3; // r0
int v4; // r9
unsigned int v5; // r3
int v6; // r0
int v7; // r1
char *v8; // r2
int v9; // r4
signed int v10; // r3
int v11; // r6
int v12; // r10
unsigned int v13; // r6
int v14; // r3
char *v15; // r11
char *v16;
int i; // r0
unsigned int v17; // r1
int v18; // r10
char *v19; // r2
int v20; // t1
int *v21; // r2
int v22; // lr
int v23; // r1
char *v24; // r8
int v25; // r12
int j; // r4
int k; // r4
int v28; // r9
int v29; // r5
int v30; // r1
int v31; // r4
int v32; // r4
int v33; // r5
int v34; // r0
int v59;
v1 = s;
v3 = strlen(s);
if ( v3 <= 128 )
{
v4 = v3;
v5 = 0;
v6 = 0x1021;
v7 = v4;
v8 = v1;
while ( v7 >= 1 )
{
v9 = v5 ^ (*v8 << 8);
v10 = 8;
while ( v10 )
{
v11 = v6 ^ 2 * v9;
if ( !(v9 & 0x8000) )
v11 = 2 * v9;
--v10;
v9 = v11;
}
--v7;
v5 = v9;
++v8;
}
v59 = 128;
v12 = v5 & 0xF;
v13 = (v5 >> 8) & 0x3F;
v15 = (char *)malloc(128);
for ( i = 0; v13 != i; ++i )
v15 = *v1;
v17 = v13 + v4;
v18 = v12 + 16;
v19 = v1;
printf("before %s %d \n",v15, strlen(v15));
while ( v13 < v17 )
{
v20 = *v19++;
v14 = v20;
v15[v13++] = v20;
}
printf("input %s %d \n",v15, strlen(v15));
while ( v13 <= 0x7F )
v15[v13++] = v1[v4 - 1];
printf("after %s %d \n",v15, strlen(v15));
unsigned char byte_2B98[8] = {0x77, 0x23, 0x9D, 0xAC, 0x13, 0x27, 0xCF, 0xFE};
unsigned char v21[8] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
while ( v22 != 16 )
{
v25 = 8 * v22;
for ( j = 0; j != 8; ++j )
v21[j] ^= v15[v25 + j];
for ( k = 0; k != 8; ++k )
*(v21 + k) ^= byte_2B98[k];
v28 = 0;
while ( v28 != v18 )
{
v14 = v21[7];
v29 = 7;
while ( v29 >= 1 )
{
v21[v29] = (v21[v29]&0xf0) + (v21[v29-1]&0xf);
--v29;
}
v32 = 0;
v33 = v21[0] & 0xF0;
v21[0] = (v14 & 0xF) + v33;
v34 = v21[0];
while ( v32 != 7 )
{
v21[v32] = (v21[v32]&0xf) + (v21[v32+1]&0xf0);
++v32;
}
v21[7] = (v21[7]&0xf) + (v34&0xf0);
++v28;
}
v30 = 0;
while(v30 != 8){
printf("0x%x ", v21[v30] ^ 0x88);
++v30;
}
printf("\n");
++v22;
}
}
return 0;
}
|
解密 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
| void decrypt(){
char *v1; // r8
char *v2;
size_t v3; // r0
int v4; // r9
unsigned int v5; // r3
int v6; // r0
int v7; // r1
char *v8; // r2
int v9; // r4
signed int v10; // r3
int v11; // r6
int v12; // r10
unsigned int v13; // r6
int v14; // r3
char *v15; // r11
char *v16;
int i; // r0
unsigned int v17; // r1
int v18; // r10
char *v19; // r2
int v20; // t1
int v22; // lr
int v23; // r1
char *v24; // r8
int v25; // r12
int j; // r4
int k; // r4
int v28; // r9
int v29; // r5
int v32; // r4
int v33; // r5
int v34; // r0
unsigned char byte_2B98[8] = {0x77, 0x23, 0x9D, 0xAC, 0x13, 0x27, 0xCF, 0xFE};
unsigned char v21[8] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
unsigned char unk_2BA0[128] = {
0xA8, 0xAF, 0x56, 0x98, 0x88, 0xEF, 0x40, 0x06, 0xFD, 0xAE, 0xE9, 0x9E, 0xB9, 0xEA, 0xAD, 0x52,
0xCC, 0xAB, 0x04, 0xCA, 0xEC, 0xEB, 0x12, 0x54, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00,
0xA8, 0xAF, 0x56, 0x98, 0x88, 0xEF, 0x40, 0x06, 0xFD, 0xAE, 0xE9, 0x9E, 0xB9, 0xEA, 0xAD, 0x52,
0xCB, 0xAA, 0x19, 0xE5, 0xEC, 0xEB, 0x12, 0x5E, 0xA0, 0xA6, 0xA8, 0xF3, 0x8B, 0xD6, 0xCD, 0x6F,
0xCB, 0xC0, 0x4F, 0xC1, 0xE0, 0xDA, 0x74, 0x00, 0x91, 0xBE, 0xC6, 0x83, 0xD0, 0xA6, 0x8D, 0x2E,
0xBE, 0xFC, 0x3F, 0xAD, 0x9B, 0xE0, 0x26, 0x52, 0xF5, 0xBA, 0x94, 0xD1, 0xB4, 0xA2, 0xDF, 0x7C,
0xDA, 0xF8, 0x6D, 0xFF, 0xFF, 0xE4, 0x74, 0x00, 0x91, 0xBE, 0xC6, 0x83, 0xD0, 0xA6, 0x8D, 0x2E,
0xBE, 0xFC, 0x3F, 0xAD, 0x9B, 0xE0, 0x26, 0x52, 0xF5, 0xBA, 0x94, 0xD1, 0xB4, 0xA2, 0xDF, 0x7C
};
unsigned char flag[128];
for(int i=0;i<16;i++){
unsigned char data[8];
unsigned char data1[8];
for(int j=0;j<8;j++){
data[j] = unk_2BA0[i*8 + j] ^ 0x88;
data1[j] = data[j];
}
v28 = 0;
while ( v28 != 28 )
{
v34 = data[0];
v32 = 0;
while ( v32 != 7 )
{
data[v32] = (data[v32]&0xf) + (data[v32+1]&0xf0);
++v32;
}
data[7] = (data[7]&0xf) + (v34&0xf0);
v14 = data[7];
v29 = 7;
while ( v29 >= 1 )
{
data[v29] = (data[v29]&0xf0) + (data[v29-1]&0xf);
--v29;
}
v33 = data[0] & 0xF0;
data[0] = (v14 & 0xF) + v33;
++v28;
}
for(j=0; j<8; j++) {
data[j] ^= byte_2B98[j];
data[j] ^= v21[j];
v21[j] = data1[j];
printf("%c", data[j]);
}
}
}
|
运行结果:
Android记去花指令还原算法
0x03 总结1、识别花指令
2、对Jumpout进行修复
3、动态调试
4、算法还原 |