2011年10月31日星期一

[轉載]ZwLoadDriver用其他註冊表路徑加載驅動的辦法~


作 者: cvcvxk
時 間: 2011-10-28,13:44:38
鏈接: http://bbs.pediy.com/showthread.php?t=142021

先上代碼~

代碼:
#include <stdio.h>
#include <tchar.h>
#include <locale.h>
#include <windows.h>
#include <stdlib.h>
#include <psapi.h>
#include <Tlhelp32.h>
#pragma comment (lib, "psapi.lib")
typedef struct _UNICODE_STRING {
  USHORT Length;
  USHORT MaximumLength;
  PWSTR Buffer;
} UNICODE_STRING;
typedef UNICODE_STRING *PUNICODE_STRING;
typedef struct _STRING {
  USHORT Length;
  USHORT MaximumLength;
  PCHAR Buffer;
} ANSI_STRING;
typedef ANSI_STRING *PANSI_STRING;
DWORD drvldr_load(char* drvname)
{
  char regkey[512];
  int buflen;
  UNICODE_STRING us;
  ANSI_STRING as;
  HMODULE hNtdll = LoadLibraryA( ("ntdll.dll") );
  int (__stdcall *RtlAnsiStringToUnicodeString)(void*,void*,int)=
    (int (__stdcall *)(void *,void *,int))GetProcAddress(hNtdll,"RtlAnsiStringToUnicodeString");
  int (__stdcall *ZwLoadDriver)(void*)=
    (int (__stdcall *)(void *))GetProcAddress(hNtdll,"ZwLoadDriver");
  ///
  buflen=sprintf(regkey,"\\Registry\\Machine\\SOFTWARE\\%s",drvname);
  as.Buffer = (PCHAR)regkey;
  as.Length = (USHORT)buflen;
  as.MaximumLength=(USHORT)buflen;
  RtlAnsiStringToUnicodeString(&us, &as,TRUE);

  return ZwLoadDriver(&us);
}
DWORD drvldr_reg(char* drvname,DWORD start_type,char* path)
  //path should be kernel type like \??\xxx
{
  char regkey[512];
  DWORD regdata;
  int buflen;
  HKEY hkey;
  ///
  sprintf(regkey,"SOFTWARE\\%s",drvname);
  if(RegCreateKeyA(HKEY_LOCAL_MACHINE, regkey, &hkey)!=ERROR_SUCCESS)
  {return -1;}
  regdata=SERVICE_KERNEL_DRIVER;
  RegSetValueExA(hkey, ("Type"), 0, REG_DWORD, (const unsigned char *)&regdata, 4);
  regdata=SERVICE_ERROR_NORMAL;
  RegSetValueExA(hkey, ("ErrorControl"), 0, REG_DWORD, (const unsigned char *)&regdata, 4);
  regdata = start_type;
  RegSetValueExA(hkey, ("Start"), 0, REG_DWORD, (const unsigned char *)&regdata, 4);
  buflen = strlen(drvname);
  RegSetValueExA(hkey, ("DisplayName"), 0, REG_EXPAND_SZ, (const unsigned char *)drvname, buflen);
  buflen = strlen(path);
  RegSetValueExA(hkey, ("ImagePath"), 0, REG_EXPAND_SZ, (const unsigned char *)path, buflen);
  RegSetValueExA(hkey,("Group"),0,REG_SZ,(const BYTE *)"Base",strlen("Base"));
  RegCloseKey(hkey);
  //sprintf(regkey,"System\\CurrentControlSet\\Services\\%s",drvname);
  //RegCreateKeyA(HKEY_LOCAL_MACHINE, regkey, &hkey);
  //regdata=SERVICE_KERNEL_DRIVER;
  //RegCloseKey(hkey);
  sprintf(regkey,"System\\Setup\\AllowStart\\%s",drvname);
  RegCreateKeyA(HKEY_LOCAL_MACHINE, regkey, &hkey);
  regdata=SERVICE_KERNEL_DRIVER;
  RegCloseKey(hkey);
  return 0;
}
//Jan 4 2005
//Enable specific privilege
BOOL EnableSpecificPrivilege(BOOL bEnable,LPCTSTR Name)
{
  BOOL bResult = FALSE;
  HANDLE hToken;
  TOKEN_PRIVILEGES TokenPrivileges;

  if(OpenProcessToken(GetCurrentProcess(),TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES,&hToken) == 0)
  {
    return FALSE;
  }

  TokenPrivileges.PrivilegeCount = 1;
  TokenPrivileges.Privileges[0].Attributes = bEnable ? SE_PRIVILEGE_ENABLED : 0;
  bResult = LookupPrivilegeValue(NULL,Name,&TokenPrivileges.Privileges[0].Luid);
  if(!bResult)
  {
    CloseHandle(hToken);
    return FALSE;
  }

  bResult = AdjustTokenPrivileges(hToken,FALSE,&TokenPrivileges,sizeof(TOKEN_PRIVILEGES),NULL,NULL);
  if(GetLastError() != ERROR_SUCCESS || !bResult)
  {
    CloseHandle(hToken);
    return FALSE;
  }

  CloseHandle(hToken);
  return TRUE;
}

//Jan 4 2005
//Enable all privilege, return num of privileges successfully enabled
DWORD EnableAllPrivilege(BOOL bEnable)
{
  DWORD count=0;
  ///
  count+=EnableSpecificPrivilege(bEnable,SE_ASSIGNPRIMARYTOKEN_NAME);
  count+=EnableSpecificPrivilege(bEnable,SE_AUDIT_NAME);
  count+=EnableSpecificPrivilege(bEnable,SE_BACKUP_NAME);
  count+=EnableSpecificPrivilege(bEnable,SE_CHANGE_NOTIFY_NAME);
  count+=EnableSpecificPrivilege(bEnable,SE_CREATE_PAGEFILE_NAME);
  count+=EnableSpecificPrivilege(bEnable,SE_CREATE_PERMANENT_NAME);
  count+=EnableSpecificPrivilege(bEnable,SE_CREATE_TOKEN_NAME);
  count+=EnableSpecificPrivilege(bEnable,SE_DEBUG_NAME);
  count+=EnableSpecificPrivilege(bEnable,SE_INC_BASE_PRIORITY_NAME);
  count+=EnableSpecificPrivilege(bEnable,SE_INCREASE_QUOTA_NAME);
  count+=EnableSpecificPrivilege(bEnable,SE_LOAD_DRIVER_NAME);
  count+=EnableSpecificPrivilege(bEnable,SE_LOCK_MEMORY_NAME);
  count+=EnableSpecificPrivilege(bEnable,SE_PROF_SINGLE_PROCESS_NAME);
  count+=EnableSpecificPrivilege(bEnable,SE_REMOTE_SHUTDOWN_NAME);
  count+=EnableSpecificPrivilege(bEnable,SE_RESTORE_NAME);
  count+=EnableSpecificPrivilege(bEnable,SE_SECURITY_NAME);
  count+=EnableSpecificPrivilege(bEnable,SE_SHUTDOWN_NAME);
  count+=EnableSpecificPrivilege(bEnable,SE_SYSTEM_ENVIRONMENT_NAME);
  count+=EnableSpecificPrivilege(bEnable,SE_SYSTEM_PROFILE_NAME);
  count+=EnableSpecificPrivilege(bEnable,SE_SYSTEMTIME_NAME);
  count+=EnableSpecificPrivilege(bEnable,SE_TAKE_OWNERSHIP_NAME);
  count+=EnableSpecificPrivilege(bEnable,SE_TCB_NAME);
  count+=EnableSpecificPrivilege(bEnable,SE_UNSOLICITED_INPUT_NAME);
  count+=EnableSpecificPrivilege(bEnable,SE_MACHINE_ACCOUNT_NAME);

  return count;
}
int _tmain(int argc, _TCHAR* argv[])
{
  EnableAllPrivilege(TRUE);
  drvldr_reg("caonima",3,"\\??\\D:\\1.sys");
  drvldr_load("caonima");
  return 0;
}
對於1.sys的有些特殊的要求,首先1.sys裡的代碼不會被真的執行,但是1.sys這個驅動導入的動態庫驅動可以被執行
舉例下圖:
1.sys的導入表樣子:
x1.jpg下載此附件需要消耗2Kx,下載中會自動扣除。
導入的dllsys的導出表樣子:
x.jpg下載此附件需要消耗2Kx,下載中會自動扣除。
真正被執行的代碼要寫在動態庫驅動的NTSTATUS DllInitialize(IN PUNICODE_STRING RegistryPath)裡~~
具體怎麼寫內核版動態庫,請自行baidu研究。
好了爆完~這個方法的好處就是寫註冊表的過程基本不觸發任何報警——至於加載部分,仁者見仁吧,這玩意2005年的東西了~

2011年10月18日星期二

[轉載]MS11-081中IE9導致的一個CHM漏洞簡單分析


作 者: promsied
時 間: 2011-10-12,21:35:15
鏈接: http://bbs.pediy.com/showthread.php?t=141323

這個漏洞只存在於IE9,會導致無法下載CHM中內嵌的文件,在沒有安裝KB2586448更新的機器上測試
首先新建一個1.html文件,內容如下

代碼:
<a href = "1.rar">1.rar</a>
然後和一個1.rar文件用HTML Help Workshop一起編譯成CHM
打開CHM點擊1.rar鏈接,也可以在IE地址欄輸入mk:@MSITStore:XXX(具體路徑)\1.chm::/1.rar,報錯調試
停在CDownloadUtilities::MarshalBindContextToStream函數內,這個函數為下載1.rar文件做一些準備工作
聲明如下
HRESULT CDownloadUtilities::MarshalBindContextToStream(CInterThreadMarshal **ppITM<eax>, CDownloadThreadParam *pDTP, wchar_t *szURL, IBindCtx *pBC)
簡單分析下其代碼

代碼:
CDownloadUtilities::MarshalBindContextToStream(CInterThreadMarshal **ppITM<eax>, CDownloadThreadParam *pDTP, wchar_t *szURL, IBindCtx *pBC):
7003D23A mov edi,edi
7003D23C push ebp
7003D23D mov ebp,esp
7003D23F sub esp,0Ch ;HRESULT hr<[ebp-4h]>, IUnknown *pUnk<[ebp-8h]>, IEUserBroker *pIEUB<[ebp-0Ch]>
7003D242 push ebx
7003D243 push esi
7003D244 push edi
7003D245 lea edx,[ebp-8] ;edx = &pUnk
7003D248 push edx ;&pUnk
7003D249 mov edi,eax ;edi = ppITM
7003D24B mov eax,dword ptr [ebp+10h] ;eax = pBC
7003D24E mov ecx,dword ptr [eax] ;ecx = pBC->lpVtbl
7003D250 push offset __GUID_0000000e_0000_0000_c000_000000000046 (6FF65DA8h) ;&IID_IUnknown
7003D255 push eax ;pBC
7003D256 call dword ptr [ecx] ;eax = pBC->lpVtbl->QueryInterface(pBC, &IID_IUnknown, &pUnk)
7003D258 xor ebx,ebx
7003D25A mov dword ptr [ebp-4],eax ;hr = eax
7003D25D cmp eax,ebx
7003D25F jl CDownloadUtilities::MarshalBindContextToStream+0C6h (7003D300h) ;if (FAILED(hr)) goto 7003D300h
7003D265 push 3
7003D267 push offset string L"mk:" (70019BB4h)
7003D26C push dword ptr [ebp+0Ch]
7003D26F call dword ptr [__imp___wcsnicmp (6FE91364h)]
7003D275 add esp,0Ch
7003D278 test eax,eax
7003D27A jne CDownloadUtilities::MarshalBindContextToStream+74h (7003D2AEh) ;if (_wcsnicmp(szURL, L"mk:", 3)) goto 7003D2AEh
7003D27C mov eax,dword ptr [ebp-8] ;eax = pUnk
7003D27F mov ecx,dword ptr [eax] ;ecx = pUnk->lpVtbl
7003D281 push eax ;pUnk
7003D282 call dword ptr [ecx+8] ;pUnk->lpVtbl->Release(pUnk)
7003D285 push ebx ;0
7003D286 lea eax,[ebp+10h] ;eax = &pBC
7003D289 push eax ;&pBC
7003D28A push ebx ;0
7003D28B push ebx ;0
7003D28C push ebx ;0
7003D28D push ebx ;0
7003D28E mov dword ptr [ebp-8],ebx ;pUnk = 0
7003D291 call dword ptr [__imp__CreateAsyncBindCtxEx@24 (7023B14Ch)] ;eax = CreateAsyncBindCtxEx(0, 0, 0, 0, &pBC, 0)
7003D297 mov esi,dword ptr [edi] ;esi = *ppITM
7003D299 mov dword ptr [ebp-4],eax ;hr = eax
7003D29C cmp esi,ebx
7003D29E je CDownloadUtilities::MarshalBindContextToStream+74h (7003D2AEh) ;if (*ppITM == 0) goto 7003D2AEh
7003D2A0 call CInterThreadMarshal::~CInterThreadMarshal (701D7894h) ;(*ppITM)->~CInterThreadMarshal()(this<esi>)
7003D2A5 push esi
7003D2A6 call operator delete (6FEA35D9h) ;delete *ppITM
7003D2AB pop ecx
7003D2AC mov dword ptr [edi],ebx ;*ppITM = 0
7003D2AE cmp dword ptr [ebp-4],ebx
7003D2B1 jl CDownloadUtilities::MarshalBindContextToStream+0C6h (7003D300h) ;if (FAILED(hr)) goto 7003D300h
7003D2B3 mov eax,dword ptr [edi] ;eax = *ppITM
7003D2B5 cmp eax,ebx
7003D2B7 je CDownloadUtilities::MarshalBindContextToStream+89h (7003D2C3h) ;goto 7003D2C3h
7003D2B9 mov ecx,dword ptr [ebp+8] ;ecx = pDTP
7003D2BC mov dword ptr [ecx+18h],eax ;*(pDTP + 18h) = eax
7003D2BF mov dword ptr [edi],ebx ;*ppITM = 0
7003D2C1 jmp CDownloadUtilities::MarshalBindContextToStream+0BDh (7003D2F7h)
7003D2C3 push 4
7003D2C5 call operator new (6FE9E771h) ;eax = new LPSTREAM
7003D2CA pop ecx
7003D2CB cmp eax,ebx
7003D2CD je CDownloadUtilities::MarshalBindContextToStream+99h (7003D2D3h) ;if (eax == 0) goto 7003D2D3h
7003D2CF mov dword ptr [eax],ebx ;[eax] = 0
7003D2D1 jmp CDownloadUtilities::MarshalBindContextToStream+9Bh (7003D2D5h)
7003D2D3 xor eax,eax
7003D2D5 mov ecx,dword ptr [ebp+8] ;ecx = pDTP
7003D2D8 mov dword ptr [ecx+18h],eax ;*(pDTP + 18h) = eax
7003D2DB mov dword ptr [ebp-4],8007000Eh ;hr = 8007000Eh
7003D2E2 cmp eax,ebx
7003D2E4 je CDownloadUtilities::MarshalBindContextToStream+0BDh (7003D2F7h) ;if (eax == 0) goto 7003D2F7h
7003D2E6 push eax
7003D2E7 push dword ptr [ebp-8] ;pUnk
7003D2EA push offset _IID_IBindCtx (6FF2AB8Ch)
7003D2EF call _CoMarshalInterThreadInterfaceInStream@12 (701FF7F2h) ;eax = CoMarshalInterThreadInterfaceInStream(&IID_IBindCtx, pUnk, eax)
7003D2F4 mov dword ptr [ebp-4],eax ;hr = eax
7003D2F7 mov eax,dword ptr [ebp-8] ;eax = pUnk
7003D2FA mov ecx,dword ptr [eax] ;ecx = pUnk->lpVtbl
7003D2FC push eax ;pUnk
7003D2FD call dword ptr [ecx+8] ;pUnk->lpVtbl->Release(lpVtbl)
7003D300 call LCIEUnifiedFrame (6FF9CD1Fh)
7003D305 test al,al
7003D307 je CDownloadUtilities::MarshalBindContextToStream+128h (7003D362h)
7003D309 lea eax,[ebp-0Ch]
7003D30C push eax
7003D30D call dword ptr [__imp_CoCreateUserBroker (6FE925A4h)]
7003D313 test eax,eax
7003D315 js CDownloadUtilities::MarshalBindContextToStream+128h (7003D362h)
7003D317 lea eax,[ebp+0Ch]
7003D31A push eax
7003D31B push dword ptr [ebp-0Ch]
7003D31E push offset _IID_IEUserBroker (6FF37F6Ch)
7003D323 call dword ptr [__imp__CoMarshalInterThreadInterfaceInStream@12 (6FE91FECh)]
7003D329 mov dword ptr [ebp-4],eax
7003D32C cmp eax,ebx
7003D32E jne CDownloadUtilities::MarshalBindContextToStream+11Fh (7003D359h)
7003D330 mov edi,dword ptr [ebp+8]
7003D333 mov eax,dword ptr [edi+14h]
7003D336 mov esi,dword ptr [ebp+0Ch]
7003D339 cmp eax,ebx
7003D33B je CDownloadUtilities::MarshalBindContextToStream+109h (7003D343h)
7003D33D mov ecx,dword ptr [eax]
7003D33F push eax
7003D340 call dword ptr [ecx+8]
7003D343 mov dword ptr [edi+14h],esi
7003D346 cmp esi,ebx
7003D348 je CDownloadUtilities::MarshalBindContextToStream+116h (7003D350h)
7003D34A mov eax,dword ptr [esi]
7003D34C push esi
7003D34D call dword ptr [eax+4]
7003D350 mov eax,dword ptr [ebp+0Ch]
7003D353 mov ecx,dword ptr [eax]
7003D355 push eax
7003D356 call dword ptr [ecx+8]
7003D359 mov eax,dword ptr [ebp-0Ch]
7003D35C mov ecx,dword ptr [eax]
7003D35E push eax
7003D35F call dword ptr [ecx+8]
7003D362 mov eax,dword ptr [ebp-4]
7003D365 pop edi
7003D366 pop esi
7003D367 pop ebx
7003D368 leave
7003D369 ret 0Ch
7003D36C nop
7003D36D nop
7003D36E nop
7003D36F nop
7003D370 nop
簡單改寫成C++代碼

代碼:
HRESULT CDownloadUtilities::MarshalBindContextToStream(CInterThreadMarshal **ppITM<eax>, CDownloadThreadParam *pDTP, wchar_t *szURL, IBindCtx *pBC)
{
  IEUserBroker *pIEUB;
  IUnknown *pUnk;
  HRESULT hr;
  if (SUCCEEDED(hr = pBC->QueryInterface(&IID_IUnknown, &pUnk)))
  {
    if (!_wcsnicmp(szURL, L"mk:", 3))
    {
      pUnk->Release();
      pUnk = NULL;
      hr = CreateAsyncBindCtxEx(0, 0, 0, 0, &pBC, 0);
      if (*ppITM)
      {
        (*ppITM)->~CInterThreadMarshal();
        delete *ppITM;
        *ppITM = NULL;
      }
    }
    if (SUCCEEDED(hr))
    {
      if (*ppITM)
      {
        *(pDTP + 18h) = *ppITM;
        *ppITM = NULL;
      }
      else
      {
        IStream **ppStm = new LPSTREAM;
        if (ppStm)
        {
          *ppStm = 0;
        }
        else
        {
          ppStm = NULL;
        }
        *(pDTP + 18h) = ppStm;
        hr = 8007000Eh;
        if (ppStm)
        {
          hr = CoMarshalInterThreadInterfaceInStream(&IID_IBindCtx, pUnk, ppStm);
        }
      }
      pUnk->Release();
    }
  }
  //omit
  return hr;
}
首先函數調用pBC->QueryInterface獲得pBC對象的IUnknown接口pUnk,由於IBindCtx只繼承自IUnknown,所以IUnknown接口和IBindCtx接口地址實際相同,當下載CHM內嵌的文件時,CDownloadUtilities::MarshalBindContextToStream函數的szURL參數為形如mk:XXX的字符串,所以函數調用pUnk->Release又設pUnk=NULL,而pUnk原本的計數應為1,pUnk->Release徹底釋放了pBC對象,於是函數調用CreateAsyncBindCtxEx重新獲得pBC對象, pBC是更新了但pUnk仍然為NULL,此外函數又徹底釋放了*ppITM對象並設*ppITM=NULL,於是之後函數會調用CoMarshalInterThreadInterfaceInStream列集接口指針,但調用時第二個參數為pUnk,但pUnk= NULL,實際應為pBC,這還不是大問題,緊跟著的pUnk->Release才是禍根,由於pUnk=NULL,取pUnk的虛函數表指針造成訪問違例,程序報錯,不過幸好之前pUnk設為了NULL,不然後果就更糟糕了,解決方法應該是換用pBC。當szURL參數不是形如mk:XXX的字符串時就不會產生問題了。
附件是編譯好的一個CHM,其中的1.rar是本人寫的一個小工具,說明見本人博客http://hi.baidu.com/promised_lu

2011年10月17日星期一

[轉載]EditPlus另類破解


作 者: wangweilll
時 間: 2011-10-12,14:07:09
鏈接: http://bbs.pediy.com/showthread.php?t=141294

由於需要EditPlus來編寫一些東東,就下載了這個軟件。雖然網上有好的註冊碼和破解版的軟件。
呵呵~~好久沒有玩破解軟件了。就想自已來破解看看~~~~~~~,找註冊碼的算法要的時間太長了~
因為軟件只有30天的試用我們把時間調一下在運行下軟件看看~一運行就直接出現了


沒有哪麼多的時間!於是就想到了爆破~~~~~~。爆破點選擇是註冊碼還時間呢,我暈打開OD看看吧~
看有什麼好的信息~~

SetTimer 這個API有什麼用呢百度一下就可以知道了我們下個斷點吧! ! ! ! ! !按F9就來到了

看看

代碼:
mov eax,121E
哪AL == 1E
打開計算器看看1E 是10進制的多少

喔 是30啊! ! ! ! ! ! ! ! ! ! ! !呵呵在想想我們我軟件試用多少天啊,好像是3​​0天喔
哪我們改改看改成試試

F9運行看看發現可以用了

呵呵有的朋友就會想哪保存一下吧~~
可是這個動態鏈接庫中不修改數據

我們反彙編跟隨一下看看來到

ESI中保存的就SetTimer這個API
我們在來看看

我就想可不可以跳過
mov eax,0X121E 不執行它呢
我發現如果ESI 5.我們CALL ESI時它就跳過了
mov eax,0X121E
直接從 mov edx,7FFE0300
F9一下哈哈競然OK了
哪我們就來PATH一下吧

F9運行一下OK沒有問題就保存一份吧~~~~~~~~~~~~
打玩收功吧~~~~~~~~~~~~~~~~

2011年10月13日星期四

[轉載]【修改】打造自己的反彙編引擎



作 者: DoItFreely
時 間: 2011-10-12,12:32:02
鏈接: http://bbs.pediy.com/showthread.php?t=141285

原始信息來自http://bbs.pediy.com/showthread.php?t=75094
本人水平有限,沒有太多理論,只是增強了PE的處理,並稍微hack了一下代碼。
為什麼要增強?處理特定文件格式(比如PE、ELF)並不是反彙編器的功能,原作者僅僅演示了一下如何編寫和使用反彙編器,沒有深入處理特定文件格式也不奇怪
主要增強如下:
1) load整個PE,而不是只處理.text節
         這樣就可以處理.sys等entry point不在.text段的程序
         跨段的調用、引用也能正常處理

2) 從entry point開始反彙編,只有引用到的代碼才反彙編
         反彙編整個.text很可能會得到不准確的結果。對於用C編寫的程序,導出函數和entry point引用到的函數覆蓋了所有的代碼。
         數據段、資源段等“非代碼”段在開始反彙編之前就被排除掉了
         當前的輸出沒有按照地址排序,比較凌亂

3) 檢測對字符常量和導入函數的引用
          如果找到了引用的目標,就輸出在後面

4) 檢測代碼塊的結束
         無條件跳轉指令(JMP)和返回指令(ret,retn)

5) 增加了對實模式代碼的處理(不完善)
         檢測返回DOS的調用

主要修改:
1) 一處原作者忘記轉換偏移量的類型(應該是signed displacement)而導致算錯偏移量的問題
     與IDA對比時發現的

2) 增加了浮點指令的反彙編(有問題)
      沒有讀intel的指令手冊,僅僅是“依葫蘆畫瓢”,結果可想而知。 (原來遇到浮點指令顯示為???,現在是亂抓了一條來充數)


待增強功能:
1)反彙編浮點(d8~df)和f0 xx序列


文件說明:
    dasm.exe 修改後的執行文件
    dasm.txt 用dasm.exe反彙編它自己的輸出
    filedisk.sys v1.7版(?)的filedisk驅動(該軟件開源,地球人應該都知道吧?)
    filedisk.asm IDA 反彙編filedisk.sys的輸出
    filedisk.idb IDA 5.2生成的數據庫,6.1版的IDA應該也能打開吧(?)
    filedisk.txt 用dasm.exe反彙編filedisk.sys的輸出
    dasmMain.exe 原作者編譯的執行文件
    *.h *.c *.dsw *.dsp 工程文件和源碼

(原作者的代碼可以在前面的鏈接頁面裡面找到,dasm.rar。)



實例效果:(來自filedisk.txt)
===============================
11550: Proc_11550 proc


11550: Loc_11550:
11550: mov edi, edi ;8B FF
11552: push ebp ;55
11553: mov ebp, esp ;8B EC
11555: mov eax, dword ptr [ebp + 8h] ;8B 45 08
11558: push esi ;56
11559: mov esi, dword ptr [eax + 28h] ;8B 70 28
1155C: push 0h ;6A 00
1155E: push dword ptr [esi + Ch] ;FF 76 0C
11561: call dword ptr [10C04h] ;FF 15 04 0C 01 00 ExFreePoolWithTag
11567: push dword ptr [esi + 4h] ;FF 76 04
1156A: call dword ptr [10C38h] ;FF 15 38 0C 01 00 ZwClose
11570: mov eax, dword ptr [ebp + Ch] ;8B 45 0C
11573: mov byte ptr [esi], 0h ;C6 06 00
11576: and dword ptr [eax + 18h], 0h ;83 60 18 00
1157A: and dword ptr [eax + 1Ch], 0h ;83 60 1C 00
1157E: xor eax, eax ;33 C0
11580: pop esi ;5E
11581: pop ebp ;5D
11582: retn 8h ;C2 08 00
11550: Proc_12FD9C endp
===============================


(來自dasm.txt, main()片段)

===============================
402180: Proc_402180 proc


402180: Loc_402180:
402180: mov eax, dword ptr [esp + 4h] ;8B 44 24 04
402184: sub esp, 164h ;81 EC 64 01 00 00
40218A: cmp eax, 2h ;83 F8 02
40218D: push ebx ;53
40218E: push ebp ;55
40218F: push esi ;56
402190: push edi ;57
402191: jz short Loc_4021A9 ;74 16 ->
402193: push 408418h ;68 18 84 40 0​​0 string 'Usage: dasm.exe PEFile
'
402198: call dword ptr [40702Ch] ;FF 15 2C 70 40 0​​0 printf
40219E: add esp, 4h ;​​83 C4 04
4021A1: push 0h ;6A 00
4021A3: call dword ptr [40703Ch] ;FF 15 3C 70 40 0​​0 exit
4021A9: mov esi, dword ptr [esp + 17Ch + 0h];8B B4 24 7C 01 00 00
4021B0: mov eax, dword ptr [esi + 4h] ;8B 46 04
4021B3: push eax ;50
4021B4: call Proc_405430 ;E8 77 32 00 00 =>
4021B9: mov ecx, dword ptr [esi + 4h] ;8B 4E 04
4021BC: mov ebx, dword ptr [40702Ch] ;8B 1D 2C 70 40 0​​0 printf
4021C2: push ecx ;51
4021C3: push 408414h ;68 14 84 40 0​​0 string '%s
'
4021C8: mov ebp, eax ;8B E8
4021CA: call ebx ;FF D3
4021CC: push 408404h ;68 04 84 40 0​​0 string 'DOS Header:
'
4021D1: call ebx ;FF D3
4021D3: push 4083B8h ;68 B8 83 40 0​​0 string '====================================== ===================================
'
4021D8: call ebx ;FF D3
4021DA: lea edx, dword ptr [esp + 28h] ;8D 54 24 28
4021DE: push edx ;52
4021DF: push ebp ;55
4021E0: call Proc_405470 ;E8 8B 32 00 00 =>
4021E5: sub esp, 24h ;83 EC 24
4021E8: mov ecx, Fh ;B9 0F 00 00 00
4021ED: lea esi, dword ptr [esp + 54h] ;8D 74 24 54
4021F1: mov edi, esp ;8B FC
4021F3: rep movsd ;F3 A5
4021F5: movsw ;66 A5
4021F7: call Proc_4054C0 ;E8 C4 32 00 00 =>
4021FC: mov eax, dword ptr [esp + 90h + 0h] ;8B 84 24 90 00 00 00
402203: add esp, 40h ;83 C4 40
402206: cmp ax, 1Eh ;66 3D 1E 00
40220A: jb Loc_402325 ;0F 82 15 01 00 00 ->
402210: mov ecx, dword ptr [esp + 2Ch] ;8B 4C 24 2C
402214: test cx, cx ;66 85 C9
402217: jz short Loc_40222D ;74 14 ->
402219: cmp cx, ax ;66 3B C8
40221C: jnb short Loc_40222D ;73 0F ->
40221E: and ecx, FFFFh ;81 E1 FF FF 00 00
402224: and eax, FFFFh ;25 FF FF 00 00
402229: sub eax, ecx ;2B C1
40222B: jmp short Loc_402235 ;EB 08 ->


402235: Loc_402235:
402235: mov esi, eax ;8B F0
402237: push esi ;56
402238: push esi ;56
402239: push 408388h ;68 88 83 40 0​​0 string '
disassemly dos stub code, %d(0x%x) bytes
'
40223E: call ebx ;FF D3
402240: push esi ;56
402241: call dword ptr [407094h] ;FF 15 94 70 40 0​​0 malloc
402247: mov edi, eax ;8B F8
402249: add esp, 10h ;83 C4 10
40224C: test edi, edi ;85 FF
40224E: jnz short Loc_40226F ;75 1F ->
402250: mov eax, dword ptr [407020] ;A1 20 70 40 0​​0 _iob
402255: push 40836Ch ;68 6C 83 40 0​​0 string 'Memory allocation error !
'
40225A: add eax, 40h ;83 C0 40
40225D: push eax ;50
40225E: call dword ptr [40701Ch] ;FF 15 1C 70 40 0​​0 fprintf
402264: add esp, 8h ;83 C4 08
402267: push FFh ;6A FF
402269: call dword ptr [40703Ch] ;FF 15 3C 70 40 0​​0 exit
40226F: mov eax, dword ptr [esp + 2Ch] ;8B 44 24 2C
402273: cmp ax, word ptr [esp + 50h] ;66 3B 44 24 50
402278: jnb short Loc_40228C ;73 12 ->
40227A: and eax, FFFFh ;25 FF FF 00 00
40227F: push 0h ;6A 00
402281: push eax ;50
402282: push ebp ;55
402283: call dword ptr [407038h] ;FF 15 38 70 40 0​​0 fseek
402289: add esp, Ch ;83 C4 0C
40228C: push ebp ;55
40228D: push esi ;56
40228E: push 1h ;6A 01
402290: push edi ;57
402291: call dword ptr [407034h] ;FF 15 34 70 40 0​​0 fread
402297: mov ecx, dword ptr [esp + 3Ah] ;8B 4C 24 3A
40229B: push esi ;56
40229C: and ecx, FFFFh ;81 E1 FF FF 00 00
4022A2: shl ecx, 10h ;C1 E1 10
4022A5: push ecx ;51
4022A6: call Proc_401000 ;E8 55 ED FF FF <=
4022AB: mov edx, dword ptr [esp + 42h] ;8B 54 24 42
4022AF: mov eax, dword ptr [esp + 40h] ;8B 44 24 40
4022B3: and edx, FFFFh ;81 E2 FF FF 00 00
4022B9: and eax, FFFFh ;25 FF FF 00 00
4022BE: shl edx, 10h ;C1 E2 10
4022C1: or edx, eax ;0B D0
4022C3: push edx ;52
4022C4: call Proc_401270 ;E8 A7 EF FF FF <=
4022C9: mov ecx, dword ptr [esp + 46h] ;8B 4C 24 46
4022CD: mov edx, dword ptr [esp + 44h] ;8B 54 24 44
4022D1: and ecx, FFFFh ;81 E1 FF FF 00 00
4022D7: and edx, FFFFh ;81 E2 FF FF 00 00
4022DD: shl ecx, 10h ;C1 E1 10
4022E0: or ecx, edx ;0B CA
4022E2: mov dword ptr [40AB34h], 1h ;C7 05 34 AB 40 0​​0 01 00 00 00
4022EC: push ecx ;51
4022ED: push 408350h ;68 50 83 40 0​​0 string '%X: realmode_entry_point:
下載請到:http://bbs.pediy.com/showthread.php?t=141285

2011年10月12日星期三

[轉載]計算機病毒對消息鉤子的利用與對抗


作 者: dncwbc
時 間: 2011-10-07,23:57:15
鏈接: http://bbs.pediy.com/showthread.php?t=141059

一、消息鉤子的概念
    1、基本概念
   Windows應用程序是基於消息驅動的,任何線程只要註冊窗口類都會有一個消息隊列用於接收用戶輸入的消息和系統消息。為了攔截消息,Windows提出了鉤子的概念。鉤子(Hook)是Windows消息處理機制中的一個監視點,鉤子提供一個回調函數。當在某個程序中安裝鉤子後,它將監視該程序的消息,在指定消息還沒到達窗口之前鉤子程序先捕獲這個消息。這樣就有機會對此消息進行過濾,或者對Windows消息實現監控。
    2、分類
    消息鉤子分為局部鉤子和全局鉤子。局部鉤子是指僅攔截指定一個進程的指定消息,全局鉤子將攔截系統中所有進程的指定消息。
    3、實現步驟
    使用鉤子技術攔截消息通常分為如下幾個步驟:
    設置鉤子回調函數;(攔截到消息後所調用的函數)
    安裝鉤子;(使用SetWindowsHookEx函數)
    卸載鉤子。 (使用UnhookWindowsHookEx函數)
    4、功能
    利用消息鉤子可以實現特效界面、同步消息、監控消息、自啟動等功效。

二、病毒對消息鉤子技術的利用
    計算機病毒經常利用消息鉤子實現兩種功能:
    1、監控用戶按鍵,盜取用戶信息。
    這樣的病毒會啟動一個常駐內存的EXE病毒進程,然後安裝一個全局鍵盤消息鉤子,鉤子回調函數位於病毒進程中,這樣系統中任何有按鍵操作的進程,其按鍵詳細信息都會被病毒進程攔截記錄。
    2、自啟動
    這樣的病毒會將鉤子回調函數放在一個DLL文件中,然後安裝一個全局消息(容易觸發的消息,如WH_CBT、WH_GETMESSAGE等)鉤子,這樣凡響應該消息的進程都會自動加載病毒的DLL,病毒也就跟著自動運行了。

三、消息鉤子病毒的對抗技術(重點)
    1、對抗技術原理
    對付消息鉤子病毒方法很簡單,只要將病毒安裝的鉤子卸載掉即可。 (注意:對於系統中許多進程已經因為全局鉤子而加載了病毒DLL的情況,並不需要去卸載這些DLL,只要安裝的消息鉤子被卸載那麼對應的DLL也都會被在這些進程中自動卸載。 )卸載鉤子有兩種方法:
    (1)、結束掉安裝鉤子的進程
    將設置鉤子的進程結束,進程在退出之前會自行卸載掉該進程安裝的所有消息鉤子。這種方法很適合對付監控用戶按鍵的病毒。
    (2)、獲得消息鉤子句柄,然後調用UnhookWindowsHookEx函數即可將消息鉤子卸載。
    如果病毒單獨啟動了一個病毒進程安裝了一個全局消息鉤子,然後就常駐內存。這時我們將這個病毒進程結束掉即可。但是如果病毒在系統進程中註入代碼而安裝的鉤子,這樣鉤子句柄就位於系統進程中,我們不可以結束系統進程,這時就只能獲取這個消息鉤子句柄,然後調用函數卸載。
    2、對抗技術實現細節
    對於結束掉安裝鉤子進程從而卸載病毒消息鉤子的方法很容易實現,只要找到病毒進程結束即可。而對於獲取病毒消息鉤子句柄,然後調用函數卸載鉤子的方法比較複雜,也是本文重點討論的內容,將在下一個標題中詳細介紹。

四、查找病毒消息鉤子句柄然後卸載的方法實現(重點、難點)
    1、實現原理分析
    系統會將所有安裝的鉤子句柄保存在內核中,要查找病毒安裝的消息鉤子句柄,我們要枚舉所有的消息鉤子句柄。如何枚舉稍後講解,還要解決一個問題,就是在枚舉過程中,我們怎麼知道哪個句柄是病毒安裝的呢?
    通過分析病毒樣本我們通常可以得到病毒安裝鉤子就是為了令其他合法進程加載病毒DLL,所以它會將鉤子回調函數寫在該DLL中。在枚舉消息鉤子句柄時,同時也可以得到該句柄所對應的回調函數所屬的DLL模塊,根據這個DLL模塊是不是病毒的DLL模塊即可找到病毒的消息鉤子句柄,最後將其卸載即可。
    關於如何枚舉系統消息鉤子句柄,對於不同的操作系統方法大不相同,這裡介紹一種用戶層讀內存的方法,此方法僅在2000/XP系統下可用。
    在2000/XP系統下有一個Windows用戶界面相關的應用程序接口User32.dll。它用於包括Windows窗口處理,基本用戶界面等特性,如創建窗口和發送消息。當它被加載到內​​存後,它保存了所有Windows窗口、消息相關的句柄,其中就包括消息鉤子句柄。這些句柄被保存在一塊共享內存段中,通常稱為R3層的GUI TABLE。所以只要我們找到GUI TABLE,然後在其中的句柄中篩選出消息鉤子句柄。 GUI TABLE這塊內存段可以被所有進程空間訪問。 GUI TABLE被定義成如下結構:
typedef struct tagSHAREDINFO {
  struct tagSERVERINFO *pServerInfo; //指向tagSERVERINFO結構的指針
  struct _HANDLEENTRY *pHandleEntry; // 指向句柄表
  struct tagDISPLAYINFO *pDispInfo; //指向tagDISPLAYINFO結構的指針
  ULONG ulSharedDelta;
  LPWSTR pszDllList;
} SHAREDINFO, *PSHAREDINFO;
    tagSHAREDINFO結構體的第一個成員pServerInfo所指向的tagSERVERINFO結構體定義如下。
typedef struct tagSERVERINFO {
    short wRIPFlags ;
    short wSRVIFlags ;
    short wRIPPID ;
    short wRIPError ;
    ULONG cHandleEntries; //句柄表中句柄的個數
}SERVERINFO,*PSERVERINFO;
    可以看出通過tagSERVERINFO結構的cHandleEntries成員即可得到tagSHAREDINFO結構的pHandleEntry成員所指向的句柄表中的句柄數。
    tagSHAREDINFO結構體的第二個成員pHandleEntry是指向_HANDLEENTRY結構體數組起始地址的指針,該數組的一個成員對應一個句柄。句柄結構體_HANDLEENTRY定義如下。
typedef struct _HANDLEENTRY{
    PVOID pObject; //指向句柄所對應的內核對象
    ULONG pOwner;
      BYTE bType; //句柄的類型
    BYTE bFlags;
    short wUniq;
}HANDLEENTRY,*PHANDLEENTRY;
    _HANDLEENTRY結構體成員bType是句柄的類型,通過該變量的判斷可以篩選消息鉤子句柄。 User32中保存的句柄類型通常有如下種類。
typedef enum _HANDLE_TYPE
{
        TYPE_FREE = 0,
        TYPE_WINDOW = 1 ,
        TYPE_MENU = 2, //菜單句柄
     TYPE_CURSOR = 3, //光標句柄
     TYPE_SETWINDOWPOS = 4,
        TYPE_HOOK = 5, //消息鉤子句柄
     TYPE_CLIPDATA = 6 ,
        TYPE_CALLPROC = 7,
        TYPE_ACCELTABLE = 8,
        TYPE_DDEACCESS = 9,
        TYPE_DDECONV = 10,
        TYPE_DDEXACT = 11,
        TYPE_MONITOR = 12,
        TYPE_KBDLAYOUT = 13 ,
        TYPE_KBDFILE = 14 ,
        TYPE_WINEVENTHOOK = 15 ,
        TYPE_TIMER = 16,
        TYPE_INPUTCONTEXT = 17 ,
        TYPE_CTYPES = 18 ,
        TYPE_GENERIC = 255
}HANDLE_TYPE;
    _HANDLEENTRY結構體的成員pObject是指向句柄對應的內核對象的指針。
    這樣只要通過pObject就可以得到句柄的詳細信息(其中包括創建進程,線程、回調函數等信息),通過bType就可以的值句柄的類型。
_HANDLEENTRY結構體的其他成員可以忽略不看。
    (知識要點補充:如何在用戶層程序中讀取內核內存)
    需要注意的是,pObject指針指向的是內核內存,不可以在用戶層直接訪問內核內存。後面還有些地方也同樣是內核內存,需要加以注意。應該把內核內存的數據讀取到用戶層內存才可以訪問。且不可以直接訪問,畢竟不是在驅動中。
    在用戶層讀取內核內存使用ZwSystemDebugControl函數,它是一個Native API。其原型如下。
NTSYSAPI
NTSTATUS
NTAPI
ZwSystemDebugControl(
    IN DEBUG_CONTROL_CODE Con​​trolCode,//控制代碼
   IN PVOID InputBuffer OPTIONAL, //輸入內存
   IN ULONG InputBufferLength, //輸入內存長度
   OUT PVOID OutputBuffer OPTIONAL, //輸出內存
   IN ULONG OutputBufferLength, //輸出內存長度
   OUT PULONG ReturnLength OPTIONAL //實際輸出的長度);
ZwSystemDebugControl函數可以用於讀/寫內核空間、讀/寫MSR、讀/寫物理內存、讀/寫IO端口、讀/寫總線數據、KdVersionBlock等。由第一個參數ControlCode控制其功能,可以取如下枚舉值。
    typedef enum _SYSDBG_COMMAND {
    //以下5個在Windows NT各個版本上都有
    SysDbgGetTraceInformation = 1,
      SysDbgSetInternalBreakpoint = 2,
      SysDbgSetSpecialCall = 3,
      SysDbgClearSpecialCalls = 4,
      SysDbgQuerySpecialCalls = 5,
    // 以下是NT 5.1 新增的
    SysDbgDbgBreakPointWithStatus = 6,
    //獲取KdVersionBlock
      SysDbgSysGetVersion = 7,
    //從內核空間複製到用戶空間,或者從用戶空間複製到用戶空間
    //但是不能從用戶空間複製到內核空間
    SysDbgCopyMemoryChunks_0 = 8,
   //SysDbgReadVirtualMemory = 8,
    //從用戶空間複製到內核空間,或者從用戶空間複製到用戶空間
    //但是不能從內核空間複製到用戶空間
    SysDbgCopyMemoryChunks_1 = 9,
    //SysDbgWriteVirtualMemory = 9,
    //從物理地址複製到用戶空間,不能寫到內核空間
    SysDbgCopyMemoryChunks_2 = 10,
    //SysDbgReadVirtualMemory = 10,
    //從用戶空間複製到物理地址,不能讀取內核空間
    SysDbgCopyMemoryChunks_3 = 11,
    //SysDbgWriteVirtualMemory = 11,
    //讀/寫處理器相關控制塊
    SysDbgSysReadControlSpace = 12,
    SysDbgSysWriteControlSpace = 13,
    //讀/寫端口
    SysDbgSysReadIoSpace = 14,
      SysDbgSysWriteIoSpace = 15,
    //分別調用RDMSR@4和_WRMSR@12
      SysDbgSysReadMsr = 16,
      SysDbgSysWriteMsr = 17,
    //讀/寫總線數據
    SysDbgSysReadBusData = 18,
      SysDbgSysWriteBusData = 19,
      SysDbgSysCheckLowMemory = 20,
// 以下是NT 5.2 新增的
    //分別調用_KdEnableDebugger@0和_KdDisableDebugger@0
      SysDbgEnableDebugger = 21,
      SysDbgDisableDebugger = 22,
    //獲取和設置一些調試相關的變量
    SysDbgGetAutoEnableOnEvent = 23,
      SysDbgSetAutoEnableOnEvent = 24,
      SysDbgGetPitchDebugger = 25,
      SysDbgSetDbgPrintBufferSize = 26,
      SysDbgGetIgnoreUmExceptions = 27,
      SysDbgSetIgnoreUmExceptions = 28
    } SYSDBG_COMMAND, *PSYSDBG_COMMAND;
    我們這裡要讀取內核內存,所以參數ControlCode應取值為SysDbgReadVirtualMemory。
當ControlCode取值為SysDbgReadVirtualMemory時,ZwSystemDebugControl函數的第4個參數和第5個參數被忽略,使用時傳入0即可。第二個參數InputBuffer是一個指向結構體_MEMORY_CHUNKS的指針,該結構體定義如下。
typedef struct _MEMORY_CHUNKS {
    ULONG Address; //內核內存地址指針(要讀的數據)
    PVOID Data; //用戶層內存地址指針(存放讀出的數據)
    ULONG Length; //讀取的長度
}MEMORY_CHUNKS, *PMEMORY_CHUNKS;
第三個參數InputBufferLength是_MEMORY_CHUNKS結構體的大小。使用sizeof運算符得到即可。
SysDbgReadVirtualMemory函數執行成功將返回0。否則返回錯誤代碼。
為了方便使用,我們可以封裝一個讀取內核內存的函數GetKernelMemory,實現如下:
#define SysDbgReadVirtualMemory 8
//定義ZwSystemDebugControl函數指針類型
typedef DWORD (WINAPI *ZWSYSTEMDEBUGCONTROL)(DWORD,PVOID,
DWORD,PVOID,DWORD,PVOID);
BOOL GetKernelMemory(PVOID pKernelAddr, PBYTE pBuffer, ULONG uLength)
{
    MEMORY_CHUNKS mc ;
    ULONG uReaded = 0;
    mc.Address=(ULONG)pKernelAddr; //內核內存地址
    mc.pData = pBuffer;//用戶層內存地址
    mc.Length = uLength; //讀取內存的長度
    ULONG st = -1 ;
  //獲得ZwSystemDebugControl函數地址
  ZWSYSTEMDEBUGCONTROL ZwSystemDebugControl = (ZWSYSTEMDEBUGCONTROL) GetProcAddress(
    GetModuleHandle("ntdll.dll"), "ZwSystemDebugControl");
  //讀取內核內存數據到用戶層
    st = ZwSystemDebugControl(SysDbgReadVirtualMemory, &mc, sizeof(mc), 0, 0, &uReaded);
    return st == 0;
}


    對於不同類型的句柄,其內核對象所屬內存對應的結構體不同,對於消息鉤子句柄,它的內核對象所屬內存對應的結構體實際上是_HOOK_INFO類型,其定義如下。
typedef struct _HOOK_INFO
{
  HANDLE hHandle; //鉤子的句柄
  DWORD Unknown1;
  PVOID Win32Thread; //一個指向win32k!_W32THREAD 結構體的指針
  PVOID Unknown2;
  PVOID SelfHook; //指向結構體的首地址
  PVOID NextHook; //指向下一個鉤子結構體
  int iHookType; //鉤子的類型。
  DWORD OffPfn; //鉤子函數的地址偏移,相對於所在模塊的偏移
  int iHookFlags; //鉤子標誌
  int iMod; //鉤子函數做在模塊的索引號碼,利用它可以得到模塊基址
  PVOID Win32ThreadHooked; //被鉤的線程結構指針
} HOOK_INFO,*PHOOK_INFO;
由上可以看出,得到鉤子內核對像數據後,該數據對應HOOK_INFO結構體信息。其中:
hHandle是鉤子句柄,使用它就可以卸載鉤子。
iHookType是鉤子的類型,消息鉤子類型定義如下。
typedef enum _HOOK_TYPE{
        MY_WH_MSGFILTER = -1,
        MY_WH_JOURNALRECORD = 0,
        MY_WH_JOURNALPLAYBACK = 1,
        MY_WH_KEYBOARD = 2,
        MY_WH_GETMESSAGE = 3,
        MY_WH_CALLWNDPROC = 4,
        MY_WH_CBT = 5,
        MY_WH_SYSMSGFILTER = 6,
        MY_WH_MOUSE = 7,
        MY_WH_HARDWARE =​​ 8,
        MY_WH_DEBUG = 9,
        MY_WH_SHELL = 10,
        MY_WH_FOREGROUNDIDLE = 11,
        MY_WH_CALLWNDPROCRET = 12,
        MY_WH_KEYBOARD_LL = 13,
        MY_WH_MOUSE_LL = 14
}HOOK_TYPE;
    OffPfn是鉤子回調函數的偏移地址,該偏移地址是相對於鉤子函數所在模塊基址的偏移。
   Win32Thread是指向_W32THREAD結構體的指針,通過這個結構體可以獲得鉤子所在進程ID和線程ID。該結構體定義如下。
typedef struct _W32THREAD
{
    PVOID pEThread ; //該指針用以獲得進程ID和線程ID
    ULONG RefCount ;
    ULONG ptlW32 ;
    ULONG pgdiDcattr ;
    ULONG pgdiBrushAttr ;
    ULONG pUMPDObjs ;
    ULONG pUMPDHeap ;
    ULONG dwEngAcquireCount ;
    ULONG pSemTable ;
    ULONG pUMPDObj ;
    PVOID ptl;
    PVOID ppi; //該指針用以獲得模塊基址
}W32THREAD, *PW32THREAD;
    _W32THREAD結構體第一個參數pEThread指向的內存偏移0x01EC處分別保存著進程ID和線程ID。注意pEThread指針指向的內存是內核內存。
   _W32THREAD結構體最後一個參數ppi指向的內存偏移0xA8處是所有模塊基址的地址表, _HOOK_INFO結構體的iMod成員就標識了本鉤子所屬模塊基址在此地址表中的位置。 (每個地址佔4個字節)所以通常使用ppi+0xa8+iMod*4定位模塊基址的地址。注意ppi指向的內存是內核內存。
    2、實現細節
    首先編寫程序枚舉消息鉤子句柄,需要得到GUI TABLE,它的地址實際上存儲於User32.dll的一個全局變量中,該模塊導出的函數UserRegisterWowHandlers將返回該全局變量的值。所以我們只要調用這個函數就能夠得到GUI TABLE。然而UserRegisterWowHandlers是一個未公開的函數,不確定它的函數原型,需要反彙編猜出它的原型。筆者反彙編後得到的原型如下。
typedef PSHAREDINFO (__stdcall *USERREGISTERWOWHANDLERS) (PBYTE ,PBYTE );
僅知道它兩個參數是兩個指針,但是不知道它的兩個參數的含義,所以我們無法構造出合理的參數。如果隨便構造參數傳進去又會導致user32.dll模塊發生錯誤。所以通過調用這個函數接收其返回值的方法就不能用了。再次反彙編該函數的實現可以看出,在不同操作系統下該函數的最後三行代碼如下。
2K系統:(5.0.2195.7032)
:77E3565D B880D2E477 mov eax, 77E4D280
:77E35662 C20800 ret 0008
XP系統:(5.1.2600.2180)
:77D535F5 B88000D777 mov eax, 77D70080
:77D535FA 5D pop ebp
:77D535FB C20800 ret 0008
2003系統:(5.2.3790.1830)
:77E514D9 B8C024E777 mov eax, 77E724C0
:77E514DE C9 leave
:77E514DF C2080000 ret 0008
可以看到共同點,該函數的倒數第三行代碼就是將保存GUI TABLE指針的全局變量值賦值給寄存器EAX,只要我們想辦法搜索到這個值即可。能夠看出無論是哪個版本的函數實現中,都有C20800代碼,含義是ret 0008。我們可以自UserRegisterWowHandlers函數的入口地址開始一直搜索到C20800,找到它以後再向前搜索B8指令,搜到以後B8指令後面的四個字節數據就是我們需要的數據。代碼如下。
//獲得UserRegisterWowHandlers函數的入口地址
DWORD UserRegisterWowHandlers = (DWORD) GetProcAddress(LoadLibrary("user32.dll"), "UserRegisterWowHandlers");
PSHAREDINFO pGUITable; //保存GUITable地址的指針
for(DWORD i=UserRegisterWowHandlers; i<UserRegisterWowHandlers+1000; i++)
{
  if((*(USHORT*)i==0x08c2)&&*(BYTE *)(i+2)== 0x00)
  { //已找到ret 0008指令,然後往回搜索B8
    for (int j=i; j>UserRegisterWowHandlers; j--)
    { //找到B8它後面四個字節保存的數值即為GUITable地址
      if (*(BYTE *)j == 0xB8)
      {
        pGUITable = (PSHAREDINFO)*(DWORD *)(j+1);
        break;
      }
    }break;
  }
}
    得到SHAREDINFO結構指針後,它的成員pServerInfo的成員cHandleEntries就是句柄的總個數,然後循環遍歷每一個句柄,找到屬於指定模塊的消息鉤子句柄。代碼如下。
int iHandleCount = pGUITable->pServerInfo->cHandleEntries;
HOOK_INFO HookInfo;
DWORD dwModuleBase;
struct TINFO
{
  DWORD dwProcessID;
  DWORD dwThreadID;
};
char cModuleName[256] = {0};
for (i=0; i<iHandleCount; i++)
{ //判斷句柄類型是否為消息鉤子句柄
  if (pGUITable->pHandleEntry[i].bType == TYPE_HOOK)
  {
    DWORD dwValue = (DWORD)pGUITable->pHandleEntry[i].pObject;
    //獲得消息鉤子內核對像數據
    GetKernelMemory(pGUITable->pHandleEntry[i].pObject, (BYTE *)&HookInfo, sizeof(HookInfo));
    W32THREAD w32thd;
    if( GetKernelMemory(HookInfo.pWin32Thread,(BYTE *)&w32thd , sizeof(w32thd)) )
    { //獲取鉤子函數所在模塊的基址
      if (!GetKernelMemory((PVOID)((ULONG)w32thd.ppi+0xA8+4*HookInfo.iMod),
        (BYTE *)&dwModuleBase, sizeof(dwModuleBase)))
      {
        continue;
      }
      TINFO tInfo;
      //獲取鉤子所屬進程ID和線程ID
      if (!GetKernelMemory((PVOID)((ULONG)w32thd.pEThread+0x1ec),
        (BYTE *)&tInfo, sizeof(tInfo)))
      {
        continue;
      }
      HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, tInfo.dwProcessID);
      if (hProcess == INVALID_HANDLE_VALUE)
      {
        continue;
      }
      //根據模塊基址,獲取鉤子函數所屬模塊的名稱
      if (GetModuleFileNameEx(hProcess, (HMODULE)dwModuleBase, cModuleName, 256))
      {
        OutputDebugString(cModuleName);
        OutputDebugString("\r\n");
      }
    }
  }
}

    利用上面的代碼就可以找到所屬病毒DLL的消息鉤子句柄,然後調用UnhookWindowsHookEx函數卸載這個消息鉤子就OK了​​。

    文章寫完了,寫點題外話。上述文章內容大部分摘自王倍昌的《計算機病毒揭秘與對抗》一書,該書已於2011年10月1日出版。該書揭秘了Windows系統下計算機病毒常用的技術和對抗技術。如:隱藏注入技術、SPI網絡劫持技術、DLL劫持技術、瀏覽器綁架技術、服務劫持技術、感染型病毒技術、加殼實現與靜態脫殼技術、反病毒虛擬機技術、主動防禦技術等。歡迎閱讀此書,並提出寶貴意見。關於此書的更多內容請查看www.safe163.com網站。

2011年10月11日星期二

[轉載]SEH分析筆記(X86篇)六


附錄2 《nt!__local_unwind2 的反彙編代碼》


代碼:
      kd> uf __local_unwind2
      nt!__local_unwind2 [d:\dnsrv\base\crts\crtw32\misc\i386\exsup.asm @ 205]:
        8087257b push ebx
        8087257c push esi
        8087257d push edi
        8087257e mov eax,dword ptr [esp+10h] ; eax = pExceptionRegistration
        80872582 push ebp
        80872583 push eax
        80872584 push 0FFFFFFFEh
        80872586 push offset nt!__unwind_handler (80872540)
        8087258b push dword ptr fs:[0]
        80872592 mov dword ptr fs:[0],esp ; __local_unwind2 自身也會構建_EXCEPTION_REGISTRATION

      nt!__local_unwind2+0x1e [d:\dnsrv\base\crts\crtw32\misc\i386\exsup.asm @ 222]:
> 80872599 mov eax,dword ptr [esp+24h] ; eax = pExceptionRegistration
: 8087259d mov ebx,dword ptr [eax+8] ; ebx = pExceptionRegistration->scopetable
: 808725a0 mov esi,dword ptr [eax+0Ch] ; esi = pExceptionRegistration->trylevel
: 808725a3 cmp esi,0FFFFFFFFh ; cmp pExceptionRegistration->trylevel, TRYLEVEL_NONE
:< 808725a6 je nt!_NLG_Return2+0x2 (808725dd)
::
:: nt!__local_unwind2+0x2d [d:\dnsrv\base\crts\crtw32\misc\i386\exsup.asm @ 228]:
:: 808725a8 cmp dword ptr [esp+28h],0FFFFFFFFh
::< 808725ad je nt!__local_unwind2+0x3a (808725b5)
:::
::: nt!__local_unwind2+0x34 [d:\dnsrv\base\crts\crtw32\misc\i386\exsup.asm @ 230]:
::: 808725af cmp esi,dword ptr [esp+28h]
:::< 808725b3 jbe nt!_NLG_Return2+0x2 (808725dd)
::::
:::: nt!__local_unwind2+0x3a [d:\dnsrv\base\crts\crtw32\misc\i386\exsup.asm @ 234]:
::>: 808725b5 lea esi,[esi+esi*2]
:: : 808725b8 mov ecx,dword ptr [ebx+esi*4] ; move ecx, [pExceptionRegistration->scopetable[i].previousTryLevel]
:: : 808725bb mov dword ptr [esp+8],ecx ; 這個esp+8 只寫沒讀,什麼情況?
:: : 808725bf mov dword ptr [eax+0Ch],ecx ; pExceptionRegistration->trylevel = ecx
:: : 808725c2 cmp dword ptr [ebx+esi*4+4],0 ; cmp pExceptionRegistration->scopetable->lpfnFilter, NULL
:: :< 808725c7 jne nt!_NLG_Return2 (808725db)
:: ::
:: :: nt!__local_unwind2+0x4e [d:\dnsrv\base\crts\crtw32\misc\i386\exsup.asm @ 243]:
:: :: ; RegistrationPointer->scopetable->lpfnFilter 等於NULL,即這裡是__try & __finally 的組合
:: :: 808725c9 push 101h
:: :: 808725ce mov eax,dword ptr [ebx+esi*4+8]
:: :: 808725d2 call nt!_NLG_Notify (80872617) ; 這個函數對理解SEH 不重要,可以暫時忽略
:: :: 808725d7 call dword ptr [ebx+esi*4+8] ; pExceptionRegistration->scopetable->lpfnHandler()
:: ::
:: :: nt!_NLG_Return2 [d:\dnsrv\base\crts\crtw32\misc\i386\exsup.asm @ 251]:
<: :> 808725db jmp nt!__local_unwind2+0x1e (80872599) ; 循環
 : :
 : : nt!_NLG_Return2+0x2 [d:\dnsrv\base\crts\crtw32\misc\i386\exsup.asm @ 253]:
 > > 808725dd pop dword ptr fs:[0]
        808725e4 add esp,10h
        808725e7 pop edi
        808725e8 pop esi
        808725e9 pop ebx
        808725ea ret

2011年10月10日星期一

[轉載]SEH分析筆記(X86篇)五


附錄1 《Ntfs!_except_handler3 的反彙編代碼》


代碼:
         kd> uf Ntfs!_except_handler3
         ;nt!_except_handler3 [d:\dnsrv\base\crts\crtw32\misc\i386\exsup3.asm @ 172]:
           80872c00 push ebp
           80872c01 mov ebp,esp
           80872c03 sub esp,8
           80872c06 push ebx
           80872c07 push esi
           80872c08 push edi
           80872c09 push ebp
           80872c0a cld
           80872c0b mov ebx,dword ptr [ebp+0Ch] ; pExceptionRegistration
           80872c0e mov eax,dword ptr [ebp+8] ; pExceptionRecord
           80872c11 test dword ptr [eax+4],6 ; test pExceptionRecord->ExceptionFlags, (EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND)
< 80872c18 jne nt!_except_handler3+0xc9 (80872cc9)
:
: ;nt!_except_handler3+0x1e [d:\dnsrv\base\crts\crtw32\misc\i386\exsup3.asm @ 202]:
: ; ebp-8 和ebp-4 是一個類型為PEXCEPTION_POINTERS 的結構體,稱之為l_ExceptionPointers
: 80872c1e mov dword ptr [ebp-8],eax ; l_ExceptionPointers->ExceptionRecord = pExceptionRecord
: 80872c21 mov eax,dword ptr [ebp+10h] ; eax = pContext
: 80872c24 mov dword ptr [ebp-4],eax ; l_ExceptionPointers->ContextRecord = pContext
: 80872c27 lea eax,[ebp-8] ; eax = lException
: 80872c2a mov dword ptr [ebx-4],eax ; ebx-4 指向pExceptionRegistration 所在棧上類型為PEXCEPTION_POINTERS 的變量
: ; 具體棧的構造形式請參考當時建立pExceptionRegistration 的代碼
: ; 這裡是賦值給該PEXCEPTION_POINTERS 變量,以提供給GetExceptionInformation 和GetExceptionCode 使用
: 80872c2d mov esi,dword ptr [ebx+0Ch] ; esi = pExceptionRegistration->trylevel
: 80872c30 mov edi,dword ptr [ebx+8] ; edi = pExceptionRegistration->scopetable
: 80872c33 push ebx
: 80872c34 call nt!_ValidateEH3RN (8087cde8)
: 80872c39 add esp,4
: 80872c3c or eax,eax
:< 80872c3e je nt!_except_handler3+0xbb (80872cbb)
::
:: ;nt!_except_handler3+0x40 [d:\dnsrv\base\crts\crtw32\misc\i386\exsup3.asm @ 218]:
:: > 80872c40 cmp esi,0FFFFFFFFh ; cmp pExceptionRegistration->trylevel, TRYLEVEL_NONE
::< : 80872c43 je nt!_except_handler3+0xc2 (80872cc2)
::: :
::: : ;nt!_except_handler3+0x45 [d:\dnsrv\base\crts\crtw32\misc\i386\exsup3.asm @ 220]:
::: : 80872c45 lea ecx,[esi+esi*2] ; esi *= 3; 下面要將eis*4,總共esi*12,這是因為scopetable_entry 大小是12
::: : 80872c48 mov eax,dword ptr [edi+ecx*4+4] ; eax = pExceptionRegistration->scopetable[i].lpfnFilter
::: : 80872c4c or eax,eax
:::< : 80872c4e je nt!_except_handler3+0xa9 (80872ca9) ; lpfnFilter 為NULL 則跳轉
:::: :
:::: : ;nt!_except_handler3+0x50 [d:\dnsrv\base\crts\crtw32\misc\i386\exsup3.asm @ 226]:
:::: : 80872c50 push esi
:::: : 80872c51 push ebp
:::: : 80872c52 lea ebp,[ebx+10h] ; ebp = pExceptionRegistration->_ebp
:::: : 80872c55 xor ebx,ebx
:::: : 80872c57 xor ecx,ecx
:::: : 80872c59 xor edx,edx
:::: : 80872c5b xor esi,esi
:::: : 80872c5d xor edi,edi
:::: : 80872c5f call eax ; pExceptionRegistration->scopetable[i].lpfnFilter()
:::: : 80872c61 pop ebp
:::: : 80872c62 pop esi
:::: : 80872c63 mov ebx,dword ptr [ebp+0Ch] ; ebx = pExceptionRegistration
:::: : 80872c66 or eax,eax
::::< : 80872c68 je nt!_except_handler3+0xa9 (80872ca9) ; EXCEPTION_CONTINUE_SEARCH
::::: :
::::: : ;nt!_except_handler3+0x6a [d:\dnsrv\base\crts\crtw32\misc\i386\exsup3.asm @ 245]:
::::: : ; 如果lpfnFilter 返回EXCEPTION_CONTINUE_EXECUTION,跳過下面的展開操作
:::::<: 80872c6a js nt!_except_handler3+0xb4 (80872cb4) ; EXCEPTION_CONTINUE_EXECUTION
:::::::
::::::: ;nt!_except_handler3+0x6c [d:\dnsrv\base\crts\crtw32\misc\i386\exsup3.asm @ 249]:
::::::: ; lpfnFilter 返回EXCEPTION_EXECUTE_HANDLER,開始展開
::::::: 80872c6c mov edi,dword ptr [ebx+8] ; edi = pExceptionRegistration->scopetable
::::::: 80872c6f push ebx
::::::: 80872c70 call nt!__global_unwind2 (80872520)
::::::: 80872c75 add esp,4
:::::::
::::::: 80872c78 lea ebp,[ebx+10h] ; ebp = pExceptionRegistration->_ebp
::::::: 80872c7b push esi ; 展開到當前trylevel 為止(不包含本scopetable_entry)
::::::: 80872c7c push ebx
::::::: 80872c7d call nt!__local_unwind2 (8087257b)
::::::: 80872c82 add esp,8
:::::::
::::::: 80872c85 lea ecx,[esi+esi*2]
::::::: 80872c88 push 1
::::::: 80872c8a mov eax,dword ptr [edi+ecx*4+8] ; pExceptionRegistration->scopetable[i].lpfnHandler
::::::: 80872c8e call nt!_NLG_Notify (80872617)
::::::: 80872c93 mov eax,dword ptr [edi+ecx*4] ;
::::::: 80872c96 mov dword ptr [ebx+0Ch],eax ; pExceptionRegistration->trylevel = RegistrationPointer->scopetable[i].previousTryLevel
::::::: 80872c99 mov eax,dword ptr [edi+ecx*4+8] ; pExceptionRegistration->scopetable[i].lpfnHandler
::::::: 80872c9d xor ebx,ebx
::::::: 80872c9f xor ecx,ecx
::::::: 80872ca1 xor edx,edx
::::::: 80872ca3 xor esi,esi
::::::: 80872ca5 xor edi,edi
::::::: 80872ca7 call eax ; pExceptionRegistration->scopetable[i].lpfnHandler(); 這裡不會返回的! !
:::::::
::::::: ;nt!_except_handler3+0xa9 [d:\dnsrv\base\crts\crtw32\misc\i386\exsup3.asm @ 285]:
::::::: ; 找到scopetable 中的下一個scopetable_entry,繼續循環
:::>>:: 80872ca9 mov edi,dword ptr [ebx+8]
::: :: 80872cac lea ecx,[esi+esi*2]
::: :: 80872caf mov esi,dword ptr [edi+ecx*4]
::: :< 80872cb2 jmp nt!_except_handler3+0x40 (80872c40)
::: :
::: : ;nt!_except_handler3+0xb4 [d:\dnsrv\base\crts\crtw32\misc\i386\exsup3.asm @ 291]:
::: > 80872cb4 mov eax,0 ; eax = ExceptionContinueExecution (0)
::: < 80872cb9 jmp nt!_except_handler3+0xde (80872cde)
::: :
::: : ;nt!_except_handler3+0xbb [d:\dnsrv\base\crts\crtw32\misc\i386\exsup3.asm @ 295]:
:>: : 80872cbb mov eax,dword ptr [ebp+8]
: : : 80872cbe or dword ptr [eax+4],8
: : :
: : : ;nt!_except_handler3+0xc2 [d:\dnsrv\base\crts\crtw32\misc\i386\exsup3.asm @ 298]:
: > : 80872cc2 mov eax,1 ; eax = ExceptionContinueSearch (1)
: :< 80872cc7 jmp nt!_except_handler3+0xde (80872cde)
: ::
> :: ;nt!_except_handler3+0xc9 [d:\dnsrv\base\crts\crtw32\misc\i386\exsup3.asm @ 302]:
      :: ; 設置了(EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND),開始展開
      :: 80872cc9 push ebp
      :: 80872cca lea ebp,[ebx+10h] ;ebp = pExceptionRegistration->_ebp
      :: 80872ccd push 0FFFFFFFFh
      :: 80872ccf push ebx
      :: 80872cd0 call nt!__local_unwind2 (8087257b)
      :: 80872cd5 add esp,8
      :: 80872cd8 pop ebp
      :: 80872cd9 mov eax,1 eax = ExceptionContinueSearch (1)
      ::
      :: ;nt!_except_handler3+0xde [d:\dnsrv\base\crts\crtw32\misc\i386\exsup3.asm @ 313]:
      >> 80872cde pop ebp
           80872cdf pop edi
           80872ce0 pop esi
           80872ce1 pop ebx
           80872ce2 mov esp,ebp
           80872ce4 pop ebp
           80872ce5 ret

2011年10月9日星期日

[轉載]SEH分析筆記(X86篇)四


三、展開 (unwind)
  為了說明這個概念,需要先回顧下異常發生後的處理流程。

  我們假設一系列使用SEH 的函數調用流程:
  func1 -> func2 -> func3。在 func3 執行的過程中觸發了異常。

  看看分發異常流程RtlRaiseException -> RtlDispatchException -> RtlpExecuteHandlerForException
  RtlDispatchException 會遍歷異常鍊錶,對每個EXCEPTION_REGISTRATION 都調用RtlpExecuteHandlerForException。
  RtlpExecuteHandlerForException 會調用EXCEPTION_REGISTRATION::handler,也就是PassThrough!_except_handler4。如咱們上面分析,該函數內部遍歷EXCEPTION_REGISTRATION::scopetable,如果遇到有scopetable_entry::lpfnFilter 返回EXCEPTION_EXECUTE_HANDLER,那麼scopetable_entry::lpfnHandler 就會被調用,來處理該異常。
  因為lpfnHandler 不會返回到PassThrough!_except_handler4,於是執行完lpfnHandler 後,就會從lpfnHandler 之後的代碼繼續執行下去。也就是說,假設func3 中觸發了一個異常,該異常被func1 中的__except 處理塊處理了,那__except 處理塊執行完畢後,就從其後的指令繼續執行下去,即異常處理完畢後,接著執行的就是func1 的代碼。不會再回到func2 或者func3,這樣就有個問題,func2 和func3 中佔用的資源怎麼辦?這些資源比如申請的內存是不會自動釋放的,豈不是會有資源洩漏問題?

  這就需要用到“展開”了。
  說白了,所謂“展開”就是進行清理。 (注:這裡的清理主要包含動態分配的資源的清理,棧空間是由func1 的“mov esp,ebp” 這類操作順手清理的。當時我被“誰來清理棧空間”這個問題困擾了很久… …)
 
  那這個展開工作由誰來完成呢?由func1 來完成肯定不合適,畢竟func2 和func3 有沒有申請資源、申請了哪些資源,func1 無從得知。於是這個展開工作還得要交給func2 和func3 自己來完成。

  展開分為兩種:“全局展開”和“局部展開”。
  全局展開是指針對異常鍊錶中的某一段,局部展開針對指定EXCEPTION_REGISTRATION。用上面的例子來講,局部展開就是針對func3 或func2 (某一個函數)內部進行清理,全局展開就是func2 和func3 的局部清理的總和。再歸納一下,局部展開是指具體某一函數內部的清理,而全局展開是指,從異常觸發點(func3)到異常處理點(func1)之間所有函數(包含異常觸發點func3)的局部清理的總和。

  來看反彙編代碼:

代碼:
    kd> uf PassThrough!_EH4_GlobalUnwind
        PassThrough!_EH4_GlobalUnwind [d:\5359\minkernel\crts\crtw32\misc\i386\exsup4.asm @ 485]:
          f87205fa push ebp
          f87205fb mov ebp,esp
          f87205fd push ebx
          f87205fe push esi
          f87205ff push edi
          f8720600 push 0 ; pReturnValue
          f8720602 push 0 ; pExceptionRecord
          f8720604 push offset PassThrough!_EH4_GlobalUnwind+0x15 (f872060f) ; pReturnEip
          f8720609 push ecx ; pExceptionRegistration
          f872060a call PassThrough!RtlUnwind (f8720678)
          f872060f pop edi
          f8720610 pop esi
          f8720611 pop ebx
          f8720612 pop ebp
          f8720613 ret


  RtlUnwind 的原型:
        VOID RtlUnwind(
            PEXCEPTION_REGISTRATION pExceptionRegistration
            PVOID pReturnEip
            PEXCEPTION_RECORD pExceptionRecord,
            PVOID pReturnValue
        );


代碼:
    kd> uf PassThrough!RtlUnwind ; 提示:此函數wrk 提供了實現源碼
                  nt!RtlUnwind [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 423]:
                    80867358 push ebp
                    80867359 mov ebp,esp
                    8086735b sub esp,37Ch
                    80867361 mov eax,dword ptr [nt!__security_cookie (80895388)]
                    80867366 push esi
                    80867367 mov esi,dword ptr [ebp+10h] ; esi = pExceptionRecord
                    8086736a mov dword ptr [ebp-4],eax
                    8086736d push edi
                    8086736e lea eax,[ebp-2D8h] ; [ebp-2D8h] = l_pHighLimit
                    80867374 push eax
                    80867375 lea eax,[ebp-2D4h] ; [ebp-2D4h] = l_pLowLimit
                    8086737b push eax
                    8086737c call nt!RtlpGetStackLimits (80887cdc)
                    80867381 xor edi,edi
                    80867383 cmp esi,edi ; pExceptionRecord 是否為NULL
< 80867385 jne nt!RtlUnwind+0x5a (808673b2)
:
: nt!RtlUnwind+0x2f [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 452]:
: 80867387 mov eax,dword ptr [ebp+4]
: 8086738a lea esi,[ebp-37Ch] ; ebp-37Ch 是局部變量l_ExceptionRecord
: 80867390 mov dword ptr [ebp-37Ch],0C0000027h ; l_ExceptionRecord.ExceptionCode = STATUS_UNWIND
: 8086739a mov dword ptr [ebp-378h],edi ; l_ExceptionRecord.ExceptionFlags = 0
: 808673a0 mov dword ptr [ebp-374h],edi ; l_ExceptionRecord.ExceptionRecord = NULL
: 808673a6 mov dword ptr [ebp-370h],eax ; l_ExceptionRecord.ExceptionAddress = ret_addr (PassThrough!RtlUnwind 執行完畢後的返回地址)
: 808673ac mov dword ptr [ebp-36Ch],edi ; l_ExceptionRecord.ExceptionInformation[0] = 0;
:
: nt!RtlUnwind+0x5a [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 462]:
> 808673b2 cmp dword ptr [ebp+8],edi ; pExceptionRegistration 是否為NULL
 < 808673b5 je nt!RtlUnwind+0x65 (808673bd)
 :
 : nt!RtlUnwind+0x5f [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 463]:
 : 808673b7 or dword ptr [esi+4],2 ; l_ExceptionRecord.ExceptionFlags |= EXCEPTION_UNWINDING (0x2)
 :< 808673bb jmp nt!RtlUnwind+0x69 (808673c1)
 ::
 :: nt!RtlUnwind+0x65 [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 466]:
 >: 808673bd or dword ptr [esi+4],6 ; l_ExceptionRecord.ExceptionFlags |= EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND (0x2 | 0x4)
  :
  : nt!RtlUnwind+0x69 [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 466]:
  > 808673c1 push ebx
                    808673c2 lea eax,[ebp-2D0h] ; ebp-2D0 是局部變量l_Context
                    808673c8 push eax
                    808673c9 mov dword ptr [ebp-2D0h],10007h ; lContext.ContextFlags = CONTEXT_i386 | CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS (0x10000 | 0x1 | 0x2 | 0x3)

                    808673d3 call nt!RtlpCaptureContext (80887c50)
                    808673d8 mov eax,dword ptr [ebp+14h] ; eax = ReturnValue
                    808673db add dword ptr [ebp-20Ch],10h ; -20C = -2D0+C4, lContext.Esp += 0x10
                    808673e2 mov dword ptr [ebp-220h],eax ; -220 = -2D0+B0, lContext.Eax = ReturnValue
                    808673e8 call nt!RtlpGetRegistrationHead (80887d04)
                    808673ed mov ebx,eax ; ebx = 異常鍊錶頭,這裡暫命名為l_pExceptionRegistration
< 808673ef jmp nt!RtlUnwind+0x1d1 (80867529)
:
: nt!RtlUnwind+0x9c [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 500]:
: > 808673f4 cmp ebx,dword ptr [ebp+8] ; cmp l_pExceptionRegistration,pExceptionRegistration
:< : 808673f7 jne nt!RtlUnwind+0xb0 (80867408)
:: :
:: : nt!RtlUnwind+0xa1 [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 501]:
:: : ; pExceptionRegistration 等於l_pExceptionRegistration,說明展開完畢
:: : 808673f9 push edi ; TestAlert = FALSE
:: : 808673fa lea eax,[ebp-2D0h] ; eax = &l_Context
:: : 80867400 push eax
:: : 80867401 call nt!ZwContinue (8082c0b8) ; 這裡不會返回
::< : 80867406 jmp nt!RtlUnwind+0xe6 (8086743e)
::: :
::: : nt!RtlUnwind+0xb0 [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 509]:
:>: : 80867408 cmp dword ptr [ebp+8],edi ; cmp pExceptionRegistration, NULL
: :< : 8086740b je nt!RtlUnwind+0xe6 (8086743e)
: :: :
: :: : nt!RtlUnwind+0xb5 [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 509]:
: :: : 8086740d cmp dword ptr [ebp+8],ebx ; cmp pExceptionRegistration,l_pExceptionRegistration
: ::< : 80867410 jae nt!RtlUnwind+0xe6 (8086743e)
: ::: :
: ::: : nt!RtlUnwind+0xba [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 514]:
: ::: : ; pExceptionRegistration 在l_pExceptionRegistration 之下,即pExceptionRegistration 超出了異常鍊錶的棧範圍
: ::: : 80867412 lea eax,[ebp-32Ch] ; ebp-32Ch 是局部變量l_ExceptionRecord
: ::: : 80867418 push eax
: ::: : 80867419 mov dword ptr [ebp-32Ch],0C0000029h ; l_ExceptionRecord.ExceptionCode = STATUS_INVALID_UNWIND_TARGET
: ::: : 80867423 mov dword ptr [ebp-328h],1 ; l_ExceptionRecord.ExceptionFlags = EXCEPTION_NONCONTINUABLE (1)
: ::: : 8086742d mov dword ptr [ebp-324h],esi ; l_ExceptionRecord.ExceptionRecord = &l_ExceptionRecord
: ::: : 80867433 mov dword ptr [ebp-31Ch],edi ; l_ExceptionRecord.NumberParameters = 0
: ::: : 80867439 call nt!RtlRaiseException (80887a94)
: ::: :
: ::: : nt!RtlUnwind+0xe6 [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 530]:
: >>> : 8086743e cmp ebx,dword ptr [ebp-2D4h] ; cmp l_pExceptionRegistration, l_pLowLimit
: : 80867444 lea edi,[ebx+8] ; edi = l_pExceptionRegistration->scopetable_entry
: < : 80867447 jb nt!RtlUnwind+0x162 (808674ba) ; l_pExceptionRegistration 低於線程棧底,跳到錯誤處理
: : :
: : : nt!RtlUnwind+0xf1 [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 530]:
: : : 80867449 cmp edi,dword ptr [ebp-2D8h] ; cmp l_pExceptionRegistration->scopetable_entry,l_pHighLimit
: :< : 8086744f ja nt!RtlUnwind+0x162 (808674ba) ; l_pExceptionRegistration 高於線程棧頂,跳到錯誤處理
: :: :
: :: : nt!RtlUnwind+0xf9 [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 530]:
: :: : ; l_pExceptionRegistration 處於合法棧中,檢查它是否正確對齊
: :: : 80867451 test bl,3 ; 檢查l_pExceptionRegistration 是否4字節對齊
: ::< : 80867454 jne nt!RtlUnwind+0x1a2 (808674fa) ; 沒對齊,跳到錯誤處理
: ::: :
: ::: : nt!RtlUnwind+0x102 [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 580]:
: ::: : ; 進行局部展開
: ::: : 8086745a push dword ptr [ebx+4] ; l_pExceptionRegistration->Handler
: ::: : 8086745d lea eax,[ebp-2DCh] ; 這裡是局部變量l_Dis​​patchContext
: ::: : 80867463 push eax ;
: ::: : 80867464 lea eax,[ebp-2D0h] ; eax = &l_Context
: ::: : 8086746a push eax ; l_Context
: ::: : 8086746b push ebx ; l_pExceptionRegistration
: ::: : 8086746c push esi ; &l_ExceptionRecord
: ::: : 8086746d call nt!RtlpExecuteHandlerForUnwind (80887b54) ; 內部調用l_pExceptionRegistration->Handler
: ::: : 80867472 dec eax
: :::< : 80867473 je nt!RtlUnwind+0x156 (808674ae) ; 如果返回ExceptionContinueSearch 則跳轉
: :::: :
: :::: : nt!RtlUnwind+0x11d [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 586]:
: :::: : 80867475 dec eax
: :::: : 80867476 dec eax
: :::: < : 80867477 je nt!RtlUnwind+0x150 (808674a8) ; 如果返回ExceptionCollidedUnwind 則跳轉
: :::: : :
: :::: : : nt!RtlUnwind+0x121 [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 621]:
: :::: : : ; 返回ExceptionContinueSearch 和ExceptionCollidedUnwind 之外的非法值
: :::: : : 80867479 and dword ptr [ebp-31Ch],0 ; l_ExceptionRecord.NumberParameters = 0
: :::: : : 80867480 lea eax,[ebp-32Ch] ; eax = &l_ExceptionRecord
: :::: : : 80867486 push eax
: :::: : : 80867487 mov dword ptr [ebp-32Ch],0C0000026h ; l_ExceptionRecord.ExceptionCode = STATUS_INVALID_DISPOSITION
: :::: : : 80867491 mov dword ptr [ebp-328h],1 ; l_ExceptionRecord.ExceptionFlags = EXCEPTION_NONCONTINUABLE (1)
: :::: : : 8086749b mov dword ptr [ebp-324h],esi ; l_ExceptionRecord.ExceptionRecord = &l_ExceptionRecord
: :::: : : 808674a1 call nt!RtlRaiseException (80887a94)
: ::::<: : 808674a6 jmp nt!RtlUnwind+0x156 (808674ae)
: :::::: :
: :::::: : nt!RtlUnwind+0x150 [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 608]:
: :::::: : ; RtlpExecuteHandlerForUnwind 返回ExceptionCollidedUnwind,從l_Dis​​patchContext.RegistrationPointer 繼續處理
: :::::> : 808674a8 mov ebx,dword ptr [ebp-2DCh] ; l_pExceptionRegistration = l_Dis​​patchContext.RegistrationPointer
: ::::: :
: ::::: : nt!RtlUnwind+0x156 [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 630]:
: ::::: : ; 局部展開完畢,從異常鍊錶中摘除l_pExceptionRegistration
: :::>> : 808674ae mov eax,ebx ; eax = l_pExceptionRegistration
: ::: : 808674b0 mov ebx,dword ptr [ebx] ; l_pExceptionRegistration = l_pExceptionRegistration.prev
: ::: : 808674b2 push eax
: ::: : 808674b3 call nt!RtlpUnlinkHandler (80887c10) ; 摘除
: :::< : 808674b8 jmp nt!RtlUnwind+0x1cf (80867527)
: :::: :
: :::: : nt!RtlUnwind+0x162 [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 540]:
: :::: : ; l_pExceptionRegistration 並不處在當前線程棧中,這種情況不一定是出錯,有可能當前正在執行DPC。
: :::: : ; 當時還需要檢查是否對齊,不對齊就一定是出錯
: >>:: : 808674ba test bl,3 ; 檢查l_pExceptionRegistration 是否4字節對齊
: ::< : 808674bd jne nt!RtlUnwind+0x1a2 (808674fa)
: ::: :
: ::: : nt!RtlUnwind+0x167 [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 540]:
: ::: : ; 如果當前正在執行DPC,那IRQL 一定不得低於DISPATCH_LEVEL(難道不是應該一定等於DISPATCH_LEVEL ?)
: ::: : 808674bf call dword ptr [nt!_imp__KeGetCurrentIrql (8080102c)]
: ::: : 808674c5 cmp al,2 ; DISPATCH_LEVEL (2)
: :::< : 808674c7 jb nt!RtlUnwind+0x1a2 (808674fa) ; 低於DISPATCH_LEVEL,跳到錯誤處理點
: :::: :
: :::: : nt!RtlUnwind+0x171 [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 542]:
: :::: : 808674c9 mov eax,dword ptr fs:[00000020h] ; eax = 當前CPU 的KPCR::Prcb
: :::: : 808674cf cmp byte ptr [eax+95Ah],0 ; KPCR::Prcb->DpcRoutineActive (BOOLEAN 類型)
: :::: : 808674d6 mov ecx,dword ptr [eax+948h] ; ecx = KPCR::Prcb->DpcStack
: ::::< : 808674dc je nt!RtlUnwind+0x1a2 (808674fa) ; 當前不是在執行DPC,跳到錯誤處理點
: ::::: :
: ::::: : nt!RtlUnwind+0x186 [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 547]:
: ::::: : ; KPCR::Prcb->DpcRoutineActive 為TRUE,當前異常是由DpcRoutine 觸發
: ::::: : 808674de cmp edi,ecx ; 比較l_pExceptionRegistration->scopetable_entry 和DPC 棧頂
: :::::< : 808674e0 ja nt!RtlUnwind+0x1a2 (808674fa) ; 高出DPC 棧頂,跳到錯誤處理點
: :::::: :
: :::::: : nt!RtlUnwind+0x18a [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 547]:
: :::::: : 808674e2 lea eax,[ecx-3000h] ; eax = KPCR::Prcb->DpcStack - KERNEL_STACK_SIZE, 即DPC 棧底
: :::::: : 808674e8 cmp ebx,eax ; 比較l_pExceptionRegistration 和DPC 棧底
: ::::::< : 808674ea jb nt!RtlUnwind+0x1a2 (808674fa) ; 低於DPC 棧底,跳到錯誤處理點
: ::::::: :
: ::::::: : nt!RtlUnwind+0x194 [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 555]:
: ::::::: : 808674ec mov dword ptr [ebp-2D8h],ecx ; l_pHighLimit = DPC 棧頂
: ::::::: : 808674f2 mov dword ptr [ebp-2D4h],eax ; l_pLowLimit = DPC 棧底
: :::::::<: 808674f8 jmp nt!RtlUnwind+0x1cf (80867527)
: :::::::::
: ::::::::: nt!RtlUnwind+0x1a2 [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 564]:
: ::::::::: ; 棧錯誤
: >:>>>>>:: 808674fa and dword ptr [ebp-31Ch],0
: : :: 80867501 lea eax,[ebp-32Ch] ; eax = &l_ExceptionRecord
: : :: 80867507 push eax
: : :: 80867508 mov dword ptr [ebp-32Ch],0C0000028h ; l_ExceptionRecord.ExceptionCode = STATUS_BAD_STACK
: : :: 80867512 mov dword ptr [ebp-328h],1 ; l_ExceptionRecord.ExceptionFlags = EXCEPTION_NONCONTINUABLE (1)
: : :: 8086751c mov dword ptr [ebp-324h],esi ; l_ExceptionRecord.ExceptionRecord = &l_ExceptionRecord
: : :: 80867522 call nt!RtlRaiseException (80887a94)
: : ::
: : :: nt!RtlUnwind+0x1cf [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 493]:
: > >: 80867527 xor edi,edi
: :
: : nt!RtlUnwind+0x1d1 [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 493]:
> : 80867529 cmp ebx,0FFFFFFFFh ; cmp l_pExceptionRegistration, EXCEPTION_CHAIN​​_END
               < 8086752c jne nt!RtlUnwind+0x9c (808673f4)
                
                  nt!RtlUnwind+0x1da [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 647]:
                    80867532 cmp dword ptr [ebp+8],0FFFFFFFFh ; cmp pExceptionRegistration, EXCEPTION_CHAIN​​_END
                    80867536 pop ebx
                    80867537 lea eax,[ebp-2D0h] ; eax = &l_Context
                    8086753d push edi ; bTestAlert = FALSE 或bFirstChance = FALSE
                    8086753e push eax
< 8086753f jne nt!RtlUnwind+0x1f0 (80867548)
:
: nt!RtlUnwind+0x1e9 [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 656]:
: ; 展開整個異常鍊錶
: 80867541 call nt!ZwContinue (8082c0b8)
:< 80867546 jmp nt!RtlUnwind+0x1f6 (8086754e)
::
:: nt!RtlUnwind+0x1f0 [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 667]:
:: ; pExceptionRegistration 等於EXCEPTION_CHAIN​​_END。
:: ; 沒有理解這裡觸發異常是為什麼,但是不妨礙分析異常處理流程。
>: 80867548 push esi ; &l_ExceptionRecord
 : 80867549 call nt!ZwRaiseException (8082ccd4)
 :
 : nt!RtlUnwind+0x1f6 [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 671]:
 > 8086754e mov ecx,dword ptr [ebp-4]
                    80867551 pop edi
                    80867552 pop esi
                    80867553 call nt!__security_check_cookie (80874e87)
                    80867558 leave
                    80867559 ret 10h

        kd> uf 80887be9​​ ; RtlpExecuteHandlerForUnwind 的異常處理函數
        nt!ExecuteHandler2+0x61 [C:\wrk\wrk-v1.2\base\ntos\rtl\i386\xcptmisc.asm @ 329]:
          80887be9​​ mov ecx,dword ptr [esp+4] ; ecx = pExceptionRecord
          80887bed test dword ptr [ecx+4],6 ; test pExceptionRecord->ExceptionFlags, EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND
          80887bf4 mov eax,1 ; eax = ExceptionContinueSearch
          80887bf9 je nt!ExecuteHandler2+0x85 (80887c0d)

        nt!ExecuteHandler2+0x73 [C:\wrk\wrk-v1.2\base\ntos\rtl\i386\xcptmisc.asm @ 341]:
          ; 展開過程中 Handler 觸發異常
          80887bfb mov ecx,dword ptr [esp+8] ; ecx = pExceptionRegistration
          80887bff mov edx,dword ptr [esp+10h] ; edx = pDispatcherContext
          80887c03 mov eax,dword ptr [ecx+8] ; eax = pExceptionRegistration->prev
          80887c06 mov dword ptr [edx],eax ; pDispatcherContext->RegistrationPointer = pExceptionRegistration->prev,
                                                    ; 然後pDispatcherContext->RegistrationPointer 就是展開過程中正在被展開的
                                                    ; 那個 EXCEPTION_REGISTRATION_RECORD
          80887c08 mov eax,3 ; eax = ExceptionCollidedUnwind

        nt!ExecuteHandler2+0x85 [C:\wrk\wrk-v1.2\base\ntos\rtl\i386\xcptmisc.asm @ 349]:
          80887c0d ret 10h


    kd> uf RtlpExecuteHandlerForUnwind
        nt!RtlpExecuteHandlerForUnwind [C:\wrk\wrk-v1.2\base\ntos\rtl\i386\xcptmisc.asm @ 142]:
          80887b54 mov edx,offset nt!ExecuteHandler2+0x61 (80887be9​​)
          80887b59 lea ecx,[ecx]
          80887b5c push ebx
          80887b5d push esi
          80887b5e push edi
          80887b5f xor eax,eax
          80887b61 xor ebx,ebx
          80887b63 xor esi,esi
          80887b65 xor edi,edi
          80887b67 push dword ptr [esp+20h] ; pExceptionRoutine
          80887b6b push dword ptr [esp+20h] ; pDispatcherContext
          80887b6f push dword ptr [esp+20h] ; pContext
          80887b73 push dword ptr [esp+20h] ; pExceptionRegistration
          80887b77 push dword ptr [esp+20h] ; pExceptionRecord
          80887b7b call nt!ExecuteHandler2 (80887b88)
          80887b80 pop edi
          80887b81 pop esi
          80887b82 pop ebx
          80887b83 ret 14h

    kd> uf nt!ExecuteHandler2
        nt!ExecuteHandler2 [C:\wrk\wrk-v1.2\base\ntos\rtl\i386\xcptmisc.asm @ 188]:
          80887b88 push ebp
          80887b89 mov ebp,esp
          80887b8b push dword ptr [ebp+0Ch] ; pExceptionRegistration
          80887b8e push edx ; _EXCEPTION_REGISTRATION_RECORD::Handler (nt!ExecuteHandler2+0x61 (80887be9​​))
          80887b8f push dword ptr fs:[0] ; _EXCEPTION_REGISTRATION_RECORD::Next
          80887b96 mov dword ptr fs:[0],esp; 注意:這裡使用的是原始版本的異常處理
          80887b9d push dword ptr [ebp+14h] ; pDispatcherContext
          80887ba0 push dword ptr [ebp+10h] ; pContext
          80887ba3 push dword ptr [ebp+0Ch] ; pExceptionRegistration
          80887ba6 push dword ptr [ebp+8] ; pExceptionRecord
          80887ba9 mov ecx,dword ptr [ebp+18h] ; ecx = pExceptionRoutine
          80887bac call ecx ; pExceptionRoutine(...),即pExceptionRegistration->handler(...)
          80887bae mov esp,dword ptr fs:[0]
          80887bb5 pop dword ptr fs:[0] ; 摘掉當前_EXCEPTION_REGISTRATION_RECORD
          80887bbc mov esp,ebp
          80887bbe pop ebp
          80887bbf ret 14h

  代碼不長,主要功能也不復雜:從異常鍊錶頭開始遍歷,一直遍歷到指定EXCEPTION_REGISTRATION_RECORD,對每個遍歷到的EXCEPTION_REGISTRATION_RECORD,執行RtlpExecuteHandlerForUnwind 進行局部展開。
  這段代碼裡有一個細節我沒想明白,或者我想複雜了。在nt!RtlUnwind 地址80867401 處,當展開到指定EXCEPTION_REGISTRATION 後,RtlUnwind 通過ZwContinue 返回,而不是使用ret 指令。通過靜態分析和動態分析我都沒有找到用ZwContinue 的好處,或者不可替代的原因。如果有朋友有不同的結論,請分享一下。
  在分析全局展開時發生一件很囧的事,我反彙編完成,梳理流程的時候,總感覺“這個邏輯怎麼這麼熟悉,貌似在哪見過~ 難道wrk 裡有源碼?”,翻開wrk,果然有…… 不過話說回來,在反彙編分析過程讓我對一些細節理解的更深刻了(也只能這麼安慰自己了……)。

  下面我們來看看局部展開。
  在前面講PassThrough!_except_handler4 時,有提到該函數既負責處理異常也負責局部展開。其區分功能的標誌就是判斷EXCEPTION_RECORD::ExceptionFlags 是否包含EXCEPTION_UNWIND 標誌位。可以參考PassThrough!_except_handler4 中地址為f87203dc 的指令:
  f87203dc test byte ptr [eax+4],66h ; pExceptionRecord->ExceptionFlags & EXCEPTION_UNWIND,判斷是異常處理過程還是展開過程
 
  該標誌是在RtlUnwind 中被設置的,可以參考RtlUnwind 中地址為808673b7 和808673bd 出的指令:
  808673b7 or dword ptr [esi+4],2 ; l_ExceptionRecord.ExceptionFlags |= EXCEPTION_UNWINDING (0x2)
  808673bd or dword ptr [esi+4],6 ; l_ExceptionRecord.ExceptionFlags |= EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND (0x2 | 0x4)

  反彙編代碼:

  首先是我整理的_EH4_LocalUnwind 的原型:
       VOID fastcall _EH4_LocalUnwind(
            PEXCEPTION_REGISTRATION pExceptionRegistartion,
            ULONG ulUntilTryLevel
            )


代碼:
    kd> uf PassThrough!_EH4_LocalUnwind
        PassThrough!_EH4_LocalUnwind [d:\5359\minkernel\crts\crtw32\misc\i386\exsup4.asm @ 529]:
          f8720614 push ebp
          f8720615 mov ebp,dword ptr [esp+8] ; ebp = pExceptionRegistartion->_ebp
          f8720619 push edx ; ulUntilTryLevel
          f872061a push ecx ; pExceptionRegistartion
          f872061b push dword ptr [esp+14h] ; push __security_cookie
          f872061f call PassThrough!_local_unwind4 (f87204ec)
          f8720624 add esp,0Ch
          f8720627 pop ebp
          f8720628 ret 8

    PassThrough!_EH4_LocalUnwind 的原型:
       VOID local_unwind4(
           PEXCEPTION_REGSTRATION pExceptionRegstration,
           ULONG ulUntilTryLevel
           )

    kd> uf PassThrough!_local_unwind4
      PassThrough!_local_unwind4 [d:\5359\minkernel\crts\crtw32\misc\i386\exsup4.asm @ 177]:
        f87204ec push ebx
        f87204ed push esi
        f87204ee push edi
        f87204ef mov edx,dword ptr [esp+10h] ; edx = __security_cookie
        f87204f3 mov eax,dword ptr [esp+14h] ; eax = pExceptionRegistartion
        f87204f7 mov ecx,dword ptr [esp+18h] ; ecx = ulUntilTryLevel
        f87204fb push ebp
        f87204fc push edx ; __security_cookie
        f87204fd push eax ; pExceptionRegistartion
        f87204fe push ecx ; ulUntilTryLevel
        f87204ff push ecx ; 這裡壓入的值後續被f8720513 處的指令修改
        f8720500 push offset PassThrough!_unwind_handler4 (f872056f) ; _EXCEPTION_REGISTRATION_RECORD::handler
        f8720505 push dword ptr fs:[0] ; _EXCEPTION_REGISTRATION_RECORD::prev, 注意這裡使用的是原始版本
        f872050c mov eax,dword ptr [PassThrough!__security_cookie (f87220b0)]
        f8720511 xor eax,esp ; 這裡是用esp 來異或,而esp 此時指向新_EXCEPTION_REGISTRATION_RECORD
        f8720513 mov dword ptr [esp+8],eax ; [esp+8] 被賦予加密後的__security_cookie
        f8720517 mov dword ptr fs:[0],esp
    
      PassThrough!_local_unwind4+0x32 [d:\5359\minkernel\crts\crtw32\misc\i386\exsup4.asm @ 209]:
   >> f872051e mov eax,dword ptr [esp+30h] ; eax = pExceptionRegistartion
   :: f8720522 mov ebx,dword ptr [eax+8] ; ebx = pExceptionRegistration->scopetable, 將ebx 假稱為l_pCurScopeEntry
   :: f8720525 mov ecx,dword ptr [esp+2Ch] ; ecx = &__security_cookie
   :: f8720529 xor ebx,dword ptr [ecx] ; 解密scopetable
   :: f872052b mov esi,dword ptr [eax+0Ch] ; esi = pExceptionRegistration->trylevel
   :: f872052e cmp esi,0FFFFFFFEh ; cmp pExceptionRegistration->trylevel,TRYLEVEL_NONE
< :: f8720531 je PassThrough!_local_unwind4+0x75 (f8720561) ; pExceptionRegistration->trylevel 等於TRYLEVEL_NONE,無需遍歷,返回
: ::
: :: PassThrough!_local_unwind4+0x47 [d:\5359\minkernel\crts\crtw32\misc\i386\exsup4.asm @ 216]:
: :: f8720533 mov edx,dword ptr [esp+34h] ; edx = ulUntilTryLevel
: :: f8720537 cmp edx,0FFFFFFFEh ; cmp ulUntilTryLevel, TRYLEVEL_NONE
:< :: f872053a je PassThrough!_local_unwind4+0x54 (f8720540)
:: ::
:: :: PassThrough!_local_unwind4+0x50 [d:\5359\minkernel\crts\crtw32\misc\i386\exsup4.asm @ 219]:
:: :: f872053c cmp esi,edx ; cmp pExceptionRegistration->trylevel, ulUntilTryLevel
::<:: f872053e jbe PassThrough!_local_unwind4+0x75 (f8720561) ; 已經遍歷到指定scopetable_entry 了,返回
:::::
::::: PassThrough!_local_unwind4+0x54 [d:\5359\minkernel\crts\crtw32\misc\i386\exsup4.asm @ 221]:
:>::: f8720540 lea esi,[esi+esi*2]
: ::: f8720543 lea ebx,[ebx+esi*4+10h] ; ebx - pExceptionRegistration->scopetable[pExceptionRegistration->trylevel]
: ::: f8720547 mov ecx,dword ptr [ebx] ; ecx = l_pCurScopeEntry->previousTryLevel
: ::: f8720549 mov dword ptr [eax+0Ch],ecx ; pExceptionRegistration->trylevel = ebx->l_pCurScopeEntry->previousTryLevel
: ::: f872054c cmp dword ptr [ebx+4],0 ; cmp pExceptionRegistration->lpfnFilter,NULL
: :<: f8720550 jne PassThrough!_local_unwind4+0x32 (f872051e)
: : :
: : : PassThrough!_local_unwind4+0x66 [d:\5359\minkernel\crts\crtw32\misc\i386\exsup4.asm @ 240]:
: : : f8720552 mov ecx,1
: : : f8720557 mov eax,dword ptr [ebx+8] ; eax = pExceptionRegistration->lpfnHandler
: : : f872055a call PassThrough!_NLG_Call (f8720630) ; 調用pExceptionRegistration->lpfnHandler
: : < f872055f jmp PassThrough!_local_unwind4+0x32 (f872051e)
: :
: : PassThrough!_local_unwind4+0x75 [d:\5359\minkernel\crts\crtw32\misc\i386\exsup4.asm @ 248]:
> > f8720561 pop dword ptr fs:[0]
        f8720568 add esp,18h
        f872056b pop edi
        f872056c pop esi
        f872056d pop ebx
        f872056e ret
    
        kd> uf PassThrough!_NLG_Call
        PassThrough!_NLG_Call [d:\5359\minkernel\crts\crtw32\misc\i386\nlgsupp.asm @ 120]:
          f8720630 call eax
          f8720632 ret


  PassThrough!_EH4_LocalUnwind 中f8720500 處的指令用到的異常處理函數PassThrough!_unwind_handler4 的反彙編代碼:



代碼:
    kd> uf PassThrough!_unwind_handler4
        PassThrough!_unwind_handler4 [d:\5359\minkernel\crts\crtw32\misc\i386\exsup4.asm @ 294]:
          f872056f mov ecx,dword ptr [esp+4] ; ecx = pExceptionRecord
          f8720573 test dword ptr [ecx+4],6 ; test pExceptionRecord->ExceptionFlags, EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND
          f872057a mov eax,1 ; eax = ExceptionContinueSearch (1)
          f872057f je PassThrough!_unwind_handler4+0x45 (f87205b4)

        PassThrough!_unwind_handler4+0x12 [d:\5359\minkernel\crts\crtw32\misc\i386\exsup4.asm @ 307]:
          ; 在展開過程中又觸發了異常,那麼處理該新異常(實際操作只是做展開,即清理掉該異常佔用的資源,並沒有真正處理該異常)
          f8720581 mov eax,dword ptr [esp+8] ; eax = pExceptionRegistration
          f8720585 mov ecx,dword ptr [eax+8] ; ecx = __security_cookie, 注意這裡不是__pExceptionRegistration->scopetable
          f8720588 xor ecx,eax ; 這裡用pExceptionRegistration 解密,PassThrough!_local_unwind4 中也正是用它進行加密的
                                    ; 參考 f8720511 處的指令
          f872058a call PassThrough!__security_check_cookie (f8720638)
          f872058f push ebp
          f8720590 mov ebp,dword ptr [eax+18h] ; eax+18h 是被PassThrough!_local_unwind4 壓入棧的ebp,
                                                    ; 參考 f87204fb 處的指令
          f8720593 push dword ptr [eax+0Ch] ; push ulUntilTryLevel
          f8720596 push dword ptr [eax+10h] ; pExceptionRegistartion
          f8720599 push dword ptr [eax+14h] ; __security_cookie
          f872059c call PassThrough!_local_unwind4 (f87204ec)
          f87205a1 add esp,0Ch
          f87205a4 pop ebp
          f87205a5 mov eax,dword ptr [esp+8] ; eax = pExceptionRegistration
          f87205a9 mov edx,dword ptr [esp+10h] ; edx = pDispatcherContext (調整pDispatcherContext,改變遍歷順序)
          f87205ad mov dword ptr [edx],eax ; pDispatcherContext->RegistrationPointer = pExceptionRegistration
          f87205af mov eax,3 ; eax = ExceptionCollidedUnwind (3)

        PassThrough!_unwind_handler4+0x45 [d:\5359\minkernel\crts\crtw32\misc\i386\exsup4.asm @ 336]:
          f87205b4 ret


  到這裡概要流程就講完了。在處理異常和展開過程中多處涉及到遍歷操作,咱們來總結一下這些遍歷操作。
  1. 在異常處理過程中,每個被"捲入是非"的異常都至少會遍歷異常鍊錶兩次(如果發生嵌套異常,比如在展開過程中
EXCEPTION_REGISTRATION_RECORD::Handler 又觸發異常,則會遍歷更多次。不過這也可以算作是一個新異常了。看如何理解。 )。
  一次是在RtlDispatchException 中,遍歷的目的是找到願意處理該異常的_EXCEPTION_REGISTRATION_RECORD。
  另一次是在展開過程中、RtlUnwind 函數內,遍歷的目錄是為了對每個遍歷到的EXCEPTION_REGISTRATION_RECORD 進行局部展開。
  2. 同樣的,每個被"捲入是非"的異常的scopetable 也會被遍歷至少兩次,
  一次是在modulename!_except_handler? 中,遍歷目的也是找到願意處理該異常的scopetable_entry。
  另一次是在展開過程中、_local_unwind4 函數內,遍歷的目的是找到所有指定範圍內的scopetable_entry::lpfnFilter 為NULL 的scopetable_entry,調用它們的lpfnHandler (即__finally 處理塊)。

  在展開過程中,__finally 代碼塊會被執行,在執行過程中有可能觸發新的異常,增強版通過返回ExceptionCollidedUnwind (3) 來標識這種情況(參考PassThrough!_unwind_handler4 中f87205af 處的指令)。咱來回顧下這類返回值:


代碼:
    typedef enum _EXCEPTION_DISPOSITION {
        ExceptionContinueExecution,
        ExceptionContinueSearch,
        ExceptionNestedException,
        ExceptionCollidedUnwind
    } EXCEPTION_DISPOSITION;


  前面的代碼中已經展示了上述4中的三種:
  ExceptionContinueExecution 表示繼續執行(異常已經被修復);
  ExceptionContinueSearch 表示繼續搜索(當前搜索到的異常註冊信息不處理);
  ExceptionCollidedUnwind 表示在展開過程中再次觸發異常。
  ExceptionNestedException 呢?到目前咱們還沒有遇到過,什麼情況會用到它?
 
  從字面上看ExceptionNestedException 大概意思是“嵌套的異常”,是不是可以理解為“處理異常過程中再次觸發的異常”?比如類似於ExceptionCollidedUnwind,只不過ExceptionCollidedUnwind 是在展開過程。而ExceptionNestedException 是在處理異常過程中?
  咱們順著這個思路去尋找,首先來看PassThrough!_except_handler4,它是異常處理的入口了。處理和展開都是它負責的。可是翻遍了它和它的工具函數的反彙編碼也沒有找到它直接返回或者通過註冊異常處理信息來間接返回。於是我決定繼續向上層即調用者方向搜尋,於是找到瞭如下彙編碼:


代碼:
        kd> uf RtlpExecuteHandlerForException
        nt!RtlpExecuteHandlerForException [C:\wrk\wrk-v1.2\base\ntos\rtl\i386\xcptmisc.asm @ 87]:
          80887b4c mov edx,offset nt!ExecuteHandler2+0x3a (80887bc2)
          80887b51 jmp nt!ExecuteHandler (80887b5c)

        nt!ExecuteHandler [C:\wrk\wrk-v1.2\base\ntos\rtl\i386\xcptmisc.asm @ 160]:
          80887b5c push ebx
          80887b5d push esi
          80887b5e push edi
          80887b5f xor eax,eax
          80887b61 xor ebx,ebx
          80887b63 xor esi,esi
          80887b65 xor edi,edi
          80887b67 push dword ptr [esp+20h]
          80887b6b push dword ptr [esp+20h]
          80887b6f push dword ptr [esp+20h]
          80887b73 push dword ptr [esp+20h]
          80887b77 push dword ptr [esp+20h]
          80887b7b call nt!ExecuteHandler2 (80887b88)
          80887b80 pop edi
          80887b81 pop esi
          80887b82 pop ebx
          80887b83 ret 14h

        kd> uf ExecuteHandler2
        nt!ExecuteHandler2 [C:\wrk\wrk-v1.2\base\ntos\rtl\i386\xcptmisc.asm @ 188]:
          80887b88 push ebp
          80887b89 mov ebp,esp
          80887b8b push dword ptr [ebp+0Ch]
          80887b8e push edx
          80887b8f push dword ptr fs:[0]
          80887b96 mov dword ptr fs:[0],esp ; 註冊新的異常處理結構
          80887b9d push dword ptr [ebp+14h]
          80887ba0 push dword ptr [ebp+10h]
          80887ba3 push dword ptr [ebp+0Ch]
          80887ba6 push dword ptr [ebp+8]
          80887ba9 mov ecx,dword ptr [ebp+18h]
          80887bac call ecx
          80887bae mov esp,dword ptr fs:[0]
          80887bb5 pop dword ptr fs:[0]
          80887bbc mov esp,ebp
          80887bbe pop ebp
          80887bbf ret 14h

        kd> uf 80887bc2 ; ExecuteHandler2 使用的異常處理函數
        nt!ExecuteHandler2+0x3a [C:\wrk\wrk-v1.2\base\ntos\rtl\i386\xcptmisc.asm @ 268]:
          80887bc2 mov ecx,dword ptr [esp+4]
          80887bc6 test dword ptr [ecx+4],6
          80887bcd mov eax,1 ; eax = ExceptionContinueSearch (1)
          80887bd2 jne nt!ExecuteHandler2+0x5e (80887be6)

        nt!ExecuteHandler2+0x4c [C:\wrk\wrk-v1.2\base\ntos\rtl\i386\xcptmisc.asm @ 279]:
          80887bd4 mov ecx,dword ptr [esp+8]
          80887bd8 mov edx,dword ptr [esp+10h]
          80887bdc mov eax,dword ptr [ecx+8]
          80887bdf mov dword ptr [edx],eax
          80887be1 mov eax,2 ; eax = ExceptionNestedException (2)

        nt!ExecuteHandler2+0x5e [C:\wrk\wrk-v1.2\base\ntos\rtl\i386\xcptmisc.asm @ 287]:
          80887be6 ret 10h

  RtlpExecuteHandlerForException 函數是在RtlDispatchException 中被調用。 RtlDispatchException 在遍歷異常鏈過程中並不直接調用EXCEPTION_REGISTRATION_RECORD::Handler,而是通過RtlpExecuteHandlerForException 來間接調用。 RtlpExecuteHandlerForException 通過ExecuteHandler2 建立一個異常處理塊。
  PassThrough!_except_handler4 處理異常的過程中,如果再次觸發異常,則會由地址為80887bc2 的異常處理函數返回ExceptionNestedException。因為這幾個函數都很簡單,而且跟之前分析的函數很類似,不再贅述。

  分析完這些,我有個疑問還是沒有解開,我個人一直很​​奇怪增強版的使用方式為何不能這樣:
        __try
        {
        }
        __except()
        {
        }
        __finally
        {
        }
  其中__except 過濾塊和處理塊可以省略。
  但是很遺憾,MSC 中__except 和__finally 只能選其一。 (當然,咱們可以用雙層__try 來實現同樣的效果,但是總感覺不太爽,特別是會導致代碼塊被迫縮進兩次)
  在分析的過程中我也發現我希望的這種方式更合理一些,__except 負責處理異常,在處理異常代碼中被執行。如果沒有處理異常,那麼在展開過程中__finally 代碼塊被執行,做一些清理操作。這樣兩者都存在,各負責各的,不是更好嗎?不知道是不是什麼歷史原因。

  還有一個地方我也覺得不太完美。我們分析原始版本的時候發現,原始版本自身並沒有直接使用展開,RtlDispatchException 等異常處理函數並沒有直接調用RtlUnwind。後者實際上是在增強版中才用到。我個人感覺這種模型並不完美,原始版本並沒有自成體系,而是與增強版糾結在一起,沒有很好的分層。

  另外,在實際應用中SEH 機制可能會導致很難分析的內存洩漏。我們來看一個例子,
  調用流程:func1 -> func2
  其中,func1 的代碼如下:
        VOID Func1()
        {
            __try
            {
                Func2();
            }
            __except(EXCEPTION_EXECUTE_HANDLER)
            {
                // 一些善後處理
            }
        }
 
  Func2 沒有應用SEH,它申請了一塊內存,對這塊內存進行操作,然後釋放該內存。但是在操作內存時候觸發異常,異常被Func1 處理了,但是因為Func2 沒有__finally 處理塊,於是展開過程中Func2 並沒有機會去釋放這塊內存。結果就是:程序依然“正常”的在運行,但是實際上已經造成內存洩漏。隨著程序執行,洩漏的資源可能越來越多。最後導致嚴重的系統故障。
       遇到這種問題,程序猿通過靜態分析是很難找到洩漏的原因的。

  到這裡差不多就囉嗦完了。最後,來一段總結。

  本文只是我分析x86 下windows 異常處理機製過程的一些筆記的集合。因為興趣的原因,我只分析了內核部分,應用層SEH 我沒有琢磨。感興趣的朋友可以參考《軟件調試》中的相關內容,貌似挺詳細的。
  後續我會抽時間再琢磨琢磨x64 下windows 的異常處理機制,前段時間查閱x64 資料的時候看到其異常處理機制調整了很多,比如EXCEPTION_REGISTRATION 不在是放在棧上,而是做成表。如果內部實現改變的較多,我會再寫一份筆記來分享。 sinister 師傅有過這麼一段話,我很認同:
  “交流都是建立在平等的基礎上,在抱怨氛圍和環境不好的同時應該先想一想自己究竟付出了多少?只知索取不願付出的人也就不用抱怨了,要怪也只能怪自己。發自己心得的人無非是兩種目的,一是引發一些討論,好糾正自己錯誤的認識,以便從中獲取更多的知識使自己進步的更快。二是做一份備忘,當自己遺忘的時候能夠馬上找到相關資料。”
  其中提到的兩種目的我都有 :-)
  還是那句老話,FIXME。