吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 7440|回复: 12
收起左侧

[Android 脱壳] fart脱壳修复

[复制链接]
Shutd0wn 发表于 2020-11-9 12:33
本帖最后由 Shutd0wn 于 2020-11-9 12:36 编辑

fart脱壳后修复

用过fart的大佬都知道,拖出来的dex文件一般是需要修复一下的...

好久没有脱壳了,最近遇到一个apk需要脱壳,于是偷懒使用大神编译好的镜像刷入手机,脱壳一切顺利,上网搜了几个fart.py修复...

拖进jadx,喝口水,开始分析代码:

我看到了这些:

255efc: 0000                |0000: nop // spacer
255efe: 14004b1f003c        |0001: const v0, #int 0x3c001f4b
255f04: 0000                |0004: nop // spacer
255f06: 0000                |0005: nop // spacer
255f08: 0000                |0006: nop // spacer
255f0a: 0000                |0007: nop // spacer
255f0c: 0000                |0008: nop // spacer
255f0e: 0000                |0009: nop // spacer
255f10: 0000                |000a: nop // spacer
255f12: 0000                |000b: nop // spacer
255f14: 0000                |000c: nop // spacer
255f16: 0000                |000d: nop // spacer
255f18: 0000                |000e: nop // spacer
255f1a: 0000                |000f: nop // spacer
255f1c: 0000                |0010: nop // spacer
255f1e: 0000                |0011: nop // spacer
255f20: 1200                |0012: const/4 v0, #int 0 // #0
255f22: 0f00                |0013: return v0

.catches(1 try-catch)

  .try[0013->0014]
   catch(Ljava/lang/Exception;)->handler(0000)

.endmethod

于是我又修复了一边,然后,还是老样子...,此时我一脸懵逼!!!

到底哪里出现了问题???

难道加固又升级了吗,还是方法的inis没有拖出来,让我来打开bin文件看看

开始研究

打开bin文件,随便找一条拖出来的ins看看是不是没有成功拖出来

{name:void com.*.AccountLockActivity$1.<init>(com.*.AccountLockActivity, android.app.Activity, boolean, java.lang.String),method_idx:2013,offset:5953880,code_item_len:28,ins:BQAFAAQAAAAUV5wABgAAAFsB/gBwQLshIEMOAA==}

解出来

050005000400000014579c00060000005b01fe007040bb2120430e00

然后手动贴到dex文件对应的偏移中,使用jadx打开正常了。

就是dex修复的python有问题了...

准备下载hanbing大佬的python看看,代码太复杂被劝退了,于是在github上冲浪了好久,发现了几个简单的脚本,研究了一下,发现就是简单的读取offset,然后把inis解码后写进去就好了,原理很简单,为什么就没修复正确呢?

我们再来看看bin文件

重新观察一下bin文件

{name:void com.*.onError(java.lang.Throwable),method_idx:2014,offset:-1658796796,code_item_len:73,ins:BgACAAEAAQAAAAAAFgAAABIwIwCsJBIBTQQAARIRTQUAARIhEwNNAHEQPJEDAAwCTQIAAXEQggYAAA4AFQAAAAEAAQABAfosAA==}

这个的offset是负值,应该就是这个问题了

现在怎么办呢?拖出来的方法信息包括:

name, method_idx, offset, code_item_len, ins

似乎只能通过method_idx来确定了,打开一个正确的dex,先看看encoded_method:

名称 格式 说明
method_idx_diff uleb128 此方法标识(包括名称和描述符)的 method_ids 列表中的索引;它会表示为与列表中前一个元素的索引之间的差值。列表中第一个元素的索引则直接表示出来。
access_flags uleb128 方法的访问标记(publicfinal等)。如需了解详情,请参阅“access_flags 定义”。
code_off uleb128 从文件开头到此方法的代码结构的偏移量;如果此方法是 abstractnative,则该值为 0。偏移量应该是到 data 区段中某个位置的偏移量。数据格式由下文的“code_item”指定。

可以通过method_idx_diff属性值的叠加计算出method_idx,尝试读取原始dex的code_off来获取正确的偏移,然后完成修复。

写代码

第一个问题,是否有python版本的dex文件结构解析脚本...

通过google终于找到了,感谢kaitaistruct

# This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

from pkg_resources import parse_version
import kaitaistruct
from kaitaistruct import KaitaiStruct, KaitaiStream, BytesIO
from enum import Enum

if parse_version(kaitaistruct.__version__) < parse_version('0.9'):
    raise Exception("Incompatible Kaitai Struct Python API: 0.9 or later is required, but you have %s" % (kaitaistruct.__version__))

import vlq_base128_le
class Dex(KaitaiStruct):
    """Android OS applications executables are typically stored in its own
    format, optimized for more efficient execution in Dalvik virtual
    machine.

    This format is loosely similar to Java .class file format and
    generally holds the similar set of data: i.e. classes, methods,
    fields, annotations, etc.

    .. seealso::
       Source - https://source.android.com/devices/tech/dalvik/dex-format
    """

顺便找到了uleb128的解析python脚本

# This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

from pkg_resources import parse_version
import kaitaistruct
from kaitaistruct import KaitaiStruct, KaitaiStream, BytesIO

if parse_version(kaitaistruct.__version__) < parse_version('0.9'):
    raise Exception("Incompatible Kaitai Struct Python API: 0.9 or later is required, but you have %s" % (kaitaistruct.__version__))

class VlqBase128Le(KaitaiStruct):
    """A variable-length unsigned integer using base128 encoding. 1-byte groups
    consist of 1-bit flag of continuation and 7-bit value chunk, and are ordered
    "least significant group first", i.e. in "little-endian" manner.

    This particular encoding is specified and used in:

    * DWARF debug file format, where it's dubbed "unsigned LEB128" or "ULEB128".
      http://dwarfstd.org/doc/dwarf-2.0.0.pdf - page 139
    * Google Protocol Buffers, where it's called "Base 128 Varints".
      https://developers.google.com/protocol-buffers/docs/encoding?csw=1#varints
    * Apache Lucene, where it's called "VInt"
      http://lucene.apache.org/core/3_5_0/fileformats.html#VInt
    * Apache Avro uses this as a basis for integer encoding, adding ZigZag on
      top of it for signed ints
      http://avro.apache.org/docs/current/spec.html#binary_encode_primitive

    More information on this encoding is available at https://en.wikipedia.org/wiki/LEB128

    This particular implementation supports serialized values to up 8 bytes long.
    """

下来就是解析修复dex了

# https://formats.kaitai.io/vlq_base128_le/python.html
# https://github.com/kaitai-io/kaitai_struct/issues/439
# https://formats.kaitai.io/dex/python.html
# https://github.com/gagalin95/FART_repairdex/blob/master/repairdex.py
# python fart_fix_dex.py -d 11902988_dexfile.dex -b 11902988_ins_5455.bin

from dex import Dex
import optparse
import sys
import re
import base64
import binascii

def args_check():
    parser = optparse.OptionParser()
    parser.add_option('-d',
                      '--dex',
                      action='store',
                      dest='dex_file_path',
                      help='dex file path',
                      default='')
    parser.add_option('-b',
                      '--bin',
                      action='store',
                      dest='bin_file_path',
                      help='bin file path',
                      default='')
    return parser.parse_args()

def bin_init(bin_file_path):
    '''
    参数检查
    '''
    with open(bin_file_path, 'r') as file:
        bin_file = file.read()
    inists = bin_file.split(';')
    dex_bin_codes = {}
    for inis in inists:
        if inis != '':
            method_index = re.search(r'method_idx:(\d*)', inis).group(1)
            inis_code = re.search(r'ins:(\S*)}', inis).group(1)
            dex_bin_codes[method_index] = inis_code
    # print("origin info", dex_bin_codes)
    return dex_bin_codes

def fix_dex(dex_file_path, dex_bin_codes):
    '''
    修复dex
    '''
    dex_file = open(dex_file_path, 'rb+')
    dex = Dex.from_bytes(dex_file.read())

    class_defs = dex.class_defs
    for class_def in class_defs:
        # print('class def info', class_def.type_name)
        class_data = class_def.class_data
        if class_data is not None:
            virtual_method_index = 0
            # print('\tvitual method code length =', len(class_data.virtual_methods))
            for x in range(len(class_data.virtual_methods)):
                if x == 0:
                    virtual_method_index = class_data.virtual_methods[
                        x].method_idx_diff.value
                else:
                    virtual_method_index += class_data.virtual_methods[
                        x].method_idx_diff.value
                virtual_method_code_offset = class_data.virtual_methods[
                    x].code_off.value
                inis_code = dex_bin_codes.get(str(virtual_method_index))
                # 在实际测试中发现有可能需要修复triessize和debug
                if inis_code is not None:
                    dex_file.seek(virtual_method_code_offset, 0)
                    inis_code = base64.b64decode(inis_code.encode())
                    dex_file.write(inis_code)
                    dex_file.flush()
                # print('\tvirtual method code index:', virtual_method_index)
                # print('\tvirtual method code offset:',
                #       virtual_method_code_offset)

                # print('\tvirtual method inins:', binascii.b2a_hex(iniscode).decode())
            direct_method_index = 0
            # print('\tdirect method code length =', len(class_data.direct_methods))
            for x in range(len(class_data.direct_methods)):
                if x == 0:
                    direct_method_index = class_data.direct_methods[
                        x].method_idx_diff.value
                else:
                    direct_method_index += class_data.direct_methods[
                        x].method_idx_diff.value
                direct_method_code_offset = class_data.direct_methods[
                    x].code_off.value

                inis_code = dex_bin_codes.get(str(direct_method_index))
                if inis_code is not None:
                    # print('Yes')
                    dex_file.seek(direct_method_code_offset, 0)
                    inis_code = base64.b64decode(inis_code.encode())
                    dex_file.write(inis_code)
                    dex_file.flush()
                # print('\tvirtual method code index:', direct_method_index)
                # print('\tvirtual method code offset:',
                #       direct_method_code_offset)
                # print('\tdirect method inins:', binascii.b2a_hex(iniscode).decode())
    dex_file.close()

if __name__ == '__main__':
    options, _ = args_check()
    if options.dex_file_path == '' or options.bin_file_path == '':
        print('Usage:')
        print('\tpython3 fart_fix_dex.py -d dex_file_path -b bin_file_path')
    else:
        dex_file_path = options.dex_file_path
        bin_file_path = options.bin_file_path
        dex_bin_codes = bin_init(bin_file_path)
        fix_dex(dex_file_path, dex_bin_codes)

到这里就结束了,就可以尝试修复dex文件了

windows下安装使用

安装kaitaistruct

pip install --upgrade git+https://github.com/kaitai-io/kaitai_struct_python_runtime.git

使用

python fart_fix_dex.py -d dexfile -b binfile

Android下的修复app

实现了一个Android版本的修复dex的app,基本原理与python版本一致,再次感谢KaitaiStruct,让我们有了java版本的dex文件解析方法。

https://github.com/sepyeight/FartDexFix.git

免费评分

参与人数 3吾爱币 +3 热心值 +3 收起 理由
笙若 + 1 + 1 谢谢@Thanks!
丶咖啡猫丶 + 1 + 1 谢谢@Thanks!
tail88 + 1 + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

Crack_Transport 发表于 2020-11-9 21:09
学到了学到了 感谢分享
liujieboss 发表于 2020-11-10 00:22
hhhaiai 发表于 2020-11-10 09:48
头像被屏蔽
benq7378 发表于 2020-11-10 21:05
提示: 作者被禁止或删除 内容自动屏蔽
醉酒戏红颜 发表于 2020-11-12 09:51
感谢大神讲解分享
Memory丶冷风 发表于 2020-11-16 15:19
没有看懂,也不太明白   但是可以慢慢学习
alxstar 发表于 2020-11-17 08:50
谢谢老师分享
horse5700 发表于 2020-11-22 11:33
“于是偷懒使用大神编译好的镜像刷入手机” 这种镜像哪里找???
回不去的时光 发表于 2020-12-12 22:33
安卓版能用再发出来,ok?, 随便写几个代码就发出来,估计你连测试都没有
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-28 08:24

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表