[原创]百度加固逆向分析
最近一直在研究百度壳,发现网上这方面的资料非常少。所以我把自己做的发出来跟大家分享,共同学习进步。
下面开始:
一、init_array
我们发现init_array中存在多个函数地址,JNI_Onload为加密状态


动态调试在init_array上下断跟着进入一个大循环。发现他在此处对so进行了抹头操作。


之后遇到反调试崩溃退出。后来发现反调试检测了以下字段:
android_server
gdbserver
gdb
TracePid:
/proc/self/task/%s/status
isDebuggerConnected等。。
二、bypass
编写了一个loader程序调用该so中的JNI_Onload函数bypass壳代码和反调试。
#include <stdio.h>
#include <string.h>
#include <dlfcn.h>
#include <jni.h>
int main()
{
JavaVM* vm;
JNIEnv* env;
jint res;
JavaVMInitArgs vm_args;
JavaVMOption options[1];
options[0].optionString = "-Djava.class.path=.";
vm_args.version=0x00010002;
vm_args.options=options;
vm_args.nOptions =1;
vm_args.ignoreUnrecognized=JNI_TRUE;
printf("[+] dlopen libdvm.so\n");
void *handle = dlopen("/system/lib/libdvm.so", RTLD_LAZY);//RTLD_LAZY RTLD_NOW
if(!handle){
printf("[-] dlopen libdvm.so failed!!\n");
return 0;
}
//这里我先创建一个java虚拟机。因为JNI_ONload函数参数第一个参数为JavaVM。
typedef int (*JNI_CreateJavaVM_Type)(JavaVM**, JNIEnv**, void*);
JNI_CreateJavaVM_Type JNI_CreateJavaVM_Func = (JNI_CreateJavaVM_Type)dlsym(handle, "JNI_CreateJavaVM");
if(!JNI_CreateJavaVM_Func){
printf("[-] dlsym failed\n");
return 0;
}
res=JNI_CreateJavaVM_Func(&vm,&env,&vm_args)
void* si=dlopen("/data/local/tmp/libbaiduprotect.so",RTLD_LAZY);
if(si == NULL){
printf("[-] dlopen err!\n");
return 0;
}
typedef jint (*FUN)(JavaVM* vm,void* res);
FUN func_onload=(FUN)dlsym(si,"JNI_OnLoad");
if(func_onload==NULL)//我将断点下在了这里可以正好获取到JNI_Onload的函数地址。
return 0;
func_onload(vm,NULL);
return 0;
}
此时R0为dlsym返回的JNI_Onload地址.我们跳转过去,按C将字节码转成代码,发现此时JNI_Onload已经解密。

这时候我们将so从内存中dump出来。将原来的so头部和section修复回去。代码的解密已经完成。
3、字符串解密
这时候我们发现so的字符串还是加密状态,每个字符串对应一个解密函数。


经过分析发现。解密算法是一样的,只是异或的偏移地址不一样。
_BYTE *__fastcall sub_17A28(_BYTE *a1)
{
_BYTE *v1; // r4@1
int v2; // r0@1
int v3; // r1@2
unsigned int v4; // r5@3
int v5; // r3@3
int v6; // r7@3
_BYTE *v7; // r4@4
int v8; // r6@4
_BYTE *v9; // r5@4
unsigned int v10; // r2@5
signed int v11; // r0@5
unsigned int v12; // r1@7
int v13; // r3@7
int v14; // r7@7
int v15; // r0@9
int v16; // r2@9
_BYTE *v18; // [sp+4h] [bp-1Ch]@3
v1 = a1;
v2 = (int)(a1 - 1);
do
v3 = *(_BYTE *)(v2++ + 1);
while ( v3 );
v4 = (unsigned int)(v2 - (_DWORD)v1) >> 1;
v18 = j_j_malloc(v4 + 1);
v5 = 0;
v18[v4] = 0;
v6 = *v1;
if ( *v1 )
{
v7 = v1 + 2;
v8 = 0;
v9 = v18;
do
{
v10 = v7[~v5];
v11 = 208;
if ( v10 >= 0x3A )
v11 = 201;
v12 = (unsigned __int8)v6;
v13 = 16 * v6;
v14 = 16 * v6 + 144;
if ( v12 >= 0x3A )
v13 = v14;
v15 = v13 + v11 + v10;
v5 = 0;
v16 = 0;
if ( v8 != 8 )
v16 = v8;
*v9 = byte_5FB25[v16] ^ v15;//此处byte数组值不同。
v8 = v16 + 1;
++v9;
v6 = *v7;
v7 += 2;
}
while ( v6 );
}
return v18;
}分析完解密算法后,写了个ida py解密脚本。由于初次写ida脚本。借鉴了一些网上的代码。写的很糟糕。
import re
def getDispatchAddress(ea):
ea = FindText(ea, SEARCH_DOWN |SEARCH_NEXT | SEARCH_REGEX,0, 0,"BL sub")
#Message("Find in %x\n" % ea)
if ea == BADADDR:
#Message("Cann't find the Dispatch address")
address = BADADDR
else:
address = GetOperandValue(ea,0)
#Message("Dispatch address is %x\n" % address)
return address
def getFunctionInstructions(addr):
Instructions = []
DispatchBeginAddress = getDispatchAddress(addr)
if DispatchBeginAddress == BADADDR:
Message("Cann't find the Function Instructions List")
return None
DispatchEndAddress = GetFunctionAttr(DispatchBeginAddress,FUNCATTR_END)
i = DispatchBeginAddress
while True:
#Instructions.append(GetDisasm(i))
Instructions.append(i)
tmp = i + ItemSize(i)
if tmp < DispatchEndAddress:
i = i + ItemSize(i)
else:
break
address = i
return Instructions
def getaddr(addr):
for x in XrefsTo(addr,flags = 0):
#print hex(x.frm)
num=0
got=0
for i in getFunctionInstructions(x.frm):
mnem = GetMnem(i)
if "LDR" in mnem :
if num == 5:
#print hex(i)
op = GetOperandValue(i,1)
b3=Byte(op+3)<<24
b2=Byte(op+2)<<16
b1=Byte(op+1)<<8
b0=Byte(op+0)
got=b0+b1+b2+b3+(i+6)
if num == 6:
#print hex(i)
op = GetOperandValue(i,1)
b3=Byte(op+3)<<24
b2=Byte(op+2)<<16
b1=Byte(op+1)<<8
b0=Byte(op+0)
tmp=b0+b1+b2+b3
off=got+tmp
#print hex(off&0xffffffff)
return off&0xffffffff
break
num=num+1
break
def decrypt1(addr,str):
len1=len(str)>>1
out=''
for i in range(0,len1):
v1=208
v2=16*ord(str[2*i])
if ord(str[2*i+1])>=0x3a:
v1=201
if ord(str[2*i])>=0x3a:
v2=16*ord(str[2*i])+144
v3=(v1+v2+ord(str[2*i+1]))&0xff
out=out+chr(Byte(addr+i%8)^v3)
print out
def decrypt(addr1,str):#这里就是主要的解密
addr=getaddr(addr1)
if addr == None:
return ''
len1=len(str)>>1
out=''
for i in range(0,len1):
v1=208
v2=16*ord(str[2*i])
if ord(str[2*i+1])>=0x3a:
v1=201
if ord(str[2*i])>=0x3a:
v2=16*ord(str[2*i])+144
v3=(v1+v2+ord(str[2*i+1]))&0xff
out=out+chr(Byte(addr+i%8)^v3)
print out
#print hex(addr1)
MakeComm(addr1,out)
for x in XrefsTo(addr1,flags = 0):
MakeComm(x.frm,out)
def GetAllString(addr):
str_1=GetString(addr,-1,ASCSTR_C)
len_1=len(str_1)
if str_1 != '' and addr<0x5f6c8:
if re.search("^[0-9A-Z]+$",str_1):
#print addr,str_1
decrypt(addr,str_1)
return GetAllString(addr+len_1+1)
else:
decrypt(addr,str_1)
addr=0x5d8e8
GetAllString(addr)运行之后可以看到字符串已解密成功。
之后就可以继续分析so对dex的保护过程,我目前还未分析完成。大家有兴趣的可以一起分析。分析好后我会再发一篇文章。
libbaiduprotect.so我放到附件了
