天天看點

《C++ 黑客程式設計揭秘與防範(第2版)》——6.4 PE相關程式設計執行個體define NAMELEN 20define SIGNLEN 32

本節書摘來自異步社群出版社《c++ 黑客程式設計揭秘與防範(第2版)》一書中的第6章,第6.4節,作者:冀雲,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視。

c++ 黑客程式設計揭秘與防範(第2版)

前面講的都是概念性的知識,本節主要編寫一些關于pe檔案結構的程式代碼,以幫助讀者加強對pe結構的了解。

寫pe檢視器并不是件複雜的事情,隻要按照pe結構一步一步地解析就可以了。下面簡單地解析其中幾個字段内容,顯示一下節表的資訊,其餘的内容隻要稍作修改即可。pe檢視器的界面如圖6-26所示。

pe檢視器的界面按照圖6-26所示的設定,不過這個可以按照個人的偏好進行布局設定。編寫該pe檢視器的步驟為打開檔案并建立檔案記憶體映像,判斷檔案是否為pe檔案并獲得pe格式相關結構體的指針,解析基本的pe字段,枚舉節表,最後關閉檔案。需要在類中添加幾個成員變量及成員函數,添加的内容如圖6-27所示。

《C++ 黑客程式設計揭秘與防範(第2版)》——6.4 PE相關程式設計執行個體define NAMELEN 20define SIGNLEN 32

按照前面所說的順序,依次實作添加的各個成員函數。

handle createfilemapping(

 handle hfile,               // handle to file

 lpsecurity_attributes lpattributes,    // security

 dword flprotect,             // protection

 dword dwmaximumsizehigh,          // high-order dword of size

 dword dwmaximumsizelow,           // low-order dword of size

 lpctstr lpname               // object name

);<code>`</code>

參數說明如下。

hfile:該參數是createfile()函數傳回的句柄。

lpattributes:是安全屬性,該值通常是null。

flprotect:建立檔案映射後的屬性,通常設定為可讀可寫page_readwrite。如果需要像裝載可執行檔案那樣把檔案映射入記憶體的話,那麼需要使用sec_image。

最後3個參數在這裡為0。如果建立的映射需要在多程序中共享資料的話,那麼最後一個參數設定為一個字元串,以便通過該名稱找到該塊共享記憶體。

該函數的傳回值為一個記憶體映射的句柄。

bool unmapviewoffile(

 lpcvoid lpbaseaddress  // starting address

該函數的參數就是mapviewoffile()函數的傳回值。

接着說pe檢視器,檔案已經打開,就要判斷檔案是否為有效的pe檔案了。如果是有效的pe檔案,就把解析pe格式的相關結構體的指針也得到。代碼如下:

void cpeparsedlg::parsebasepe()

{

  cstring strtmp;

  // 入口位址

  strtmp.format("%08x", m_pnthdr-&gt;optionalheader.addressofentrypoint);

  setdlgitemtext(idc_edit_ep, strtmp);

  // 映像基位址

  strtmp.format("%08x", m_pnthdr-&gt;optionalheader.imagebase);

  setdlgitemtext(idc_edit_imagebase, strtmp);

  // 連接配接器版本号

  strtmp.format("%d.%d",

    m_pnthdr-&gt;optionalheader.majorlinkerversion,

    m_pnthdr-&gt;optionalheader.minorlinkerversion);

  setdlgitemtext(idc_edit_linkversion, strtmp);

  // 節表數量

  strtmp.format("%02x", m_pnthdr-&gt;fileheader.numberofsections);

  setdlgitemtext(idc_edit_sectionnum, strtmp);

  // 檔案對齊值大小

  strtmp.format("%08x", m_pnthdr-&gt;optionalheader.filealignment);

  setdlgitemtext(idc_edit_filealign, strtmp);

  // 記憶體對齊值大小

  strtmp.format("%08x", m_pnthdr-&gt;optionalheader.sectionalignment);

  setdlgitemtext(idc_edit_secalign, strtmp);

}<code>`</code>

pe格式的基礎資訊,就是簡單地擷取結構體的成員變量,沒有過多複雜的内容。擷取導入表、導出表比擷取基礎資訊複雜。關于導入表、導出表的内容将在後面介紹。接下來進行節表的枚舉,具體代碼如下:

"x55x8bxecx6axffx68x00x65x41x00" \

"x68xe8x2dx40x00x64xa1x00x00x00" \

"x00x50x64x89x25x00x00x00x00x83" \

"xc4x94"<code>`</code>

根據這個步驟,把aspack的特征碼也提取出來,提取結果如下:

typedef struct _sign

  char szname[namelen];

  byte bsign[signlen + 1];

}sign, *psign;

利用該資料結構定義2個儲存特征碼的全局變量,如下:

sign sign[2] =

  {

    // vc6

    "vc6",

    "x55x8bxecx6axffx68x00x65x41x00" \

    "x68xe8x2dx40x00x64xa1x00x00x00" \

    "x00x50x64x89x25x00x00x00x00x83" \

    "xc4x94"

  },

    // aspack

    "aspack",

    "x60xe8x03x00x00x00xe9xebx04x5d" \

    "x45x55xc3xe8x01x00x00x00xebx5d" \

    "xbbxedxffxffxffx03xddx81xebx00"

    "xc0x01"

  }};<code>`</code>

程式界面是在pe檢視器的基礎上完成的,如圖6-32所示。

《C++ 黑客程式設計揭秘與防範(第2版)》——6.4 PE相關程式設計執行個體define NAMELEN 20define SIGNLEN 32

圖6-32 查殼程式結果

提取特征碼後,查殼工作隻剩特征碼比對了。這非常簡單,隻要用檔案的入口處代碼和特征碼進行比對,比對相同就會給出相應的資訊。查殼的代碼如下:

dword cpeparsedlg::getaddr()

  char szaddr[10] = { 0 };

  dword dwaddr = 0;

  switch ( m_nselect )

  case 1:

    {

      getdlgitemtext(idc_edit_va, szaddr, 10);

      hexstrtoint(szaddr, &amp;dwaddr);

      break;

    }

  case 2:

      getdlgitemtext(idc_edit_rva, szaddr, 10);

  case 3:

      getdlgitemtext(idc_edit_fileoffset, szaddr, 10);

  }

  return dwaddr;

擷取該位址所屬的第幾個節的代碼如下:

void cpeparsedlg::calcaddr(int ninnum, dword dwaddr)

  dword dwva = 0;

  dword dwrva = 0;

  dword dwfileoffset = 0;

      dwva = dwaddr;

      dwrva = dwva - m_pnthdr-&gt;optionalheader.imagebase;

      dwfileoffset = m_psechdr[ninnum].pointertorawdata

              + (dwrva - m_psechdr[ninnum].virtualaddress);

      dwva = dwaddr + m_pnthdr-&gt;optionalheader.imagebase;

      dwrva = dwaddr;

      dwfileoffset = dwaddr;

      dwrva = m_psechdr[ninnum].virtualaddress

          + (dwfileoffset - m_psechdr[ninnum].pointertorawdata);

      dwva = dwrva + m_pnthdr-&gt;optionalheader.imagebase;

  setdlgitemtext(idc_edit_section, (const char *)m_psechdr[ninnum].name);

  cstring str;

  str.format("%08x", dwva);

  setdlgitemtext(idc_edit_va, str);

  str.format("%08x", dwrva);

  setdlgitemtext(idc_edit_rva, str);

  str.format("%08x", dwfileoffset);

  setdlgitemtext(idc_edit_fileoffset, str);

代碼都不複雜,關鍵就是calcaddr()中3種位址的轉換。如果讀者沒能了解代碼,請參考前面手動轉換3種位址的方法,這裡就不進行介紹了。

添加節區在很多場合都會用到,比如在加殼中、在免殺中都會經常用到對pe檔案添加一個節區。添加一個節區的方法有4步,第1步是在節表的最後面添加一個image_secti on_header,第2步是更新image_file_header中的numberofsections字段,第3步是更新image_optional_

header中的sizeofimage字段,最後一步則是添加檔案的資料。當然,前3步是沒有先後順序的,但是最後一步一定要明确如何改變。

注:某些情況下,在添加新的節區項以後會向新節區項的資料部分添加一些代碼,而這些代碼可能要求在程式執行之前就被執行,那麼這時還需要更新image_optional _header中的addressofentrypoint字段。

1.手動添加一個節區

先來進行一次手動添加節區的操作,這個過程是個熟悉上述步驟的過程。網上有很多現成的添加節區的工具。這裡自己編寫工具的目的是掌握和了解其實作方法,鍛煉程式設計能力;手動添加節區是為了鞏固前面的知識,熟悉添加節區的步驟。

接下來還是使用前面的測試程式。使用c32asm用十六進制編輯方式打開這個程式,并定位到其節表處,如圖6-35所示。

《C++ 黑客程式設計揭秘與防範(第2版)》——6.4 PE相關程式設計執行個體define NAMELEN 20define SIGNLEN 32

圖6-35 節表位置資訊

從圖6-35中可以看到,該pe檔案有3個節表。直接看十六進制資訊可能很不友善(看多了就習慣了),為了直覺友善地檢視節表中image_section_header的資訊,那麼使用lordpe進行檢視,如圖6-36所示。

《C++ 黑客程式設計揭秘與防範(第2版)》——6.4 PE相關程式設計執行個體define NAMELEN 20define SIGNLEN 32

圖6-36 使用lordpe檢視該節表資訊

用lordpe工具檢視的确直覺多了。對照lordpe顯示的節表資訊來添加一個節區。回顧一下image_section_header結構體的定義,如下:

  m_hmap = createfilemapping(m_hfile, null,

                page_readwrite /| sec_image/,

                0, 0, 0);

  if ( m_hmap == null )

    closehandle(m_hfile);

    return bret;

  }<code>`</code>

這裡要把sec_image宏注釋掉。因為要修改記憶體檔案映射,有這個值會使添加節區失敗,是以要将其注釋掉或者直接删除掉。

《C++ 黑客程式設計揭秘與防範(第2版)》——6.4 PE相關程式設計執行個體define NAMELEN 20define SIGNLEN 32

圖6-46 添加節區界面

程式的界面如圖6-46所示。

首先編寫“添加”按鈕響應事件,代碼如下:

void cpeparsedlg::addsec(char *szsecname, int nsecsize)

  int nsecnum = m_pnthdr-&gt;fileheader.numberofsections;

  dword dwfilealignment = m_pnthdr-&gt;optionalheader.filealignment;

  dword dwsecalignment = m_pnthdr-&gt;optionalheader.sectionalignment;

  pimage_section_header ptmpsec = m_psechdr + nsecnum;

  // 拷貝節名

  strncpy((char *)ptmpsec-&gt;name, szsecname, 7);

  // 節的記憶體大小

  ptmpsec-&gt;misc.virtualsize = alignsize(nsecsize, dwsecalignment);

  // 節的記憶體起始位置

  ptmpsec-&gt;virtualaddress=m_psechdr[nsecnum-1].virtualaddress+alignsize(m_psechdr

[nsecnum - 1].misc.virtualsize, dwsecalignment);

  // 節的檔案大小

  ptmpsec-&gt;sizeofrawdata = alignsize(nsecsize, dwfilealignment);

  // 節的檔案起始位置

  ptmpsec-&gt;pointertorawdata=m_psechdr[nsecnum-1].pointertorawdata+alignsize(m_pse

chdr[nsecnum - 1].sizeofrawdata, dwsecalignment);

  // 修正節數量

  m_pnthdr-&gt;fileheader.numberofsections ++;

  // 修正映像大小

  m_pnthdr-&gt;optionalheader.sizeofimage += ptmpsec-&gt;misc.virtualsize;

  flushviewoffile(m_lpbase, 0);

  // 添加節資料

  addsecdata(ptmpsec-&gt;sizeofrawdata);

  enumsections();

代碼中每一步都按照相應的步驟來完成,其中用到的2個函數分别是alignsize()和addsecdata()。前者是用來進行對齊的,後者是用來在檔案中添加實際的資料内容的。這兩個函數非常簡單,代碼如下:

繼續閱讀