首页
论坛
课程
招聘
[调试逆向] [原创]用IDAPython实现的反反调试小脚本
2018-5-24 18:19 9515

[调试逆向] [原创]用IDAPython实现的反反调试小脚本

2018-5-24 18:19
9515

更新

加一个github地址DBGHider,里面加了inline hook。

背景

许多人喜欢用IDA进行静态分析,用OllyDbg进行动态分析,似乎很少人喜欢使用IDA进行调试。然而我这个菜鸟不会用OD,所以没有体会它的强大之处,我更喜欢用IDA的调试器。

 

IDA的调试器功能也很强大。

  • 支持x64位程序。
  • 可以充分利用静态分析的结果。
  • 支持多种平台,Windows版本的IDA支持本地Windows调试,同时也支持远程Windows、远程Linux、远程Android、远程Mac调试。
  • 支持多种架构,远程Android调试自然要用到ARM架构,配合QEMU模拟器还可以调试MIPS等架构。

由于IDA 7.x更改的SDK的API,就带来了一个很尴尬的事情,没有使用于IDA 7.x的反反调试插件,所以利用空闲时间写了一个小脚本,实现了对部分反调试方法的绕过。

反调试方法的分类

就我个人的理解,反调试的方法可大致分为以下3类:

  • PEB->BeingDebugged类,包括调用IsDebuggerPresent()或手动检测BeingDebugged标志。以及由于设置了BeingDebugged标志所引起的连锁反应,包括NtGlobalFlag, Heap Flags等。这种反调试方法可以通过在程序运行前清除标记位来绕过反调试。
  • 函数调用类,包括调用CheckRemoteDebuggerPresent()NtQueryInformationProcess()函数等。这类方法无法通过清楚标志位来绕过,但可以通过Hook相应函数来绕过。
  • 触发异常类,包括int 3, icebp等,原理是通过触发异常,然后检测是否收到异常来确定是否正在被调试。这类方法可以直接修改调试器的配置,使调试器把相应的异常传递给被调试的程序

DBG_Hooks

在IDAPython中,可以通过idaapi.DBG_Hooks类挂钩调试器的行为,进行一些操作。利用DBG_Hooks可以挂钩程序的开始,退出,加载dll,卸载dll等操作。具体挂钩点可以参考下面的Python脚本,这个脚本取自https://github.com/idapython/src。

#---------------------------------------------------------------------
# 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 *

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))

    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))

    def dbg_library_unload(self, pid, tid, ea, info):
        print("Library unloaded: pid=%d tid=%d ea=0x%x info=%s" % (pid, tid, ea, info))
        return 0

    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))

    def dbg_process_detach(self, pid, tid, ea):
        print("Process detached, pid=%d tid=%d ea=0x%x" % (pid, tid, ea))
        return 0

    def dbg_library_load(self, pid, tid, ea, name, base, size):
        print "Library loaded: pid=%d tid=%d name=%s base=%x" % (pid, tid, name, base)

    def dbg_bpt(self, tid, ea):
        print "Break point at 0x%x pid=%d" % (ea, tid)
        # 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

    def dbg_suspend_process(self):
        print "Process suspended"

    def dbg_exception(self, pid, tid, ea, exc_code, exc_can_cont, exc_ea, exc_info):
        print("Exception: pid=%d tid=%d ea=0x%x exc_code=0x%x can_continue=%d exc_ea=0x%x exc_info=%s" % (
            pid, tid, ea, exc_code & idaapi.BADADDR, exc_can_cont, exc_ea, exc_info))
        # return values:
        #   -1 - to display an exception warning dialog
        #        if the process is suspended.
        #   0  - to never display an exception warning dialog.
        #   1  - to always display an exception warning dialog.
        return 0

    def dbg_trace(self, tid, ea):
        print("Trace tid=%d ea=0x%x" % (tid, ea))
        # return values:
        #   1  - do not log this trace event;
        #   0  - log it
        return 0

    def dbg_step_into(self):
        print("Step into")
        self.dbg_step_over()

    def dbg_run_to(self, pid, tid=0, ea=0):
        print "Runto: tid=%d" % tid
        idaapi.continue_process()


    def dbg_step_over(self):
        eip = get_reg_value("EIP")
        print("0x%x %s" % (eip, GetDisasm(eip)))

        self.steps += 1
        if self.steps >= 5:
            request_exit_process()
        else:
            request_step_over()


# 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()

编写脚本

脚本实现的方式是,对于PEB->BeingDebugged类的反调试方法,可以利用dbg_process_start函数,在进程建立之初就清楚掉BeingDebugged,也一举解决了NtGlobalFlag等标志。对于函数调用类反调试,在程序的入口点处,给相应函数的入口下断点来Hook函数,更改函数行为。对于触发异常类反调试,不用脚本处理,更改调试器的设置即可。

 

目前实现的功能有:

  • 清除了BeingDebugged标志位(包括BeingDebugged引起的NtGlobalFlag)等
  • Hook了NtQueryProcessInformation()(这解决了CheckRemoteDebuggerPresent()),NtClose()两个函数。

由于IDA的改动,此脚本只适用于IDA 7.x

 

实现的脚本如下:

import idaapi
import idc
import idautils

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

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


        # add breakpoint at entry
        idc.add_bpt(StartEA(), 0, BPT_ENABLED | BPT_UPDMEM)


        # IDA creates a segment named "TIB[XXXXXXXX]", which points to
        # wow_peb64 antually. We can get peb from wow_peb64 with 0x1000 offset.
        #               peb_addr = wow_peb64_addr + 0x1000
        # Note: IDA has not created segment "TIB[XXXXXXXX]" at this point.

        # tid = get_current_thread()        
        # tib_segm_name = "TIB[%08X]" % tid
        # print tib_segm_name
        # tib_segm = get_segm_by_name(tib_segm_name)
        # wow_peb64 = tib_segm.start_ea
        # peb = tib_segm.start_ea + 0x1000

        # on debugging start, ebx points to peb
        # get addrs of peb and wow_peb64
        ebx = get_reg_value("ebx")
        peb = ebx
        wow_peb64 = peb - 0x1000

        # patch peb->BeingDebugged
        # solving peb->NtGlobalFlag and "Heap Magic" anti-debug method
        # at the same time.
        patch_dbg_byte(peb + 2, 0)
        patch_dbg_byte(wow_peb64 + 2, 0)


        # patching peb process paramters
        peb_process_parameters = idaapi.get_dword(peb + 0x10)
        flag = idaapi.get_dword(peb_process_parameters + 0x8)
        patch_dword(peb_process_parameters + 0x8, flag | 0x4000)

        # patching peb64 process paramters
        peb64_process_parameters = idaapi.get_qword(wow_peb64 + 0x20)
        flag = idaapi.get_dword(peb64_process_parameters + 0x8)
        patch_dword(peb64_process_parameters + 0x8, flag | 0x4000)


    def dbg_process_exit(self, pid, tid, ea, code):
        print("MyDbgHook Process exited pid=%d tid=%d ea=0x%x code=%d" % (pid, tid, ea, code))
        if self.nt_query_infomation_process != BADADDR:
            idc.del_bpt(self.nt_query_infomation_process)
        if self.nt_close != BADADDR:
            idc.del_bpt(self.nt_close)
        if self.process32_next != BADADDR:
            idc.del_bpt(self.process32_next)

    def dbg_library_unload(self, pid, tid, ea, info):
        print("MyDbgHook Library unloaded: pid=%d tid=%d ea=0x%x info=%s" % (pid, tid, ea, info))
        return 0

    def dbg_process_attach(self, pid, tid, ea, name, base, size):
        print("MyDbgHook Process attach pid=%d tid=%d ea=0x%x name=%s base=%x size=%x" % (pid, tid, ea, name, base, size))

    def dbg_process_detach(self, pid, tid, ea):
        print("MyDbgHook Process detached, pid=%d tid=%d ea=0x%x" % (pid, tid, ea))
        return 0

    def dbg_library_load(self, pid, tid, ea, name, base, size):
        print "MyDbgHook Library loaded: pid=%d tid=%d name=%s base=%x" % (pid, tid, name, base)

    def dbg_suspend_process(self):
        print "MyDbgHook Process suspended"
        ev_ea = get_event_ea()
        if ev_ea == StartEA():
            self.nt_query_infomation_process = get_name_ea_simple("ntdll_NtQueryInformationProcess")
            if self.nt_query_infomation_process != BADADDR:
                print("adding conditional breakpoint at NtQueryInformationProcess 0x%x" % self.nt_query_infomation_process)
                idc.add_bpt(self.nt_query_infomation_process)

                cond = """
                import idautils
                import idaapi

                process_information_class = idaapi.get_dword(idautils.cpu.esp + 8)
                if process_information_class == 7 or process_information_class == 30 \
                    or process_information_class == 31 or process_information_class ==35:
                    return True
                else:
                    return False
                """
                idc.set_bpt_cond(self.nt_query_infomation_process, cond)


            self.nt_close = get_name_ea_simple("ntdll_NtClose")
            if self.nt_close != BADADDR:
                print("adding breakpoint at NtClose 0x%x" % self.nt_close)
                cond = """
                import idautils
                import idaapi
                handle = idaapi.get_dword(idautils.cpu.esp + 4)
                if handle > 0x1000:
                    return True
                else:
                    return False
                """
                idc.add_bpt(self.nt_close)
                idc.set_bpt_cond(self.nt_close, cond)

            self.process32_next = get_name_ea_simple("kernel32_Process32Next")
            if self.process32_next != BADADDR:
                idc.add_bpt(self.process32_next)    
            idc.del_bpt(StartEA())
            idaapi.continue_process()


        elif ev_ea == self.nt_query_infomation_process:
            print "NtQueryInformationProcess"
            ret_addr = idaapi.get_dword(cpu.esp)
            process_handle = idaapi.get_dword(cpu.esp + 4)
            process_information_class = idaapi.get_dword(cpu.esp + 8)
            process_information = idaapi.get_dword(cpu.esp + 12)
            process_information_length = idaapi.get_dword(cpu.esp + 16)
            return_length = idaapi.get_dword(cpu.esp + 20)
            if process_information_class == 7 or process_information_class ==30:
                print("NtQueryInformationProcess(ProcessHandle=%x, ProcessInformationClass=%x, ProcessInformation=%x, ProcessInformationLength=%d, ReturnLength=%d)" \
                        % (process_handle, process_information_class, process_information, process_information_length, return_length))
                patch_dword(process_information, 0)
                cpu.eax = 0
                cpu.esp = cpu.esp + 24
                cpu.eip = ret_addr
            elif process_information_class == 31:
                print("NtQueryInformationProcess(ProcessHandle=%x, ProcessInformationClass=%x, ProcessInformation=%x, ProcessInformationLength=%d, ReturnLength=%d)" \
                        % (process_handle, process_information_class, process_information, process_information_length, return_length))
                patch_dword(process_information, 1)
                cpu.eax = 0
                cpu.esp = cpu.esp + 24
                cpu.eip = ret_addr
            elif process_information_class == 35:
                print("NtQueryInformationProcess(ProcessHandle=%x, ProcessInformationClass=%x, ProcessInformation=%x, ProcessInformationLength=%d, ReturnLength=%d)" \
                        % (process_handle, process_information_class, process_information, process_information_length, return_length))
                patch_word(process_information, 0)
                cpu.eax = 0
                cpu.esp = cpu.esp + 24
                cpu.eip = ret_addr
            idaapi.continue_process()
        elif ev_ea == self.nt_close:
            ret_addr = idaapi.get_dword(cpu.esp)
            handle  = idaapi.get_dword(cpu.esp + 4)
            print("NtClose = %x" % self.nt_close)
            print("NtClose(Handle=0x%x)" % handle)
            cpu.eax = 0
            cpu.esp = cpu.esp + 8
            cpu.eip = ret_addr
            idaapi.continue_process()

        elif ev_ea == self.process32_next:
            ret_addr = idaapi.get_dword(cpu.esp)
            h_snapshot  = idaapi.get_dword(cpu.esp + 4)
            lppe = idaapi.get_dword(cpu.esp + 8)
            print("Process32Next(hSnapshot=0x%x, lppe=0x%x)" % (h_snapshot, lppe))
            cpu.eax = 0
            cpu.esp = cpu.esp + 12
            cpu.eip = ret_addr
            idaapi.continue_process()


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

# Install the debug hook
debughook = MyDbgHook()
debughook.hook()
load_and_run_plugin('python', 3)

测试结果

使用了ScyllaHide中的ScyllaTest_x86.exe看雪.TSRC 2017CTF秋季赛中的第三题CrackMe做了测试,结果如下。

 

ScyllaTest_x86.exe

 

CrackMe,其特点是用了很多反调试手段,但是由于作者的失误,导致出现了多解,并且其中一个解还会在调试过程中以明文的形式作为memcmp的参数。


测试Crackme时对异常的设置

下一步的改进

  • 目前,Hook掉的函数还只有两个,可以添加更多的函数Hook
  • 现在所有的Hook都是在程序的入口点做的,可以利用dbg_library_load在dll加载时进行Hook
  • 利用软断点(0xCC)进行Hook可能被检测,可以改为利用动态插桩技术。

总结

反调试的方法很多,一个脚本肯定是解决不完的,只有搞清楚了反调试原理才能更好的对付反调试。写这个小脚本,是为了学习windows平台下各种反调试技术,同时学习一下IDAPython


《0day安全 软件漏洞分析技术(第二版)》第三次再版印刷预售开始!

最后于 2018-6-19 15:48 被iweizime编辑 ,原因:
收藏
点赞3
打赏
分享
最新回复 (15)
雪    币: 2694
活跃值: 活跃值 (55)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
BlackJZero 活跃值 2018-5-25 08:26
2
0
雪    币: 375
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
gegon 活跃值 2018-5-25 09:11
3
0
牛人,支持
雪    币: 6694
活跃值: 活跃值 (115)
能力值: ( LV7,RANK:107 )
在线值:
发帖
回帖
粉丝
SnowFox 活跃值 2018-5-25 11:26
4
0
雪    币: 3621
活跃值: 活跃值 (230)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
smartdon 活跃值 1 2018-5-25 12:49
5
0
雪    币: 4294
活跃值: 活跃值 (176)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
jgs 活跃值 2018-7-28 21:11
6
0
雪    币: 2043
活跃值: 活跃值 (95)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
coolsnake 活跃值 2018-7-28 21:42
7
0
厉害了,这个脚本
雪    币: 228
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
VsosV 活跃值 2018-8-9 10:31
8
0
厉害
雪    币: 48
活跃值: 活跃值 (19)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
一生热爱 活跃值 2019-1-24 14:35
9
0
大佬有没有学习IDAPython的资料分享一下啊
雪    币: 958
活跃值: 活跃值 (60)
能力值: ( LV15,RANK:728 )
在线值:
发帖
回帖
粉丝
iweizime 活跃值 7 2019-1-24 21:39
10
0
一生热爱 大佬有没有学习IDAPython的资料分享一下啊
https://leanpub.com/IDAPython-Book 免费的电子书
雪    币: 48
活跃值: 活跃值 (19)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
一生热爱 活跃值 2019-2-14 16:16
11
0
iweizime https://leanpub.com/IDAPython-Book 免费的电子书
嘤嘤嘤
雪    币: 113
活跃值: 活跃值 (215)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
又见飞刀z 活跃值 2020-3-11 20:30
12
0
请问大佬用的哪个版本的IDA?
雪    币: 958
活跃值: 活跃值 (60)
能力值: ( LV15,RANK:728 )
在线值:
发帖
回帖
粉丝
iweizime 活跃值 7 2020-3-17 22:45
13
0
又见飞刀z 请问大佬用的哪个版本的IDA?[em_13]
7.x 版本,可能是7.1
雪    币: 55
活跃值: 活跃值 (142)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
killpy 活跃值 2 2020-3-18 05:07
14
0
雪    币: 10
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
jetming 活跃值 2020-4-19 11:06
15
0
7.0版本可以么
雪    币: 5
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_shnxvjgx 活跃值 2020-4-20 21:41
16
0
可以的
游客
登录 | 注册 方可回帖
返回