作者:莊曉立 (liigo),2010/7/12
本文首發地址:aspx">http://blog.csdn.net/liigo/archive/2010/07/12/5727859.aspx
轉載請注明出處:http://blog.csdn.net/liigo
本文目標:在指定的某個LIB或OBJ文件中,搜索定位某一段可執行代碼(X86指令集合),最終確定其所屬函數。
原由:假設我們自己編寫的軟件被殺毒軟件誤報為病毒,又假設我們已經通過某種途徑獲知軟件(EXE)中被視為病毒特征碼的某段代碼(X86指令集合)(詳見本人(liigo)前一篇博客),又假設我們已經通過某種途徑得知這段代碼來自編譯鏈接過程中的某個LIB/OBJ文件(仍見本人前一篇博客),接下來呢,還要進一步在此LIB/OBJ文件中檢索定位特征碼,確定它是來自哪一個函數,這就是本文要討論的內容。
基本思路:解析LIB文件二進制格式(關於LIB/OBJ基本結構,可參見我(liigo)之前的一篇博文),遍歷LIB文件中的所有OBJ,遍歷每一個OBJ中的所有節(Section),在節(Section)的數據塊(RawData)中搜索特征碼,如果搜到則打印出此節(Section)中定義的所有符號(Symbol),根據其中的函數符號及相關偏移即可判斷特征碼所屬函數。解析LIB文件二進制格式,不在本文討論范圍之內。主要代碼框架如下:
//在LIB文件中搜索
CLibInfo libinfo;
if(!libinfo.LoadCLibFile(szLibFile))
{
printf("Can not load the lib file: %s
", szLibFile);
return 0;
}
CObjInfo* pObj = NULL;
for(unsigned int objIndex = 0; objIndex < libinfo.m_NumberOfMembers; objIndex++)
{
pObj = libinfo.m_pObjs[objIndex];
COFF_SectionHeader* pSectionHeader = pObj->m_SectionHeaders;
for(int sectionNumber = 1; sectionNumber <= pObj->m_pCoffHeader->NumberOfSections; sectionNumber++,pSectionHeader++)
{
void* pSectionRawData = pObj->m_pCoffData + pSectionHeader->PointerToRawData;
int lenSectionRawData = pSectionHeader->SizeOfRawData;
//search in section raw data
int matchedRate = 0, matchOffset = 0;
matchOffset = searchdata((unsigned char*)pSectionRawData, lenSectionRawData, (unsigned char*)signatureMem.GetData(), signatureMem.GetDataSize(), ignoreByte, minMatchRate, matchedRate);
if(matchOffset >= 0)
{
int libFileOffset = pObj->m_pCoffData + pSectionHeader->PointerToRawData - libinfo.m_pCLibData + matchOffset;
printf("in obj #%d (%s), match %d%% (at section offset %d, lib offset %d) in section #%d (%s), which contains symbol(s):
",
objIndex+1, pObj->m_szFileName, matchedRate, matchOffset, libFileOffset, sectionNumber, pObj->GetSectionName(pSectionHeader));
printSymbolNameInSection(pObj, sectionNumber);
printf(" matched data: ");
printDataBytes((unsigned char*)pSectionRawData + matchOffset, signatureMem.GetDataSize());
printf("
");
}
}
}
在上面的代碼中,一旦在某個節(Section)中匹配到特征碼,則輸出該節的序號和名稱,及其所屬obj的序號和名稱,被匹配數據在節中的偏移和在文件中的偏移等信息。注意“在文件中的偏移(libFileOffset)”的計算方法,要熟悉LIB/OBJ內部格式才行。
下面是輸出指定節(Section)中符號信息的代碼。注意必須要過濾掉附加輔助符號(Aux Symbols),其實還可以過濾掉節(Section)本身的符號,以及其它無關的符號,暫時沒有處理。輸出的信息包括符號名稱,是否為函數,符號數據在節中的偏移(存疑)等,足夠我們確定特征碼所屬函數。微軟(MicroSoft) Visual C++ 系列編譯器生成的LIB/OBJ文件,通常每個節中只有一個函數定義,更加易於做出判斷。
void printSymbolNameInSection(CObjInfo* pObj, int sectionNumber)
{
COFF_Symbol* pSymbol = pObj->m_Symbols;
for(unsigned int symIndex = 0; symIndex < pObj->m_pCoffHeader->NumberOfSymbols; symIndex++, pSymbol++)
{
const char* szSymbolName = pObj->GetSymbolName(pSymbol);
if(pSymbol->SectionNumber == sectionNumber)
{
printf(" %s%s, section offset %d
", szSymbolName, (pSymbol->Type == 0x20 ? "()" : ""), pSymbol->Value);
}
symIndex += pSymbol->NumberOfAuxSymbols;
pSymbol += pSymbol->NumberOfAuxSymbols;
}
}
檢索定位特征碼時,我(liigo)引入了最小匹配率(minMatchRate)和計算匹配率時欲忽略的字節值(ignoreByte),主要是考慮到,某些X86指令(如E8指令,call xxx)操作數為相對地址或需要重定位的地址,在EXE和LIB/OBJ中未必完全一致。代碼如下:
bool matchdata (unsigned char* pSearchFrom, unsigned char* pSearchWhat, int lenSearchWhat,
unsigned char ignoreByte, int minMatchRate, int& matchedRate)
{
int matchtimes = 0, matchtimesAll = lenSearchWhat;
for(int i = 0; i < lenSearchWhat; i++)
{
if(pSearchWhat[i] == ignoreByte)
{
matchtimesAll--;
}
else
{
if(pSearchWhat[i] == pSearchFrom[i])
matchtimes++;
}
}
int rate = (matchtimes * 100 / matchtimesAll);
if(rate > minMatchRate)
{
matchedRate = rate;
return true;
}
else
return false;
}
//if searched, return the offset; if not searched, return -1
int searchdata (unsigned char* pSearchFrom, int lenSearchFrom, unsigned char* pSearchWhat, int lenSearchWhat,
unsigned char ignoreByte, int minMatchRate, int& matchedRate)
{
for(int i = 0; i < lenSearchFrom - lenSearchWhat + 1; i++)
{
if(matchdata(pSearchFrom+i, pSearchWhat, lenSearchWhat, ignoreByte, minMatchRate, matchedRate))
{
return i;
}
}
return -1;
}
此外,我們允許用戶輸入的特征碼為16進制的文本數據,形如“FF7424 10 E8 00 00 00 00 C2 1000”,程序內部需將其轉換為內存中的二進制數據,每兩個字母轉換為一個字節值,並處理其中的空格等字符:
bool HexText2Mem(char* szSignature, BufferedMem& mem)
{
int len = strlen(szSignature);
char firstchar = ;
for(int i = 0; i < len; i++)
{
char c = szSignature[i];
if(c == || c == || c == , )
{
if(firstchar)
mem.AppendByte(hexchar2decimal(firstchar));
firstchar = ;
continue;
}
bool isLetterChar = ((c >= A && c <= F) || (c >= a && c <= f));
bool isNumChar = (c >= 0 && c <= 9);
if(!isLetterChar && !isNumChar)
{
szSignature[i+1] = ;
printf("
error in hexadecimal text of signature data, the printed last char is invalid:
%s
", szSignature);
return false;
}
if(firstchar == )
{
firstchar = c;
}
else
{
mem.AppendByte(hexchar2decimal(firstchar)*16 + hexchar2decimal(c));
firstchar = ;
}
}
return true;
}
int hexchar2decimal(char c)
{
if(c >= 0 && c <= 9)
return (c - 0);
else if(c >= A && c <= F)
return (c - A + 10);
else if(c >= a && c <= f)
return (c - a + 10);
else
return 0;
}
程序的最終運行結果如下圖。此搜索定位結果與上一篇用易語言定位的結果(圖)是一致的(對比搜索到的特征碼文件偏移及匹配率