本文为看雪论坛精华文章
看雪论坛作者ID:krash
流程平坦化混淆是ollvm里面最难还原,最影响分析效率的一个混淆pass,最近花了点时间,终于把它搞掉了。
刚开始是打算使用IDA的微码进行反混淆,搜到了Rolf Rolles大佬的HexRaysDeob。
https://github.com/RolfRolles/HexRaysDeob
还有它的Python版本PyHexRaysDeob。
https://github.com/idapython/pyhexraysdeob
研究了下发现微码API确实是太难用了,而且很多分析好像都得自己做,所以我暂时放弃该方案。
随后转到了binja,发现这篇文章Dissecting LLVM Obfuscator Part 1
https://rpis.ec/blog/dissection-llvm-obfuscator-p1/
貌似简单可行,修改了下,用无名侠大佬基于Unicorn 的ARM64 OLLVM反混淆帖子的样本跑,失败了。
https://bbs.pediy.com/thread-252321.htm
看来还是得自己干。
下文中反混淆用的样本是无名侠的libvdog.so(vdog)和自己编译的另外一个小程序(cff)。
反混淆主要分两步:分析和修复。
...
state_var = n;
while(1) {
switch (state_var) {
case x:
code_region_x
case y:
code_region_y
case z:
code_region_z
case ...
}
}
switch语句各case的入口, 出口基本块.即上面各个case对应的代码区域(code_region_xxx).
case编号和case入口对应关系
各case出口的跳转目标case.对于条件跳转,我们还得知道真假分支和目标case的对应关系.
43 @ 0007067c bool cond:4_1 = x9.w9 != 0x76579ace
44 @ 00070680 uint64_t x8_1 = zx.q(x9.w9)
45 @ 00070684 if (cond:4_1) then 10 @ 0x704f0 else 47 @ 0x70688
423 @ 000710c0 if (x8_26.w8 == 0xdcf244e1) then 439 @ 0x71080 else 444 @ 0x710c4
104 @ 000707bc aop_init()
105 @ 000707c4 uint64_t x8_1 = 0xc88d21cd
106 @ 000707c8 goto 10 @ 0x704f0
134 @ 00070854 bool cond:12_1 = x0_4.w0 != 0
135 @ 00070868 if (cond:12_1) then 144 else 146
---
144 @ 00070868 uint64_t x8_1 = 0x91c5439 ; 真分支
145 @ 00070868 goto 155 @ 0x7086c ; 跳转到分发器
---
146 @ 00070868 uint64_t x8_1 = 0xf291f0e9 ; 假分支
147 @ 00070868 goto 155 @ 0x7086c ; 跳转到分发器
def resolve_branch_condition(state):
il_bb = state.il_basic_block
assert len(il_bb.incoming_edges) <= 1
if len(il_bb.incoming_edges) == 1:
return il_bb.incoming_edges[0].type == BranchType.TrueBranch
else:
return True
62 @ 00000814 bool cond:5_1 = x19_1.w19 == 0xcb0c4ceb
63 @ 0000081c uint64_t x7_1 = 0xd40c7864
64 @ 00000820 uint64_t x20 = zx.q(x1.w1)
65 @ 00000824 if (cond:5_1) then 16 @ 0x768 else 76 @ 0x828
313 @ 00070e54 bool cond:60_1 = x9.w9 != x8_22.w8
314 @ 00070e58 uint64_t x8_1 = zx.q(x9.w9)
315 @ 00070e5c if (cond:60_1) then 10 @ 0x704f0 else 320 @ 0x70e64
patch二进制
处理编译器优化移动的代码
patch二进制
处理编译器优化移动的代码
void foo1()
{
int state_var = 0;
while(1) {
switch (state_var) {
case 0: {
puts("enter case 0");
state_var = 1;
break;
}
case 1: {
puts("enter case 1 or 2");
puts("enter case 1");
state_var = 2;
puts("exit case 1 or 2");
break;
}
case 2: {
puts("enter case 1 or 2");
puts("enter case 2");
state_var = 3;
puts("exit case 1 or 2");
break;
}
case 3: {
puts("enter case 3");
state_var = 4;
break;
}
}
if (state_var == 4) {
break;
}
}
}
void foo2()
{
int state_var = 0;
while(1) {
switch (state_var) {
case 0: {
puts("enter case 0");
state_var = 1;
break;
}
case 1: case 2: {
puts("enter case 1 or 2");
if (state_var == 1) {
puts("enter case 1");
state_var = 2;
} else {
puts("enter case 2");
state_var = 3;
}
puts("exit case 1 or 2");
break;
}
case 3: {
puts("enter case 3");
state_var = 4;
break;
}
}
if (state_var == 4) {
break;
}
}
}
拷贝从分发器入口,到case入口的所有代码到patch代码区域,并在执行case代码前,执行这些复制过来的指令。
同样的,在离开case时,拷贝case出口到分发器入口所有代码到patch代码区域,并在执行后继case代码前,执行这些复制过来的指令。
CMP W9, #0
CSET W9, GT
EOR W8, W9, W8
TBNZ W8, #0, to_dispatcher ; 出口1
CBZ W0, to_dispatcher ; 出口2
...
CMP W8, #0x30 ; '0'
MOV W8, #0xdeadbeef ; next case
CSEL W24, W24, W8, EQ
B to_dispatcher ; 出口3
...
while(1) {
switch (state_var) {
....
case x: {
state_var = next_case;
if (cond) {
puts("exit 1");
break;
}
puts("exit 2");
break;
}
....
}
找到case的所有出口
patch case 的出口,使其跳转到我们的path代码
拷贝patch case 出口时覆盖掉的指令到patch代码区域
拷贝case出口到分发器入口所有代码到patch代码区域
拷贝从分发器入口,到case入口的所有代码到patch代码区域
使用汇编器,生成从patch代码到真实目标跳转指令
;原始代码
tbnz w8, #0, to_dispatcher ; 出口1
cont:
cbz w0, to_dispatcher ; 出口2
;修复出口1后的代码
b patch_code ; 跳转到我们的patch代码
cont:
cbz w0, to_dispatcher ; 出口2
; patch 代码区域
patch_code:
tbnz w8, #0, to_case_entry ; 准备进入真实目标
b cont ; 返回
to_case_entry:
patch时覆盖掉的指令
离开当前基本块到进入分发器的所有指令
离开分发器到目标case的所有指令
跳转到目标case
这个基本块位于cff 0x00000798
csel w7, w15, w14, eq
mov w20, w0
b to_dispatcher
cset w7, eq ; !!!不能在此直接跳转到patch代码, 下面的mov指令是活跃的!!!
mov w20, w0
b patch_code
; patch代码区域
patch_code:
cbnz w7, true_branch (0x8)
cbz w7, false_branch
true_branch:
patch时覆盖掉的指令
离开当前基本块到进入分发器的所有指令
离开分发器到真分支入口的所有指令
跳转到真分支
false_branch:
patch时覆盖掉的指令
离开当前基本块到进入分发器的所有指令
离开分发器到假分支的所有指令
跳转到假分支
MOV W0, W20 ; W0活跃
MOV W19, W7
CMP W19, W10
loc_4000000
MOV W0, W20 ; 复制的dispatcher代码
MOV W19, W7
CMP W19, W10
CMP W19, W11
CMP W19, W8
MOV W7, W19
MOV W20, W0
B loc_7D0 ; 0x2495548b 的后继
STR W9, [X19,#0x1C]
B loc_704F0 ; 跳转到分发器
loc_4000000
STR W9, [X19,#0x1C] ; 0x000704dc基本块指令
MOV W9, W8 ; 分发器指令
CMP W9, W27
CMP W9, W23
CMP W9, W26
CMP W9, W20
MOV W8, #0x76579ACD
CMP W9, W8
...
MOV W8, #0x667472C5
CMP W9, W8
MOV W8, W9
B loc_707E0 ; 0x667472c5对应的case入口
loc_704CC
LDR X8, [X19,
MOV W8,
LDR W9, [X19,
; 下落到0x000704dc
STR W9, [X19,
B loc_704F0 ; 跳转到分发器
loc_704CC
LDR X8, [X19,
MOV W8,
B loc_4006800 ; 跳转到修复代码, 这里覆盖掉了原始指令 LDR W9, [X19,
loc_4006800
LDR W9, [X19,#0x18] ; loc_704CC被覆盖的指令
STR W9, [X19,#0x1C] ; 0x000704dc基本块指令
MOV W9, W8 ; 复制分发器代码
CMP W9, W27
CMP W9, W23
CMP W9, W26
CMP W9, W20
MOV W8, #0x76579ACD
CMP W9, W8
...
MOV W8, #0x6FA40FC9
CMP W9, W8
MOV W8, W9
B loc_70778 ; 0x6fa40fc9的后继
unsigned int target_function(unsigned int n)
__attribute((__annotate__(("fla"))))
__attribute((noinline))
{
unsigned int mod = n % 4;
unsigned int result = 0;
if (mod == 0) result = (n | 0xBAAAD0BF) * (2 ^ n);
else if (mod == 1) result = (n & 0xBAAAD0BF) * (3 + n);
else if (mod == 2) result = (n ^ 0xBAAAD0BF) * (4 | n);
else result = (n + 0xBAAAD0BF) * (5 & n);
return result;
}
unsigned int __fastcall target_function(unsigned int n)
{
unsigned int v2;
v2 = n & 3;
if ( (n & 3) == 0 )
return (n | 0xBAAAD0BF) * (n ^ 2);
if ( v2 == 1 )
return (n & 0xBAAAD0BF) * (n + 3);
if ( v2 == 2 )
return (n ^ 0xBAAAD0BF) * (n | 4);
return (n - 1163210561) * (n & 5);
}
jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
int v2;
_JNIEnv *v3;
_BOOL4 v5;
crazy *v6;
int v7;
crazy *v8;
_JNIEnv *v9;
const char *v10;
_JNIEnv *v12;
crazy *v13;
char *v14;
const char *v15;
crazy *v16;
_JNIEnv *v17;
int v18;
crazy *v19;
unsigned int v20;
crazy *v21;
__int64 v23;
__int64 v24;
__int64 v25;
__int64 v26;
char *v27;
void *v28;
int v29;
int v30;
JavaVM *v31;
__int64 *v32;
__int64 *v33;
crazy::String *v34;
void (__fastcall **v35)(JavaVM *, void *);
const char *v36;
int v37;
int v38;
int v39;
char *v40;
__int64 *v41;
const char *v42;
int v43;
__int64 *v44;
FILE *v45;
int v46;
crazy *v47;
__int64 v48;
int v49;
int v50;
crazy *v51;
char *v52;
crazy *v53;
crazy *v54;
_QWORD v55[3];
_QWORD *v56;
const char *v57;
char *v58;
int v59;
const char *v60;
JavaVM *v61;
_QWORD v62[32];
__int64 v63;
v28 = reserved;
v31 = vm;
v63 = *(_QWORD *)off_DFF90;
v29 = v2;
v30 = v2;
v32 = &v26;
v33 = &v25;
v34 = (crazy::String *)&v24;
v35 = (void (__fastcall **)(JavaVM *, void *))&v23;
v54 = 0LL;
v61 = vm;
v55[0] = (char *)*vm + 48;
v62[0] = *(_QWORD *)v55[0];
if ( ((unsigned int (__fastcall *)(JavaVM *, crazy **, __int64))v62[0])(vm, &v54, 65540LL) != 0 )
return -1;
v60 = (const char *)v54;
v61 = *(JavaVM **)v54;
v55[0] = v61 + 219;
v62[0] = v61[219];
((void (*)(void))v62[0])();
v16 = v54;
*off_DFFF8 = (__int64)v54;
v18 = crazy::GetApiLevel(v16, v17);
v19 = v54;
*off_DFF18 = v18;
v47 = v19;
v10 = (const char *)crazy::GetPlatformVersion(v19, v9);
if ( strchr(v10, 77) != 0LL )
*off_DFF18 = 23;
v38 = *off_DFF18;
if ( v38 > 23 )
*off_DFEE8 = 1;
v49 = sub_2E738();
if ( v49 == 2 )
{
v53 = v54;
v7 = sub_76F60(v53) & 1 ? -1618151004 : -990688990;
if ( v7 > -990688991 )
return -1;
}
v44 = v32;
memset(v32, 0, 0x7D0u);
v27 = (char *)v32;
v20 = getpid();
sprintf(v27, "/proc/%d/cmdline", v20);
v45 = fopen(v27, "r");
if ( v45 != 0LL )
{
v41 = v33;
memset(v33, 0, 0x7D0u);
v42 = (const char *)v33;
fscanf(v45, "%s", v33);
fclose(v45);
v5 = strchr(v42, 58) == 0LL;
if ( v5 || (v52 = strstr(v42, "sg.bigo.enterprise.live:service"), v52 != 0LL) )
anti_debug_start();
}
v43 = *off_DFF18;
if ( v43 == 15 )
j_aop_init();
anti_section_hook();
v13 = (crazy *)crazy::checkSignature_1(v54, v12);
if ( ((unsigned __int8)v13 & 1) == 0 )
crazy::AbortProcess(v13);
v14 = (char *)sub_2E998();
v40 = v14;
if ( *v14 )
{
crazy::GetPackageName((crazy *)v14);
v62[0] = v34;
v36 = *(const char **)v34;
v6 = (crazy *)strcmp(v36, v40);
v37 = (int)v6;
if ( v37 != 0 )
crazy::AbortProcess(v6);
crazy::String::~String(v34);
}
v46 = sub_2E738();
if ( v46 == 1 )
{
v50 = sub_F688();
if ( v50 == 0 )
return -1;
}
v51 = v54;
v8 = (crazy *)crazy::checkdex_1(v54, v3);
if ( ((unsigned __int8)v8 & 1) == 0 )
crazy::AbortProcess(v8);
v39 = sub_E7EC(*off_DFE98[0], "JNI_OnLoad", v35);
if ( v39 != 0 )
(*v35)(v31, v28);
v15 = (const char *)sub_2E728();
v48 = strlen(v15);
if ( v48 != 0 )
{
v56 = v62;
v21 = (crazy *)memset(v62, 0, sizeof(v62));
crazy::GetPackageName(v21);
v61 = (JavaVM *)v55;
v57 = (const char *)v55[0];
crazy::String::~String((crazy::String *)v55);
v58 = (char *)v62;
v60 = (const char *)sub_2E728();
sprintf(v58, "/data/data/%s/.hide/%s", v57, v60);
v59 = remove(v58);
}
return 65540;
}
看雪ID:krash
https://bbs.pediy.com/user-240967.htm
推荐文章++++