样本只作为脱壳学习的一个样本,样本的内部逻辑并没有仔细分析,且样本较为简单,通用性差。本帖仅作为学习记录,欢迎大家一同交流。
首先先来官网看一下免费版包括的内容
上传一个Demo用于加固,Demo反编译结果如下
加固只针对Dex进行,且对类进行抽取,其效果如下
首先Shell创建了Application获取程序的起始控制权,之后在attachBaseContext通过Helper类加载manxi.so文件
之后的步骤都是在So层进行,所以需要对So层进行分析
So通过ollvm进行混淆,首先我们先通过Qiling模拟执行去除最简单的字符串混淆
首先我们需要提取到init_array所有函数起始地址和结束地址,这里我通过IDAPython进行提取
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
|
import
idaapi
import
lief
import
struct
from
pwn
import
elf, pack, unpack, u64
idaapi.msg_clear()
elf_raw
=
elf.ELF(
"D:/new/frida-agent-example-main/agent/node_modules/libmanxi_copy.so"
)
elf_patch
=
elf.ELF(
"D:/new/frida-agent-example-main/agent/node_modules/libmanxi_copy.so"
)
print
(elf_raw.entrypoint)
init_array
=
None
binary
=
lief.parse(
"D:/new/frida-agent-example-main/agent/node_modules/libmanxi_copy.so"
)
for
sec
in
binary.sections:
if
sec.name
=
=
".init_array"
:
init_array
=
sec
# Read Init_array
funcArray
=
[]
funcArray_Address
=
[]
index
=
0
while
index < init_array.size:
offset
=
u64(elf_raw.read(init_array.virtual_address
+
index,
8
))
funcArray.append(offset)
index
+
=
8
funcArray.pop()
for
func
in
funcArray:
function
=
[]
function.append(func)
function.append(idaapi.get_func(func).end_ea)
funcArray_Address.append(function)
print
(funcArray_Address)
|
提取到地址之后即可进行模拟执行,监听内存读写,由于字符串解密函数通过OLLVM进行混淆,所以存在对于状态变量的写入,这会导致对内存写入的Hook产生多余的结果,只需要通过判断偏移是否在.data段或者.rodata段即可判断是否对加密字符串进行写入,过滤掉不需要修改的内容后把解密内存写入文件中即可(如果某个函数不能模拟执行只需要从列表中去除即可)
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
|
import
lief
from
qiling.os.const
import
POINTER, UINT, STRING
from
qiling
import
Qiling
from
qiling.const
import
QL_VERBOSE
from
qiling.const
import
QL_INTERCEPT
from
qiling
import
os
from
qiling.os.mapper
import
QlFsMappedObject
import
struct
from
pwn
import
elf, pack, unpack, u64
soData
=
lief.parse(
"libmanxi.so"
)
datasec
=
None
rodatasec
=
None
for
sec
in
soData.sections:
if
sec.name
=
=
".data"
:
datasec
=
sec
elif
sec.name
=
=
".rodata"
:
rodatasec
=
sec
data_str
=
""
elf_patch
=
elf.ELF(
"D:/new/frida-agent-example-main/agent/node_modules/libmanxi_copy.so"
)
def
write_hook(ql: Qiling, access:
int
, address:
int
, size:
int
, value:
int
):
Addr
=
address
-
base_addr
if
(
Addr >
=
datasec.virtual_address
and
Addr <
=
datasec.virtual_address
+
datasec.size
)
or
(
Addr >
=
rodatasec.virtual_address
and
Addr <
=
rodatasec.virtual_address
+
rodatasec.size
):
# if value >= 32 and value <= 128:
print
(
f
"Write Address: {hex(address-base_addr)} Value:{hex(value)} Size:{hex(size)}"
)
if
size
=
=
1
:
data
=
ql.pack8(value)
elif
size
=
=
2
:
data
=
ql.pack16(value)
elif
size
=
=
4
:
data
=
ql.pack32(value)
elif
size
=
=
8
:
data
=
ql.pack64(value)
elf_patch.write(Addr, data)
# Read Init_array
funcArray
=
[
[
22616
,
23116
],
[
28832
,
29460
],
[
39664
,
40264
],
[
42856
,
43124
],
[
43932
,
44020
],
[
46660
,
46664
],
[
55196
,
62940
],
[
63672
,
63756
],
[
63756
,
63840
],
[
63840
,
63924
],
[
73424
,
73428
],
[
73516
,
73600
],
[
87628
,
88064
],
[
122504
,
122588
],
[
123876
,
124208
],
[
124208
,
124292
],
[
125200
,
125288
],
[
127652
,
128176
],
[
131420
,
131828
],
[
135164
,
135876
],
[
153412
,
154708
],
[
164120
,
167816
],
[
175060
,
175332
],
[
183120
,
183124
],
[
183124
,
183128
],
[
186856
,
186940
],
[
193592
,
193732
],
[
215616
,
216696
],
[
219564
,
219648
],
[
232264
,
232352
],
[
232920
,
232924
],
[
233080
,
233352
],
[
244248
,
253964
],
[
255484
,
256504
],
[
258116
,
258424
],
[
259408
,
262080
],
[
262080
,
262432
],
[
264236
,
264696
],
[
268872
,
269224
],
[
269688
,
270484
],
[
274292
,
276648
],
[
283864
,
284264
],
[
286580
,
287280
],
[
288384
,
288656
],
[
291236
,
291596
],
[
292296
,
293676
],
[
294012
,
295272
],
[
295272
,
295520
],
[
295976
,
296248
],
[
299480
,
299788
],
[
300236
,
307192
],
[
307500
,
307504
],
[
311600
,
312672
],
[
313304
,
313308
],
[
316048
,
316052
],
[
324380
,
325624
],
[
378800
,
380220
],
[
382004
,
382008
],
[
389160
,
389520
],
[
390088
,
390092
],
[
392360
,
392444
],
[
407960
,
407964
],
[
410060
,
410064
],
[
414476
,
415160
],
[
448668
,
457116
],
[
464888
,
464972
],
[
469092
,
469444
],
[
471132
,
471388
],
[
472156
,
472160
],
[
472180
,
472264
],
[
472512
,
472596
],
[
472596
,
472680
],
[
472680
,
472684
],
[
472684
,
472772
],
[
485160
,
489068
],
[
491672
,
493608
],
[
493608
,
493692
],
[
506756
,
506988
],
[
520552
,
521200
],
[
523132
,
523532
],
[
528344
,
528576
],
[
533168
,
539904
],
[
548616
,
550592
],
[
568596
,
569860
],
[
573168
,
575808
],
[
579536
,
579792
],
[
589616
,
589620
],
[
610480
,
610484
],
[
626444
,
626916
],
[
636500
,
636504
],
[
641936
,
643184
],
[
644460
,
644464
],
[
644816
,
644820
],
[
42852
,
42856
],
]
# # Init Binary File
ql
=
Qiling(
[
"D:/new/frida-agent-example-main/agent/node_modules/libmanxi.so"
],
r
"D:/new/rootfs/arm64_android"
,
verbose
=
QL_VERBOSE.DISASM,
)
base_addr
=
ql.mem.get_lib_base(
"libmanxi.so"
)
str
=
f
"Base Address: {hex(base_addr)}"
ql.hook_mem_write(write_hook)
for
func
in
funcArray:
ql.run(func[
0
]
+
base_addr, func[
1
]
+
base_addr
-
4
)
elf_patch.save(
"aaa.so"
)
|
解密之后就可以看到很多敏感字符串,但实际上该程序没有检测Root、Fart,只在程序启动时对Frida检测,也就是说可通过Attach来进行附加
首先需要绕过启动时的Frida检测,奇怪的是我Hook dlopen
和android_dlopen_ext
都无法获取到libmanxi.so的加载,但是通过Process.enumerateModules()
可以得到so,这也导致了后续难以定位到检测的位置和绕过。于是使用魔改Frida (可绕过检测)
简单Hook了一下程序中的SVC指令,通过Radare2
的/adj svc
可导出所有SVC指令的地址
其中addr是导出的结果,这里只针对openat进行过滤
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
function
hook_svc() {
console.log(
"=====Hook SVC====="
)
var
base_addr = Module.findBaseAddress(
"libmanxi.so"
);
addr.forEach(
function
(svc) {
var
offset = `0x` + svc.offset.toString(16)
Interceptor.attach(base_addr.add(offset), {
onEnter:
function
(args) {
if
(
this
.context.x8 == 0x38) {
console.log(
"Openat:"
+ ptr(
this
.context.x1).readCString())
}
},
onLeave:
function
(retval) {
}
})
})
}
|
发现其创建线程不断打开status文件进而查询TracerPID字段
首先尝试Hook LoadMethod进而进行脱壳
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
|
function
dump_memory(base, size) {
Java.perform(
function
() {
var
currentApplication = Java.use(
"android.app.ActivityThread"
).currentApplication();
var
dir =
"/data/local/tmp"
;
var
file_path = dir +
"/mydump.so"
;
var
file_handle =
new
File(file_path,
"w+"
);
if
(file_handle && file_handle !=
null
) {
Memory.protect(ptr(base), size,
'rwx'
);
var
libso_buffer = ptr(base).readByteArray(size);
file_handle.write(libso_buffer);
file_handle.flush();
file_handle.close();
console.log(
"[dump]:"
, file_path);
}
});
}
var
size = 0
var
dump_addr = 0
function
hook_libart() {
Java.perform(
function
() {
var
loadMethod = Module.findExportByName(
"libart.so"
,
"_ZN3art11ClassLinker10LoadMethodERKNS_7DexFileERKNS_13ClassAccessor6MethodENS_6HandleINS_6mirror5ClassEEEPNS_9ArtMethodE"
)
var
dumped = 0
Interceptor.attach(loadMethod, {
onEnter:
function
(args) {
if
(dumped == 0) {
dump_addr = ptr(args[1].add(8).readPointer())
size = args[1].add(16).readInt()
dump_memory(dump_addr, size)
dumped = 1
}
},
onLeave:
function
(retval) {}
})
})
}
function
main() {
hook_libart()
}
setImmediate(main)
|
然而脱下来的壳依旧只有Shell代码,即便我们通过延迟脱壳(即首先获取DexFile的begin和size,之后通过命令行调用脱壳函数)依旧如此
所以尝试别的办法,首先我们知道整体加固的原理是替换ClassLoader来加载原Dex,而Shell Dex尾部恰好存在着大量数据,可以猜测这就是被加密的Dex。由于Shell加载完成之后会将程序控制权归还给原程序,所以我们可以Hook performLaunchActivity
,在Activity启动之前获取系统ClassLoader进而得到其加载的Dex文件进行Dump。经过测试,最后一个覆盖的Dex文件为原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
|
function
dump_memory(base, size) {
Java.perform(
function
() {
var
currentApplication = Java.use(
"android.app.ActivityThread"
).currentApplication();
var
dir =
"/data/local/tmp"
;
var
file_path = dir +
"/mydump.so"
;
var
file_handle =
new
File(file_path,
"w+"
);
if
(file_handle && file_handle !=
null
) {
Memory.protect(ptr(base), size,
'rwx'
);
var
libso_buffer = ptr(base).readByteArray(size);
file_handle.write(libso_buffer);
file_handle.flush();
file_handle.close();
console.log(
"[dump]:"
, file_path);
}
});
}
function
getDexFile() {
Hook_Invoke()
Java.perform(
function
() {
var
ActivityThread = Java.use(
"android.app.ActivityThread"
)
ActivityThread[
"performLaunchActivity"
].implementation =
function
(args) {
var
env = Java.vm.getEnv()
var
classloader =
this
.mInitialApplication.value.getClassLoader()
var
BaseDexClassLoader = Java.use(
"dalvik.system.BaseDexClassLoader"
)
var
elementsClass = Java.use(
"dalvik.system.DexPathList$Element"
)
classloader = Java.cast(classloader, BaseDexClassLoader)
var
pathList = classloader.pathList.value
var
elements = pathList.dexElements.value
console.log(elements)
//console.log(elements.value)
for
(
var
i
in
elements) {
var
element;
try
{
element = Java.cast(elements[i], elementsClass);
}
catch
(e) {
console.log(e)
}
//之后需要获取DexFile
var
dexFile = element.dexFile.value
//getMethod(dexFile, classloader)
var
mCookie = dexFile.mCookie.value
//$h获取句柄
var
length = env.getArrayLength(mCookie.$h, 0)
//console.log(length)
var
Array = env.getLongArrayElements(mCookie.$h, 0)
//第一个不是DexFile
for
(
var
i = 1; i < length; ++i) {
var
DexFilePtr = Memory.readPointer(ptr(Array).add(i * 8))
var
DexFileBegin = ptr(DexFilePtr).add(Process.pointerSize).readPointer()
var
DexFileSize = ptr(DexFilePtr).add(Process.pointerSize * 2).readU32()
console.log(hexdump(DexFileBegin))
console.log(
"Size => "
+ DexFileSize.toString(16))
dump_memory(DexFileBegin,DexFileSize)
//dex_begin = DexFileBegin
//dex_size = DexFileSize
}
}
return
this
.performLaunchActivity(arguments[0], arguments[1])
}
})
}
|
反编译结果如下,可以发现onCreate的方法内容未被填充
所以尝试延迟Dump的时机,通过Hook ArtMethod::Invoke
来Dump,最终代码如下
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
|
function
Hook_Invoke() {
var
InvokeFunc = Module.findExportByName(
"libart.so"
,
"_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc"
)
Interceptor.attach(InvokeFunc, {
onEnter:
function
(args) {
console.log(args[1])
// args[0].add(8).readInt().toString(16) == 0
console.log(
"Method Idx -> "
+ args[0].add(8).readInt().toString(16) +
" "
+ args[0].add(12).readInt().toString(16))
dump_memory(dex_begin, dex_size)
//dump_memory(dex_begin, dex_size)
},
onLeave:
function
(retval) {}
})
}
var
dex_begin = 0
var
dex_size = 0
function
dump_memory(base, size) {
Java.perform(
function
() {
var
currentApplication = Java.use(
"android.app.ActivityThread"
).currentApplication();
var
dir =
"/data/local/tmp"
;
var
file_path = dir +
"/mydump.so"
;
var
file_handle =
new
File(file_path,
"w+"
);
if
(file_handle && file_handle !=
null
) {
Memory.protect(ptr(base), size,
'rwx'
);
var
libso_buffer = ptr(base).readByteArray(size);
file_handle.write(libso_buffer);
file_handle.flush();
file_handle.close();
console.log(
"[dump]:"
, file_path);
}
});
}
function
getDexFile() {
Hook_Invoke()
Java.perform(
function
() {
var
ActivityThread = Java.use(
"android.app.ActivityThread"
)
ActivityThread[
"performLaunchActivity"
].implementation =
function
(args) {
var
env = Java.vm.getEnv()
var
classloader =
this
.mInitialApplication.value.getClassLoader()
var
BaseDexClassLoader = Java.use(
"dalvik.system.BaseDexClassLoader"
)
var
elementsClass = Java.use(
"dalvik.system.DexPathList$Element"
)
classloader = Java.cast(classloader, BaseDexClassLoader)
var
pathList = classloader.pathList.value
var
elements = pathList.dexElements.value
console.log(elements)
//console.log(elements.value)
for
(
var
i
in
elements) {
var
element;
try
{
element = Java.cast(elements[i], elementsClass);
}
catch
(e) {
console.log(e)
}
//之后需要获取DexFile
var
dexFile = element.dexFile.value
//getMethod(dexFile, classloader)
var
mCookie = dexFile.mCookie.value
//$h获取句柄
var
length = env.getArrayLength(mCookie.$h, 0)
//console.log(length)
var
Array = env.getLongArrayElements(mCookie.$h, 0)
//第一个不是DexFile
for
(
var
i = 1; i < length; ++i) {
var
DexFilePtr = Memory.readPointer(ptr(Array).add(i * 8))
var
DexFileBegin = ptr(DexFilePtr).add(Process.pointerSize).readPointer()
var
DexFileSize = ptr(DexFilePtr).add(Process.pointerSize * 2).readU32()
console.log(hexdump(DexFileBegin))
console.log(
"Size => "
+ DexFileSize.toString(16))
dex_begin = DexFileBegin
dex_size = DexFileSize
}
}
return
this
.performLaunchActivity(arguments[0], arguments[1])
}
})
}
|
反编译效果如下
更多【蛮犀加固免费版脱壳】相关视频教程:www.yxfzedu.com