其他
使用unidbg还原标准ollvm的fla控制流程平坦化
本文为看雪论坛精华文章
std::string calcKey(std::string data){
if (data.length()>10){
data="ceshi";
}else if(data.length()>30){
data="ollvm";
}else{
data="fla";
}
return data;
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_ollvmdemo2_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
hello=calcKey(hello);
return env->NewStringUTF(hello.c_str());
}
unsigned __int64 __usercall calcKey@<X0>(unsigned __int64 result@<X0>, __int64 a2@<X8>)
{
signed int v2; // w8
signed int v3; // w8
unsigned __int64 v4; // [xsp+10h] [xbp-40h]
__int64 v5; // [xsp+18h] [xbp-38h]
signed int v6; // [xsp+24h] [xbp-2Ch]
bool v7; // [xsp+3Fh] [xbp-11h]
unsigned __int64 v8; // [xsp+40h] [xbp-10h]
bool v9; // [xsp+4Fh] [xbp-1h]
v6 = 910266824;
v5 = a2;
v4 = result;
while ( v6 != -1929161090 )
{
switch ( v6 )
{
case -1578842400:
v6 = 56578246;
break;
case -1521928633:
v9 = v8 > 0x1E;
v6 = -124633339;
break;
case -124633339:
if ( v9 )
v3 = 1872828810;
else
v3 = 1744272424;
v6 = v3;
break;
case 56578246:
v6 = 1089662144;
break;
case 256858465:
if ( v7 )
v2 = 1938015978;
else
v2 = 1363893773;
v6 = v2;
break;
case 910266824:
v6 = 2056492010;
break;
case 1089662144:
result = sub_F8E4(v5, v4);
v6 = -1929161090;
break;
case 1363893773:
result = sub_F77C(v4);
v8 = result;
v6 = -1521928633;
break;
case 1493239651:
v6 = 1089662144;
break;
case 1697837166:
v6 = 56578246;
break;
case 1744272424:
result = sub_F830(v4, "fla");
v6 = 1697837166;
break;
case 1872828810:
result = sub_F830(v4, "ollvm");
v6 = -1578842400;
break;
case 1938015978:
result = sub_F830(v4, "ceshi");
v6 = 1493239651;
break;
case 2056492010:
result = sub_F77C(v4);
v7 = result > 0xA;
v6 = 256858465;
break;
}
}
return result;
}
思路:先射箭后画靶
一、手动还原
case -1521928633: //0xF694
v9 = v8 > 0x1E;
v6 = -124633339;
break;
case -124633339: //0xF6BC
if ( v9 )
v3 = 1872828810;
else
v3 = 1744272424;
v6 = v3;
break;
case 256858465: //0xF624
if ( v7 )
v2 = 1938015978;
else
v2 = 1363893773;
v6 = v2;
break;
case 1089662144: //0xF750
result = sub_F8E4(v5, v4);
v6 = -1929161090;
break;
case 1363893773: //0xF678
result = sub_F77C(v4);
v8 = result;
v6 = -1521928633;
break;
case 1744272424: //0xF710
result = sub_F830(v4, "fla");
v6 = 1697837166;
break;
case 1872828810: //0xF6E0
result = sub_F830(v4, "ollvm");
v6 = -1578842400;
break;
case 1938015978: //0xF648
result = sub_F830(v4, "ceshi");
v6 = 1493239651;
break;
case 2056492010: //0xF5F8
result = sub_F77C(v4);
v7 = result > 0xA;
v6 = 256858465;
break;
unsigned __int64 __usercall calcKey@<X0>(unsigned __int64 result@<X0>, __int64 a2@<X8>)
{
v6 = 910266824;
v5 = a2;
v4 = result;
while ( v6 != -1929161090 )
{
switch ( v6 )
{
....
case 910266824:
v6 = 2056492010;
break;
....
case 2056492010: //0xF5F8 第一个真实块
result = sub_F77C(v4);
v7 = result > 0xA;
v6 = 256858465;
break;
....
case 256858465: //0xF624 第二个真实块
if ( v7 )
v2 = 1938015978;
else
v2 = 1363893773;
v6 = v2;
}
}
return result;
}
.text:000000000000F470 loc_F470
.text:000000000000F470 LDR W8, [SP,#0x50+var_2C]
.text:000000000000F474 MOV W9, #0x567E
.text:000000000000F478 MOVK W9, #0x8D03,LSL#16
.text:000000000000F47C CMP W8, W9
.text:000000000000F480 STR W8, [SP,#0x50+var_44]
.text:000000000000F484 B.EQ loc_F76C
.text:000000000000F488 B loc_F48C
.text:000000000000F470 loc_F470
.text:000000000000F470 LDR W8, [SP,#0x50+var_2C]
.text:000000000000F474 B loc_F5F8 ; Keypatch modified this from:
.text:000000000000F474 ; MOV W9, #0x567E
.text:000000000000F478 ; ---------------------------------------------------------------------------
.text:000000000000F478 MOVK W9, #0x8D03,LSL#16
.text:000000000000F47C CMP W8, W9
.text:000000000000F480 STR W8, [SP,#0x50+var_44]
.text:000000000000F484 B.EQ loc_F76C
.text:000000000000F488 B loc_F48C
.text:000000000000F5F8 loc_F5F8
.text:000000000000F5F8
.text:000000000000F5F8 LDR X0, [SP,#0x50+var_40]
.text:000000000000F5FC BL sub_F77C
.text:000000000000F600 CMP X0, #0xA
.text:000000000000F604 CSET W8, HI
.text:000000000000F608 MOV W9, #1
.text:000000000000F60C AND W8, W8, W9
.text:000000000000F610 STURB W8, [X29,#var_11]
.text:000000000000F614 MOV W8, #0x5961
.text:000000000000F618 MOVK W8, #0xF4F,LSL#16
.text:000000000000F61C STR W8, [SP,#0x50+var_2C]
.text:000000000000F620 B loc_F778
.text:000000000000F618 MOVK W8, #0xF4F,LSL#16
.text:000000000000F61C STR W8, [SP,#0x50+var_2C]
.text:000000000000F620 B loc_F624 ; Keypatch modified this from:
.text:000000000000F620 ; B loc_F778
case 256858465:
if ( v7 ) //根据条件让第三个真实块不固定了
v2 = 1938015978;
else
v2 = 1363893773;
v6 = v2;
case 256858465:
if ( v7 ) //根据条件让第三个真实块不固定了
//直接跳转到0xF648
else
//直接跳转到0xF678
v6 = v2;
.text:000000000000F624 loc_F624
.text:000000000000F624
.text:000000000000F624 LDURB W8, [X29,#var_11]
.text:000000000000F628 MOV W9, #0xC6EA
.text:000000000000F62C MOVK W9, #0x7383,LSL#16
.text:000000000000F630 MOV W10, #0x5E0D
.text:000000000000F634 MOVK W10, #0x514B,LSL#16
.text:000000000000F638 TST W8, #1
.text:000000000000F63C CSEL W8, W9, W10, NE
.text:000000000000F640 STR W8, [SP,#0x50+var_2C]
.text:000000000000F644 B loc_F778
.text:000000000000F624 loc_F624
.text:000000000000F624
.text:000000000000F624 LDURB W8, [X29,#var_11]
.text:000000000000F628 MOV W9, #0xC6EA
.text:000000000000F62C MOVK W9, #0x7383,LSL#16
.text:000000000000F630 MOV W10, #0x5E0D
.text:000000000000F634 MOVK W10, #0x514B,LSL#16
.text:000000000000F638 CMP W8, #1 ; Keypatch modified this from:
.text:000000000000F638 ; TST W8, #1
.text:000000000000F63C B.EQ loc_F648 ; Keypatch modified this from:
.text:000000000000F63C ; CSEL W8, W9, W10, NE
.text:000000000000F640 B loc_F678 ; Keypatch modified this from:
.text:000000000000F640 ; STR W8, [SP,#0x50+var_2C]
0xF474 ---> 0xF5F8
0xF620 ---> 0xF624
0xF63C ---> 0xF648
0xF640 ---> 0xF678
0xF664 ---> 0xF750
0xF690 ---> 0xF694
0xF6B8 ---> 0xF6BC
0xF6D4 ---> 0xF6E0
0xF6D8 ---> 0xF710
0xF6FC ---> 0xF750
.text:000000000000F778 loc_F778
.text:000000000000F778
.text:000000000000F778 B loc_F470 //修改成nop
void __usercall calcKey(unsigned __int64 result@<X0>, __int64 a2@<X8>)
{
__int64 v2; // x0
unsigned __int64 v3; // [xsp+10h] [xbp-40h]
__int64 v4; // [xsp+18h] [xbp-38h]
bool v5; // [xsp+4Fh] [xbp-1h]
v4 = a2;
v3 = result;
if ( (unsigned __int64)sub_F77C(result) > 0xA == 1 )
{
sub_F830(v3, "ceshi");
}
else
{
v5 = (unsigned __int64)sub_F77C(v3) > 0x1E;
if ( v5 == 1 )
sub_F830(v3, "ollvm");
else
sub_F830(v3, "fla");
}
v2 = sub_F8E4(v4, v3);
sub_F77C(v2);
}
二、自动化反混淆
public class FlaTest {
private final AndroidEmulator emulator;
private final VM vm;
private final DvmClass mainActivityDvm;
public static void main(String[] args) {
FlaTest bcfTest = new FlaTest();
bcfTest.call_calckey();
}
private FlaTest(){
emulator = AndroidEmulatorBuilder
.for64Bit()
.build();
Memory memory = emulator.getMemory();
LibraryResolver resolver = new AndroidResolver(23);
memory.setLibraryResolver(resolver);
vm = emulator.createDalvikVM(null);
vm.setVerbose(false);
mainActivityDvm = vm.resolveClass("com/example/ollvmdemo2/MainActivity");
DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/resources/example_binaries/ollvm_fla/libnative-lib.so"), false);
dm.callJNI_OnLoad(emulator);
}
//主动调用目标函数
private void call_calckey(){
StringObject res = mainActivityDvm.callStaticJniMethodObject(emulator, "stringFromJNI()Ljava/lang/String;");
System.out.println(res.toString());
}
}
.text:000000000000F700 loc_F700
.text:000000000000F700 MOV W8, #0x50C6
.text:000000000000F704 MOVK W8, #0x35F,LSL#16
.text:000000000000F708 STR W8, [SP,#0x50+var_2C]
.text:000000000000F70C B loc_F778
//用来保存所有执行过的block
ArrayList<Pair<Long, Capstone.CsInsn[]>> blocks=new ArrayList<Pair<Long, Capstone.CsInsn[]>>();
//主动调用目标函数
private void call_calckey(){
//这里BlockHook就是按照一个block的触发
emulator.getBackend().hook_add_new(new BlockHook() {
@Override
public void hook(Backend backend, long address, int size, Object user) {
//这里的insns是整个block。
Capstone.CsInsn[] insns = emulator.disassemble(address, size,0);
blocks.add(new Pair<Long, Capstone.CsInsn[]>(address,insns));
}
},0x4000F44C,0x4000F44C+0x330,null);
//调用一个返回值为object的静态的jni函数
StringObject res = mainActivityDvm.callStaticJniMethodObject(emulator, "stringFromJNI()Ljava/lang/String;");
System.out.println(res.toString());
for(Pair<Long, Capstone.CsInsn[]> block : blocks){
System.out.println(String.format("block address:0x%x",block.getKey()) );
}
}
//如果指令在队列中。则返回true。这是用来筛选block。如果block中有非以下指令的。说明很有可能是一个真实块
private boolean OpstrContains(String opstr){
ArrayList<String> flags= new ArrayList(Arrays.asList("ldur","ldr","str","b","movz","movk","cmp","b.eq"));
if(flags.contains(opstr)){
return true;
}
return false;
}
//存储所有真实的block块
ArrayList<Pair<Long, Capstone.CsInsn[]>> readlyBlock=new ArrayList<Pair<Long, Capstone.CsInsn[]>>();
//过滤出真实块
private void LoadReadlyAddress(){
//第一个块肯定是要有的
readlyBlock.add(blocks.get(0));
for(int i=1;i<blocks.size();i++){
Pair<Long, Capstone.CsInsn[]> pdata=blocks.get(i);
Capstone.CsInsn[] insns=pdata.getValue();
Long address=pdata.getKey();
boolean isReadly=false;
//遍历所有指令。检查出真实块。保存起来
for(Capstone.CsInsn ins :insns){
if(readlyBlock.contains(address)){
continue;
}
if(!OpstrContains(ins.mnemonic)){
isReadly=true;
String opstr= ARM.assembleDetail(emulator,ins,address,false,false);
System.out.println(String.format("block readly address:0x%x opstr:%s",ins.address,opstr) );
break;
}
else{
// String opstr= ARM.assembleDetail(emulator,ins,address,false,false);
// System.out.println(String.format("block fla address:0x%x opstr:%s",ins.address,opstr) );
}
}
if(isReadly){
readlyBlock.add(new Pair<Long,Capstone.CsInsn[]>(address,insns));
}
}
}
LoadReadlyAddress();
String modulePath="unidbg-android/src/test/resources/example_binaries/ollvm_fla/libnative-lib.so";
byte[] sodata=readFile(modulePath);
//遍历真实块。然后直接修改成跳转真实块
for(int i=0;i<readlyBlock.size();i++){
if(i<readlyBlock.size()-1){
//获取当前真实块
Pair<Long, Capstone.CsInsn[]>block=readlyBlock.get(i);
System.out.println(String.format("curBlock address:0x%x",block.getKey()));
//获取下一个真实块
Pair<Long, Capstone.CsInsn[]>nextBlock=readlyBlock.get(i+1);
//取出当前真实块最后一个指令的地址
int end_address= GetEndAddress(block.getValue()).intValue();
if(end_address<=0){
continue;
}
//获取下一个真实块第一个指令的地址
int start_address=GetStartAddress(nextBlock.getValue()).intValue();
//这里是最后跳转出结束的地方。由于已经被我们nop掉了循环。所以如果下一个块是跳转出while的。可以直接跳过了
if(nextBlock.getKey().intValue()==0x4000f76c){
continue;
}
//准备转换汇编代码进行替换
try (Keystone keystone = new Keystone(KeystoneArchitecture.Arm64, KeystoneMode.LittleEndian)) {
int subAddress=start_address-end_address;
//用来patch修改的asm指令。这里是要计算出当前地址的相对地址跳转。所以上面要减一下。
String asmStr=String.format("b #0x%x",subAddress);
//这个是我们显示日志看结果的。看看和我们之前手动分析的是不是差不多
String showStr=String.format("b #0x%x",start_address);
// System.out.println(String.format("address:0x%x chang asm:%s",end_address,showStr));
//转换汇编
KeystoneEncoded encoded = keystone.assemble(asmStr);
byte[] patch = encoded.getMachineCode();
if (patch.length <=0) {
System.out.println("转换汇编失败");
return;
}
// System.out.println(bytesToHexString(patch));
//替换原来的字节数据
for(int y =0;y<patch.length;y++){
sodata[end_address+y]=patch[y];
}
}
}
}
//循环的地方给nop掉
byte[] nop_byte=new byte[]{0x1F,0x20,0x03,(byte)0xD5};
int nop_address=0xF778;
for(int y =0;y<nop_byte.length;y++){
sodata[nop_address+y]=nop_byte[y];
}
String savepath="unidbg-android/src/test/resources/example_binaries/ollvm_fla/libnative-lib.patch.so";
writeFile(sodata,savepath);
System.out.println("处理完成");
address:0xf484 chang asm:b #0xf5f8
address:0xf620 chang asm:b #0xf624
address:0xf644 chang asm:b #0xf648
address:0xf654 chang asm:b #0xf750
address:0xf758 chang asm:b #0xf76c
emulator.getBackend().hook_add_new(new BlockHook() {
@Override
public void hook(Backend backend, long address, int size, Object user) {
//这里的insns是整个block。
Capstone.CsInsn[] insns = emulator.disassemble(address, size,0);
// System.out.println(String.format("address:0x%x size:0x%x",address,insns.length));
if(brance_data==0){
blocks.add(new Pair<Long, Capstone.CsInsn[]>(address,insns));
}else{
branchBlocks.add(new Pair<Long, Capstone.CsInsn[]>(address,insns));
}
}
},0x4000F44C,0x4000F778,null);
//碰到csel的时候把w8的值修改下。控制走其他分支
emulator.getBackend().hook_add_new(new CodeHook() {
@Override
public void hook(Backend backend, long address, int size, Object user) {
//这里的insns是整个block。
Capstone.CsInsn[] insns = emulator.disassemble(address, size,0);
for(Capstone.CsInsn ins :insns){
//这里就是控制如果brance_data为0就固定走第一个分支。为1就固定走第二个分支
if(ins.mnemonic.equals("csel")){
int w9=emulator.getBackend().reg_read(Arm64Const.UC_ARM64_REG_W9).intValue();
int w10=emulator.getBackend().reg_read(Arm64Const.UC_ARM64_REG_W10).intValue();
if(brance_data==0){
emulator.getBackend().reg_write(Arm64Const.UC_ARM64_REG_W10,w9);
}else{
emulator.getBackend().reg_write(Arm64Const.UC_ARM64_REG_W9,w10);
}
}
}
}
},0x4000F44C,0x4000F778,null);
//调用一个返回值为object的静态的jni函数
StringObject res = mainActivityDvm.callStaticJniMethodObject(emulator, "stringFromJNI()Ljava/lang/String;");
System.out.println(res.toString());
brance_data=1;
StringObject res2 = mainActivityDvm.callStaticJniMethodObject(emulator, "stringFromJNI()Ljava/lang/String;");
System.out.println(res2.toString());
LoadReadlyAddress();
//获取当前块中的csel地址。如果没有csel则为0
Long cselAddress=GetCselddress(block.getValue());
//存在csel指令说明是有分支。然后要特殊处理
if(cselAddress>0){
//获取另一个分支的下一个真实块
int start_address2=GetBranchReadlyAddress(block.getKey());
try (Keystone keystone = new Keystone(KeystoneArchitecture.Arm64, KeystoneMode.LittleEndian)){
/*
*要改以下三行的汇编。前面获取出来的cselAddress是CSEL的地址。
* TST W8, #1
* CSEL W8, W9, W10, NE
* STR W8, [SP,#0x50+var_2C]
* */
int subAddress1=start_address-(cselAddress.intValue())+4;
int subAddress2=start_address2-(cselAddress.intValue())+4;
String asm1="cmp w8,#0x1";
String showAsm1=String.format("b.eq 0x%x",start_address);
String showAsm2=String.format("b #0x%x",start_address2);
// System.out.println(String.format("address:0x%x chang asm1:%s",cselAddress,showAsm1));
// System.out.println(String.format("address:0x%x chang asm2:%s",cselAddress,showAsm2));
String asm2=String.format("b.eq 0x%x",subAddress1);
String asm3=String.format("b 0x%x",subAddress2);
//转换汇编
KeystoneEncoded encoded = keystone.assemble(Arrays.asList(asm1,asm2,asm3));
byte[] patch = encoded.getMachineCode();
if (patch.length <=0) {
System.out.println("转换汇编失败");
return;
}
System.out.println(bytesToHexString(patch));
//这里去掉基址。再往上一个指令的位置开始写入
int replace_address=cselAddress.intValue()-4;
//替换原来的字节数据
for(int y =0;y<patch.length;y++){
sodata[replace_address+y]=patch[y];
}
}
continue;
}
__int64 __usercall calcKey@<X0>(__int64 a1@<X0>, __int64 a2@<X8>)
{
__int64 v2; // x0
__int64 v4; // [xsp+10h] [xbp-40h]
__int64 v5; // [xsp+18h] [xbp-38h]
v5 = a2;
v4 = a1;
if ( (unsigned __int64)sub_F77C(a1) > 0xA == 1 )
{
sub_F830(v4, "ceshi");
v2 = sub_F8E4(v5, v4);
}
else
{
v2 = sub_F77C(v4);
}
return sub_F77C(v2);
}
看雪ID:misskings
https://bbs.pediy.com/user-home-659397.htm
*本文由看雪论坛 misskings 原创,转载请注明来自看雪社区。
# 往期推荐
球分享
球点赞
球在看
点击“阅读原文”,了解更多!