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方法


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM