• android逆向奇技淫巧二十一:ida反反调试&加密算法跟踪(未完待续)(六)


      上周用ida调试x音的时候遇到了大量的弹窗,要是一不小心选择了“pass to app”,结果大概率直接崩掉......... 弹窗这个问题困扰我好久了,如果不解决,后面的trace就没法做了,该怎么解决了?这就要从弹窗的原理说起了!近期用ida调试时遇到的弹窗提示整理如下:

    3B745B60: got SIGSEGV signal (Segmentation violation) (exc.code b, tid 17222)
    3B745B60: got SIGSEGV signal (Segmentation violation) (exc.code b, tid 17222
    C2087AB6: got SIGILL signal (Illegal instruction) (exc.code 4, tid 23457)
    C197C3BA: got SIGSEGV signal (Segmentation violation) (exc.code b, tid 8991)
    C1984B64: got SIGCHLD signal (Child status has changed) (exc.code 11, tid 8973)
    C1710B64: got SIGCHLD signal (Child status has changed) (exc.code 11, tid 23546)
    C17083BA: got SIGSEGV signal (Segmentation violation) (exc.code b, tid 23565)
    D736D93C: got SIGSTOP signal (Stop unblockable) (exc.code 13, tid 23384)
    C1FEF3BA: got SIGSEGV signal (Segmentation violation) (exc.code b, tid 3838)
    F739B2B8: got SIGABRT signal (Abort) (exc.code 6, tid 3980)

      主要有segmentation violation、illegal instrction、child status has changed、abort、stop unblocked等,可谓是五花八门,什么样的都有!大家有没有想过为什么会弹窗了(这不废话么,当然是客户端为了保护自己故意反调试的啦)? 弹窗本质上也是一段代码,既然展示了出来,说明这段代码肯定被执行了!可是我们明明正在调试主线程,这些弹窗的代码都是怎么执行的了? 那就只能时另一种可能了:其他线程执行的!这些弹窗都是通过信号量提示的,说明不同的线程在利用信号量通信!在导入表里面搜索,确实能找到sigaction和pthrad_cond_signal函数,并且还在好几个地方被其他函数调用过!如果直接简单粗暴地NOP这些代码,我担心破坏原有正常的业务逻辑,所以就只能挂起其他线程了!这里直接使用看雪大佬YANG的脚本来挂起其他线程,然后再调试,整个ida再也没有弹窗,清爽多了!

      上次分析到:sub_6221C函数内部使用了base64码表,通过ida调试也在内存发现了疑似X-Argus、X-Ladon、X-Tyhon的字符串,这里为了进一步确认,从函数头开始逐行调试,整个逻辑清晰多了:

          

       R1指向加密字符串的末尾,每次移动4字节;加密字节分别存放在R0、R1和R6中,分别通过不同的偏移从R4指向的码表中取值!所以现在的关键就是确认这些码表内偏移是怎么得到的了!继续往上追溯:这几个偏移都是从R5计算而来的,而R5又是通过下面的方式得到:从这里可以看出,R5或R11指向的内存区域并不是字符串,这里有点失望!

      一般情况下:服务端为了确认接收到的数据没被篡改,会让客户端将加密的校验字段和原文一起发送;服务端接收后用同样的加密算法计算原文,如果和客户端发送的校验字段一致,说明原文没被篡改(数字证书就用到了这个原理);我原本的猜想:客户端会选择一下https包的原文字符串通过加密算法计算出校验字段,然后把校验字段和原文一起发送,所以在逐行调试时应该能找到原文字符串,结果大失所望,这里并不是!

      

       估计是网上追溯的层次不够,那就继续追呗!在sub_6221C开始的地方发现R11是R3得到的,这个R3应该是上层函数调用的参数:

           

          继续往上一层函数追溯,发现这里直接跳转过来了,所以R3又是由R5决定的!

          

         已经追溯到偏移为0x6C41E的地方,暂时还没找到https包的用来加密的原文,下一步打算根据栈回溯来挨个查找!

          

      

    小结:

      (1)这里确认找到了加密字段的生成代码,但是还没找到生成加密字段的原文是啥,需要继续追踪! 

           (2)信号量:主线程和子线程之间的通信方式,可能的反调试手段有:

      • 子线程不停地读取主线程status,一旦发现tracerpid不为0说明被调试了,可以发个kill或其他的信号,然后被调试器捕获,就弹窗给使用人员看了
      • 子线程和主线程通过信号量通信,一旦发现对方长时间不回复,说明“出事”了,所以要挂起其他线程,只留主线程  

    脚本:可挂起线程、指令级别地trace;

    # -*- coding: utf-8 -*-
    
    import idaapi
    import idc
    import re
    import ida_dbg
    import ida_idd
    from idaapi import *
    from collections import OrderedDict
    import logging
    import time
    import datetime
    import os
    
    
    debughook = None
    
    def xx_hex(ea):
        return hex(ea).rstrip("L").lstrip("0x")
    
    def set_breakpoint(ea):
        #idc.SetReg(ea, "T", 1)
        #idc.MakeCode(ea)#ida7.5报错:AttributeError: module 'idc' has no attribute 'MakeCode',新版本ida不兼容旧版本的api,链接有替代的接口
        idc.create_insn(ea)#https://hex-rays.com/products/ida/support/ida74_idapython_no_bc695_porting_guide.shtml
        idc.add_bpt(ea)
    
    def my_get_reg_value(register):
        rv = ida_idd.regval_t()
        ida_dbg.get_reg_val(register, rv)
        current_addr = rv.ival
        return current_addr
    
    
    def suspend_other_thread():
        current_thread = idc.get_current_thread()
        thread_count = idc.get_thread_qty()
        for i in range(0, thread_count):
            other_thread = idc.getn_thread(i)
            if other_thread != current_thread:
                idc.suspend_thread(other_thread)
    
    def resume_process():
        current_thread = idc.get_current_thread()
        thread_count = idc.get_thread_qty()
        for i in range(0, thread_count):
            other_thread = idc.getn_thread(i)
            if other_thread != current_thread:
                idc.resume_thread(other_thread)
        idc.resume_thread(current_thread)
        idc.resume_process()
    
    class MyDbgHook(DBG_Hooks):
        """ Own debug hook class that implementd the callback functions """
    
        def __init__(self, modules_info, skip_functions, end_ea):
            super(MyDbgHook, self).__init__()
            self.modules_info = modules_info
            self.skip_functions = skip_functions
            self.trace_step_into_count = 0
            self.trace_step_into_size = 1
            self.trace_total_size = 300000
            self.trace_size = 0
            self.trace_lr = 0
            self.end_ea = end_ea
            self.bpt_trace = 0
            self.Logger = None
            self.line_trace = 0
            print("__init__")
    
        def start_line_trace(self):
            self.bpt_trace = 0
            self.line_trace = 1
            self.start_hook()
    
        def start_hook(self):
            self.hook()
            print("start_hook")
    
        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):
            self.unhook()
            if self.Logger:
                self.Logger.log_close()
            print("Process exited pid=%d tid=%d ea=0x%x code=%d" % (pid, tid, ea, code))
    
        def dbg_process_detach(self, pid, tid, ea):
            self.unhook()
            self.Logger.log_close()
            return 0
    
        def dbg_bpt(self, tid, ea):
            print("Break point at 0x%x tid=%d" % (ea, tid))
            if ea in self.end_ea:
                ida_dbg.enable_insn_trace(False)
                ida_dbg.enable_step_trace(False)
                ida_dbg.suspend_process()
                return 0
            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
            if self.line_trace:
                in_mine_so = False
                for module_info in self.modules_info:
                    # print (module_info)
                    so_base = module_info["base"]
                    so_size = module_info["size"]
                    if so_base <= ea <= (so_base + so_size):
                        in_mine_so = True
                        break
    
                self.trace_size += 1
                if (not in_mine_so) or (ea in self.skip_functions):
                    if (self.trace_lr != 0) and (self.trace_step_into_count < self.trace_step_into_size):
                        self.trace_step_into_count += 1
                        return 0
    
                    if (self.trace_lr != 0) and (self.trace_step_into_count == self.trace_step_into_size):
                        ida_dbg.enable_insn_trace(False)
                        ida_dbg.enable_step_trace(False)
                        ida_dbg.suspend_process()
                        if self.trace_size > self.trace_total_size:
                            self.trace_size = 0
                            ida_dbg.request_clear_trace()
                            ida_dbg.run_requests()
    
                        ida_dbg.request_run_to(self.trace_lr)
                        ida_dbg.run_requests()
                        self.trace_lr = 0
                        self.trace_step_into_count = 0
                        return 0
    
                    if self.trace_lr == 0:
                        self.trace_lr = my_get_reg_value("LR")  #arm thumb LR, arm64 X30:注意这里的返回寄存器根据不同的指令选不同的寄存器
                return 0
    
        def dbg_run_to(self, pid, tid=0, ea=0):
            # print("dbg_run_to 0x%x pid=%d" % (ea, pid))
            if self.line_trace:
                ida_dbg.enable_insn_trace(True)
                ida_dbg.enable_step_trace(True)
                ida_dbg.request_continue_process()
                ida_dbg.run_requests()
    
    
    def unhook():
        global debughook
        # Remove an existing debug hook
        try:
            if debughook:
                print("Removing previous hook ...")
                debughook.unhook()
                debughook.Logger.log_close()
        except:
            pass
    
    
    def starthook():
        global debughook
        if debughook:
            debughook.start_line_trace()
    
    
    def main():
        global debughook
        unhook()
        skip_functions = []
        modules_info = []
        start_ea = 0
        end_ea = []
        so_modules = ["libmetasec_ml.so"]
        for module in idc._get_modules():
            module_name = os.path.basename(module.name)
            for so_module in so_modules:
                if re.search(so_module, module_name, re.IGNORECASE):
                    print("modules_info append %08X %s %08X" % (module.base, module.name, module.size))
                    if module_name == "libmetasec_ml.so":
                        modules_info.append({"base": module.base, "size": module.size, "name": module.name})
                        #start_ea = (module.base + 0x69d9c+1)      #X-Gorgon相关函数的开头
                        #end_ea = [((module.base + 0x6a44a+1))]   #函数结尾
                        start_ea = (module.base + 0x6221C+1)      #另外3个相关函数的开头
                        end_ea = [((module.base + 0x62312+1))]   #函数结尾
                        break
    
        if start_ea:
            set_breakpoint(start_ea)
        if end_ea:
            for ea in end_ea:
                set_breakpoint(ea)
    
        if skip_functions:
            print("skip_functions")
            for skip_function in skip_functions:
                print ("%08X" % skip_function)
        
        debughook = MyDbgHook(modules_info, skip_functions, end_ea)
        
        pass
    
    
    if __name__ == "__main__":
        main()
        pass

    补充:

      1、我这里的逆向方式有点“不走寻常路”:是先找加密代码,再回溯输入;因为arm有LR寄存器,所以函数调用时返回地址会先放入LR寄存器;如果存在函数嵌套调用,就会把LR的值push入栈,函数执行完后再pop到PC达到返回的目的(对比了一下,感觉还是x86更容易做栈回溯,因为有ebp栈帧;ebp+4就是返回地址,ebp+8就是参数),所以用ida回溯有两种方式:

    • LR回溯
    • 根据栈回溯

           2、“寻常路”的做法:参考第4条有个脚本,可以hook到native方法的注册;运行后发现libmetasec_ml.so被某个java层的函数唯一注册,对应的偏移是0x1094d;至于java层的哪个函数,感兴趣的小伙伴建议自行尝试一下,很容易找到的!可以用ida从0x1094d开始调式,看看java层的输入是怎么一步一步到加密函数的,也就能够确定java层的哪些输入字符串被so层的函数加密了!

    参考:

    1、https://www.cnblogs.com/wblyuyang/archive/2012/11/13/2768923.html  sigaction函数

    2、https://www.cnblogs.com/coffee520/p/10770918.html  APP加固反调试汇总

    3、https://gtoad.github.io/2017/06/25/Android-Anti-Debug/  Android反调试技术整理与实践

    4、https://github.com/lasting-yang/frida_hook_libart/blob/master/hook_RegisterNatives.js   registerNative的hook函数,可以查找所有的native函数注册地址(注意:第15行也就是for循环结束那行一定要加个break,否则会找到错误的registerNativeMethod地址,导致hook不到native方法

  • 相关阅读:
    大家帮忙出几个招聘考试题目吧
    单元测试和设计模式在重构中的应用
    想起去年和女朋友第一次去吃饭的事情
    为什么我们常忘记使用正则表达式
    .NET实用设计模式:观察者模式(Observer)
    一个Outlook宏写的小程序,献给象我一样粗心大意的人
    单元测试应该测什么,不应该测什么?
    .NET实用设计模式:工厂模式(Factory)
    2021 系统架构设计师备考分享
    系统架构设计师论文企业集成
  • 原文地址:https://www.cnblogs.com/theseventhson/p/15145299.html
Copyright © 2020-2023  润新知