3.1 IDA Pro编写IDC脚本入门
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");
}
總結
以上是生活随笔為你收集整理的3.1 IDA Pro编写IDC脚本入门的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 解决Few-shot问题的两大方法:元学
- 下一篇: php开发之文件上传的实现