概述:
用到的工具: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
二、首先出张分析结果的流程图(概况)
如果你已经对我们今天的对象特别了解了那请看下我整理的流程图,如果发现哪里与你的印象不同欢迎讨论:
三、分享上手分析过程
首先查看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)
解密结果:
打开看了一下没错是我想要的