3.1 IDA Pro編寫IDC指令碼入門

2023-11-11 12:00:54

IDA Pro內建的IDC指令碼語言是一種靈活的、C語言風格的指令碼語言,旨在幫助逆向工程師更輕鬆地進行反組合和靜態分析。IDC指令碼語言支援變數、表示式、迴圈、分支、函數等C語言中的常見語法結構,並且還提供了許多特定於反組合和靜態分析的函數和操作符。由於其靈活性和可延伸性,許多逆向工程師都喜歡使用IDC指令碼語言來自動化反組合和靜態分析過程,以提高效率和準確性。

在IDA中如果讀者按下Shift + F2則可調出指令碼編輯器,如下圖所示,其中左側代表當前指令碼的名稱列表,右側則代表指令碼的具體實現細節,底部存在三個選單,第一個按鈕是執行指令碼,第二個按鈕是覆蓋匯入指令碼,第三個則是追加匯入,他們之間的功能個有不同,讀者可自行體會;

3.1.1 IF語句的構建

IF語句的使用非常容易,如下程式碼,通過ScreenEA()函數識別到當前遊標所在位置處的指令記憶體地址,並對比該記憶體地址是否符合特定的條件,如果符合則輸出,不符合則最終輸出沒有找到;

#include <idc.idc>

static main()
{
    auto CurrAddress = ScreenEA(); 
    if(CurrAddress == 0x0046E31A)
    {
       Message("程式OEP => 0x%x \n",CurrAddress);
    }
    else if(CurrAddress == 0x0046E331)
    {
       Message("程式OEP => 0x%x \n",CurrAddress);
    }
    else
    {
      Message("沒有扎到OEP \n");
    }
}

3.1.2 FOR語句的構建

與C語言格式幾乎一致,For語句的構建也很容易理解,首先程式通過GetFunctionAttr()函數並設定FUNCATTR_START屬性獲取到當前遊標所指向程式段的開始地址,通過FUNCATTR_END設定遊標的結束位置,最後呼叫For迴圈,一次輸出當前記憶體地址及下一個記憶體地址,直到將本段內容全部輸出為止;

#include <idc.idc>

static main()
{
    auto origEA,currEA,funcStart,funcEnd;
    origEA = ScreenEA();
    
    // origEA = OEP 如果origEA 不在函數內則返回-1
    funcStart = GetFunctionAttr(origEA,FUNCATTR_START);
    funcEnd = GetFunctionAttr(origEA,FUNCATTR_END);
    Message("OEP: %x 起始地址: %x --> 結束地址: %x \n",origEA,funcStart,funcEnd);
    
    // NextHead 在currEA開始的位置尋找下一條指令的地址
    for(currEA = funcStart; currEA != -1; currEA=NextHead(currEA,funcEnd))
    {
        Message("指令地址:%8x \n",currEA);
    }
}

3.1.3 WHILE語句的構建

該語句的構建與FOR語句基本一致,與FOR語句唯一的不同在於該語句只能接受一個引數,如下程式碼中讀者需要注意GetFunctionName()可用於獲取當前遊標所在位置處所屬函數的名稱。

#include <idc.idc>

static main()
{
    auto origEA,currEA,funcStart,funcEnd;
    origEA = ScreenEA();

    // origEA = OEP 如果origEA 不在函數內則返回-1
    funcStart = GetFunctionAttr(origEA,FUNCATTR_START);
    funcEnd = GetFunctionAttr(origEA,FUNCATTR_END);
    Message("OEP: %x 起始地址: %x --> 結束地址: %x \n",origEA,funcStart,funcEnd);
    
    while(currEA != BADADDR)
    {
        Message("--> %x name: %s \n",currEA,GetFunctionName(currEA));
        currEA = NextHead(currEA,funcEnd);
    }
}

3.1.4 函數的實現

IDA中使用函數通常可在一個字串之前定義為static,函數的參數列一般而言是以逗號進行間隔開的,當函數存在返回值是則通過return語句返回。

#include <idc.idc>

// 定義一個函數
static OutPutAddress(MyString)
{
    auto currAddress;
    currAddress = ScreenEA();
    Message("%d \n",MyString);
    return currAddress;
}

// 傳遞多個引數
static OutPutAddressB(x,y)
{
    return x+y;
}

static main()
{
    auto ret = OutPutAddress(123);
    Message("返回當前地址 = 0x%x \n",ret);
    
    auto ref = OutPutAddressB(100,200);
    Message("計算數值 = %d \n",ref);
    
}

3.1.5 定義並使用陣列

與高階語言類似,IDC指令碼中同樣支援陣列操作,不同於C語言中的陣列,IDC中在使用時首先需要通過CreateArray("array")建立一個陣列,當陣列指標被建立成功後下一步則是通過GetArrayId("array")得到該陣列的指標,通過指標讀者可以使用SetArrayString設定一個字串變數,或使用SetArrayLong設定整數變數,當用戶需要使用變數時則需要通過GetArrayElement()函數對陣列內的資料進行提取,提取時AR_STR代表提取字串,AR_LONG則代表提取整數型別,當讀者需要刪除陣列內的特定元素可使用DelArrayElement()函數,最後使用結束呼叫DeleteArray()登出整個陣列;

#include <idc.idc>

static main()
{
    // 建立陣列元素
    auto array_ptr = CreateArray("array");
    // 獲取陣列指標
    auto ptr = GetArrayId("array");
    
    Message("獲取到的操作指標: %x \n",ptr);
    
    // 設定兩個字串變數
    SetArrayString(ptr,0,"hello");
    SetArrayString(ptr,1,"lyshark");
    
    // 設定兩個整數變數
    SetArrayLong(ptr,2,100);
    SetArrayLong(ptr,3,200);
    
    // 如果提取字串使用 AR_STR 標記 ,提取整數使用 AR_LONG
    auto st = GetArrayElement(AR_STR,ptr,0);
    auto st1 = GetArrayElement(AR_STR,ptr,1);
    Message("提取字串變數: %s %s !\n",st,st1);
    
    auto lo = GetArrayElement(AR_LONG,ptr,2);
    Message("提取整數變數: %d \n",lo);
    
    // 刪除陣列的0號元素
    DelArrayElement(AR_STR,ptr,0);
    // 登出整個陣列
    DeleteArray(ptr);
}

3.1.6 字串處理

IDC中讀者可以使用form()函數實現對特定字串的格式化輸出操作,IDC中同樣也內建了各類轉換函數,如下程式碼所示,則是IDC中可以經常被用到的函數呼叫,讀者可自行參考;

#include <idc.idc>

static main()
{
    // 格式化字串,類似於sprintf
    auto name = form("hello %s","lyshark");
    Message("格式化後的內容: %s \n",name);
    
    Message("十六進位制轉為整數: %d \n",xtol("0x41"));
    Message("十進位制100轉為八進位制: %d \n",ltoa(100,8));
    Message("十進位制100轉換二進位制: %d \n",ltoa(100,2));
    Message("字元A的ASCII: %d \n",ord("A"));
    Message("計算字串長度: %d \n",strlen("hello lyshark"));
    
    // 在主字串中尋找子串
    auto main = "hello lyshark";
    auto sub = "lyshark";
    Message("尋找子串: %d \n",strstr(main,sub));
}

3.1.7 列舉所有函數

如下指令碼實現了列舉當前指標所在位置處所有函數名稱及地址,首先通過ScreenEA()函數獲取當前指標所在位置,通過SegStart()用於獲取該指標所在位置處模組的開始地址,與之對應的是SegEnd();則用於獲取結束地址,接著通過呼叫GetFunctionName();得到當前地址處的函數名,並依次通過NextFunction();得到下一個模組地址,最終輸出所有函數名及其地址資訊;

#include <idc.idc>

static main()
{
    auto currAddr,func,endSeg,funcName,counter;
    
    currAddr = ScreenEA();
    func = SegStart(currAddr);
    endSeg = SegEnd(currAddr);
    Message("%x --> %x \n",func,endSeg);
    
    counter = 0;
    while(func != BADADDR && func < endSeg)
    {
        funcName = GetFunctionName(func);
        if(funcName != " ")
        {
            Message("%x --> %s \n",func,funcName);
            counter++;
        }
        func = NextFunction(func);
    }
}

當然讀者可以通過增加IF語句來判斷funcName函數名是否是我們所需要列舉的,如果是則輸出,如果不是則繼續下一個函數,依次類推實現函數列舉功能,讀者只需要在上述程式碼基礎上稍加改進即可實現;

#include <idc.idc>

static main()
{
    auto currAddr,func,endSeg,funcName,counter;
    
    currAddr = ScreenEA();
    func = SegStart(currAddr);
    endSeg = SegEnd(currAddr);
    Message("%x --> %x \n",func,endSeg);
    counter = 0;
    
    while(func != BADADDR && func < endSeg)
    {
        funcName = GetFunctionName(func);
        if(funcName != " ")
        {
            if(funcName == "__lock")
            {
                Message("%x --> %s \n",func,funcName);
            }
            counter++;
        }
        func = NextFunction(func);
    }
}

3.1.8 設定記憶體區域標籤高亮

標籤高亮功能的實現依賴於SetColor函數,該函數傳入三個引數,其中引數1用於指定需要檢索的範圍,該範圍可以通過NextHead()函數獲取到,只要該節點不會返回BADADDR則可以繼續遍歷下一個節點,第二個引數則代表標註型別,第三個引數代表要在那個位置進行標註;

#include <idc.idc>

static main(void)
{
    auto head, op;
    head = NextHead(0x00000000, 0xFFFFFFFF);
    while ( head != BADADDR )
    {
        op = GetMnem(head);
        Message("%x %s \n",head,op);

        
        if ( op == "jmp" || op == "call" )
            SetColor(head, CIC_ITEM, 0x010187);
            
        if (op == "xor")
            SetColor(head, CIC_ITEM, 0x010198);
        head = NextHead(head, 0xFFFFFFFF);
    }
}

3.1.9 地址反組合輸出

在IDA中有時我們需要對特定位置進行反組合,並以指令碼的方式輸出,此時讀者可使用GetDisasm(inst)函數來實現,該函數傳入一個RfirstB生成的迭代型別,並依次迴圈輸出,直到對100行輸出為止;

#include <idc.idc>

static main(void)
{
    auto decode = 0x401000;
    auto xref;
    
    for(xref = RfirstB(decode); xref != BADADDR; xref = RnextB(decode,xref))
    {
        Message("xref: %x\n",xref);
        auto i = 0;
        auto inst = xref;
        auto op;
        
       while((i < 100) )
       {
            // 向後列舉下一個
            inst = FindCode(inst,0x00);
        
            // 輸出反組合
            op = GetDisasm(inst);
            Message("%x --> %s \n",inst,op);
            i++;
       }
    } 
}

當具備了反組合功能後,那麼讀者則可通過各種方式實現對指令集的判斷,並以此來實現過濾特定指令地址並輸出的目的,如下所示,通過strstr()函數對符合特定條件的字串進行過濾,當找到後返回該函數的所在位置;

#include <idc.idc>

static main()
{
    auto currAddr,startSeg,endSeg;
    
    currAddr = ScreenEA();
    startSeg = SegStart(currAddr);
    endSeg = SegEnd(currAddr);
    
    Message("OEP = %x 起始地址: %x 結束地址: %x \n",currAddr,startSeg,endSeg);
    
    while(startSeg < endSeg)
    {
        auto op = GetDisasm(startSeg);
        
        // 查詢第一條指令
        if(strstr(op,"push    esi")==0)
        {
            startSeg++;
            op = GetDisasm(startSeg);
            if(strstr(op,"push    edi"))
            {
                Message("特徵: %x \n",startSeg-1);
            }
        }
        startSeg++;
    }
}

當然反組合函數並非只有GetDisasm讀者同樣可以使用GetMnem返回位於特定地址處的指令,GetOpnd用於返回特定位置處的機器碼,同樣可以使用FindBinary實現對特定地址的特徵碼搜尋功能;

#include <idc.idc>

static main()
{
    // 搜尋特徵碼
    auto code = FindBinary(0x401020,1,"55 8B EC");
    Message("%x \n",code);

    // 返回反組合程式碼
    code = GetDisasm(0x401000);
    Message("%s \n",code);
    
    // 返回位於地址處的指令
    code = GetMnem(0x401000);
    Message("%s \n",code);
    
    // 返回opcode機器碼
    code = GetOpnd(0x401070,0);
    Message("%s \n",code);
}

3.1.10 列舉函數棧幀

生成每個函數的棧幀,通過NextFunction()函數可實現列舉當前模組內所有函數地址,通過迴圈並呼叫GetFram()來得到當前函數棧幀大小,並使用GetMemberOffset()儲存棧中返回地址偏移量,依次迴圈輸出當前函數內的完整棧幀資料;

#include <idc.idc>

static main()
{
    auto addr,args,end,locals,frame,firstArg,name,ret;
    
    for(addr = NextFunction(addr); addr != BADADDR; addr = NextFunction(addr))
    {
        name = Name(addr);
        end = GetFunctionAttr(addr,FUNCATTR_END);
        locals = GetFunctionAttr(addr,FUNCATTR_FRSIZE);
        
        // 得到棧幀大小
        frame = GetFrame(addr);
        
        // 棧中儲存返回地址偏移量
        ret = GetMemberOffset(frame," r");
        if(ret == -1)
        {
            continue;
        }
        
        firstArg = ret +4;
        args = GetStrucSize(frame) - firstArg;
        
        Message("函數: %s 開始: 0x%x 結束: 0x%x 大小: %d bytes 棧幀: %d bytes (%d args) \n",name,addr,end,locals,args,args/4);
    }
}

3.1.11 檢索交叉參照

列舉當前模組中的交叉參照,通過XrefType()函數可列舉出當前被分析程式中的交叉參照情況,如下案例中實現了對當前程式內所有交叉參照的列舉工作,並輸出三個引數,引數1代表主函數,引數2代表被參照函數,引數3代表當前函數的記憶體地址;

#include <idc.idc>

static main()
{
    auto func,end,target,inst,name,flags,xref;
    flags = SEARCH_DOWN | SEARCH_NEXT;
    func = GetFunctionAttr(ScreenEA(),FUNCATTR_START);
    
    if(func != -1)
    {
        name =Name(func);
        end = GetFunctionAttr(func,FUNCATTR_END);
        for(inst = func;inst < end; inst = FindCode(inst,flags))
        {
            for(target = Rfirst(inst);target != BADADDR; target = Rnext(inst,target))
            {
                xref = XrefType();
                if(xref == fl_CN || xref == fl_CF)
                {
                    Message("%s | %s | %x \n",name,Name(target),inst);
                }
            }
        }
    }
}

如果讀者想要實現列舉特定一個函數的交叉參照資訊,則可通過使用LocByName(bad_func)增加過濾條件,並依次實現過濾特定函數的目的,程式碼的修改只需要小改即可;

#include <idc.idc>

static FindFunction(bad_func)
{
    auto func,addr,xref,source;
    
    func = LocByName(bad_func);
    if(func == BADADDR)
    {
        Message("error \n");
    }
    else
    {
        for(addr = RfirstB(func);addr != BADADDR; addr = RnextB(func,addr))
        {
            xref = XrefType();
            if(xref == fl_CN || xref == fl_CF)
            {
                source = GetFunctionName(addr);
                Message("%s call => %0x in %s \n",bad_func,addr,source);
            }
        }
    }  
}

static main()
{
    FindFunction("LoadString");
}