[Android]某加固分析

360加固保免费版分析

前言

分析一下某加固,通过加固


的思路学习逆向技术,首先写一个APP使用进行加固

APK界面:

file

特征分析

file

某加固保的特征其实就是Stub类和tianyu字样了。

观察attachBaseContext类:

file

值得注意的是attachBaseContext与onCreate类都属于Application的回调方法,会进行一些初始化操作。

这里我们观察他在加载字符串的时候都使用了a.a方法,那么显然a.a方法就是就是一个解混淆的方法咯,我们还可以hook住看一下都做了些什么

let a = Java.use("com.tianyu.util.a");
a["a"].overload('java.lang.String').implementation = function (str) {
    console.log(`a.a is called: str=${str}`);
    let result = this["a"](str);
    console.log(`a.a result=${result}`);
    return result;
};

file

在Stub最开始我们可以找到这个字符串:

private static String b = "libjiagu";
对他交叉引用看一看

file

可以发现加固保会判断手机的架构,针对不同的架构加载不同的Native文件。

Native层分析

显然我们可以发现某加固是通过Native层去释放Dex文件的,因此,我们主要分析的还是Native层的内容。

file

这里我们主要分析arm64:

壳ELF导入导出表修复:

file

可以发现某加固对于最外面的ELF做了处理,抹除了SO的导出表,如果没有导入导出表的话,这个ELF文件是如何运行的呢,那么不难发现其实加固保应该是使用了自己定义的链接器,在装载内存的时候才做相应的链接操作。

首先我们先hook dlopen来查看APK加载了哪些so文件:

function hook_dlopne() {
    Interceptor.attach(Module.findExportByName("libdl.so", "android_dlopen_ext"), {
        onEnter: function (args) {
            console.log("Load -> ", args[0].readCString());
        }, onLeave: function () {

        }
    })
}

setImmediate(hook_dlopne);

正常来说,我们如果Hook dlopen的话,在安卓7.0之后我们需要hook的值则为"android_dlopen_ext":

file

我们可以发现加载了libjiagu_64.so,接下来就是想办法把它dump下来了,首先我们启动APP,使用frida -U -F -l Hook.js
参数注入如下脚本

function dump_so() {
    var soName = "libjiagu_64.so";
    var libSo = Process.getModuleByName(soName);
    var save_path = "/data/data/com.swdd.txjgtest/" + libSo.name + "_Dump";
    console.log("[Base]->", libSo.base);
    console.log("[Size]->", ptr(libSo.size));
    var handle = new File(save_path, "wb");
    Memory.protect(ptr(libSo.base), libSo.size, 'rwx');
    var Buffer = libSo.base.readByteArray(libSo.size);
    handle.write(Buffer);
    handle.flush();
    handle.close();
    console.log("[DumpPath->]", save_path);

}
setImmediate(dump_so);

file

[Base]-> 0x78c1443000
[Size]-> 0x27d000
[DumpPath->] /data/data/com.swdd.txjgtest/libjiagu_64.so_Dump

这三个参数很重要,等下修复SO的时候需要使用。

SoFixer:https://github.com/F8LEFT/SoFixer

接下来我们需要使用SoFixer修复我们dump下来的So文件:

 .\SoFixer-Windows-64.exe -s .\libjiagu_64.so_Dump -o .\libjiagu_64.so_Fix -m 0x78c1443000 -d

-m是刚刚我们脚本输出的偏移地址

file

壳ELF分析:

接下来我们要做的就是分析ELF的逻辑了:

file

刚开始拿到这个ELF我们还无从下手,但是根据加固思路,我们可以先hook open函数,查看读取了哪些文件。

function hookOpen() {
    var openPtr = Module.getExportByName(null, 'open');
    const open = new NativeFunction(openPtr, 'int', ['pointer', 'int']);
    Interceptor.replace(openPtr, new NativeCallback(function (fileNamePtr, flag) {
        var fileName = fileNamePtr.readCString();
        if (fileName.indexOf('dex') != -1) {
            console.log("[Open]-> ", fileName);
        }
        return open(fileNamePtr, flag);

    }, 'int', ['pointer', 'int']))
}
function hook_dlopne() {

    Interceptor.attach(Module.findExportByName("libdl.so", "android_dlopen_ext"), {
        onEnter: function (args) {
            var loadFileName = args[0].readCString();
          //  console.log("Load -> ", loadFileName);
            if (loadFileName.indexOf('libjiagu') != -1) {
                this.is_can_hook = true;
            }
        }, onLeave: function () {
            if (this.is_can_hook) {
                hookOpen();
            }
        }
    })
}

setImmediate(hook_dlopne);

得到输出如下:

file

我们发现,非常奇怪,居然没有打开与我们程序相关的dex文件,我们取消对dex的过滤,查看一下都打开了一些什么内容。

file

多次的打开了maps文件,那么我们知道该文件包含了进程的内存映射信息,程序频繁读取是为了什么呢,其实猜测就是为了隐藏打开dex的操作,那么我们只需要重定向一下maps就可以了,hook open将打开open时如果存在扫描maps,就定向到自己的fakemaps。


function hookOpen() {
    var FakeMaps = "/data/data/com.swdd.txjgtest/maps";
    var openPtr = Module.getExportByName(null, 'open');
    const open = new NativeFunction(openPtr, 'int', ['pointer', 'int']);
    var readPtr = Module.findExportByName("libc.so", "read");

    Interceptor.replace(openPtr, new NativeCallback(function (fileNamePtr, flag) {
        var FD = open(fileNamePtr, flag);
        var fileName = fileNamePtr.readCString();

        if (fileName.indexOf("maps") >= 0) {
            console.warn("[Warning]->mapsRedirect Success");
            var filename = Memory.allocUtf8String(FakeMaps);
            return open(filename, flag);
        }
        if (fileName.indexOf('dex') != -1) {
            console.log("[OpenDex]-> ", fileName);
        }
        return FD;

    }, 'int', ['pointer', 'int']))
}
function hook_dlopne() {

    Interceptor.attach(Module.findExportByName("libdl.so", "android_dlopen_ext"), {
        onEnter: function (args) {
            var loadFileName = args[0].readCString();
          //  console.log("Load -> ", loadFileName);
            if (loadFileName.indexOf('libjiagu') != -1) {
                this.is_can_hook = true;
            }
        }, onLeave: function () {
            if (this.is_can_hook) {
                hookOpen();
            }
        }
    })
}

setImmediate(hook_dlopne);

file

那么我们能够发现确实使用了open去打开classes,并且实锤了是通过处理maps隐藏了内存映射,接下来我们就可以通过

console.log('RegisterNatives called from:\n' + Thread.backtrace(this.context, Backtracer.FUZZY).map(DebugSymbol.fromAddress).join('\n') + '\n');

来打印读取dex文件的Native地址。


function hookOpen() {
    var FakeMaps = "/data/data/com.swdd.txjgtest/maps";
    var openPtr = Module.getExportByName(null, 'open');
    const open = new NativeFunction(openPtr, 'int', ['pointer', 'int']);
    var readPtr = Module.findExportByName("libc.so", "read");

    Interceptor.replace(openPtr, new NativeCallback(function (fileNamePtr, flag) {
        var FD = open(fileNamePtr, flag);
        var fileName = fileNamePtr.readCString();

        if (fileName.indexOf("maps") >= 0) {
            console.warn("[Warning]->mapsRedirect Success");
            var filename = Memory.allocUtf8String(FakeMaps);
            return open(filename, flag);
        }
        if (fileName.indexOf('dex') != -1) {
            console.info("[OpenDex]-> ", fileName);
            console.log('RegisterNatives called from:\n' + Thread.backtrace(this.context, Backtracer.FUZZY).map(DebugSymbol.fromAddress).join('\n') + '\n');
        }
        return FD;

    }, 'int', ['pointer', 'int']))
}
function hook_dlopne() {

    Interceptor.attach(Module.findExportByName("libdl.so", "android_dlopen_ext"), {
        onEnter: function (args) {
            var loadFileName = args[0].readCString();
          //  console.log("Load -> ", loadFileName);
            if (loadFileName.indexOf('libjiagu') != -1) {
                this.is_can_hook = true;
            }
        }, onLeave: function () {
            if (this.is_can_hook) {
                hookOpen();
            }
        }
    })
}

setImmediate(hook_dlopne);

获取到的输出如下:

file

可以发现每次打开dex的调用栈基本一致,我们在IDA中查看这个地址。

file

居然都未识别。

翻了一下这段数据,翻到头的时候可以发现

file

在这一段有被引用,我们跟过去看一看

file

后缀被加了.so,然后分段加载了一些东西。这个时候基本可以猜测是在linker另一个so了。

主ELF解密

在自实现linker的时候,完成linker之后肯定是需要用dlopen去加载这个so的,那么我们hook一下dlopen验证一下我们的猜想。

function hook_dlopne() {
    Interceptor.attach(Module.findExportByName("libdl.so", "android_dlopen_ext"), {
        onEnter: function (args) {
            console.warn("[android_dlopen_ext] -> ", args[0].readCString());
        }, onLeave: function () {

        }
    })
}

function hook_dlopne2() {
    Interceptor.attach(Module.findExportByName("libdl.so", "dlopen"), {
        onEnter: function (args) {
            console.log("[dlopen] -> ", args[0].readCString());
        }, onLeave: function () {

        }
    })
}
setImmediate(hook_dlopne2);
setImmediate(hook_dlopne);

file

流程说明了一切,直接实锤了自定义linker加固so,那接下来我们应该如何做呢,首先需要把另一个ELF给分离出来。

自定义linker SO加固的大部分思路其实就是分离出 program header 等内容进行单独加密,然后在link的时候补充soinfo。

我们使用010在之前Fix后的so里面查找ELF

file

在e8000处我们发现了ELF头,我们使用python脚本将其分离出来:

with open('libjiagu_64.so_Dump','rb') as f:
    s=f.read()
with open('libjiagu_64.so','wb') as f:
    f.write(s[0xe8000::])

但是program header已经被加密了,那么接下来我们需要做的就是找到在哪儿解密的。

这里推荐oacia大佬的项目:https://github.com/oacia/stalker_trace_so

但是在使用大佬的项目的时候,我输出的js文件显示的内容都是乱码,所以对源码做了一些修改

改动点如下:

file

接下来利用stalker_trace_so来分析程序执行流:

file

注意此处需要改为libjiagu_64.so

接下来我们拿到如下执行流:

call1:JNI_OnLoad
call2:j_interpreter_wrap_int64_t
call3:interpreter_wrap_int64_t
call4:_Znwm
call5:sub_13364
call6:_Znam
call7:sub_10C8C
call8:memset
call9:sub_9988
call10:sub_DE4C
call11:calloc
call12:malloc
call13:free
call14:sub_E0B4
call15:_ZdaPv
call16:sub_C3B8
call17:sub_C870
call18:sub_9538
call19:sub_9514
call20:sub_C9E0
call21:sub_C5A4
call22:sub_9674
call23:sub_15654
call24:sub_15DCC
call25:sub_15E98
call26:sub_159CC
call27:sub_1668C
call28:sub_15A4C
call29:sub_15728
call30:sub_15694
call31:sub_94B0
call32:sub_C8C8
call33:sub_CAC4
call34:sub_C810
call35:sub_906C
call36:dladdr
call37:strstr
call38:setenv
call39:_Z9__arm_a_1P7_JavaVMP7_JNIEnvPvRi
call40:sub_9A08
call41:sub_954C
call42:sub_103D0
call43:j__ZdlPv_1
call44:_ZdlPv
call45:sub_9290
call46:sub_7BAC
call47:strncpy
call48:sub_5994
call49:sub_5DF8
call50:sub_4570
call51:sub_59DC
call52:_ZN9__arm_c_19__arm_c_0Ev
call53:sub_9F60
call54:sub_957C
call55:sub_94F4
call56:sub_CC5C
call57:sub_5D38
call58:sub_5E44
call59:memcpy
call60:sub_5F4C
call61:sub_583C
call62:j__ZdlPv_3
call63:j__ZdlPv_2
call64:j__ZdlPv_0
call65:sub_9F14
call66:sub_9640
call67:sub_5894
call68:sub_58EC
call69:sub_9B90
call70:sub_2F54
call71:uncompress
call72:sub_C92C
call73:sub_440C
call74:sub_4BFC
call75:sub_4C74
call76:sub_5304
call77:sub_4E4C
call78:sub_5008
call79:mprotect
call80:strlen
call81:sub_3674
call82:dlopen
call83:sub_4340
call84:sub_3A28
call85:sub_3BDC
call86:sub_2F8C
call87:dlsym
call88:strcmp
call89:sub_5668
call90:sub_4C40
call91:sub_5BF0
call92:sub_7CDC
call93:sub_468C
call94:sub_7E08
call95:sub_86FC
call96:sub_8A84
call97:sub_7FDC
call98:interpreter_wrap_int64_t_bridge
call99:sub_9910
call100:sub_15944
call101:puts

现在我们知道了控制流,然而还不够,因为自定义linker加固so,最后还是需要dlopen去手动加载的,那么我们对导入表中的dlopen进行交叉。

file

发现只有一次掉用,我们跟踪过去看一看。

file

全是Switch case:
http://androidxref.com/9.0.0_r3/xref/bionic/linker/linker.cpp

file

可以看看linker源码中的预链接部分,代码如出一折,那么此时我们就可以导入soinfo结构体了

在 ida 中依次点击 View->Open subviews->Local Types ,导入结构体(点击insert)

//IMPORTANT
//ELF64 启用该宏
#define __LP64__  1
//ELF32 启用该宏
//#define __work_around_b_24465209__  1

/*
//https://android.googlesource.com/platform/bionic/+/master/linker/Android.bp
架构为 32 位 定义__work_around_b_24465209__宏
arch: {
        arm: {cflags: ["-D__work_around_b_24465209__"],},
        x86: {cflags: ["-D__work_around_b_24465209__"],},
    }
*/

//android-platform\bionic\libc\include\link.h
#if defined(__LP64__)
#define ElfW(type) Elf64_ ## type
#else
#define ElfW(type) Elf32_ ## type
#endif

//android-platform\bionic\linker\linker_common_types.h
// Android uses RELA for LP64.
#if defined(__LP64__)
#define USE_RELA 1
#endif

//android-platform\bionic\libc\kernel\uapi\asm-generic\int-ll64.h
//__signed__-->signed
typedef signed char __s8;
typedef unsigned char __u8;
typedef signed short __s16;
typedef unsigned short __u16;
typedef signed int __s32;
typedef unsigned int __u32;
typedef signed long long __s64;
typedef unsigned long long __u64;

//A12-src\msm-google\include\uapi\linux\elf.h
/* 32-bit ELF base types. */
typedef __u32   Elf32_Addr;
typedef __u16   Elf32_Half;
typedef __u32   Elf32_Off;
typedef __s32   Elf32_Sword;
typedef __u32   Elf32_Word;

/* 64-bit ELF base types. */
typedef __u64   Elf64_Addr;
typedef __u16   Elf64_Half;
typedef __s16   Elf64_SHalf;
typedef __u64   Elf64_Off;
typedef __s32   Elf64_Sword;
typedef __u32   Elf64_Word;
typedef __u64   Elf64_Xword;
typedef __s64   Elf64_Sxword;

typedef struct dynamic{
  Elf32_Sword d_tag;
  union{
    Elf32_Sword d_val;
    Elf32_Addr  d_ptr;
  } d_un;
} Elf32_Dyn;

typedef struct {
  Elf64_Sxword d_tag;       /* entry tag value */
  union {
    Elf64_Xword d_val;
    Elf64_Addr d_ptr;
  } d_un;
} Elf64_Dyn;

typedef struct elf32_rel {
  Elf32_Addr    r_offset;
  Elf32_Word    r_info;
} Elf32_Rel;

typedef struct elf64_rel {
  Elf64_Addr r_offset;  /* Location at which to apply the action */
  Elf64_Xword r_info;   /* index and type of relocation */
} Elf64_Rel;

typedef struct elf32_rela{
  Elf32_Addr    r_offset;
  Elf32_Word    r_info;
  Elf32_Sword   r_addend;
} Elf32_Rela;

typedef struct elf64_rela {
  Elf64_Addr r_offset;  /* Location at which to apply the action */
  Elf64_Xword r_info;   /* index and type of relocation */
  Elf64_Sxword r_addend;    /* Constant addend used to compute value */
} Elf64_Rela;

typedef struct elf32_sym{
  Elf32_Word    st_name;
  Elf32_Addr    st_value;
  Elf32_Word    st_size;
  unsigned char st_info;
  unsigned char st_other;
  Elf32_Half    st_shndx;
} Elf32_Sym;

typedef struct elf64_sym {
  Elf64_Word st_name;       /* Symbol name, index in string tbl */
  unsigned char st_info;    /* Type and binding attributes */
  unsigned char st_other;   /* No defined meaning, 0 */
  Elf64_Half st_shndx;      /* Associated section index */
  Elf64_Addr st_value;      /* Value of the symbol */
  Elf64_Xword st_size;      /* Associated symbol size */
} Elf64_Sym;

#define EI_NIDENT   16

typedef struct elf32_hdr{
  unsigned char e_ident[EI_NIDENT];
  Elf32_Half    e_type;
  Elf32_Half    e_machine;
  Elf32_Word    e_version;
  Elf32_Addr    e_entry;  /* Entry point */
  Elf32_Off e_phoff;
  Elf32_Off e_shoff;
  Elf32_Word    e_flags;
  Elf32_Half    e_ehsize;
  Elf32_Half    e_phentsize;
  Elf32_Half    e_phnum;
  Elf32_Half    e_shentsize;
  Elf32_Half    e_shnum;
  Elf32_Half    e_shstrndx;
} Elf32_Ehdr;

typedef struct elf64_hdr {
  unsigned char e_ident[EI_NIDENT]; /* ELF "magic number" */
  Elf64_Half e_type;
  Elf64_Half e_machine;
  Elf64_Word e_version;
  Elf64_Addr e_entry;       /* Entry point virtual address */
  Elf64_Off e_phoff;        /* Program header table file offset */
  Elf64_Off e_shoff;        /* Section header table file offset */
  Elf64_Word e_flags;
  Elf64_Half e_ehsize;
  Elf64_Half e_phentsize;
  Elf64_Half e_phnum;
  Elf64_Half e_shentsize;
  Elf64_Half e_shnum;
  Elf64_Half e_shstrndx;
} Elf64_Ehdr;

/* These constants define the permissions on sections in the program
   header, p_flags. */
#define PF_R        0x4
#define PF_W        0x2
#define PF_X        0x1

typedef struct elf32_phdr{
  Elf32_Word    p_type;
  Elf32_Off p_offset;
  Elf32_Addr    p_vaddr;
  Elf32_Addr    p_paddr;
  Elf32_Word    p_filesz;
  Elf32_Word    p_memsz;
  Elf32_Word    p_flags;
  Elf32_Word    p_align;
} Elf32_Phdr;

typedef struct elf64_phdr {
  Elf64_Word p_type;
  Elf64_Word p_flags;
  Elf64_Off p_offset;       /* Segment file offset */
  Elf64_Addr p_vaddr;       /* Segment virtual address */
  Elf64_Addr p_paddr;       /* Segment physical address */
  Elf64_Xword p_filesz;     /* Segment size in file */
  Elf64_Xword p_memsz;      /* Segment size in memory */
  Elf64_Xword p_align;      /* Segment alignment, file & memory */
} Elf64_Phdr;

typedef struct elf32_shdr {
  Elf32_Word    sh_name;
  Elf32_Word    sh_type;
  Elf32_Word    sh_flags;
  Elf32_Addr    sh_addr;
  Elf32_Off sh_offset;
  Elf32_Word    sh_size;
  Elf32_Word    sh_link;
  Elf32_Word    sh_info;
  Elf32_Word    sh_addralign;
  Elf32_Word    sh_entsize;
} Elf32_Shdr;

typedef struct elf64_shdr {
  Elf64_Word sh_name;       /* Section name, index in string tbl */
  Elf64_Word sh_type;       /* Type of section */
  Elf64_Xword sh_flags;     /* Miscellaneous section attributes */
  Elf64_Addr sh_addr;       /* Section virtual addr at execution */
  Elf64_Off sh_offset;      /* Section file offset */
  Elf64_Xword sh_size;      /* Size of section in bytes */
  Elf64_Word sh_link;       /* Index of another section */
  Elf64_Word sh_info;       /* Additional section information */
  Elf64_Xword sh_addralign; /* Section alignment */
  Elf64_Xword sh_entsize;   /* Entry size if section holds table */
} Elf64_Shdr;

//android-platform\bionic\linker\linker_soinfo.h
typedef void (*linker_dtor_function_t)();
typedef void (*linker_ctor_function_t)(int, char**, char**);

#if defined(__work_around_b_24465209__)
#define SOINFO_NAME_LEN 128
#endif

struct soinfo {
#if defined(__work_around_b_24465209__)
  char old_name_[SOINFO_NAME_LEN];
#endif
  const ElfW(Phdr)* phdr;
  size_t phnum;
#if defined(__work_around_b_24465209__)
  ElfW(Addr) unused0; // DO NOT USE, maintained for compatibility.
#endif
  ElfW(Addr) base;
  size_t size;

#if defined(__work_around_b_24465209__)
  uint32_t unused1;  // DO NOT USE, maintained for compatibility.
#endif

  ElfW(Dyn)* dynamic;

#if defined(__work_around_b_24465209__)
  uint32_t unused2; // DO NOT USE, maintained for compatibility
  uint32_t unused3; // DO NOT USE, maintained for compatibility
#endif

  soinfo* next;
  uint32_t flags_;

  const char* strtab_;
  ElfW(Sym)* symtab_;

  size_t nbucket_;
  size_t nchain_;
  uint32_t* bucket_;
  uint32_t* chain_;

#if !defined(__LP64__)
  ElfW(Addr)** unused4; // DO NOT USE, maintained for compatibility
#endif

#if defined(USE_RELA)
  ElfW(Rela)* plt_rela_;
  size_t plt_rela_count_;

  ElfW(Rela)* rela_;
  size_t rela_count_;
#else
  ElfW(Rel)* plt_rel_;
  size_t plt_rel_count_;

  ElfW(Rel)* rel_;
  size_t rel_count_;
#endif

  linker_ctor_function_t* preinit_array_;
  size_t preinit_array_count_;

  linker_ctor_function_t* init_array_;
  size_t init_array_count_;
  linker_dtor_function_t* fini_array_;
  size_t fini_array_count_;

  linker_ctor_function_t init_func_;
  linker_dtor_function_t fini_func_;

/*
#if defined (__arm__)
  // ARM EABI section used for stack unwinding.
  uint32_t* ARM_exidx;
  size_t ARM_exidx_count;
#endif
  size_t ref_count_;
// 怎么找不 link_map 这个类型的声明...
  link_map link_map_head;

  bool constructors_called;

  // When you read a virtual address from the ELF file, add this
  //value to get the corresponding address in the process' address space.
  ElfW (Addr) load_bias;

#if !defined (__LP64__)
  bool has_text_relocations;
#endif
  bool has_DT_SYMBOLIC;
*/
};

file

可以发现还是不完全,证明这个soinfo是被魔改过的

对其交叉,向上查看:

file

下方还调用了一个函数,我们进去看看。

file

发现这里的步长是0x38

file

好巧不巧,程序头就是0x38大小,那么这个方法肯定是在加载程序头了。

既然需要加载程序头,那么他肯定是需要解密之前被加密的程序头段的
加载在sub_5668调用于sub_4340又调用于sub_440C最后形成闭环

file

那我们只需要找sub_7BAC到sub_440C之间调用的函数就可以了。

file

这个uncompress就及其可疑了,这是个解压缩的方法,我们稍微向上找找:

就能发现我们的老熟人了

file

妥妥一RC4,但是找不到他的初始化算法,虽然就算没有初始化算法,我们也可以通过dump他的S盒,来解密,但是总感觉怪怪的,一筹莫展之际,我们向上交叉发现了loc_571c

file

file

未识别居然,我们识别看看

file

哟,这不是初始化算法么。

我们hook他看看密钥

file


function hook_Rc4() {
    var module = Process.findModuleByName("libjiagu_64.so");
    Interceptor.attach(module.base.add(0x571C), {
        onEnter(args) {
            console.log(hexdump(args[0], {
                offset: 0, length: 0x10, header: true, ansi: true
            }))
        }, onLeave(reval) {

        }

    });
}

function hook_dlopne() {

    Interceptor.attach(Module.findExportByName("libdl.so", "android_dlopen_ext"), {
        onEnter: function (args) {
            var loadFileName = args[0].readCString();
            //  console.log("Load -> ", loadFileName);
            if (loadFileName.indexOf('libjiagu') != -1) {
                this.is_can_hook = true;
            }
        }, onLeave: function () {
            if (this.is_can_hook) {
                hook_Rc4();

            }
        }
    })
}

setImmediate(hook_dlopne);

file

在最开始找到的这个函数中,我们可以看到加载的地址和文件大小,那么我们直接使用python读取这个大小的段进行解密

最开始写一个python脚本解密的时候我们出现了如下问题

file

显然RC4解密出问题了,我们再回去看看rc4加密部分

file

对着其一顿还原,发现居然有魔改

file

但是解密出来依旧不对,我们看看S盒是否一致


function hook_Rc4_init() {
    var module = Process.findModuleByName("libjiagu_64.so");
    Interceptor.attach(module.base.add(0x58EC), {
        onEnter(args) {
            console.log(hexdump(args[2], {
                offset: 0, length: 0x100, header: true, ansi: true
            }))
        }, onLeave(reval) {

        }

    });
}

function hook_Rc4_cry() {
    var module = Process.findModuleByName("libjiagu_64.so");
    Interceptor.attach(module.base.add(0x571C), {
        onEnter(args) {
            console.log(hexdump(args[0], {
                offset: 0, length: 0x10, header: true, ansi: true
            }))
        }, onLeave(reval) {

        }

    });
}

function hook_dlopne() {

    Interceptor.attach(Module.findExportByName("libdl.so", "android_dlopen_ext"), {
        onEnter: function (args) {
            var loadFileName = args[0].readCString();
            //  console.log("Load -> ", loadFileName);
            if (loadFileName.indexOf('libjiagu') != -1) {
                this.is_can_hook = true;
            }
        }, onLeave: function () {
            if (this.is_can_hook) {
                hook_Rc4_init();
                hook_Rc4_cry();
            }
        }
    })
}

setImmediate(hook_dlopne);

file

file

显然Sbox是不一致的,那么反正有了Sbox,我们就不需要再去管init方法了,直接使用现成的Sbox就可以了。

file

还是没能解出来,有点难受了,还有一个细节就是i , j 来自于Sbox的第257和258位,他们会不会不是0呢?我们多dump两个看看

果然不是0!

file

file

解密成功

file

拿到的东西,似乎依旧不是正确的,继续查看调用链,最后在sub_5304找到了这样的一个函数

file

file

这里其实是一个向量运算,第一个字节为异或的值,后面的四个字节表示异或的大小。

接下来重新用脚本把他们都解密出来:

import zlib
import struct
def RC4(data, key):
    for i in range(0,8):
        print(hex(data[i]))
    S = list(range(256))
    j = 0
    out = []

    S = [0x76,0xac,0x57,0x5d,0x84,0x1a,0x43,0x9d,0xfb,0x5f,0xf8,0x59,0x35,0x9c,0x05,0x36,0xcd,0xd1,0x01,0xcc,0x39,0x49,0xb6,0x10,0x0e,0x5e,0x2e,0x2a,0x29,0x7f,0x72,0x88,0x9f,0x13,0x2c,0x6f,0x44,0x9b,0x67,0x4a,0xe0,0xee,0x77,0x34,0x97,0x0b,0x68,0x0c,0x4f,0xcf,0x8f,0x95,0x83,0x52,0xef,0x78,0x6a,0xde,0x09,0x1d,0xb5,0x48,0xa8,0xa1,0x46,0x85,0x02,0xe7,0xcb,0x41,0xb3,0x3e,0x71,0xb9,0x3b,0xe4,0x53,0xc9,0x73,0x42,0xe5,0x30,0x25,0x75,0xf9,0xdf,0x14,0x38,0xae,0xd2,0x0d,0x82,0x6c,0x93,0x6e,0xbe,0x5b,0x20,0xf3,0x47,0xd8,0xf1,0x8b,0x64,0xb1,0xab,0xad,0xf6,0xb8,0x7a,0x80,0x4d,0xb7,0x56,0xec,0xb0,0x66,0x18,0xc4,0x92,0x33,0xc8,0x60,0x4e,0x31,0xd9,0x5a,0x03,0xe6,0x15,0xd3,0xa3,0x21,0xa7,0x1c,0xc1,0x26,0x3c,0x1e,0x70,0xbf,0xa2,0xc5,0xc3,0xa0,0xc2,0xc0,0x98,0x28,0x89,0x50,0x4b,0x90,0x6b,0xe1,0x55,0x79,0x7c,0xfd,0xff,0xe3,0xaa,0x2b,0xa4,0xbd,0x62,0x2f,0x16,0xb4,0x7e,0xc6,0xfe,0x63,0xda,0x51,0xd6,0x32,0x3a,0x11,0xc7,0x3f,0x8e,0xd5,0xea,0xa5,0xba,0xca,0xed,0x08,0x22,0x74,0x5c,0x24,0x4c,0x7b,0xbb,0xa9,0x8d,0x96,0x91,0x1b,0xf2,0x17,0x94,0x45,0x19,0xce,0x06,0x8a,0x65,0x37,0x86,0xf5,0x12,0x9a,0x69,0x8c,0x87,0xd4,0xe8,0x6d,0xeb,0x58,0x23,0x00,0x40,0x1f,0xaf,0x99,0xdd,0x04,0x9e,0x7d,0x0a,0xa6,0x81,0xf0,0xf7,0x3d,0xe9,0xdb,0x0f,0xbc,0x27,0xfa,0xe2,0xfc,0xf4,0xb2,0xd0,0xdc,0xd7,0x54,0x07,0x2d,0x61]
    i = 0x3
    j = 0x5
    for ch in data:
        i = (i + 2) % 256
        j = (j + S[i] + 1) % 256
        S[i], S[j] = S[j], S[i]
        out.append(ch ^ S[(S[i] + S[j]) % 256])

    return out

def RC4decrypt(ciphertext, key):
    return RC4(ciphertext, key)

wrap_elf_start = 0x2D260
wrap_elf_size = 0xB9956
key = b"vUV4#\x91#SVt"
with open('libjiagu_64.so_Fix','rb') as f:
    wrap_elf = f.read()

# 对密文进行解密
dec_compress_elf = RC4decrypt(wrap_elf[wrap_elf_start:wrap_elf_start+wrap_elf_size], key)
dec_elf = zlib.decompress(bytes(dec_compress_elf[4::]))
with open('wrap_elf','wb') as f:
    f.write(dec_elf)

class part:
    def __init__(self):
        self.name = ""
        self.value = b''
        self.offset = 0
        self.size = 0

index = 1
extra_part = [part() for _ in range(7)]
seg = ["a", "b", "c", "d"]
v_xor = dec_elf[0]
for i in range(4):
    size = int.from_bytes(dec_elf[index:index + 4], 'little')
    index += 4
    extra_part[i + 1].name = seg[i]
    extra_part[i + 1].value = bytes(map(lambda x: x ^ v_xor, dec_elf[index:index + size]))
    extra_part[i + 1].size = size
    index += size
for p in extra_part:
    if p.value != b'':
        filename = f"libjiagu.so_{hex(p.size)}_{p.name}"
        print(f"[{p.name}] get {filename}, size: {hex(p.size)}")
        with open(filename, 'wb') as f:
            f.write(p.value)

file

得到每个段的大小和偏移,还记不记得我们之前讲过程序头是6个大小0x38的字节组成的,那么计算一波这个a正好满足程序头的大小,那么我们使用010修补原本程序

然后我们继续观察,.rela.plt,.rela.dyn储存的内容是要远远大于dynamic的,所以我们可以锁定dynamic是d,那么我们根据program header table找到dynamic的偏移:

file

CTRL+G跳转过去

填入d的内容

file

接下来:

file

根据这个我们就能知道
0x7是dyn偏移,0x8是dyn大小
0x17是plt偏移,0x2是plt大小

file

对应的那么b就是plt了,c就是dyn了,依旧是填入修复:

file

至此,主so修复完成。

file

Dex释放分析

为了方便hook和分析,我们需要设置基地址为这个so的地址:这里是0xe8000

file

设置完了之后大家是否还记得最开始我们跟踪open的时候那几个函数呢,我们现在继续过去看看。

file

我们可以观察到在打开dex之后会调用0x136788,调用完他之后调用了artso里面的一些擀卷函数FindClass???显然在0x136788之后就解密完成了,那么我们跟过去看看

file

这附近肯定就存在解密解密点了,向下看:

file

我们可以找到该方法,通过之前hook的方式打印他的调用栈。
file

file

之后立马就是加载findclass这些,我们直接看看他的参数是什么样的,这里需要注意,之前我们hook的是Android_dlopen_ext,但是由于这个是主elf不是使用上面的加载的了,所以我们得改用对dlopen做hook。

hook一下0x193868,然后看一下内存

file

全体起立。

接下来就是把这个内存给dump下来了,那么要dump多大还不知道,我们看看别的参数。

file

发现aargs[2]就是dex的大小,那么我们根据这个写脚本就可以了。

function travedec() {
    var base = Process.findModuleByName("libjiagu_64.so").base.add(0x193868);
    var fileIndex = 0
    //console.log(base);
    Interceptor.attach(base, {
        onEnter: function (args) {
            console.log(hexdump(args[1], {
                offset: 0, length: 0x30, header: true, ansi: true
            }))
            console.log(args[2]);

            try {
                var length = args[2].toInt32();
                var data = Memory.readByteArray(args[1], length);

                var filePath = "/data/data/com.swdd.txjgtest/files/" + fileIndex + ".dex";
                var file_handle = new File(filePath, "wb");

                if (file_handle && file_handle != null) {
                    file_handle.write(data);
                    file_handle.flush();
                    file_handle.close();
                    console.log("Data written to " + filePath);
                    fileIndex++;
                } else {
                    console.log("Failed to create file: " + filePath);
                }
            } catch (e) {
                console.log("Error: " + e.message);
            }

        }, onLeave: function (args) { }
    })
}

function hook_dlopne() {
    var once = true;
    Interceptor.attach(Module.findExportByName(null, "dlopen"), {
        onEnter: function (args) {
            var loadFileName = args[0].readCString();
            if (loadFileName.indexOf('libjiagu') != -1) {
                console.log("Load -> ", loadFileName);
                this.is_can_hook = true;
            }
        }, onLeave: function () {
            if (this.is_can_hook && once) {
                travedec();
                once = false;

            }
        }
    })
}

setImmediate(hook_dlopne);

file

file

至此,结束。

评论

  1. Aaron
    2 年前
    2024-8-11 16:23:47

    太强了!|´・ω・)ノ

  2. R3n2h1
    2 年前
    2024-9-29 9:36:57

    TQL!!!!!!!!!!!!

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇