某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: " + moduleNamereturn Noneelse:return basedef 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 headerVerifyElfHeader() &&    //验证elf headerReadProgramHeader() &&  //读取program headerReserveAddressSpace() &&  //分配空间LoadSegments() &&         //按照program header的指示装载segmentsFindPhdr();               //找到装载后的phdr
}

下面看下libjiagu.so的实现,只是少了验证elf header的环节。

int __fastcall sub_4780(MELFReader *a1, EncryptSoInfo *a2)
{Byte *v2; // r5@1Byte *v3; // r4@1int result; // r0@3v2 = 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的指示装载segmentsresult = sub_46CC(v3);      //找到装载后的phdrelseresult = 0;return result;
}

load ELF Header

装载的第一步就是加载ELF Header,这一过程比较简单,直接看代码

int __fastcall sub_40B0(MELFReader *a1, EncryptSoInfo *a2)
{Byte *v2; // r4@1const void *base; // r1@1int v4; // r4@3Byte *v6; // r5@5_DWORD *v7; // r0@5_DWORD *v8; // r6@5v2 = a2;base = (const void *)*((_DWORD *)a2 + 2); //a2->uncompressedSoif ( base&& *((_DWORD *)v2 + 3) > 0x34u   //a2->int sizeOfUncompressSo&& (v6 = a1 + 4,memcpy(a1 + 4, base, 0x34u),  //拷贝加密的后的ELF Headerv7 = 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@1Byte *v3; // r4@1size_t v4; // r0@2size_t v5; // r6@3void *v6; // r0@3_DWORD *v7; // r0@4_DWORD *v8; // r5@4int v9; // r4@6v2 = 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@1int size; // r0@1signed int result; // r0@2_BYTE *v4; // r5@3_BYTE *v5; // r0@3int v6; // r5@4void *addr; // [sp+Ch] [bp-14h]@1v1 = 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:

  1. 计算 segment 在内存空间中的起始地址 segstart 和结束地址 seg_end,seg_start 等于虚拟偏移加上基址load_bias,同时由于 mmap 的要求,都要对齐到页边界得到 seg_page_start 和 seg_page_end。
  2. 计算 segment 在文件中的页对齐后的起始地址 file_page_start 和长度 file_length。
  3. 使用mprotect和memcpy将段映射到内存,指定映射地址为 seg_page_start,长度为 file_length,文件偏移为 file_page_start。

  4. 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]@1v24 = 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参数。

  5. char *__fastcall sub_3D60(EncryptSoInfo *a1)
    {const char *v1; // r4@1char *result; // r0@2char *v3; // r5@2v1 = (const char *)(a1 + 20);if ( strlen((const char *)a1 + 20) > 0x7F ){result = 0;}else{result = (char *)operator new(0x128u); //创建soinfov3 = 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最重要的一步就是链接,链接主要步骤是:

  1. 定位 dynamic segment
  2. 解析 dynamic section
  3. 加载该so所依赖的so
  4. 重定位
    这一部分是由sub_33F8函数完成的,因为这个函数很长,所以节选主要步骤贴出来
signed int __fastcall sub_33F8(soinfo *a1)
{//遍历program header找到类型为Dynamic的program header,从而定位到Dynamic segmentsub_45F4(*((_DWORD *)a1 + 32), *((_DWORD *)v2 + 33), *((_DWORD *)v2 + 62), (_DWORD *)v2 + 37, &v38);v3 = *((_DWORD *)v2 + 37); //dynamic segment//解析dynamic segmentv4 = *(_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);  //加载所需要的soif ( !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] ^= 0x50return str(result)def dump_so(start_address, size):fp = open("E:\\dump.so", "wb")#read elf header and decrypt headerdata = idaapi.dbg_read_memory(start_address, 52)header = decryptso(data, 52)fp.write(header)#read elf program header tablesphnum = 9data = idaapi.dbg_read_memory(start_address + 52,  phnum * 32)pht = decryptso(data, phnum * 32)fp.write(pht)#read other partdata = 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 RegisterNativesaddBrk("libart.so", RegisterNative_offset)fn_f9()delBrk("libart.so", RegisterNative_offset)#JNINativeMethod method[]r2 = GetRegValue("R2")#nMethodsr3 = 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]@1int v6; // [sp+20h] [bp-8h]@1int v7; // [sp+24h] [bp-4h]@1v7 = a4;v6 = a3;v5 = a2;return sub_D930(0, a1, &v5);
}

直接调用了sub_D930,进入sub_D930分析,这个函数比较长,大致分析了一下流程:

  1. jni的一些初始化工作,FindClass,GetMethodID之类的工作
  2. 利用java.lang.Thread.getStackTrace获取到调用当前方法的类的类名以及函数名
  3. 通过上一步获取的类名以及方法名获取被保护方法的DexCode结构
  4. 调用自己的虚拟机执行代码

其中第二步的核心代码如下:

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)为codeoffv114 = (int)jni_env;v123 = *(_DWORD *)v56;                      // DexProtoIdv107 = *(_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,进入虚拟机入口后,就如下图

具体原理就是:

  1. 取出加密后的指令
  2. 根据加密后指令,还原操作数,并算出一个分支数
  3. 跳转到具体分支解释执行。

具体分支数的计算算法,参考文章中说的很详细了,就不重复了。至此壳子的基本原理分析完了,接下来还要学习下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 Noneelse:return basedef 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.sofn_f7()#add breakpoint at timeaddBrk("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_1E9Clr = GetRegValue("LR")AddBpt(lr)fn_f9()DelBpt(lr)#get rtld_db_dlactivity addressr0 = GetRegValue("R0")print("rtld_db_dlactivity address is: %x" % (r0 - 1))#set rtld_db_dlactivity nopPatchDword(r0 - 1, 0x46c0)#add breakpoint at strtoladdBrk("libjiagu.so", 0x1A80)fn_f9()delBrk("libjiagu.so", 0x1A80)print("call strtol")lr = GetRegValue("LR")#add breakpoint at return from strtolAddBpt(lr)fn_f9()DelBpt(lr)r0 = GetRegValue("R0")print("get trace id: %x" % (r0))SetRegValue(0, "R0")#add break point at sub_6DD0addBrk("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_6EF8addBrk("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:breakaddr += 1return outdef hook_RegisterNative():#add break at RegisterNativesaddBrk("libart.so", RegisterNative_offset)fn_f9()delBrk("libart.so", RegisterNative_offset)#JNINativeMethod method[]r2 = GetRegValue("R2")#nMethodsr3 = 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 + 12def 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] ^= 0x50return str(result)def dump_so(start_address, size):fp = open("E:\\dump.so", "wb")#read elf header and decrypt headerdata = idaapi.dbg_read_memory(start_address, 52)header = decryptso(data, 52)fp.write(header)#read elf program header tablesphnum = 9data = idaapi.dbg_read_memory(start_address + 52,  phnum * 32)pht = decryptso(data, phnum * 32)fp.write(pht)#read other partdata = 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_273caddBrk("libjiagu.so", 0x273C)fn_f9()base = GetRegValue("R0")print("second so uncompress at: %x" % (base))delBrk("libjiagu.so", 0x273C)#add break at sub_3DBCaddBrk("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 case35addBrk("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 OpenMemoryaddBrk("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菜鸟编辑 ,原因: 添加附件

上传的附件:

  • debug.py (5.52kb,286次下载)
  • dump.so (649.65kb,207次下载)
  • 样本.apk (2.04MB,249次下载)

某vmp壳原理分析笔记----ELF文件的加载,链接,IDAPYTHON相关推荐

  1. ELF文件的加载和动态链接过程

    本文的目的:大家对于Hello World程序应该非常熟悉,随便使用哪一种语言,即使还不熟悉的语言,写出一个Hello World程序应该毫不费力,但是如果让大家详细的说明这个程序加载和链接的过程,以 ...

  2. Tomcat源码分析——server.xml文件的加载

    前言 作为Java程序员,对于tomcat的server.xml想必都不陌生.本文基于Tomcat7.0的Java源码,对server.xml文件是如何加载的进行分析. 源码分析 Bootstrap的 ...

  3. flume源码分析2--配置文件的加载

    上面提到Application启动的时候,PollingPropertiesFileConfigurationProvider作为唯一的LifecycleAware类型的组件被交给监护者Lifecyc ...

  4. Tomcat7.0源码分析——server.xml文件的加载与解析

    前言 作为Java程序员,对于Tomcat的server.xml想必都不陌生.本文基于Tomcat7.0的Java源码,对server.xml文件是如何加载和解析进行分析. 加载过程分析 Bootst ...

  5. 从一个ELF程序的加载窥探操作系统内核-(5)

    从一个ELF程序的加载窥探操作系统内核-(5) 操作系统加载一个ELF程序看似一个EASY的动作,其实下面隐藏了很多很多OS内核的关键实现,让我们一起来解密其中的流程 作者是一个micro kerne ...

  6. VC从文件中加载图片

    用MFC做GDI开发的朋友肯定熟悉CBitmap类,该类封装了HBITMAP对象,简化了关于HBITMAP的API操作,如LoadBitmap方法可直接加载资源中指定ID的图片,但是很多情况下我们需要 ...

  7. 分析BootstrapClassLoader/ExtClassLoader/AppClassLoader的加载路径 及父委托机制

    http://blog.csdn.net/irelandken/article/details/7048817 分析BootstrapClassLoader/ExtClassLoader/AppCla ...

  8. Android之Launcher分析和修改4——初始化加载数据

    上面一篇文章说了Launcher是如何被启动的,Launcher启动的过程主要是加载界面数据然后显示出来, 界面数据都是系统APP有关的数据,都是从Launcher的数据库读取,下面我们详细分析Lau ...

  9. Mybatis 源码分析(一)配置文件加载流程

    Mybatis 源码分析(一)配置文件加载流程 1.项目构建 引入依赖 <dependency><groupId>org.mybatis</groupId>< ...

最新文章

  1. java 嵌套类 继承_Java嵌套类 - 爱吃苹果的搬运工的个人空间 - OSCHINA - 中文开源技术交流社区...
  2. iOS开发-开发总结
  3. Python-opencv在线帮助
  4. oracle某用户历史sql语句,查看oracle 用户执行的sql语句历史记录
  5. Spring MVC和Thymeleaf:如何从模板访问数据
  6. Vs中新建 网站 和Web应用程序的区别
  7. yaml文件解析:nodejs篇
  8. 再过十年,电脑游戏会被手机游戏完全取代吗?
  9. 长途货运4大痛,Uber新上的「自动驾驶卡车」如何改善?
  10. 代码提示(支持3.X和4.X)—ArcGIS API forJavaScript
  11. 第四章 Spring.Net 如何管理您的类___统一资源访问接口
  12. motion filter_Android Motion布局
  13. 太原市智能家居行业协会成立
  14. 1028: [JSOI2007]麻将 - BZOJ
  15. 【Interfacenavigation】隐藏导航栏(52)
  16. 【免费】抖音去水印教程保存本地相册方法
  17. 如何设计一个电商平台积分兑换系统!
  18. 王炸!10分钟把ChatGPT部署成24小时微信机器人!
  19. h5页面定时跳转+读秒
  20. thread ‘main‘ panicked at ‘called `Result::unwrap()` on an `Err` value: Os { code: 2, kind: NotFound

热门文章

  1. Intertek绿叶认证是什么?
  2. 实验十 符号计算基础与符号微积分(matlab)
  3. 仿苹果Mac Dock任务栏
  4. 基于ssm jsp超市在线销售平台的设计与实现
  5. 我的新书:《精通Excel公式、函数与图表》已出版上市
  6. JDK,JRE不同版本区别汇总
  7. Linux和Mac下获取文件CRC/MD5/SHA1/SHA256
  8. 【论文阅读】A2S-Det: Efficiency Anchor Matching in Aerial Image Oriented Object Detection
  9. 信号功率谱密度matlab,Matlab2019b中常用的音频信号分析,快速傅里叶(FFT),功率谱密度(PSD),以及通过FFT求取功率谱密度的问题...
  10. 只要让我戴上面具 , 我就会马上逃跑 ! 等下眼镜卡住了