吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 10840|回复: 40
收起左侧

[Android 原创] 基于IDA Python的OLLVM反混淆(二) 实战腾讯御安全(模拟器)

  [复制链接]
Shocker 发表于 2021-8-5 13:35
本帖最后由 Shocker 于 2021-8-10 10:33 编辑

前言

上一篇中介绍了IDA Python去混淆的基本思路,本篇将以腾讯免费御安全为例进行脚本反混淆.(以x86_32指令为例)

思路

首先分析出控制块的特征,将可能是控制块的块去掉,之后连接所有真实的块.

实战

实验环境:ida7.5+雷电模拟器(64位)4

Jadx打开样本,发现加载了libshellx-super.2019.so文件

jadx_1.jpg
IDA加载libshellx-super.2019.so,发现JNI_OnLoad被混淆
IDA_1.jpg

发现是个魔改的OLLVM.

找出疑似控制块的代码

  1. ida_jmp.jpg
  2. ida_nop.jpg
  3. IDA_mov.jpg
  4. ida_cmov.jpg

可以分析出,上面四种类型的块与真实逻辑毫无关系

去混淆脚本

对疑似控制块的块不下断点,其余块结尾下断点

#---------------------------------------------------------------------
# Debug notification hook test
#
# This script start the executable and steps through the first five
# instructions. Each instruction is disassembled after execution.
#
# Original Author: Gergely Erdelyi <gergely.erdelyi@d-dome.net>
#
# Maintained By: IDAPython Team
#
#---------------------------------------------------------------------
from idaapi import *
import keypatch

patcher=keypatch.Keypatch_Asm()

fun_offset=0x25D0 #函数地址

class MyDbgHook(DBG_Hooks):
    """ Own debug hook class that implementd the callback functions """

    def dbg_process_start(self, pid, tid, ea, name, base, size):
        print("Process started, pid=%d tid=%d name=%s" % (pid, tid, name))
        self.dbg_process_attach(pid, tid, ea, name, base, size)

    def dbg_process_exit(self, pid, tid, ea, code):
        print("Process exited pid=%d tid=%d ea=0x%x code=%d" % (pid, tid, ea, code))
        for sub_dict in self.related_dict:
            if len(self.related_dict[sub_dict])==1:
              for sub_sub_dict_key in self.related_dict[sub_dict]:
                  if idc.print_insn_mnem(sub_dict).startswith('j'):
                        disasm='jmp'+' '+hex(self.related_dict[sub_dict][sub_sub_dict_key].pop())
                        patcher.patch_code(sub_dict,disasm,patcher.syntax,True,False)

    def dbg_process_attach(self, pid, tid, ea, name, base, size):
        print("Process attach pid=%d tid=%d ea=0x%x name=%s base=%x size=%x" % (pid, tid, ea, name, base, size))
        self.pre_blcok=None
        self.ZF_flag=None
        self.related_dict=dict()
        self.block_addr_dict=dict()
        so_base=idaapi.get_imagebase()
        self.f_blocks = idaapi.FlowChart(idaapi.get_func(so_base+fun_offset), flags=idaapi.FC_PREDS)
        for block in self.f_blocks:
            start=block.start_ea
            end=idc.prev_head(block.end_ea)
            if (idc.print_insn_mnem(start)=='cmp' and idc.print_insn_mnem(idc.next_head(start)).startswith('j')) or \
                (idc.print_insn_mnem(start)=='mov' and idc.print_insn_mnem(idc.next_head(start))=='cmp' and idc.print_insn_mnem(idc.next_head(idc.next_head(start))).startswith('j')) or \
                idc.print_insn_mnem(start)=='jmp' or \
                idc.print_insn_mnem(start)=='nop' or \
                idc.print_insn_mnem(start).startswith('cmov'):
                continue
            add_bpt(end,0,BPT_SOFT)
            while start<block.end_ea:
                if idc.print_insn_mnem(start).startswith('ret'):
                    add_bpt(start,0,BPT_SOFT)
                    break
                start=idc.next_head(start)

    def dbg_bpt(self, tid, ea):
        print ("Break point at 0x%x pid=%d" % (ea, tid))

        if not self.block_addr_dict:
            so_base=idaapi.get_imagebase()
            blocks=idaapi.FlowChart(idaapi.get_func(so_base+fun_offset), flags=idaapi.FC_PREDS)
            for block in blocks:
                start=block.start_ea
                end=idc.prev_head(block.end_ea)
                self.block_addr_dict[end]=start

        if not self.pre_blcok==None:
            if self.pre_blcok in self.related_dict:
                ori_dict=self.related_dict[self.pre_blcok]
                if self.ZF_flag in ori_dict:
                    sub_set=ori_dict[self.ZF_flag]
                    sub_set.add(self.block_addr_dict[ea])
                else:
                    sub_set=set()
                    sub_set.add(self.block_addr_dict[ea])
                    ori_dict[self.ZF_flag]=sub_set
            else:
                # 不存在
                sub_set=set()
                sub_set.add(self.block_addr_dict[ea])
                sub_dict={self.ZF_flag:sub_set}
                self.related_dict.update({self.pre_blcok:sub_dict})

        self.pre_blcok=ea
        self.ZF_flag = get_reg_value("ZF")

        if idc.print_insn_mnem(ea).startswith('ret'):
            return 0
        else:
            idaapi.continue_process()
        # return values:
        #   -1 - to display a breakpoint warning dialog
        #        if the process is suspended.
        #    0 - to never display a breakpoint warning dialog.
        #    1 - to always display a breakpoint warning dialog.
        return 0

# Remove an existing debug hook
try:
    if debughook:
        print("Removing previous hook ...")
        debughook.unhook()
except:
    pass

# Install the debug hook
debughook = MyDbgHook()
debughook.hook()
debughook.steps = 0

# Stop at the entry point
ep = get_inf_attr(INF_START_IP)
request_run_to(ep)

# Step one instruction
request_step_over()

# Start debugging
run_requests()
for block in self.f_blocks:
            start=block.start_ea
            end=idc.prev_head(block.end_ea)
            if (idc.print_insn_mnem(start)=='cmp' and idc.print_insn_mnem(idc.next_head(start)).startswith('j')) or \
                (idc.print_insn_mnem(start)=='mov' and idc.print_insn_mnem(idc.next_head(start))=='cmp' and idc.print_insn_mnem(idc.next_head(idc.next_head(start))).startswith('j')) or \
                idc.print_insn_mnem(start)=='jmp' or \
                idc.print_insn_mnem(start)=='nop' or \
                idc.print_insn_mnem(start).startswith('cmov'):
                continue
            add_bpt(end,0,BPT_SOFT)
            while start<block.end_ea:   #对retn块下断点
                if idc.print_insn_mnem(start).startswith('ret'):
                    add_bpt(start,0,BPT_SOFT)
                    break
                start=idc.next_head(start)

这里对所有疑似真实块的地址下断点

def dbg_process_exit(self, pid, tid, ea, code):
        print("Process exited pid=%d tid=%d ea=0x%x code=%d" % (pid, tid, ea, code))
        for sub_dict in self.related_dict:
            if len(self.related_dict[sub_dict])==1:
              for sub_sub_dict_key in self.related_dict[sub_dict]:
                  if idc.print_insn_mnem(sub_dict).startswith('j'):
                        disasm='jmp'+' '+hex(self.related_dict[sub_dict][sub_sub_dict_key].pop())
                        patcher.patch_code(sub_dict,disasm,patcher.syntax,True,False)

记录所有真实块的下一个真实块.
我这里用一个dict嵌套dict来记录,
大致是这样
{0x2638:{0:xxxxx,1:xxxxx}} 这里的0和1是我对ZF标志的判断,xxxxx是下一个真实块的首地址.
得到所有真实块的流程后,就可以进行跳转的修复
我这里仅仅只是对真实块中只有一个后继真实块.
且结尾为jxx的块做了修复

IDA动态调试

  1. 打开模拟器,将调试文件传进模拟器,执行

    adb shell /data/local/tmp/android_x86_server
  2. 端口转发

    adb forward tcp:23946 tcp:23946
  3. 打开ddms

  4. 调试模式启动app

    adb shell am start -D -n com.shocker.jiagutest/.MainActivity
  5. IDA先载入脚本,后附加到app上
    ida_attach.jpg

  6. jdb方式启动

    jdb -connect com.sun.jdi.SocketAttach:hostname=localhost,port=8700
  7. F9运行直到运行至retn,继续F9 butu.jpg

  8. 退出程序
    exit.jpg

  9. 将Patch后的so拖进apk对应的目录中,重装app(因为patch后有些块会连在一起,所以需要再次执行脚本)

  10. 删除所有断点,或者重新载入IDA,执行一遍脚本.

  11. 运行ida-nop脚本,将没执行的块全部nop.

    结果

    去混淆后的结果
    res.jpg
    F5后的结果
    res2.jpg

相关示例代码见
https://github.com/PShocker/de-ollvm/

免费评分

参与人数 26威望 +2 吾爱币 +124 热心值 +24 收起 理由
happyBread + 1 + 1 我很赞同!
#sky# + 1 + 1 我很赞同!
Rclear + 1 + 1 谢谢@Thanks!
石碎大胸口 + 1 + 1 用心讨论,共获提升!
18377488391 + 1 + 1 谢谢@Thanks!
lingyun011 + 1 热心回复!
a3586597 + 1 热心回复!
hy8051hy + 1 谢谢@Thanks!
KylinYang + 1 + 1 我很赞同!
DancingLight + 1 + 1 谢谢@Thanks!
chenjingyes + 1 + 1 谢谢@Thanks!
404undefined + 1 + 1 我很赞同!
Apj126 + 1 + 1 热心回复!
笙若 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
gaosld + 1 + 1 谢谢@Thanks!
xmmyzht + 1 + 1 谢谢@Thanks!
zhaozhao1 + 1 + 1 谢谢@Thanks!
独行风云 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
Qfrost + 1 + 1 我很赞同!
victos + 1 + 1 用心讨论,共获提升!
fengbolee + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
yixi + 1 + 1 谢谢@Thanks!
lllliiii + 1 + 1 用心讨论,共获提升!
ZHHua + 1 + 1 用心讨论,共获提升!
24k纯金滑稽 + 1 + 1 用心讨论,共获提升!
qtfreet00 + 2 + 100 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

本帖被以下淘专辑推荐:

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

Hmily 发表于 2021-8-6 07:48
@Shocker 图片有问题,没上传成功?

看了下你两篇帖子图片都有问题,不要用引用图片地址,直接在markdown文章里用disucz的贴图方法就行,看这个演示https://www.52pojie.cn/misc.php?mod=faq&action=faq&id=29&messageid=36
nevinhappy 发表于 2021-8-5 15:58
Li1y 发表于 2021-8-5 15:58
GuXing 发表于 2021-8-5 22:42
感谢发布原创作品,吾爱破解论坛因你更精彩!
goda 发表于 2021-8-5 23:45
谢谢分享
tzlqjyx 发表于 2021-8-6 07:26
谢谢分享,学习来了
无言Y 发表于 2021-8-6 08:16
感谢分享
fhb780621 发表于 2021-8-6 08:37
学习了,很有作用
LiZan 发表于 2021-8-6 10:11
看大佬操作
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

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

GMT+8, 2024-12-22 12:06

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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