当前位置:编程学习 > C/C++ >>

在静态库LIB/OBJ文件中搜索定位病毒特征码所属函数,C/C++源码

作者:庄晓立 (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;
}

  程序的最终运行结果如下图。此搜索定位结果与上一篇用易语言定位的结果(图)是一致的(对比搜索到的特征码文件偏移及匹配率

补充:软件开发 , C++ ,
CopyRight © 2012 站长网 编程知识问答 www.zzzyk.com All Rights Reserved
部份技术文章来自网络,