某vmp殼原理分析筆記
分析的樣本爲某數字公司最新免費殼子。之前的殼子已經被很多大佬分析了,這篇筆記的主要目的是比較詳細的分析下該vmp殼子的原理,數字殼子主要分爲反調試,linker,虛擬機三部分。筆記結構如下:
-
反調試
- 時間反調試
- rtld_db_dlactivity反調試
- traceid反調試
- 端口反調試
-
linker部分:用來加載第二個so
- 裝載
- 創建soinfo
- 鏈接
- dump second so
-
虛擬機部分:解釋執行保護的代碼
- dump dex
- onCreate分析
- 虛擬機入口
準備部分
因爲懶得hook系統函數,每次過反調試就要手工更改寄存器的值,所以用IDAPython來模擬手工調試,下面是一些輔助函數,其中addBrk根據模塊名和偏移地址下斷點,fn_f7, fn_f8, fn_f9分別模擬f7, f8, f9.
def getModuleBase(moduleName):
base = GetFirstModule()
while (base != None) and (GetModuleName(base).find(moduleName) == -1):
base = GetNextModule(base)
if base == None:
print "failed to find module: " + moduleName
return None
else:
return base
def addBrk(moduleName, functin_offset):
base = getModuleBase(moduleName)
AddBpt(base + functin_offset)
def fn_f7():
idaapi.step_into()
GetDebuggerEvent(WFNE_SUSP | WFNE_SUSP, -1)
def fn_f8():
idaapi.step_over()
GetDebuggerEvent(WFNE_SUSP | WFNE_CONT, -1)
def fn_f9():
idaapi.continue_process()
GetDebuggerEvent(WFNE_SUSP | WFNE_CONT, -1)
def delBrk(moduleName, function_offset):
base = getModuleBase(moduleName)
DelBpt(base + function_offset)
時間反調試反調試
反調試的第一處就是時間反調試,在反調試的開始調用time獲取時間,在結束再調用一次time,當差值大於3就會觸發反調試。因爲使用腳本過反調試,所以兩次時間差肯定是小於3的,所以只是用腳本簡單的輸出時間就可以了。0x19FC是time在libjiagu.so的plt段地址。
#add breakpoint at time
addBrk("libjiagu.so", 0x19FC)
fn_f9()
delBrk("libjiagu.so", 0x19FC)
print("first call time")
lr = GetRegValue("LR")
AddBpt(lr)
fn_f9()
r0 = GetRegValue("R0")
DelBpt(lr)
print("first get time: %x" % (r0))
rtld_db_dlactivity反調試
rtld_db_dlactivity函數:這個函數默認情況下爲空函數,這裏的值應該爲0,而當有調試器時,這裏會被改爲斷點指令,所以可以被用來做反調試。libjiagu.so的sub_1E9C就是用來查找rtld_db_dlactivity函數的地址的。sub_1E9C的代碼就不貼了。過這個反調試我們就可以在sub_1E9C返回後,獲取到rtld_db_dlactivity的地址,然後將其修改爲nop指令0x46C0。
#add breakpoint at sub_1E9C
addBrk("libjiagu.so", 0x1E9c)
fn_f9()
delBrk("libjiagu.so", 0x1E9c)
print("call sub_1E9C")
#add breakpoint at return from sub_1E9C
lr = GetRegValue("LR")
AddBpt(lr)
fn_f9()
DelBpt(lr)
#get rtld_db_dlactivity address
r0 = GetRegValue("R0")
print("rtld_db_dlactivity address is: %x" % (r0 - 1))
#set rtld_db_dlactivity nop
PatchDword(r0 - 1, 0x46c0)
tracepid反調試
tracepid反調試:當我們使用Ptrace方式跟蹤一個進程時,目標進程會記錄自己被誰跟蹤。過tracepid反調試可以在調用strtol後,將其返回值修改爲0.
#add breakpoint at strtol
addBrk("libjiagu.so", 0x1A80)
fn_f9()
delBrk("libjiagu.so", 0x1A80)
print("call strtol")
lr = GetRegValue("LR")
#add breakpoint at return from strtol
AddBpt(lr)
fn_f9()
DelBpt(lr)
r0 = GetRegValue("R0")
print("get trace id: %x" % (r0))
SetRegValue(0, "R0")
端口反調試
檢測IDA的默認端口23946是否被佔用,通過跟蹤,可以發現sub_6DD0()函數判斷23946端口是否被佔用。原理就是查看/proc/net/tcp,過端口反調試,就可以通過更改sub_6DD0的返回值。
#add breakpoint at strtol
addBrk("libjiagu.so", 0x1A80)
fn_f9()
delBrk("libjiagu.so", 0x1A80)
print("call strtol")
lr = GetRegValue("LR")
#add breakpoint at return from strtol
AddBpt(lr)
fn_f9()
DelBpt(lr)
r0 = GetRegValue("R0")
print("get trace id: %x" % (r0))
SetRegValue(0, "R0")
時間反調試
在反調試的最後還會調用一次time,計算差值,因爲使用腳本過得,所以時間差值肯定小於3,這裏只是簡單輸出一下時間
#add break point at time in sub_6EF8
addBrk("libjiagu.so", 0x19FC)
fn_f9()
delBrk("libjiagu.so", 0x19FC)
print("second call time")
lr = GetRegValue("LR")
AddBpt(lr)
fn_f9()
DelBpt(lr)
r0 = GetRegValue("R0")
print("second get time: %x" % (r0))
#SetRegValue(r0 + 2, "R0")
Linker部分
這部分的主要作用是手工實現加載鏈接第二個so,並且調用第二個的so的JNI_Onload方法。因爲篇幅問題,只列出一些核心的步驟,具體的調試的方法是在case 31的BLX LR處下斷點,跟蹤sub_7BBC函數,就可以分析linker部分。既然是手工實現linker,那linker的主要功能就必不可少:裝載和鏈接。下面就對應libjiagu.so中的函數來分析具體的裝載與鏈接過程。分析的起點爲sub_3DBC, sub_3DBC中調用了sub_4780,sub_3D60,sub_33F8,分別對應裝載,創建soinfo,鏈接三個過程。首先定義兩個後面會用到的數據結構,這兩個數據結構的來源是通過動態調試分析出的,是後續很多函數的參數類型,和libjiagu.so的真實實現可能會有出入。
struct EncryptSoInfo {
void *encryptSo; //指向壓縮加密的so
int sizeOfEncryptSo; //壓縮加密so的大小
void *uncompressedSo; //解壓後的so位置
int sizeOfUncompressSo; //解壓後的so大小
int num; //表示某種類型,在後面會用到一次,具體含義不知道
char name[4]; //第二個so的名字
char data[124];
};
//類似於標準linker中的ELFReader
struct MElfReader {
void * soinfo
Byte[52] header; // offset 4
int num_program_header; //段表的個數 offset 56
ProgreamHeaderTable *pht; //段表 offset 60
int unkonw; // offset 64
void *load_base; //segment 起始地址 offset 68
int size; //offset 72
void *load_bias; //offset 76
};
裝載
這一步驟libjiagu.so的實現和標準linker的實現十分相似。先說下標準linker的做法,創建ElfReader對象,通過 ElfReader 對象的 Load 方法將 SO 文件裝載到內存。
//標準linker的裝載
bool ElfReader::Load() {
return ReadElfHeader() && //讀取elf header
VerifyElfHeader() && //驗證elf header
ReadProgramHeader() && //讀取program header
ReserveAddressSpace() && //分配空間
LoadSegments() && //按照program header的指示裝載segments
FindPhdr(); //找到裝載後的phdr
}
下面看下libjiagu.so的實現,只是少了驗證elf header的環節。
int __fastcall sub_4780(MELFReader *a1, EncryptSoInfo *a2)
{
Byte *v2; // r5@1
Byte *v3; // r4@1
int result; // r0@3
v2 = a2;
v3 = a1;
if ( a2 && sub_40B0(a1, a2) //讀取elf header
&& sub_41A8(v3, v2) //讀取program header
&& sub_4424((int)v3, (int)v2) && //分配空間
sub_4448(v3, v2) ) //按照program header的指示裝載segments
result = sub_46CC(v3); //找到裝載後的phdr
else
result = 0;
return result;
}
load ELF Header
裝載的第一步就是加載ELF Header,這一過程比較簡單,直接看代碼
int __fastcall sub_40B0(MELFReader *a1, EncryptSoInfo *a2)
{
Byte *v2; // r4@1
const void *base; // r1@1
int v4; // r4@3
Byte *v6; // r5@5
_DWORD *v7; // r0@5
_DWORD *v8; // r6@5
v2 = a2;
base = (const void *)*((_DWORD *)a2 + 2); //a2->uncompressedSo
if ( base
&& *((_DWORD *)v2 + 3) > 0x34u //a2->int sizeOfUncompressSo
&& (v6 = a1 + 4,
memcpy(a1 + 4, base, 0x34u), //拷貝加密的後的ELF Header
v7 = sub_6534(*((_DWORD *)v2 + 40)), //新建解密對象v7
(v8 = v7) != 0)
&& sub_58F0((int)v7, (int)v6, 52) //初始化解密對象
&& (v4 = (*(int (__fastcall **)(_DWORD *))(*v8 + 12))(v8)) != 0 //解密加密後的so
)
{
(*(void (__fastcall **)(_DWORD *))(*v8 + 4))(v8); //析構解密對象
}
else
{
v4 = 0;
}
return v4;
}
load program header
這一過程也比較簡單,利用上一步讀取的program header,得到program header數量,然後分配空間,拷貝加密後到的program header table到分配的空間,然後解密。
int __fastcall sub_41A8(MELFReader *a1, EncryptSoInfo *a2)
{
Byte *v2; // r5@1
Byte *v3; // r4@1
size_t v4; // r0@2
size_t v5; // r6@3
void *v6; // r0@3
_DWORD *v7; // r0@4
_DWORD *v8; // r5@4
int v9; // r4@6
v2 = a2;
v3 = a1;
if ( a2
&& (v4 = *((_WORD *)a1 + 24), //program header 數量
*((_DWORD *)v3 + 14) = v4, ((v4 - 1) & 0xFFFF) <= 0x7FF)
&& (v5 = 32 * v4, *((_DWORD *)v3 + 16) = 32 * v4, v6 = calloc(v4, 0x20u), // 分配空間
(*((_DWORD *)v3 + 15) = v6) != 0)
&& (memcpy(v6, (const void *)(*((_DWORD *)v2 + 2) + *((_DWORD *)v3 + 8)), v5), //拷貝段表
v7 = sub_6534(*((_DWORD *)v2 + 41)),
(v8 = v7) != 0)
&& sub_58F0((int)v7, *((_DWORD *)v3 + 15), *((_DWORD *)v3 + 16))
&& (v9 = (*(int (__fastcall **)(_DWORD *))(*v8 + 12))(v8)) != 0 ) //解密段表
{
(*(void (__fastcall **)(_DWORD *))(*v8 + 4))(v8);
}
else
{
v9 = 0;
}
return v9;
}
計算加載所需的空間並分配空間
ELF文件中所有類型爲LOAD的segment都需加載到內存,這一步驟主要是計算加載所需要的空間並分配空間。這裏需要說明一下loadbias,因爲so可以指定加載基址,但指定的基址可能不是頁對齊,所以實際加載的基址可能會和指定基址有個差值,loadbias用來保存這個差值。但一般so都是可以加載到任意地址的,所以loadbias的值一般就是so實際加載的基址。
signed int __fastcall sub_43A8(MELFReader *a1)
{
Byte *v1; // r4@1
int size; // r0@1
signed int result; // r0@2
_BYTE *v4; // r5@3
_BYTE *v5; // r0@3
int v6; // r5@4
void *addr; // [sp+Ch] [bp-14h]@1
v1 = a1;
//計算加載所需的空間並且將加載的基址保存在addr中
size = sub_4264(*((_DWORD *)a1 + 15), *((_DWORD *)a1 + 14), (unsigned int *)&addr, 0);
*((_DWORD *)v1 + 18) = size;
//mmap分配空間
if ( size && (v4 = addr, v5 = mmap(addr, size, 0, 34, -1, 0), v5 != (_BYTE *)-1) )
{
v6 = v5 - v4; //計算 loadbias
*((_DWORD *)v1 + 17) = v5;
result = 1;
*((_DWORD *)v1 + 19) = v6;
}
else
{
result = 0;
}
return result;
}
sub_4264的原理,因爲篇幅原因就不貼代碼了,就是循環段表,讀取每個需要load的段,然後找到加載的最小虛擬地址,和最大的虛擬地址,頁對齊後,取差值就是load size。
load segment
遍歷 program header table,找到類型爲 PT_LOAD 的 segment:
- 計算 segment 在內存空間中的起始地址 segstart 和結束地址 seg_end,seg_start 等於虛擬偏移加上基址load_bias,同時由於 mmap 的要求,都要對齊到頁邊界得到 seg_page_start 和 seg_page_end。
- 計算 segment 在文件中的頁對齊後的起始地址 file_page_start 和長度 file_length。
-
使用mprotect和memcpy將段映射到內存,指定映射地址爲 seg_page_start,長度爲 file_length,文件偏移爲 file_page_start。
-
signed int __fastcall sub_4448(MELFReader *a1, EncryptSoInfo *a2) { Byte *v2; // r6@1 unsigned int v3; // r2@3 int v4; // r4@4 int v5; // r5@7 int v6; // r3@7 int v7; // r5@7 int v8; // r12@8 int v9; // r2@8 int v10; // r10@8 unsigned int v11; // r11@8 unsigned int v12; // r12@8 bool v13; // cf@8 bool v14; // zf@8 unsigned int v15; // r8@8 void *v16; // r7@8 int v17; // r12@8 int v18; // r10@8 void *v19; // r0@16 signed int v20; // r2@19 size_t n; // [sp+4h] [bp-34h]@10 unsigned int v23; // [sp+8h] [bp-30h]@2 Byte *v24; // [sp+Ch] [bp-2Ch]@1 v24 = a2; v2 = a1; if ( a2 ) { v23 = *((_DWORD *)a2 + 3); // 解壓後的so的起始地址 if ( *((_DWORD *)a2 + 3) ) { v3 = *((_DWORD *)a1 + 14); // segment 數量 if ( !v3 ) return 1; v4 = 0; while ( 1 ) { while ( 1 ) // 找到類型爲LOAD的段 { v5 = *((_DWORD *)v2 + 15); // 段表起始地址 v6 = *(_DWORD *)(v5 + 32 * v4); // 迭代讀取段表 v7 = v5 + 32 * v4; if ( v6 == 1 ) break; if ( v3 <= ++v4 ) return 1; } v8 = *(_DWORD *)(v7 + 4); // offset segment在文件的偏移 v9 = *(_DWORD *)(v7 + 16); // filesz segment在so中大小 v10 = *((_DWORD *)v2 + 19) + *(_DWORD *)(v7 + 8);// segment 在虛擬內存的結束地址 v11 = v8 & 0xFFFFF000; v12 = v8 + v9; v13 = v23 >= v12; v14 = v23 == v12; v15 = (*(_DWORD *)(v7 + 20) + 4095 + v10) & 0xFFFFF000; v16 = (void *)(v10 & 0xFFFFF000); v17 = v12 - v11; v18 = v10 + v9; if ( v14 || !v13 ) break; n = v17; if ( mprotect(v16, v15 - (_DWORD)v16, 3) == -1 ) break; if ( n ) memcpy(v16, (const void *)(*((_DWORD *)v24 + 2) + v11), n); if ( *(_DWORD *)(v7 + 24) & 2 && v18 & 0xFFF ) memset((void *)v18, 0, 4096 - (v18 & 0xFFF)); v19 = (void *)((v18 + 4095) & 0xFFFFF000); if ( v15 > (unsigned int)v19 ) memset(v19, 0, v15 - (_DWORD)v19); v20 = *(_DWORD *)(v7 + 24) & 1 ? 4 : 0; if ( mprotect(v16, v15 - (_DWORD)v16, *(_DWORD *)(v7 + 24) & 2 | (*(_DWORD *)(v7 + 24) << 29 >> 31) | v20) == -1 ) break; v3 = *((_DWORD *)v2 + 14); if ( v3 <= ++v4 ) return 1; } } } return 0; }
至此第二個so已經完成了裝載。
創建soinfo
完成裝載後,就需要創建soinfo結構,並利用MELFReader設置一些soinfo參數。
-
char *__fastcall sub_3D60(EncryptSoInfo *a1) { const char *v1; // r4@1 char *result; // r0@2 char *v3; // r5@2 v1 = (const char *)(a1 + 20); if ( strlen((const char *)a1 + 20) > 0x7F ) { result = 0; } else { result = (char *)operator new(0x128u); //創建soinfo v3 = result; if ( result ) { memset(result, 0, 0x128u); strncpy(v3, v1, 0x7Fu); result = v3; } } return result; } sub_3DBC(EncryptSoInfo *a1) { //節選 soinfo = sub_3D60(*(Byte **)v1); v3 = (int)soinfo; if ( !soinfo ) goto LABEL_16; v4 = nmemb_56; v5 = nmemb_68; v6 = nmemb_72; v7 = nmemb_76; *((_DWORD *)soinfo + 33) = nmemb_56; // 設置 soinfo->phnum; *((_DWORD *)soinfo + 35) = v5; // 設置 soinfo->loadstart *((_DWORD *)soinfo + 36) = v6; // 設置 soinfo->size; *((_DWORD *)soinfo + 62) = v7; // 設置 soinfo->loadbias // }
鏈接
自定義linker最重要的一步就是鏈接,鏈接主要步驟是:
- 定位 dynamic segment
- 解析 dynamic section
- 加載該so所依賴的so
- 重定位
這一部分是由sub_33F8函數完成的,因爲這個函數很長,所以節選主要步驟貼出來
signed int __fastcall sub_33F8(soinfo *a1)
{
//遍歷program header找到類型爲Dynamic的program header,從而定位到Dynamic segment
sub_45F4(*((_DWORD *)a1 + 32), *((_DWORD *)v2 + 33), *((_DWORD *)v2 + 62), (_DWORD *)v2 + 37, &v38);
v3 = *((_DWORD *)v2 + 37); //dynamic segment
//解析dynamic segment
v4 = *(_DWORD *)v3; //符號類型
if ( *(_DWORD *)v3 ) {
v5 = (int *)(v3 + 8);
while ( 1 ) {
switch ( v4 ) {
//根據符號類型做重定位
}
}
}
LABEL_7:
if ( *(_DWORD *)v3 )
{
v11 = 0;
do
{
if ( v10 == 1 )
{
v13 = (const char *)(*((_DWORD *)v2 + 39) + *(_DWORD *)(v3 + 4));
if ( strlen(v13) > 0x80 )
goto LABEL_21;
if ( *((_DWORD *)v2 + 72) <= v11 )
goto LABEL_21;
v14 = 136 * v11++;
strncpy((char *)(*((_DWORD *)v2 + 73) + v14 + 4), v13, 0x7Fu);
v15 = dlopen(v13, 0); //加載所需要的so
if ( !v15 )
goto LABEL_21;
*(_DWORD *)(*((_DWORD *)v2 + 73) + v14) = v15;
*(_DWORD *)(*((_DWORD *)v2 + 73) + v14 + 132) = 0;
}
v12 = *(_DWORD *)(v3 + 8);
v3 += 8;
v10 = v12;
}
while ( v12 );
}
}
dump second so
清楚第二個so的加載流程後,就可以dump出第二個so,具體做法是在sub_3DBC處下斷點,dump內存,並且解密ELF header 和 program header table。解密算法是libjiagu.so中的sub_6868.
#sub_6868
def decryptso(data, size):
result = bytearray(data)
for i in range(size):
result[i] ^= 0x50
return str(result)
def dump_so(start_address, size):
fp = open("E:\\dump.so", "wb")
#read elf header and decrypt header
data = idaapi.dbg_read_memory(start_address, 52)
header = decryptso(data, 52)
fp.write(header)
#read elf program header tables
phnum = 9
data = idaapi.dbg_read_memory(start_address + 52, phnum * 32)
pht = decryptso(data, phnum * 32)
fp.write(pht)
#read other part
data = data = idaapi.dbg_read_memory(start_address + 52 + phnum * 32, size - phnum * 32 - 52)
fp.write(data)
fp.close()
進入JNI_Onload
在加載完第二個so後,libjiagu.so通過sub_3F7C(soinfo info, char fucname),來找到第二個so的JNI_Onload入口,並在case 35中跳轉到第二個so的JNI_Onload.
虛擬機部分
這一部分就正式開始執行源apk中的代碼了。
dump dex
源dex要被執行,肯定需要加載,所以在libart.so的OpenMemory函數下斷點就可以dump出dex,使用IDAPython dump dex
#add break at OpenMemory
addBrk("libart.so", OpenMemory_offset)
fn_f9()
r0 = GetRegValue("R0")
r1 = GetRegValue("R1")
print("orgin dex at: %x" % (r0))
delBrk("libart.so", OpenMemory_offset)
#dump dex
data = idaapi.dbg_read_memory(r0, r1)
fp = open('d:\\dump.dex', 'wb')
fp.write(data)
fp.close()
用jeb打開dump出來的dex可以發現 oncreate函數native化了。
找到onCreate函數
native化通過靜態修改dex中onCreate函數的DexMethod結構就可以,但要想正確執行,必須在執行時動態註冊,通過攔截JNI註冊函數RegisterNative可以找到onCreate函數的地址
def hook_RegisterNative():
#add break at RegisterNatives
addBrk("libart.so", RegisterNative_offset)
fn_f9()
delBrk("libart.so", RegisterNative_offset)
#JNINativeMethod method[]
r2 = GetRegValue("R2")
#nMethods
r3 = GetRegValue("R3")
for index in range(r3):
name = get_string(Dword(r2))
address = Dword(r2 + 8)
print("native function %s address is %x" % (name, address))
r2 = r2 + 12
然後在oncreate函數下斷點
onCreate函數分析
通過上一步可以找到onCreate在第二個so中的地址,f5之後代碼如下:
int __fastcall onCreate(JNIEnv *a1, int a2, int a3, int a4)
{
int v5; // [sp+1Ch] [bp-Ch]@1
int v6; // [sp+20h] [bp-8h]@1
int v7; // [sp+24h] [bp-4h]@1
v7 = a4;
v6 = a3;
v5 = a2;
return sub_D930(0, a1, &v5);
}
直接調用了sub_D930,進入sub_D930分析,這個函數比較長,大致分析了一下流程:
- jni的一些初始化工作,FindClass,GetMethodID之類的工作
- 利用java.lang.Thread.getStackTrace獲取到調用當前方法的類的類名以及函數名
- 通過上一步獲取的類名以及方法名獲取被保護方法的DexCode結構
- 調用自己的虛擬機執行代碼
其中第二步的核心代碼如下:
sub_D930()
{
//節選
v54 = sub_66BD4(v122, (int)&v127);
if ( v127 & 1 )
j_j_j__ZdlPv(*((void **)v50 + 5));
if ( v54 && (v55 = *(_DWORD *)(v54 + 4), (*(_DWORD *)(v54 + 8) - v55) >> 2 > v4) )
{
v56 = v55 + 4 * v4; // **v56爲Dexprotoid, *(*v56+4)爲DexMethodID, *(*v56+12)爲codeoff
v114 = (int)jni_env;
v123 = *(_DWORD *)v56; // DexProtoId
v107 = *(_DWORD **)v54; // **v54爲dex在內存的地址
DexCode = (_WORD *)(**(_DWORD **)v54 + *(_DWORD *)(*(_DWORD *)v56 + 12));// 獲取DexCode地址
((void (*)(void))(*jni_env)->PushLocalFrame)();
v58 = j_j_j__Znwj(0x20u);
v59 = *DexCode;
*(_DWORD *)v58 = v114; // jni_env
*(_DWORD *)(v58 + 4) = v59; // 使用的寄存器個數
*(_DWORD *)(v58 + 8) = DexCode;
*(_DWORD *)(v58 + 12) = 0;
//進入虛擬機
sub_3FE5C()
}
核心爲sub_66BD4函數,具體原理沒分析。獲取到了onCreate的DexCode後,進入虛擬機執行加密後的DexCode。
虛擬機入口
虛擬機的入口在sub_3FE5C中調用的sub_3FF5C,進入虛擬機入口後,就如下圖
具體原理就是:
- 取出加密後的指令
- 根據加密後指令,還原操作數,並算出一個分支數
- 跳轉到具體分支解釋執行。
具體分支數的計算算法,參考文章中說的很詳細了,就不重複了。至此殼子的基本原理分析完了,接下來還要學習下davik虛擬機是如何解釋執行指令的。逆向新手,有錯誤歡迎指正。
參考文章:
https://bbs.pediy.com/thread-223796.htm
debug.py
import idautils
import idc
import idaapi
import time
#jni_Onload_offset = 0x1D6B50
jni_Onload_offset = 0x1D545E
RegisterNative_offset = 0x1B6610
OpenMemory_offset = 0xFC278
def getModuleBase(moduleName):
base = GetFirstModule()
while (base != None) and (GetModuleName(base).find(moduleName) == -1):
base = GetNextModule(base)
if base == None:
print "failed to find module: " + moduleName
return None
else:
return base
def addBrk(moduleName, functin_offset):
base = getModuleBase(moduleName)
AddBpt(base + functin_offset)
def fn_f7():
idaapi.step_into()
GetDebuggerEvent(WFNE_SUSP | WFNE_SUSP, -1)
def fn_f8():
idaapi.step_over()
GetDebuggerEvent(WFNE_SUSP | WFNE_CONT, -1)
def fn_f9():
idaapi.continue_process()
GetDebuggerEvent(WFNE_SUSP | WFNE_CONT, -1)
def delBrk(moduleName, function_offset):
base = getModuleBase(moduleName)
DelBpt(base + function_offset)
def antiDebug():
addBrk("libart.so", jni_Onload_offset)
fn_f9()
delBrk("libart.so", jni_Onload_offset)
#into libjiagu.so
fn_f7()
#add breakpoint at time
addBrk("libjiagu.so", 0x19FC)
fn_f9()
delBrk("libjiagu.so", 0x19FC)
print("first call time")
lr = GetRegValue("LR")
AddBpt(lr)
fn_f9()
r0 = GetRegValue("R0")
DelBpt(lr)
print("first get time: %x" % (r0))
#add breakpoint at sub_1E9C
addBrk("libjiagu.so", 0x1E9c)
fn_f9()
delBrk("libjiagu.so", 0x1E9c)
print("call sub_1E9C")
#add breakpoint at return from sub_1E9C
lr = GetRegValue("LR")
AddBpt(lr)
fn_f9()
DelBpt(lr)
#get rtld_db_dlactivity address
r0 = GetRegValue("R0")
print("rtld_db_dlactivity address is: %x" % (r0 - 1))
#set rtld_db_dlactivity nop
PatchDword(r0 - 1, 0x46c0)
#add breakpoint at strtol
addBrk("libjiagu.so", 0x1A80)
fn_f9()
delBrk("libjiagu.so", 0x1A80)
print("call strtol")
lr = GetRegValue("LR")
#add breakpoint at return from strtol
AddBpt(lr)
fn_f9()
DelBpt(lr)
r0 = GetRegValue("R0")
print("get trace id: %x" % (r0))
SetRegValue(0, "R0")
#add break point at sub_6DD0
addBrk("libjiagu.so", 0x6DD0)
fn_f9()
lr = GetRegValue("LR")
print("call sub_6DD0")
delBrk("libjiagu.so", 0x6DD0)
AddBpt(lr)
fn_f9()
SetRegValue(0, "R0")
DelBpt(lr)
#add break point at time in sub_6EF8
addBrk("libjiagu.so", 0x19FC)
fn_f9()
delBrk("libjiagu.so", 0x19FC)
print("second call time")
lr = GetRegValue("LR")
AddBpt(lr)
fn_f9()
DelBpt(lr)
r0 = GetRegValue("R0")
print("second get time: %x" % (r0))
#SetRegValue(r0 + 2, "R0")
def get_string(addr):
out = ""
while True:
if Byte(addr) != 0:
out += chr(Byte(addr))
else:
break
addr += 1
return out
def hook_RegisterNative():
#add break at RegisterNatives
addBrk("libart.so", RegisterNative_offset)
fn_f9()
delBrk("libart.so", RegisterNative_offset)
#JNINativeMethod method[]
r2 = GetRegValue("R2")
#nMethods
r3 = GetRegValue("R3")
for index in range(r3):
name = get_string(Dword(r2))
address = Dword(r2 + 8)
#AddBpt(address - 1)
print("native function %s address is %x" % (name, address))
r2 = r2 + 12
def dump_dex(start_address, size):
data = idaapi.dbg_read_memory(start_address, size)
fp = open('E:\\dump.dex', 'wb')
fp.write(data)
fp.close()
#sub_6868
def decryptso(data, size):
result = bytearray(data)
for i in range(size):
result[i] ^= 0x50
return str(result)
def dump_so(start_address, size):
fp = open("E:\\dump.so", "wb")
#read elf header and decrypt header
data = idaapi.dbg_read_memory(start_address, 52)
header = decryptso(data, 52)
fp.write(header)
#read elf program header tables
phnum = 9
data = idaapi.dbg_read_memory(start_address + 52, phnum * 32)
pht = decryptso(data, phnum * 32)
fp.write(pht)
#read other part
data = data = idaapi.dbg_read_memory(start_address + 52 + phnum * 32, size - phnum * 32 - 52)
fp.write(data)
fp.close()
def main():
antiDebug()
#add break at sub_273c
addBrk("libjiagu.so", 0x273C)
fn_f9()
base = GetRegValue("R0")
print("second so uncompress at: %x" % (base))
delBrk("libjiagu.so", 0x273C)
#add break at sub_3DBC
addBrk("libjiagu.so", 0x3DBC)
fn_f9()
r0 = GetRegValue("R0")
print("encrypted so at: %x" % Dword(r0))
print("encrypted so size: %x" % Dword(r0 + 4))
print("uncompressed so at: %x" % Dword(r0 + 8))
print("uncompressed so size: %x" % Dword(r0 + 12))
delBrk("libjiagu.so", 0x3DBC)
#dump_so(Dword(r0 + 8), Dword(r0 + 12))
#add beak at case35
addBrk("libjiagu.so", 0xAB84)
fn_f9()
delBrk("libjiagu.so", 0xAB84)
lr = GetRegValue("LR")
print("step into second so JNI_Onload: %x" % (lr - 1))
fn_f7()
hook_RegisterNative()
#add break at OpenMemory
addBrk("libart.so", OpenMemory_offset)
fn_f9()
r0 = GetRegValue("R0")
r1 = GetRegValue("R1")
print("orgin dex at: %x" % (r0))
delBrk("libart.so", OpenMemory_offset)
#dump_dex(r0, r1)
hook_RegisterNative()
hook_RegisterNative()
hook_RegisterNative()
hook_RegisterNative()
main()
[進行中] 看雪20週年慶典12月28日上海舉辦,LV四級(中級)以上會員免費參與!,同時在校學生免費參加:學生報名鏈接!
最後於 2018-4-8 21:02 被glider菜鳥編輯 ,原因: 添加附件
上傳的附件: