概述:
最近的工作中开始接触Android逆向相关的知识,但接触的更多是友商的加密和反调试逻辑很少接触脱壳,在此之前只接触过数字家的壳,也脱了好久了,所以这次找个免费版的加密壳练习下脱壳。
用到的工具:jadx-gui、ida pro、unicorn
样本:2019-08-10加密的自开发的demo
新手刚开始接触逆向,如果分析有不足或改进之处还请各位提出,提前感谢各位~
一、分享些小窍门
1.Android so绝大部分是C语言实现的,在绝大多数C编译器实现的函数调用中通过BL*指令(返回地址放入LR中)进行跳转,函数入参翻入R0-R3寄存器中如果入参数量多于4个则把入参放入栈中
2.在反调试中so在判定有调试器或hook工具后大多数会选择直接退出所属进程,常见的方式有两种一种是raise(9)这种方式与kill -9 <pid>的方式相同,另一种exit(0),当你在调试so时发现进程无故退出了.可以考虑在这两个函数的入口处下断点,然后通过LR找到上下文
二、首先出张分析结果的流程图(概况)
如果你已经对我们今天的对象特别了解了那请看下我整理的流程图,如果发现哪里与你的印象不同欢迎讨论:
三、分享上手分析过程
首先查看Manifest.xml
Manifest没被改太多只修改了Application我们看一下它
还挺明显的,后面我们来看一下其他类吧,主要是Helper有一些配置信息和native方法,DexInstall类如其名
所以主要还是要处理动态链接库果断连上IDA pro因为我是Android 6.0手机,还自己编译了内核来过反调试,所以先openMemory下断点,啥?脱出来了...好尴尬..我们来分析下它的行为吧。
首先看下各个函数,应该做了些基于switch的流程控制不过没关系,我们先头铁的分析下
首先确定没有init.array 和init所以我们再Jni_Onload下断点在openMemory等系统函数下断点然后执行.我这边借一个小例子解释下开头的分享1:
看一个函数调用
我们长话短说想看具体流程请去看开头的流程图我下面只分享些重要的代码位置
这里对应流程块7
这里对应了加密包到.cache目录的移动流程,主要的移动逻辑open打开文件描述符read,write二连进行文件复制
这里通过一些数据的转换,将需要跳转的函数放入寄存器中,这样的逻辑主要的共有三处
这是第一处重要的是hook了一些libc.so的基础函数其中read函数尤为重要,重要在当dex文件加载时也是通过read函数来进行加载此处便可以判断是否为加密的dex是的话则原地解密,这里我找了好久...本以为会在dex加载前解决的解密问题居然在这.所以为了验证我们的观点先在这里下断点.
经过一些分析我们怀疑解密函数为第二个断点处先看下逻辑,嗯,非常好看了这里有一个全局变量,明显是个字节数组.我们先把它dump下来留着以后用.
这是第二处hook了一些libart.so的dex加载函数
这是第三处判断是否有classes.dex我们这里先不理它从首次加载是不会有那个文件的 也不要指望有明文数据摆在文件夹里面
这里就是DexInstall.install()了
然后是read的解密流程框那里内容与加密后的数据一致返回的magic code可以看出是zip包
到此分析完成,后面还有反调试不过不重要了..
根据这些因素我们来编写一个自动的解密工具,我们这里就不考虑自己C语言实现解密函数了随便玩玩就不认真吧,我们这里用unicorn来跑一下
# coding=utf-8 import logging import ctypes from unicorn.arm_const import UC_ARM_REG_R0,UC_ARM_REG_R1,UC_ARM_REG_R2,UC_ARM_REG_R3,UC_ARM_REG_PC,UC_ARM_REG_R6,UC_ARM_REG_SP from unicorn import Uc,UC_ARCH_ARM,UC_MODE_THUMB,UC_PROT_ALL,UC_HOOK_CODE,UcError UC_MEM_ALIGN = 0x1000 # 分页函数 def align(addr, size, growl): to = ctypes.c_uint64(UC_MEM_ALIGN).value mask = ctypes.c_uint64(0xFFFFFFFFFFFFFFFF).value ^ ctypes.c_uint64(to - 1).value right = addr + size right = (right + to - 1) & mask addr &= mask size = right - addr if growl: size = (size + to - 1) & mask return addr, size def hook_code(uc, address, size, user_data): if address>0x102ce22: print(">>> Tracing instruction at 0x%x, instruction size = 0x%x" %(address, size)) # print("r3 0x%x"%(uc.reg_read(UC_ARM_REG_R3))) if address == 0x102cd32: uc.mem_write(address,b'\x00\xbf') uc.reg_write(UC_ARM_REG_R1,0xfac13) if address == 0x102cd2a: uc.mem_write(address,b'\x00\xbf') uc.reg_write(UC_ARM_REG_R3,0xfac13) if address == 0x102cd5e: uc.reg_write(UC_ARM_REG_R3,0x3000000) if address == 0x102ce26: uc.reg_write(UC_ARM_REG_R3,0xfac13) if __name__ == "__main__": module_base = 0x1000000 data_base = 0x2000000 key_base = 0x3000000 # 字符串数组 key = b'\x66\x97\x6C\xE8\x6D\x46\x38\xB0\x09\x5A\xA5\xD7\x0F\xCB\x9A\xA0' simulator = Uc(UC_ARCH_ARM, UC_MODE_THUMB) with open('libSecShell.so','rb') as fstream: lib_bytes = fstream.read() filesize = len(lib_bytes) (address,size) = align(module_base,filesize,True); simulator.mem_map(address,size,UC_PROT_ALL) simulator.mem_write(address,lib_bytes) simulator.mem_map(0x800000, 0x1000, UC_PROT_ALL) simulator.reg_write(UC_ARM_REG_SP,0x801000) # 映入加密数据 encode_file = open('SecShell0.jar','rb') encode_bytes = encode_file.read() (encode_address,encode_size) = align(data_base,len(encode_bytes),True) simulator.mem_map(encode_address,encode_size,UC_PROT_ALL) simulator.mem_write(encode_address,encode_bytes) simulator.reg_write(UC_ARM_REG_R0,0) simulator.reg_write(UC_ARM_REG_R1,encode_address) simulator.reg_write(UC_ARM_REG_R2,len(encode_bytes)) simulator.mem_map(key_base,0x1000,UC_PROT_ALL) simulator.mem_write(key_base,key) start_address = module_base+0x2cd24+1 end_address = start_address+0x10e-1 print('end_address:0x%x'%end_address) simulator.hook_add(UC_HOOK_CODE, hook_code, start_address, end_address) # 填充NOP过滤掉栈校验和全局变量的赋值 simulator.mem_write(0x102cd2a,b'\x00\xbf') simulator.mem_write(0x102cd32,b'\x00\xbf') simulator.mem_write(0x102cd5e,b'\x00\xbf') simulator.mem_write(0x102ce26,b'\x00\xbf') simulator.emu_start(start_address,end_address) decode_size = len(encode_bytes); print('Decoded File Size:%d'%decode_size) ret = simulator.mem_read(data_base,len(encode_bytes)) 输出解密文件 dump_file = open('dump.zip','wb'); dump_file.write(ret) dump_file.flush() dump_file.close() # print(ret)
解密结果:
打开看了一下没错是我想要的
写在最后:
想了解下逆向需要的知识有哪些?就业环境怎么样?还请大佬们能帮忙说下
最后于 15小时前 被AlickX编辑 ,原因: 图片显示不出来