从对XX加固免费版的逆向分析学习Android so的动态调试

本文分享了作者在接触Android逆向过程中对免费版加密壳的脱壳实践,包括使用jadx-gui、idapro、unicorn等工具,详细分析了脱壳流程,探讨了反调试技巧,并提供了一个自动解密工具的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概述:

       最近的工作中开始接触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)

解密结果:

打开看了一下没错是我想要的

写在最后:

       想了解下逆向需要的知识有哪些?就业环境怎么样?还请大佬们能帮忙说下

上传的附件:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值