C# + .NET framework 2.0 + Visual Studio 2010 + remote debugging = 殘廢
C# + .NET framework 2.0 + dump + WinDbg = 悲劇
C# + .NET framework 2.0 + Visual Studio 2010 + remote debugging = 殘廢
C# + .NET framework 2.0 + dump + WinDbg = 悲劇
讓我想起了以前微軟研究院利用 C# 改良版 Spec# 開發的 Singularity OS ,不過這次換成了 Lua 。
2010 年便開始開始的 GSoC project,一度變成沒人維護的項目,不過 NetBSD 7 可能即將包含它。還提到了 Python 和 Java 也是有可能的取代方案,不過考慮到這兩個語言的 object mapping 和 memory 需求量,還是沒有成真。
目前看來外國鄉民是一面倒地不看好,不過技術嘛,或許會有令人驚豔的應用或影響出現也說不定!不過 script language 大都帶有 garbage collection ,要讓這大傢伙和 kernel 裡其他的 subsystem 互動順利、尤其是記憶體管理,加上處理 interpreter 內,自帶的 synchronization mechanism 搭配 kernel scheduler ,花的功夫應該是不少,令人好奇!
News http://bsd.slashdot.org/story/13/02/16/2329259/netbsd-to-support-kernel-development-in-lua-scripting
在 Boost mail list 上看到的消息:
http://lists.boost.org/Archives/boost/2013/01/200634.php
Subject: [boost] Future of Boost Windows Installers
From: Dave Abrahams (dave_at_[hidden])
Date: 2013-01-31 14:42:33
Hi,
As many of you already know, I'm going to work at Apple in late
February, and BoostPro is closing its doors. For many years, the Boost
Getting Started Guide has suggested that Windows users pop over to
BoostPro's website and use the latest installer to get Windows binaries.
We'd like to donate to the community the technology used to build those
installers. If anyone would like to pick up the job of creating them,
we'd be happy to offer guidance.
Right now I'm on a plane where an uplink to GitHub is creeping along at
4KB/s, so I've been pushing the stuff up in bits and pieces and the
latest stuff isn't up there, but it's going to
http://github.com/boostpro/installer and will be complete within 24 hours.
Whatever happens with this installer code, someone is going to need to
update the Getting Started Guide, either to point at the installer's new
home, or to remove references to it.
-- Dave Abrahams BoostPro Computing Software Development Training http://www.boostpro.com Clang/LLVM/EDG Compilers C++ Boost
BoostPro 是幾位 C++ 大師組成的教學顧問團隊,BoostPro 上有他們的黃金陣容的介紹:
嗯, Apple 這幾年應該算是最力挺 native code 的大型軟體公司(硬體公司?!),旗下的 LLVM、Clang 等 open source projects 不說,還雇用了 Doug Gregor (Modules for C++)、Howard Hinnant(libcxx 作者) 等神人在為更底層的基礎扎根,曾幾何時,竟然要喊聲: Apple 加油啊~
在 Ptt C_and_CPP 版看到網友的分享:http://www.cppgm.org/index.html
是個免費的線上課程,但結業時要求妳/你寫出一套 C++ compiler (x86_64)、standard library、toolchain ,並且要符合 C++11 標準,過程中不可以使用任何的 3rd party code 。
Participants in this free online course will develop their own C++ compiler, standard library, and toolchain with the following features:
- Compliant with the latest 2011 standard (C++11)
- Written entirely in C++ with no third-party dependencies
- Code generator targeting Linux x86_64
- Full toolchain including preprocessor, assembler, and linker
- Will build with itself (self-hosting) and pass provided conformance test suite
真是非常的硬!!!那過了,有什麼好處?目前網頁列出了三項:
CPPGM 認證過後的一代宗師!
CPPGM 會幫你寫一封介紹信!
一代宗師網路俱樂部!
2013/2/15: 報名截止
2013/3/1: 下一期開課
有興趣的趕快衝啊~不過認真一下,網路上 google 不太到 CPPGM 的詳細資訊,網頁上的自我介紹也很簡單幾句:
ABOUT CPPGM: The CPPGM Foundation was formed by a software company that recognized the value to programmer productivity that a good knowledge of language mechanics had to new developers to their team. The C++ Grandmaster Certification began development as an internal training program, and the foundation was founded to offer it publicly.
或許是某個詐騙集團要騙 code 或是神奇企業的徵才方式?!不過若是能按部就班、完整提供這樣訓練內容的組織,大概也非等閒之輩,精通 C++11 標準、library 還有底層實務。衝著這些,報名去了~
在 WinDbg 裡頭又稱作:Change Post-Reboot Break State 。目前共有三種模式可供切換:
CTRL+ALT+K
透過 BCDEdit 開啟 boot manager 的 debug :
bcdedit /set {bootmgr} bootdebug on
接著,在 WinDbg 中設定 Break on reboot ,重開機後,我們就可以看到 WinDbg 停在 bootmgr 上了。
會有這篇文章,是因為上了張銀奎老師的課,張銀奎老師不用多做介紹,他是软件调试[2]的作者,也常在高端调试[3]出沒(應該也是該網站的建立者?!),三天的課程非常精實,把 Windows Internals 用 WinDbg 方式再講一次,受益匪淺 :)
在 Visual C++ 裡頭,C++ Directories 定義了 Visual C++ 這個 IDE 要去哪些路徑下找尋 header files/library files/sources files ,在 2005/2008 裡頭,全域的設定可以在 Tools > Options > Projects and Solutions > VC++ Directories 下找到。
圖一、VC++ 2005 下的 VC++ Directories Dialog
這個 dialog 在 VC++ 2010 不復存在了,取而代之的是請使用者使用 property sheet 以及 per-project 的設定。
如果好奇為什麼做了這樣的更動,可以參考 [2] 的文章,主要是因為:
按編:這三個理由實在都很說不過去,不過既然這是 Visual C++ team 的決定,我們也只能接受了 :~
從 2010 之後,變動後的 VC++ Directories 不再放在 Tools > Options > Projects and Solutions > VC++ Directories ,而是以 per-project 方式存在,使用者可以:
選取 VC++ Directories 。
很快地,妳/你會發現,新的 dialog 跟原有的沒什麼差別,而實際上也是,只是現在的變更都是 per-project 的,不再對所有的專案有效,因此若是妳/你想將 Boost library 作為所有 VC++ project 都會使用的函式庫,就沒辦法從這裡完成;也或是,妳/你更新了 Boost 版本,每個已經使用 Boost 又想升級的 projects ,就得手動來更改。
說完了使用上的變動,接著是檔案位置的更動,存放的位置從:
%LOCALAPPDATA%\Microsoft\VisualStudio\8.0\VCComponents.dat
換到了:
%LOCALAPPDATA%\Microsoft\MSBuild\v4.0\
下,並且以 Microsoft.Cpp.<Platform>.user 命名:
新的檔案格式稱作:Property sheet ,附檔名 props 。有了這個檔案,我們還是有機會可以做到 2005 & 2008 時的全域設定。先前提到了:為了迎接 MSBuild 的來臨,VC++ 將 INI 格式變成了 XML 格式,所以客製化上還是很簡單的,任選一個 platform 的檔案打開:
所以一種修改方式,就是直接用編輯器修改 XML ,看想修改哪些就可以改,值得一提的是,以往在 2005 & 2008 裡頭,路徑名不可以是 Visual C++ 裡頭的 Macro ,現在反倒是可以了,因為 IDE 是將檔案中的值讀入後才做解析。另外一種方式則是透過 Visual C++ 內建的 Property Manager 修改,我們放到下一節介紹。
當 Visual C++ 將 property sheet 讀入後,會當做 Inherited values ,對於當前 project 來說,是不可編輯的,我們必須繞到 props 檔去,或是使用 property manager 編輯。property manager 可以從工具列 > View > Property Manager 找到:
叫起 Property Manager 後,可以看到目前 project configuration 所涵蓋的 platform 的設定。 Double click 後,可以看到跳出一個視窗,
跳出來的視窗長得很像 project properties ,不過仔細看會發現 Configuration 和 Platform 已經被鎖死成對應的 property sheet 檔所代表的設定,接著,直接編輯,然後存檔,這樣就完成了修改全域 property sheet 了。
值得注意的是,property sheet 放在 MSBuild 的 local app data 下,而目前 2010 和 2012 所使用的 MSBuild 都是 4.0 [4],因此一旦我們修改了 property sheet ,可是會同時影響到 2010 跟 2012 的 ... Orz ...
這幾天終於趁著聽課的空檔,把電腦裝了 VC++ 2012 ,這版的一大特色:整合了 device driver 的開發。很快地試玩了一下它的 project wizard :
結果,很不幸地失敗 ...
1>C:\Program Files (x86)\Windows Kits\8.0\Include\KM\wdm.h(10920): fatal error C1003: error count exceeds 100; stopping compilation
再看一下 Error list:
Error 1 error C2220: warning treated as error - no 'object' file generated C:\Program Files (x86)\Windows Kits\8.0\Include\shared\sal.h 2884
Error 4 error C2054: expected '(' to follow '_IRQL_requires_same_' C:\Program Files (x86)\Windows Kits\8.0\Include\shared\ntdef.h 1897
Error 5 error C2085: 'EXCEPTION_ROUTINE' : not in formal parameter list C:\Program Files (x86)\Windows Kits\8.0\Include\shared\ntdef.h 1903
Error 6 error C2085: 'EXCEPTION_ROUTINE' : not in formal parameter list C:\Program Files (x86)\Windows Kits\8.0\Include\shared\ntdef.h 1905
Error 7 error C2143: syntax error : missing ';' before '*' C:\Program Files (x86)\Windows Kits\8.0\Include\shared\ntdef.h 1905
Error 8 error C2143: syntax error : missing ')' before 'constant' C:\Program Files (x86)\Windows Kits\8.0\Include\KM\wdm.h 10384
Error 9 error C2143: syntax error : missing '{' before 'constant' C:\Program Files (x86)\Windows Kits\8.0\Include\KM\wdm.h 10384
Error 10 error C2059: syntax error : '<Unknown>' C:\Program Files (x86)\Windows Kits\8.0\Include\KM\wdm.h 10384
真的是很 ooxx 。問題都是發生在 sal.h, ntdef.h wdm.h 等等基本的 header files 裡。
這類的問題通常是發生在 include 了錯誤的 header files ,所以回頭檢視 solution property : Project > Properties > Configuration Properties > VC++ Directories 。
include directories 多了很多路徑,其他像是 Executable Directories 、Library Directories 也有一樣的問題。嗯嗯,VC++ 2012 匯入了電腦上其他版本 VC++ 的設定,可能的原因之一:安裝完 2012 、第一次啟動時,我允許了 2012 去找尋舊版本的設定並匯入所導致。再點開 Include Directories 看一下:
發現這些多餘的路徑都是來自 Inherited values 。這裡,我試了兩種解決方式:
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ExecutablePath>$(ExecutablePath)</ExecutablePath>
<IncludePath>$(IncludePath)</IncludePath>
<ReferencePath>$(ReferencePath)</ReferencePath>
<LibraryPath>$(LibraryPath)</LibraryPath>
<SourcePath>$(SourcePath)</SourcePath>
<ExcludePath>$(ExcludePath)</ExcludePath>
</PropertyGroup>
</Project>
Facebook App 時下分成三類:
特別要注意的是, Facebook 只提供我們使用 Facebook 的 social platform ,但它並不提供 code hosting ,因此開發 Apps on Facebook 需要自己準備 web server 或是第三方的 web hosting ,運氣不錯的是,Facebook 和 Heroku 有正式的合作,因此若是找不到代管服務或是懶得自己架站的開發者,可以先使用 Heroku 來試試看。
登入 https://developers.facebook.com/apps 。第一次登入時, Facebook 會詢問您相關的 permission ,選 allow 就可以。之後就會看到下面的畫面,點選 Create New App
跟 Facebook 說一下,妳/你不是機器人吧~
選擇一下妳/你想用的開發語言。
若是事先沒有 Heroku 帳號也不用擔心,透過這個步驟填入 email 就會幫妳/你申請。
看到這個 dialog 出現,就恭喜妳/你成功囉。
當妳/你點下 Go To App 後,瀏覽頁會被導到 Heroku 代管的 app page 去,以這邊的例子會是:http://evening-plateau-9255.herokuapp.com
若是之前沒有 Heroku 的帳號,當完成上面的步驟後,Heroku 會寄一封啟動信到步驟四的信箱去。或是之前已經有 Heroku 帳號,可以直接到 https://dashboard.heroku.com/apps? 去看一下,會發現多了一個 evening-plateau-9255 ,點進去後,請找 Settings > Info > Git URL ,以本文的例子, Heroku 配給我們的 git url 是 git@heroku.com:evening-plateau-9255.git
步驟二、下載 Herolu toolbelttoolbelt 是 cmd line 工具包,主要包含了 Heroku 環境初始化工具、app 的 local 模擬和 Git 。
安裝完 toolbelt 後,它應該已經被加入到環境變數裡頭。這時,請準備一個新資料夾來當做 working folder ,並使用 cmd.exe 切到該 folder 下,執行:
heroku login
步驟四、Clone 一份來改吧還記得自己的 app git URL 嗎?
git clone git@heroku.com:your_app_name_in_heroku.git -o heroku
到這裡,妳/你已經可以開始做些了,若是不熟悉 Git ,可以參考下面指令把你的修改上傳到 server 上去:
git commit -am "commit message" git push heroku master
Windows Vista 之後已經不再將開機選項儲存於 boot.ini 中,而是改以 BCD (Boot Configuration Data) 儲存,因此以前透過編輯 boot.ini 啟動 Windows kernel debugging 的方法也得更新一下。這裡我們透過 bcdedit.exe 來編輯 BCD ,讓 Windows 啟動後可以進入 debug mode 。
Debuggee 部分,和以前一樣使用 named piped 來替 guest OS 模擬 serial port (COM port)。
bcdedit /dbgsettings serial debugport:2
bcdedit /copy {current} /d DebugEntry
執行完該指令後,會出現一個 ID ,代表我們要複製出來的開機選項,該 ID 在每台電腦上都不太一樣。bcdedit /displayorder {current} {ID}
bcdedit /debug {ID} ON
Vista之後,DbgPrintEx()、vDbgPrintEx()、vDbgPrintExWithPrefix()、KdPrintEx() 等選擇性的輸出函式預設是關閉的,要啟動的話,需要到 registry 上設定對應的 log level [3]:
到 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Debug Print Filter 下新增 DEFAULT=dword:0000ffff
實際數值,依照需求而定,0000ffff 足以應付大多數用途。
在 bcdedit 設定完成後,重開機,便會看到新的開機選項 DebugEntry:
選擇 DebugEntry 後,我們便可以在 debugger 的機器上的 cmd 執行下面指令來進行 kernel debugging,com_1 就是我們當初在 VMware guest OS 裡頭設定的名字:
windbg -b -k com:pipe,port=\\.\pipe\com_1,resets=0
Vista 之後的 64-bit OS ,Microsoft 都會要求 drivers 必須有 WHQL 的認證才能啟動。這對於一般使用者來說可能沒有影響,但對於有在開發、測試 drivers 的人就會是個問題。
最簡單的方式是,開機時,使用 F8 進入進階模式:
並選擇(停用驅動程式簽章增強)Disable Driver Signature Enforcement。
若是 drivers 有 test sign ,那麼我們還可以使用 BCDEdit 來允許 testing sign driver 的載入[4]:
bcdedit /set TESTSIGNING ON
設定完成後,需要重開機。
要驗證 test sign 是否成功,可以使用 bcdedit 檢查,或是觀察桌面右下角的文字說明:
去年聖誕節買的 x201 ,下單前還特別注意 CPU 是否支援 Virtualization ,直到上個月拿來跑 64 bit guest OS 時,才發現 Windows 會進入開機、當機的無窮迴圈。後來才發現一些小眉角,尤其是在筆電上:
如果不確定 CPU 是否支援 VT ,可以到 Intel/AMS 的網站查詢,像是 Intel Processor Spec. Finder 或 AMD Desktop Processor。
[感謝 Allen Lai 提醒]
VMware 提供了一個快速的檢測工具——Processor Check for 64-Bit Compatibility,雖然已經很久沒更新了,不過它的結果明確易懂,像是下圖就說明:
不過根據說明文件,它目前只支援特定的 processors ,而且還有幾項是 experimental support:
或是利用 VMware 的 CPU Identification Utility ,這是個 可用來開機的 ISO 檔,使用時只需要掛載進 VMware 即可 。
未啟動 VT 時的輸出:
啟動 VT 後:
上面的圖是 x201 開啟 VT 後的結果,根據說明文件和實驗,似乎紅框裡有一個是 Yes ,就可以支援 64-bit guest OS 。根據 Allen Lai 的測試,即使 64-bit LongMode 顯示 YES ,也並非能支援 64-bit guest OS。所以目前還沒有比較好的解讀方式 … 大概只是方便你把結果丟到 support 或 community 去發問 …
最後要注意的是,當 CPU features 設定改變後,都可能讓已經建立好的 snapshots 出問題。
2011/4/26: 補上 x201 未啟動 VT 的 VMware CPU Identification 結果截圖。
2011/4/27: 補充 Allen Lai 的連結和測試工具 Processor Check for 64-Bit Compatibility 。
原文:GCC Bug
哈,這次老師給我的作業可是難題,有好一陣子沒有碰觸 gdb ,更何況這次還要跟 FPU 打交道,下面是我的測試程式,和原文只有小部份差異,但重點部份是一樣的:
#include <cstdio>
using namespace std;
double f(double z)
{
return z;
}
void foo(double fraction)
{
double z = fraction * 20.0;
int t1 = z;
int t2 = f(z);
printf("t1=%d, t2=%d\n", t1, t2);
}
int main()
{
foo(6.0/20.0);
return 0;
}
輸出:
6, 6
是我們預期的結果。但在某些情況下卻會產生
5, 6
這樣的輸出。但若是在 compile 時加上 -ffloat-store 則又能得到正確的結果。根據 gcc optimization options 的說明:
Do not store floating point variables in registers, and inhibit other options that might change whether a floating point value is taken from a register or memory.
我們懷疑是從 register 讀出時發生意想不到的進位,導致結果有所出入。
一開始在嘗試 reproduce 時就遇上問題,在和 aaa 討論後才發現,我們使用的 gcc 版本和 compile options 都不盡相同。最後確定了發生問題的環境:
下面是我嘗試過的平台和結果:
| -O | -O3 | |
| gcc 4.4.1 | No | No |
| gcc 3.4.4 (cygwin) | Yes | No |
有趣的開始!
g++ -Wall -O3 -S -masm=intel main.cpp
這個版本是最好理解的, compiler 已經最佳化到:
看一下 assembly 就很清楚:
LC2:
.ascii "t1=%d, t2=%d\12\0"
.align 4
...
_main:
push ebp
mov eax, 16
mov ebp, esp
sub esp, 24
and esp, -16
call __alloca
call ___main
mov DWORD PTR [esp], OFFSET FLAT:LC2 ; 看一眼 LC2 ,一個取址動作 :)
mov edx, 6
mov eax, 6
mov DWORD PTR [esp+8], edx
mov DWORD PTR [esp+4], eax
call _printf
leave
xor eax, eax
ret
g++ -Wall -O -S -masm=intel main.cpp
O 版本就沒那麼聰明,而是真正的去呼叫 foo() :
_main:
push ebp
mov ebp, esp
sub esp, 8
and esp, -16
mov eax, 16
call __alloca
call ___main
fld QWORD PTR LC4
fstp QWORD PTR [esp]
call __Z3food
mov eax, 0
leave
ret
而且可以發現,LC4 是 foo() 的參數:
LC4:
.long 858993459
.long 1070805811
.text
.align 2
0.3 的 IEEE 754 Double precision (64 bits) 表示式:
0 01111111101 00110011001100110011 110011001100110011001100110011
1070805811 858993459
嗯嗯,O 版本先做了簡單的運算展開。不過在進入 foo() 的 floating point 計算前,得先對 x87 FPU 有簡單的概念 :
節錄自: Intel® 64 and IA-32 Architectures Software Developer’s Manual ─ Volume 1: Basic Architecture
x87 上用來儲存資料的 registers 被組織成一塊 stack 的,並且搭配了 control, status 等 registers 可供控制、查詢之用。 Stack 以下圖的方式運做:
節錄自: Intel® 64 and IA-32 Architectures Software Developer’s Manual ─ Volume 1: Basic Architecture
Stack 的 top 又稱為 ST(0),是許多 FPU 指令的隱藏 operand 之一,像是待會在 foo() 看見的 fld、fstp、fmul 指令都會 implicitly 操作 ST(0) 。
__Z3food:
push ebp
mov ebp, esp
push ebx
sub esp, 20
fld DWORD PTR LC1
fmul QWORD PTR [ebp+8]
fnstcw WORD PTR [ebp-6]
movzx eax, WORD PTR [ebp-6]
or ax, 3072
mov WORD PTR [ebp-8], ax
fldcw WORD PTR [ebp-8]
fist DWORD PTR [ebp-12] ; store integer
fldcw WORD PTR [ebp-6]
mov ebx, DWORD PTR [ebp-12]
fstp QWORD PTR [esp]
call __Z1fd
fnstcw WORD PTR [ebp-6]
movzx eax, WORD PTR [ebp-6]
or ax, 3072
mov WORD PTR [ebp-8], ax
fldcw WORD PTR [ebp-8]
fistp DWORD PTR [esp+8]
fldcw WORD PTR [ebp-6]
mov DWORD PTR [esp+4], ebx
mov DWORD PTR [esp], OFFSET FLAT:LC2
call _printf
add esp, 20
pop ebx
pop ebp
ret
這邊使用倒推法,來找到 t1 及其在 assembly 的表示,尋著上頭的 highlight 部份,可以發現:
反推的過程也符合程式的邏輯,所以下一步就是用 debugger 去追蹤:
Breakpoint 1, 0x0040104f in foo ()
1: x/i $pc
0x40104f <_Z3food+7>: flds 0x402030
(gdb) info float
R7: Empty 0x3ffd9999999999999800
R6: Empty 0x00e07c93022200240000
R5: Empty 0x51c27c92fe956123a74c
R4: Empty 0x79d37c9a06007c930460
R3: Empty 0x0a887c9479870022bf80
R2: Empty 0xbf9800010a886123debc
R1: Empty 0x00006123a74c7c93043e
=>R0: Empty 0xdee46123de0c6123d8cc
Status Word: 0xffff0000
TOP: 0
Control Word: 0xffff037f IM DM ZM OM UM PM
PC: Extended Precision (64-bits)
RC: Round to nearest
Tag Word: 0xffffffff
Instruction Pointer: 0x1b:0x004010c8
Operand Pointer: 0xffff0023:0x0022cd10
Opcode: 0xdd1c
我們將中斷點設在 foo() 函式,接著使用 info float 去 dump FPU 相關的 register (FPU 相關 register 似乎無法加到 gdb 的 Automatic Display!?所以每次都需手打 ),比較特別的幾個要點都被 highlight 了:
認識了 gdb 對於 FPU 的輸出後,可以開始一步步逼近問題核心 ── ebp-12 了。
foo() 的一開始,是讀入 0x402030 上的值到 FPU stack 上:
Breakpoint 1, 0x0040104f in foo () 1: x/i $pc 0x40104f <_Z3food+7>: flds 0x402030
好奇這 magic number 是何來的,可以透過之前的 disassembly 結果:
LC1: .long 1101004800 ; = 0x41a00000 .text .align 2
或是動態的用 debugger 觀看:
(gdb) x 0x402030 0x402030 <_data_start__+48>: 0x41a00000 ; = 1101004800
而 0x41a00000 便是 20.0 的 IEEE 754 0.3 的 IEEE 754 Double precision (64 bits) 表示式。來用 debugger 確認一下:
Breakpoint 1, 0x0040104f in foo () 1: x/i $pc 0x40104f <_Z3food+7>: flds 0x402030 (gdb) info float R7: Empty 0x3ffd9999999999999800 R6: Empty 0x00e07c93022200240000 R5: Empty 0x51c27c92fe956123a74c R4: Empty 0x79d37c9a06007c930460 R3: Empty 0x0a887c9479870022bf80 R2: Empty 0xbf9800010a886123debc R1: Empty 0x00006123a74c7c93043e =>R0: Empty 0xdee46123de0c6123d8cc (gdb) si 0x00401055 in foo () 1: x/i $pc 0x401055 <_Z3food+13>: fmull 0x8(%ebp) (gdb) info float =>R7: Valid 0x4003a000000000000000 +20 R6: Empty 0x00e07c93022200240000
執行完上面的指令後, FPU 的 data stack 現在是 20 ,接著進行 x 0.3 的乘法(ebp+0x8 是 foo() 的參數,即 0.3):
0x401055 <_Z3food+13>: fmull 0x8(%ebp) (gdb) si 0x00401058 in foo () 1: x/i $pc 0x401058 <_Z3food+16>: fnstcw -0x6(%ebp) (gdb) info float =>R7: Valid 0x4001bffffffffffffe00 +6 R6: Empty 0x00e07c93022200240000
得到了結果:+6 ,無誤。不過接下來的 FPU control word 卻令人意外:
(gdb) info float
...
Control Word: 0xffff037f IM DM ZM OM UM PM
PC: Extended Precision (64-bits)
RC: Round to nearest
0x40105f <_Z3food+23>: or $0xc00,%ax
0x401063 <_Z3food+27>: mov %ax,-0x8(%ebp)
0x401067 <_Z3food+31>: fldcw -0x8(%ebp)
(gdb) info float
...
Control Word: 0xffff0f7f IM DM ZM OM UM PM
PC: Extended Precision (64-bits)
RC: Round toward zero
O 版將 RC 從 Round to nearest 改變成 Round toward zero 。這會影響到 fist 的運作。在執行 fist 前,我們把預期的結果: 6.0 的 IEEE 754 Double precision 求出來:0x4018000000000000 ;而目前的 R7 值是 0x4001bffffffffffffe00 ,小於該值。表示 R7 現在是不足 6.0 ,而此時又改以 Round toward zero 來進行取整數,那結果會是 5 則是預期了。
(gdb) si
0x0040106a in foo ()
1: x/i $pc
0x40106a <_Z3food+34>: fistl -0xc(%ebp)
(gdb) info float
=>R7: Valid 0x4001bffffffffffffe00 +6
R6: Empty 0x4bc000010101e259acd0
...
Control Word: 0xffff0f7f IM DM ZM OM UM PM
PC: Extended Precision (64-bits)
RC: Round toward zero
(gdb) x 0x22ccfc ; ebp - 0xc = 0x22ccfc
0x22ccfc: 0x61100049
(gdb) si
0x0040106d in foo ()
1: x/i $pc
0x40106d <_Z3food+37>: fldcw -0x6(%ebp)
(gdb) x 0x22ccfc
0x22ccfc: 0x00000005
執行結果印證了我們的預期。
模仿取得 O 版 disassembly 的方式來取得 -ffloat-store 的結果:
g++ -Wall -O -ffloat-store -S -masm=intel main.cpp
-ffloat-store 版和 O 版相同,會去呼叫 foo() ,不過比較兩者 foo() 可以發現: -ffloat-store 版只多了兩行,在進行 x 0.3 後,會馬上將值寫回 call stack 上,並再讀入。
也就是,當 6.0 被寫入記憶體時,是以 Round to nearest 的方式寫進:
(gdb) si
0x00401058 in foo ()
1: x/i $pc
0x401058 <_Z3food+16>: fstpl -0x10(%ebp)
(gdb) info float
=>R7: Valid 0x4001bffffffffffffe00 +6
R6: Empty 0xdbc000010101e4ada9d8
...
Control Word: 0xffff037f IM DM ZM OM UM PM
PC: Extended Precision (64-bits)
RC: Round to nearest
而讀回 FPU stack 時,已經是個精確的 6.0 :
(gdb) si
0x0040105e in foo ()
1: x/i $pc
0x40105e <_Z3food+22>: fnstcw -0x12(%ebp)
(gdb) info float
=>R7: Valid 0x4001c000000000000000 +6
R6: Empty 0x10000000000400010000
...
Control Word: 0xffff037f IM DM ZM OM UM PM
PC: Extended Precision (64-bits)
RC: Round to nearest
一個 C/C++ 程式可以透過 main() 的 argument list 取得 client 端輸入的 command line arguments:
int main( int argc, char* argv[] ) { ... }
如果好奇這是如何地從無到有,可以寫一段程式碼來 trace 。
先準備好 sample code:
#include <iostream>
using namespace std;
int main( int argc, char* argv[] )
{ // 程式的內容不是重點
cout << "hello world";
cin.get();
return 0;
}
透過 VC++ 或是 WinDbg 在 main() 設定 breakpoint 來追蹤:
0:000> bp CrtDemo05!main *** WARNING: Unable to verify checksum for CrtDemo05.exe 0:000> bl 0 e 004377d0 0001 (0001) 0:**** CrtDemo05!main 0:000> g Breakpoint 0 hit eax=00383088 ebx=7ffda000 ecx=00383028 edx=00000001 esi=010df742 edi=010df6f2 eip=004377d0 esp=0012ff58 ebp=0012ffb8 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 CrtDemo05!main: 004377d0 55 push ebp 0:000> k ChildEBP RetAddr 0012ff54 0044a203 CrtDemo05!main [f:\src\_experiment\crtdemo05\crtdemo05\main.cpp @ 6] 0012ffb8 00449fbd CrtDemo05!__tmainCRTStartup+0x233 [f:\dd\vctools\crt_bld\self_x86\crt\src\crt0.c @ 327] 0012ffc0 7c816d4f CrtDemo05!mainCRTStartup+0xd [f:\dd\vctools\crt_bld\self_x86\crt\src\crt0.c @ 196] 0012fff0 00000000 kernel32!BaseProcessStart+0x23
進入 main() 之前的兩個函式都是 CRT 的一部分,負責完成基本但必要的初始化,舉凡 Global variables、Heap、I/O 等等都屬於這個範疇。這樣一來,身處 main() 後頭的我們才能順利工作。所以照這情勢看來,想知道 argument list 怎麼來,就得去 trace 這兩個 functions ,幸運的, VC++ CRT 的 source code 是隨著安裝程式散發的,通常就在:
C:\Program Files\Microsoft Visual Studio 8\VC\crt
視安裝路徑而定。也可以 double click VC++ 的 Call Stack 進入程式碼裡頭。
如果想利用 debugger 追蹤或試驗 CRT ,不妨把 CRT link 改成 debug mode 並且是 static link 。這可以省下一些時間、增加 tracing 時的可讀性和便利性,因為像是 debug 下的 dynamic link 會使用 ILT ,它所引入的間接性,會讓定位 functions 或 symbols 徒增額外的時間,造成實驗的不便。
VC++ 有所謂的 Microsoft C++ ,其程式進入點有自己的一套方式來定義,至少在名稱上,就可以找到四種:
不過它們使用的 CRT 是同一份程式碼,並且使用了跟 TCHAR.h 相同的手法來區別,但確實由 WPRFLAG 這個 macro 所控制凡;凡是見到 t、_t、__t 開頭的名稱,都有機會透過它來替換掉,不過 __tmainCRTStartup() 可說是例外。下表節錄與 command line 相關的 t、_t、_t 開頭 symbols:
|
ansi/console |
ansi/GUI |
wide/console |
wide/GUI |
|
|
_tmainCRTStartup |
mainCRTStartup |
WinMainCRTStartup |
wmainCRTStartup |
wWinMainCRTStartup |
|
_tcmdln |
_acmdln |
_acmdln |
_wcmdln |
_wcmdln |
|
_targv |
__argv |
__argv |
__wargv |
__wargv |
|
GetCommandLineT() |
GetCommandLineA |
GetCommandLineA |
__crtGetCommandLineW |
__crtGetCommandLineW |
|
_tsetargv() |
_setargv |
_setargv |
_wsetargv |
_wsetargv |
我們先從 ansi 版的 main() ,也是 C/C++ Standard 所描述的 main() 開始:
mainCRTStartup() 基本上是一個 forward function :
int _tmainCRTStartup( void )
{
__security_init_cookie();
return __tmainCRTStartup();
}
就像前面提到的:__tmainCRTStartup() 就是正港的 __tmainCRTStartup() 沒有 ansi, wide 的替換,很快地就可以梳理出跟 command line 相關的程式:
int __tmainCRTStartup( void )
{
// ...
__try {
// ...
/* get wide cmd line info */
_tcmdln = (_TSCHAR *)GetCommandLineT();
/* get wide environ info */
_tenvptr = (_TSCHAR *)GetEnvironmentStringsT();
if ( _tsetargv() < 0 )
_amsg_exit(_RT_SPACEARG);
if ( _tsetenvp() < 0 )
_amsg_exit(_RT_SPACEENV);
// ...
#ifdef _WINMAIN_
lpszCommandLine = _twincmdln();
mainret = _tWinMain( (HINSTANCE)&__ImageBase,
NULL,
lpszCommandLine,
StartupInfo.dwFlags & STARTF_USESHOWWINDOW
? StartupInfo.wShowWindow
: SW_SHOWDEFAULT
);
#else /* _WINMAIN_ */
_tinitenv = _tenviron;
mainret = _tmain(__argc, _targv, _tenviron);
#endif /* _WINMAIN_ */
// ...
}
__except ( _XcptFilter(GetExceptionCode(), GetExceptionInformation()) ) {
// ...
}
return mainret;
}
從上面的程式碼可以發現,重點就在於兩個 functions 上:
但是當我們將 breakpoint 設在 GetCommandLineT() 時,卻無法 step in 。這時可以切換到 assembly 模式,透過 WinDbg diassembly window 或是 WinDbg 的 uf 幫助來持續追蹤:
0:000> uf __tmainCRTStartup ... CrtDemo05!__tmainCRTStartup+0x1b4 [f:\dd\vctools\crt_bld\self_x86\crt\src\crt0.c @ 300]: 300 0044a184 ff1564924b00 call dword ptr [CrtDemo05!_imp__GetCommandLineA (004b9264)] 300 0044a18a a3a4814b00 mov dword ptr [CrtDemo05!_acmdln (004b81a4)],eax 303 0044a18f e8a89efeff call CrtDemo05!ILT+55(___crtGetEnvironmentStringsA) (0043403c) 303 0044a194 a3d45e4b00 mov dword ptr [CrtDemo05!_aenvptr (004b5ed4)],eax 305 0044a199 e87a9ffeff call CrtDemo05!ILT+275(__setargv) (00434118) 305 0044a19e 85c0 test eax,eax 305 0044a1a0 7d0a jge CrtDemo05!__tmainCRTStartup+0x1dc (0044a1ac) ...
在 Windows 裡頭, _imp、__imp 開頭的 name mangling 代表: symbol 代表是從 DLL export 出的 ,可見 CrtDemo05!_imp__GetCommandLineA 存在與另外一個 DLL 之中。 dereference 該位址便可以找到真正的 symbol 。
0:000> dd 004b9264 004b9264 7c812c8d 7c93043d 7c812851 7c9305d4 004b9274 7c80aa49 7c80a0c7 7c809cad 7c832e2b
由於 CrtDemo05 裡頭使用 call 來呼叫這個位址,想必這個位址會是一段可以執行的指令,使用 uf 來查找:
0:000> uf 7c812c8d kernel32!GetCommandLineA: 7c812c8d a1f435887c mov eax,dword ptr [kernel32!BaseAnsiCommandLine+0x4 (7c8835f4)] 7c812c92 c3 ret
原來是落在 kernel32.dll 這個基礎函式庫裡頭,而且發現它的實做相當簡單:僅僅是從 kernel32!BaseAnsiCommandLine+0x4 位址讀取而已。為了驗證,我們以 dereference 該位址看是否真的儲存了要傳遞給 CrtDemo05 的 arguments 。
0:000> dd 7c8835f4 7c8835f4 00151ee0 000a0008 7c80e300 ffff02ff 7c883604 00000001 000a0008 7c831874 000a0008
接著
0:000> da 00151ee0 00151ee0 "C:\test\CrtDemo05.exe test"
若是覺得 dd 兩層的 dereference 有點麻煩,針對字串可以使用 dp* ,
kd> dpa 7c8835f4 L1 7c8835f4 001522f8 "CrtDemo05.exe"
無論是哪種方式,都驗證了我們的想法── kernel32!GetCommandLineA() 是取得 command line 的函式,並且有著極簡單的實做──讀取一個預先填好值的 variable。
wmain() 版本也是相同的:
0:000> uf CrtDemo05!__tmainCRTStartup ... CrtDemo05!__tmainCRTStartup+0x1b4 [f:\dd\vctools\crt_bld\self_x86\crt\src\crt0.c @ 300]: 300 0044a194 e8ad9efeff call CrtDemo05!ILT+65(___crtGetCommandLineW) (00434046) 300 0044a199 a3e4824b00 mov dword ptr [CrtDemo05!_wcmdln (004b82e4)],eax 303 0044a19e e8439ffeff call CrtDemo05!ILT+225(___crtGetEnvironmentStringsW) (004340e6) 303 0044a1a3 a3d85e4b00 mov dword ptr [CrtDemo05!_wenvptr (004b5ed8)],eax 305 0044a1a8 e845b1feff call CrtDemo05!ILT+4845(__wsetargv) (004352f2) 305 0044a1ad 85c0 test eax,eax 305 0044a1af 7d0a jge CrtDemo05!__tmainCRTStartup+0x1db (0044a1bb) ...
函式,你可能會好奇為什麼 wide 版本並不是呼叫 _imp__GetCommandLineW() ,其實它被包裝在 ___crtGetCommandLineW() 裡頭。
0:000> uf 00434046 ... CrtDemo05!__crtGetCommandLineW+0xf [f:\dd\vctools\crt_bld\self_x86\crt\src\aw_com.c @ 52]: 52 0046336f ff1530934b00 call dword ptr [CrtDemo05!_imp__GetCommandLineW (004b9330)] 52 00463375 85c0 test eax,eax 52 00463377 740c je CrtDemo05!__crtGetCommandLineW+0x25 (00463385) ...
並且同樣地,可以使用如同 ansi 版本的方式,去 trace :
0:000> dd 004b9330 004b9330 7c816cfb 7c80c6cf 7c801eee 7c873d83 004b9340 7c81abe4 7c80180e 7c810da6 7c80cd58 0:000> uf 7c816cfb kernel32!GetCommandLineW: 7c816cfb a10430887c mov eax,dword ptr [kernel32!BaseUnicodeCommandLine+0x4 (7c883004)] 7c816d00 c3 ret 0:000> dd 7c883004 7c883004 0002062c 00000000 7c809784 7c80979d 7c883014 7c8097d2 7c8097e7 7c8097b7 00000000 0:000> du 0002062c 0002062c "C:\test\CrtDemo05.exe test"
或許你會好奇為什麼 trace command 如何填入一個 process 需要動要到 kernel debug ?其實原因很簡單,因為當 user-mode debug 無法滿足時,以這次的參數傳遞來看,當我們利用 windbg 載入一個 program 時,program 需要執行到某個狀態後, user-mode debugger 才能介入,此時 arguments 已經設置完成。
Microsoft (R) Windows Debugger Version 6.11.0001.404 X86 Copyright (c) Microsoft Corporation. All rights reserved. CommandLine: C:\test\CrtDemo05.exe Symbol search path is: srv*C:\sym_cache*http://msdl.microsoft.com/download/symbols;C:\test Executable search path is: ModLoad: 00400000 004bb000 CrtDemo05.exe ModLoad: 7c920000 7c9b5000 ntdll.dll ModLoad: 7c800000 7c91d000 C:\WINDOWS\system32\kernel32.dll (7d8.568): Break instruction exception - code 80000003 (first chance) eax=00251eb4 ebx=7ffd8000 ecx=00000001 edx=00000002 esi=00251f48 edi=00251eb4 eip=7c921230 esp=0012fb20 ebp=0012fc94 iopl=0 nv up ei pl nz na po nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202 ntdll!DbgBreakPoint: 7c921230 cc int 3 0:000> kvn # ChildEBP RetAddr Args to Child 00 0012fb1c 7c95edc0 7ffdf000 7ffd8000 00000000 ntdll!DbgBreakPoint (FPO: [0,0,0]) 01 0012fc94 7c941639 0012fd30 7c920000 0012fce0 ntdll!LdrpInitializeProcess+0xffa (FPO: [5,89,4]) 02 0012fd1c 7c92eac7 0012fd30 7c920000 00000000 ntdll!_LdrpInitialize+0x183 (FPO: [Non-Fpo]) 03 00000000 00000000 00000000 00000000 00000000 ntdll!KiUserApcDispatcher+0x7 0:000> da 00151ee0 00151ee0 "C:\test\CrtDemo05.exe test"
注意到沒?當 CrtDemo05 整支程式還在被 OS 的 loader 所初始化時,在 __tmainCRTStartup 還未執行到之前,arguments 已經填入了。因此這個例子需要透過 kernel debugging 來進行更早期的追蹤。 kernel debugging 的方式有許多種,使用 VMWare 算是便利的方法之一,可以參考 Windows Debugging – Kernel Debugging with WinDbg and VMWare 設定環境。
為了捕捉 kernel32!BaseUnicodeCommanLine 以及 kernel32!BaseAnsiCommandLine 是如何被填寫的?我們可以透過 WinDbg 的 break on access ,不過很快就會遇到第一個問題:
kd> x kernel32!BaseUnicodeCommandLine 7c885730 kernel32!BaseUnicodeCommandLine = <no type information> 7c883000 kernel32!BaseUnicodeCommandLine = <no type information>
遇到兩個同名的 symbols ,理論上這是件會造成 symbol resolve 上 ambiguous 的事。雖然暫時無解,但幸運的是,根據之前追蹤結果:
7c816cfb a10430887c mov eax,dword ptr [kernel32!BaseUnicodeCommandLine+0x4 (7c883004)]
我們可以直接選擇 7c885730 :
kd> ba r4 7c883004 kd> g Breakpoint 0 hit nt!MiCopyOnWrite+0x148: 0008:805222bc f3a5 rep movs dword ptr es:[edi],dword ptr [esi] kd> k ChildEBP RetAddr b22bad00 8051d019 nt!MiCopyOnWrite+0x148 b22bad4c 805406ec nt!MmAccessFault+0x9f9 b22bad4c 7c93a100 nt!KiTrap0E+0xcc 0012f938 7c93d8a0 ntdll!__security_init_cookie_ex+0x31 0012f944 7c93d83a ntdll!LdrpInitSecurityCookie+0x2f 0012fa40 7c939b78 ntdll!LdrpRunInitializeRoutines+0x124 0012faf0 7c939ba0 ntdll!LdrpGetProcedureAddress+0x1c6 0012fb0c 7c942334 ntdll!LdrGetProcedureAddress+0x18 0012fc94 7c941639 ntdll!LdrpInitializeProcess+0xd7a 0012fd1c 7c92eac7 ntdll!_LdrpInitialize+0x183 00000000 00000000 ntdll!KiUserApcDispatcher+0x7
這一次還不是我們想要的 stack:
kd> g Breakpoint 0 hit kernel32!_BaseDllInitialize+0x20f: 7c817ea8 ff157c10807c call dword ptr [kernel32!_imp__RtlUnicodeStringToAnsiString (7c80107c)] kd> bl 0 e 7c883004 r 4 0001 (0001) kernel32!BaseUnicodeCommandLine+0x4 kd> k ChildEBP RetAddr 0012f918 7c9211a7 kernel32!_BaseDllInitialize+0x20f 0012f938 7c93cbab ntdll!LdrpCallInitRoutine+0x14 0012fa40 7c939b78 ntdll!LdrpRunInitializeRoutines+0x344 0012faf0 7c939ba0 ntdll!LdrpGetProcedureAddress+0x1c6 0012fb0c 7c942334 ntdll!LdrGetProcedureAddress+0x18 0012fc94 7c941639 ntdll!LdrpInitializeProcess+0xd7a 0012fd1c 7c92eac7 ntdll!_LdrpInitialize+0x183 00000000 00000000 ntdll!KiUserApcDispatcher+0x7
這次 stack 看起來成功率比較高。 disassembly 一段 breakpoint 前的程式碼,使用 WinDbg 的 disassembly 視窗或是指令都可以做到:
kd> ub 7c817ea8 L10 kernel32!_BaseDllInitialize+0x1cc: ... 7c817e7f 64a118000000 mov eax,dword ptr fs:[00000018h] 7c817e85 8b4030 mov eax,dword ptr [eax+30h] 7c817e88 8b4010 mov eax,dword ptr [eax+10h] 7c817e8b 8b4840 mov ecx,dword ptr [eax+40h] 7c817e8e 6a01 push 1 7c817e90 890d0030887c mov dword ptr [kernel32!BaseUnicodeCommandLine (7c883000)],ecx 7c817e96 8b4044 mov eax,dword ptr [eax+44h] 7c817e99 680030887c push offset kernel32!BaseUnicodeCommandLine (7c883000) 7c817e9e 68f035887c push offset kernel32!BaseAnsiCommandLine (7c8835f0) 7c817ea3 a30430887c mov dword ptr [kernel32!BaseUnicodeCommandLine+0x4 (7c883004)],eax
可以發現,kernel32!BaseUnicodeCommandLine 是由 ecx 決定,而 ecx 則是由 fs:[00000018h] 所決定,這部份的解讀,需要一些的隱藏知識:在 x86 上Windows user mode 將每個 thread 的 TEB (Thread Environment Block),存放在 fs:[0];而在 kernel mode 中, fs:[0] 則是存放 KPCR (Process Control Region) 。
kd> dt _TEB ntdll!_TEB +0x000 NtTib : _NT_TIB +0x01c EnvironmentPointer : Ptr32 Void +0x020 ClientId : _CLIENT_ID +0x028 ActiveRpcHandle : Ptr32 Void +0x02c ThreadLocalStoragePointer : Ptr32 Void +0x030 ProcessEnvironmentBlock : Ptr32 _PEB ...
整個 ntdll!_TEB 有點龐大,不過根據 ub 的結果,fs:[00000018h] 落在第一個欄位 ── _NT_TIB (Thread Information Block)裡頭,再進行一次 dt :
kd> dt _NT_TIB ntdll!_NT_TIB +0x000 ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD +0x004 StackBase : Ptr32 Void +0x008 StackLimit : Ptr32 Void +0x00c SubSystemTib : Ptr32 Void +0x010 FiberData : Ptr32 Void +0x010 Version : Uint4B +0x014 ArbitraryUserPointer : Ptr32 Void +0x018 Self : Ptr32 _NT_TIB
那為何不直接使用 fs:[0] 而要選擇 fs:[00000018h] ?因為 fs:[0] 並非 process 的 virtual address :
kd> dd fs:0 003b:00000000 0012fa30 00130000 0012f000 00000000 003b:00000010 00001e00 00000000 7ffdd000 00000000 003b:00000020 00000778 00000128 00000000 00000000
因此使用上,往往透過 fs:[00000018h] 來指向正確 TEB 或說 TIB 的位址。
7c817e7f 64a118000000 mov eax,dword ptr fs:[00000018h] 7c817e85 8b4030 mov eax,dword ptr [eax+30h] 7c817e88 8b4010 mov eax,dword ptr [eax+10h] 7c817e8b 8b4840 mov ecx,dword ptr [eax+40h]
上述組語在知道 fs:[0] 的意義後,便可尋著指令進行 dt 的解碼,並寫出對應的虛擬碼:
_TEB teb = fs:[00000018h];
UNICODE_STRING kernel32!BaseUnicodeCommandLine =
teb.ProcessEnvironmentBlock->ProcessParameters.CommandLine;
到了這個步驟,我們已經知道即使是 kernel32!_BaseDllInitialize() 這般低階的初始化,也僅僅只是將 PEB 中的某個變數值讀出,那麼 PEB 中的值又是給負責填寫呢?還記得 Win32 API 中的 CreateProcess() 嗎?它或許是有嫌疑的一份子。
kd> dt _RTL_USER_PROCESS_PARAMETERS ntdll!_RTL_USER_PROCESS_PARAMETERS +0x000 MaximumLength : Uint4B +0x004 Length : Uint4B +0x008 Flags : Uint4B +0x00c DebugFlags : Uint4B +0x010 ConsoleHandle : Ptr32 Void +0x014 ConsoleFlags : Uint4B +0x018 StandardInput : Ptr32 Void +0x01c StandardOutput : Ptr32 Void +0x020 StandardError : Ptr32 Void +0x024 CurrentDirectory : _CURDIR +0x030 DllPath : _UNICODE_STRING +0x038 ImagePathName : _UNICODE_STRING +0x040 CommandLine : _UNICODE_STRING +0x048 Environment : Ptr32 Void +0x04c StartingX : Uint4B +0x050 StartingY : Uint4B +0x054 CountX : Uint4B +0x058 CountY : Uint4B +0x05c CountCharsX : Uint4B +0x060 CountCharsY : Uint4B +0x064 FillAttribute : Uint4B +0x068 WindowFlags : Uint4B +0x06c ShowWindowFlags : Uint4B +0x070 WindowTitle : _UNICODE_STRING +0x078 DesktopInfo : _UNICODE_STRING +0x080 ShellInfo : _UNICODE_STRING +0x088 RuntimeData : _UNICODE_STRING +0x090 CurrentDirectores : [32] _RTL_DRIVE_LETTER_CURDIR
目前我們已經知道,一個程式的 command line 儲存在 _RTL_USER_PROCESS_PARAMETERS 這個 strcut 裡頭,所以要做的是,找到何時會去填寫,方法有兩種:
我們使用方法 2. 來加速:
可以使用 WinDbg 的 x 指令:
kd> x *!*processparameter* 7c819f9e kernel32!BasePushProcessParameters = <no type information> 7c801488 kernel32!_imp__RtlCreateProcessParameters = <no type information> 7c801484 kernel32!_imp__RtlDestroyProcessParameters = <no type information> 7c9432ec ntdll!RtlDestroyProcessParameters = <no type information> 7c950ea3 ntdll!RtlCheckProcessParameters = <no type information> 7c9433c1 ntdll!RtlCreateProcessParameters = <no type information>
這幾個 functions 看起來都有機會,其中以 BasePushProcessParameters() 的名稱看來最有可能是 creater 建立 createe process parameter 的函式,為了驗證這想法,將 WinDbg 目前的 context 切換到 cmd.exe 下,並設定 breakpoint 來驗證:
kd> !process 0 0
**** NT ACTIVE PROCESS DUMP ****
...
PROCESS 8200d7e8 SessionId: 0 Cid: 0194 Peb: 7ffdf000 ParentCid: 0608
DirBase: 08940240 ObjectTable: e1533828 HandleCount: 33.
Image: cmd.exe
kd> .process /r /P /p 8200d7e8
kd> bp 7c819f9e
kd> g
Breakpoint 1 hit
kernel32!BasePushProcessParameters:
7c819f9e 68c0020000 push 2C0h
kd> k
ChildEBP RetAddr
0013f028 7c8199bc kernel32!BasePushProcessParameters
0013fa88 7c80235e kernel32!CreateProcessInternalW+0x184e
0013fac0 4ad031dd kernel32!CreateProcessW+0x2c
0013fc20 4ad02db0 cmd!ExecPgm+0x22b
0013fc54 4ad02e0e cmd!ECWork+0x84
0013fc6c 4ad05f9f cmd!ExtCom+0x40
0013fe9c 4ad013eb cmd!FindFixAndRun+0xcf
0013fee0 4ad0bbba cmd!Dispatch+0x137
0013ff44 4ad05164 cmd!main+0x216
0013ffc0 7c816d4f cmd!mainCRTStartup+0x125
0013fff0 00000000 kernel32!BaseProcessStart+0x23
stack 目前看來是支持我們的猜測, cmd.exe 透過 CreateProcess() 來建立 CrtDemo05.exe ,並且呼叫 BasePushProcessParameters() 來初始化 process parameter。在這邊,我們也可以切換到 CrtDemo05.exe 下,確定一下它目前的建立狀態,以瞭解它仍是否仍在建立中而非建立完成:
kd> !process 0 0
**** NT ACTIVE PROCESS DUMP ****
...
PROCESS 8200d7e8 SessionId: 0 Cid: 0194 Peb: 7ffdf000 ParentCid: 0608
DirBase: 08940240 ObjectTable: e1533828 HandleCount: 35.
Image: cmd.exe
PROCESS 82476d00 SessionId: 0 Cid: 00c4 Peb: 7ffd4000 ParentCid: 0194
DirBase: 089402c0 ObjectTable: e1d3bf68 HandleCount: 36.
Image: conime.exe
PROCESS 8244e980 SessionId: 0 Cid: 05b4 Peb: 7ffdc000 ParentCid: 0400
DirBase: 08940300 ObjectTable: e16e0700 HandleCount: 123.
Image: wuauclt.exe
PROCESS 8244a460 SessionId: 0 Cid: 0224 Peb: 7ffd5000 ParentCid: 0194
DirBase: 08940380 ObjectTable: e17871a0 HandleCount: 1.
Image: CrtDemo0
看到 Image name 被截斷,有點訝異,不過可能是因為 kernel 在填寫時被我們中斷了。現在來看看 PEB 裡頭的 command line 是否已經準備好了:
kd> !peb 7ffd5000 PEB at 7ffd5000 error 1 InitTypeRead( nt!_PEB at 7ffd5000)... kd> .process /r /P /p 8244a460 Implicit process is now 8244a460 .cache forcedecodeuser done Loading User Symbols PEB is paged out (Peb.Ldr = 7ffd500c). Type ".hh dbgerr001" for details kd> dt _PEB 7ffd5000 nt!_PEB +0x000 InheritedAddressSpace : 0 '' +0x001 ReadImageFileExecOptions : 0 '' +0x002 BeingDebugged : 0 '' +0x003 SpareBool : 0 '' +0x004 Mutant : 0xffffffff Void +0x008 ImageBaseAddress : 0x00400000 Void +0x00c Ldr : (null) +0x010 ProcessParameters : (null) +0x014 SubSystemData : (null) +0x018 ProcessHeap : (null) +0x01c FastPebLock : (null) ...
試了兩種方法,都無法正確讀取 PEB ,這正和我們的預期:表示 CrtDemo05.exe 正在被建立中,其 PEB 似乎也在混沌之中。緊接著回到 cmd.exe 的 stack 上,接下來的事就需要點耐心,由於目前只知道 BasePushProcessParameters() 跟 _RTL_USER_PROCESS_PARAMETERS 有關,但是找不到可以設立 breakpoint 的地方,根據 assembly 一步步追蹤是個方法,不過直接閱讀 assembly 來找尋跟 ProcessParameters 相關的指令或許會更快,一個很快速的想法是使用關鍵字來縮小範圍,這裡若是使用 uf 來 diassembly ,可能會遇上一些麻煩,因為 CreateProcessInternalW() 被 OMAP 最佳化過,無法依照 mapping 到記憶體上的 layout 顯示:
kd> uf CreateProcessInternalW Flow analysis was incomplete, some code may be missing kernel32!CreateProcessInternalW+0x308: ... ...
當遇到這種狀況,我們得放棄 diassembly 整個 function ,而是直接指明記憶體位址:
kd> x kernel32!CreateProcessInternalW 7c8191eb kernel32!CreateProcessInternalW =kd> u 7c8191eb L600 kernel32!CreateProcessInternalW: ... ; ProcessParameter ; ebp-234h: _RTL_USER_PROCESS_PARAMETERS* 7c81a11d 8d85ccfdffff lea eax,[ebp-234h] 7c81a123 50 push eax ; pProcessParameters 7c81a124 ff158814807c call dword ptr [kernel32!_imp__RtlCreateProcessParameters (7c801488)] 7c81a12a 33d2 xor edx,edx ; edx = 0 7c81a12c 3bc2 cmp eax,edx ... ... 7c81a538 ff158414807c call dword ptr [kernel32!_imp__RtlDestroyProcessParameters (7c801484)]
幸運的找到:
7c9432ec ntdll!RtlDestroyProcessParameters
7c9433c1 ntdll!RtlCreateProcessParameters
利用兩個 functions ,我們可以縮小可疑範圍,7c81a11d ~ 7c81a538 給了我們一個暗示:ebp-234h 是個 local variable ,並且傳遞至 RtlCreateProcessParameters() ,當結束後進行是否為 NULL 的比較,那麼 ebp-234h 基本上就很有機會就是一個 pointer point to _RTL_USER_PROCESS_PARAMETERS。另外在向上回溯,可以找找看是否還有其他參數被傳遞至 RtlCreateProcessParameters() :
7c81a0d7 ffd6 call esi 7c81a0d9 8d85b4fdffff lea eax,[ebp-24Ch] 7c81a0df 50 push eax ; parameter10 7c81a0e0 8d859cfdffff lea eax,[ebp-264h] 7c81a0e6 50 push eax ; parameter9 7c81a0e7 8d85a4fdffff lea eax,[ebp-25Ch] 7c81a0ed 50 push eax ; parameter8 7c81a0ee 8d8594fdffff lea eax,[ebp-26Ch] 7c81a0f4 50 push eaxe ; parameter7 7c81a0f5 ffb570fdffff push dword ptr [ebp-290h] ; parameter6 7c81a0fb 8d8584fdffff lea eax,[ebp-27Ch] 7c81a101 50 push eax ; parameter5 7c81a102 f7db neg ebx 7c81a104 1bdb sbb ebx,ebx 7c81a106 8d8578fdffff lea eax,[ebp-288h] 7c81a10c 23d8 and ebx,eax 7c81a10e 53 push ebx ; parameter4 7c81a10f 8d85acfdffff lea eax,[ebp-254h] 7c81a115 50 push eax ; parameter3 7c81a116 8d858cfdffff lea eax,[ebp-274h] 7c81a11c 50 push eax ; parameter2 7c81a11d 8d85ccfdffff lea eax,[ebp-234h] 7c81a123 50 push eax ; parameter1 7c81a124 ff158814807c call dword ptr [kernel32!_imp__RtlCreateProcessParameters (7c801488)]
看到了 10 個參數被傳遞至 RtlCreateProcessParameters() ,我們可以選擇 diassembly 它看是否有與 command line 相關的操作,或是繼續搜尋其他 7c81a11d ~ 7c81a538 內 ebp-234h 的操作,這裡有一個或許可行的快速篩選法,因為 CommandLine 位於 _RTL_USER_PROCESS_PARAMETERS offset 40 bytes 的地方:
kd> dt _RTL_USER_PROCESS_PARAMETERS ntdll!_RTL_USER_PROCESS_PARAMETERS ... +0x040 CommandLine : _UNICODE_STRING ...
我們便找尋 40h 的關鍵字,而搜尋結果的確有幾筆與 40h 有關的操作,但幸運地,都不是和 CommandLine 有關的。於是乎,把焦點放回 RtlCreateProcessParameters():
kd> x ntdll!RtlCreateProcessParameters 7c9433c1 ntdll!RtlCreateProcessParameters kd> u 7c9433c1 L200 ntdll!RtlCreateProcessParameters: ... 7c9435e1 6a04 push 4 ; Protect 7c9435e3 6800100000 push 1000h ; AllocationType 7c9435e8 8d45cc lea eax,[ebp-34h] 7c9435eb 50 push eax ; RegionSize 7c9435ec 53 push ebx ; ZeroBits 7c9435ed 8d45e4 lea eax,[ebp-1Ch] 7c9435f0 50 push eax ; BaseAddress 7c9435f1 6aff push 0FFFFFFFFh ; ProcessHandle 7c9435f3 e8e69efeff call ntdll!ZwAllocateVirtualMemory (7c92d4de) ...
由於 ZwAllocateVirtualMemory() 是公開的 API ,所以很快地可以對參數進行 mapping ,馬上就發現 local vaiable ebp-1ch 存放的就是 allocate 的結果,並且在稍後就有一個 +40h 的操作:
kd> u 7c9433c1 L200 ntdll!RtlCreateProcessParameters: ... 7c94369a 40 inc eax 7c94369b 40 inc eax 7c94369c 50 push eax 7c94369d 8b45e4 mov eax,dword ptr [ebp-1Ch] ; pBase 7c9436a0 83c040 add eax,40h ; pBase + 40 = CommandLine 7c9436a3 57 push edi 7c9436a4 50 push eax 7c9436a5 8d45d8 lea eax,[ebp-28h] 7c9436a8 50 push eax 7c9436a9 e8ab000000 call ntdll!RtlpCopyProcString (7c943759) ...
這和 CommandLine 的操作看似吻合,但我們仍需要找到更直接的證據,首先可以使用動態的證據,也就是設定 breakpoint 去證明,我們將 breakpoint 先設定在 add eax,40h:
kd> bp 7c9436a0 kd> g Breakpoint 2 hit ntdll!RtlCreateProcessParameters+0x2f5: 7c9436a0 83c040 add eax,40h kd> dd ebp-1c 0013ed00 00380000 0013ecd4 0013edf8 0013f018 0013ed10 7c92ee18 7c943748 00000000 0013f028 kd> dd 00380040 00380040 00000000 00000000 00010000 00000000 00380050 00000000 00000000 00000000 00000000
接著,執行數個指令後,等到 RtlpCopyProcString() 執行完成後:
kd> dd 00380040 00380040 00260024 003805c0 00010000 00000000 00380050 00000000 00000000 00000000 00000000 kd> du 003805c0 003805c0 "CrtDemo05.exe test"
再者,我們可以來個靜態分析:
kd> u 7c9433c1 L200 ntdll!RtlCreateProcessParameters: ... 7c94370b 8b45e4 mov eax,dword ptr [ebp-1Ch] 7c94370e 6689988a000000 mov word ptr [eax+8Ah],bx 7c943715 ff75e4 push dword ptr [ebp-1Ch] 7c943718 e8f6fbffff call ntdll!RtlDeNormalizeProcessParams (7c943313) 7c94371d 8b4d08 mov ecx,dword ptr [ebp+8] 7c943720 8901 mov dword ptr [ecx],eax ...
很快地就有一個可疑的證物: ebp+8 也就是 RtlCreateProcessParameters() 的第一個參數,它被 eax 給賦值。雖然 eax 在第一行被指定為 ebp-1Ch ,看似符合我們的假設,但因為中間有個 RtlDeNormalizeProcessParams() 的呼叫,不能掉以輕心,必須進去瞧瞧:
kd> uf RtlDeNormalizeProcessParams Flow analysis was incomplete, some code may be missing ntdll!RtlDeNormalizeProcessParams: 7c943313 8bff mov edi,edi 7c943315 55 push ebp 7c943316 8bec mov ebp,esp 7c943318 8b4508 mov eax,dword ptr [ebp+8] 7c94331b 85c0 test eax,eax 7c94331d 7478 je ntdll!RtlDeNormalizeProcessParams+0x84 (7c943397) ...
幸運的, RtlDeNormalizeProcessParams() 沒有可觀的實作,也僅在 function 開頭將 eax 給予 ebp+8 所代表的位址;而 ebp+8 也正就是 ebp-1Ch,其餘的對應操作都是 read 。確定了 RtlDeNormalizeProcessParams() 不會更改 ebp-1Ch 後,就可以放心回到 RtlCreateProcessParameters():
kd> u 7c9433c1 L200 ntdll!RtlCreateProcessParameters: ... 7c94345b 8b7d18 mov edi,dword ptr [ebp+18h] ... 7c943685 e8cf000000 call ntdll!RtlpCopyProcString (7c943759) 7c94368a 668b07 mov ax,word ptr [edi] 7c94368d 663b4702 cmp ax,word ptr [edi+2] 7c943691 0f84f79a0200 je ntdll!RtlCreateProcessParameters+0x2e9 (7c96d18e) 7c943697 0fb7c0 movzx eax,ax 7c94369a 40 inc eax 7c94369b 40 inc eax 7c94369c 50 push eax 7c94369d 8b45e4 mov eax,dword ptr [ebp-1Ch] ; pBase 7c9436a0 83c040 add eax,40h ; pBase + 40 = CommandLine 7c9436a3 57 push edi 7c9436a4 50 push eax 7c9436a5 8d45d8 lea eax,[ebp-28h] 7c9436a8 50 push eax 7c9436a9 e8ab000000 call ntdll!RtlpCopyProcString (7c943759)
可以發現,可疑的參數有:
為了找出實際的賦值給 ebp-1Ch+40h 的對象,這邊可以使用 breakpoint 去檢驗,分別是:
kd> bp 7c94369c kd> g Breakpoint 5 hit ntdll!RtlCreateProcessParameters+0x2f1: 7c94369c 50 push eax kd> r eax=00000026 ebx=00000000 ecx=00380034 edx=00000000 esi=00000208 edi=0013edac eip=7c94369c esp=0013ecd4 ebp=0013ed1c iopl=0 nv up ei pl nz na po cy cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000203 kd> dpu 0013edac+4 L1 0013edb0 0015adc8 "CrtDemo05.exe test" kd> bp 7c9436a8 kd> g Breakpoint 6 hit kd> dd ebp-28h 0013ecf4 003805c0 00000000 00000634 00380000 0013ed04 0013ecd4 0013edf8 0013f018 7c92ee18 kd> dpu 003805c0 003805c0 00000000
透過 breakpoint 的實驗,就可以知道 edi 才是賦值給 ebp-1Ch+40h 的來源,而且還是 RtlCreateProcessParameters() 的第五個參數。所以可以反推 edi 是怎麼來的,並一路反推,便可得到 command line 最初的來源。
kd> uf kernel32!CreateProcessW kernel32!CreateProcessW: 7c802332 8bff mov edi,edi 7c802334 55 push ebp 7c802335 8bec mov ebp,esp 7c802337 6a00 push 0 ; parameter12 7c802339 ff752c push dword ptr [ebp+2Ch] ; lpProcessInformation 7c80233c ff7528 push dword ptr [ebp+28h] ; lpStartupInfo 7c80233f ff7524 push dword ptr [ebp+24h] ; lpCurrentDirectory 7c802342 ff7520 push dword ptr [ebp+20h] ; lpEnvironment 7c802345 ff751c push dword ptr [ebp+1Ch] ; dwCreationFlags 7c802348 ff7518 push dword ptr [ebp+18h] ; bInheritHandles 7c80234b ff7514 push dword ptr [ebp+14h] ; lpThreadAttributes 7c80234e ff7510 push dword ptr [ebp+10h] ; lpProcessAttributes 7c802351 ff750c push dword ptr [ebp+0Ch] ; lpCommandLine 7c802354 ff7508 push dword ptr [ebp+8] ; lpApplicationName 7c802357 6a00 push 0 ; parameter1 7c802359 e88d6e0100 call kernel32!CreateProcessInternalW (7c8191eb) 7c80235e 5d pop ebp 7c80235f c22800 ret 28h kd> x kernel32!CreateProcessInternalW 7c8191eb kernel32!CreateProcessInternalW = <no type information> kd> u 7c8191eb L600 kernel32!CreateProcessInternalW: ... 7c819214 8b4510 mov eax,dword ptr [ebp+10h] ; lpCommandLine 7c819217 8985e0f8ffff mov dword ptr [ebp-720h],eax ... 7c819958 8b8de0f8ffff mov ecx,dword ptr [ebp-720h] ; lpCommandLine ... 7c819964 ffb56cf9ffff push dword ptr [ebp-694h] ; parameter13 7c81996a ffb500f7ffff push dword ptr [ebp-900h] ; parameter12 7c819970 8a85b7f8ffff mov al,byte ptr [ebp-749h] 7c819976 f6d8 neg al 7c819978 1bc0 sbb eax,eax 7c81997a 83e002 and eax,2 7c81997d 50 push eax ; parameter11 7c81997e ff751c push dword ptr [ebp+1Ch] ; parameter10 7c819981 8b85f4f6ffff mov eax,dword ptr [ebp-90Ch] 7c819987 0b4520 or eax,dword ptr [ebp+20h] 7c81998a 50 push eax ; parameter9 7c81998b 8d8560f7ffff lea eax,[ebp-8A0h] 7c819991 50 push eax ; parameter8 7c819992 ffb5b0f8ffff push dword ptr [ebp-750h] ; parameter7 7c819998 51 push ecx ; parameter6 7c819999 ffb550f7ffff push dword ptr [ebp-8B0h] ; parameter5 7c81999f ffb5e4f8ffff push dword ptr [ebp-71Ch] ; parameter4 7c8199a5 ffb5b8f7ffff push dword ptr [ebp-848h] ; parameter3 7c8199ab ffb594f9ffff push dword ptr [ebp-66Ch] ; parameter2 7c8199b1 ffb534f7ffff push dword ptr [ebp-8CCh] ; parameter1 7c8199b7 e8e2050000 call kernel32!BasePushProcessParameters (7c819f9e) kernel32!BasePushProcessParameters: ... 7c819fd3 8b4d1c mov ecx,dword ptr [ebp+1Ch] ; 7c819fd6 898d5cfdffff mov dword ptr [ebp-2A4h],ecx ; srcCmdLine ... 7c81a045 8d85d0fdffff lea eax,[ebp-230h] 7c81a04b 50 push eax 7c81a04c 8d858cfdffff lea eax,[ebp-274h] 7c81a052 50 push eax 7c81a053 ffd6 call esi 7c81a055 ffb55cfdffff push dword ptr [ebp-2A4h] ; srcCmdLine 7c81a05b 8d8584fdffff lea eax,[ebp-27Ch] 7c81a061 50 push eax ; commandLine 7c81a062 ffd6 call esi ; RtlInitUnicodeString ... 7c81a0cf 50 push eax 7c81a0d0 8d8594fdffff lea eax,[ebp-26Ch] 7c81a0d6 50 push eax 7c81a0d7 ffd6 call esi 7c81a0d9 8d85b4fdffff lea eax,[ebp-24Ch] 7c81a0df 50 push eax ; runtimeData 7c81a0e0 8d859cfdffff lea eax,[ebp-264h] 7c81a0e6 50 push eax ; shellInfo 7c81a0e7 8d85a4fdffff lea eax,[ebp-25Ch] 7c81a0ed 50 push eax ; desktopInfo 7c81a0ee 8d8594fdffff lea eax,[ebp-26Ch] 7c81a0f4 50 push eax ; windowTitle 7c81a0f5 ffb570fdffff push dword ptr [ebp-290h] ; environment 7c81a0fb 8d8584fdffff lea eax,[ebp-27Ch] 7c81a101 50 push eax ; commandLine, parameter5 7c81a102 f7db neg ebx 7c81a104 1bdb sbb ebx,ebx 7c81a106 8d8578fdffff lea eax,[ebp-288h] 7c81a10c 23d8 and ebx,eax 7c81a10e 53 push ebx ; currentDir 7c81a10f 8d85acfdffff lea eax,[ebp-254h] 7c81a115 50 push eax ; dllPath 7c81a116 8d858cfdffff lea eax,[ebp-274h] 7c81a11c 50 push eax ; imagePath ; ProcessParameter ; ebp-234h: _RTL_USER_PROCESS_PARAMETERS* 7c81a11d 8d85ccfdffff lea eax,[ebp-234h] 7c81a123 50 push eax ; pProcessParameters 7c81a124 ff158814807c call dword ptr [kernel32!_imp__RtlCreateProcessParameters (7c801488)]
最後,我們還沒來得及關心 creater 是如何的把 createe 的 PEB 建立完成,其實就在 RtlCreateProcessParameters() 稍後,透過 NtWriteVirtualMemory() 完成。
7c81a1eb 8b35f413807c mov esi,dword ptr [kernel32!_imp__NtWriteVirtualMemory (7c8013f4)]
...
7c81a38b 6a04 push 4 ; Protect
7c81a38d bb00100000 mov ebx,1000h
7c81a392 53 push ebx ; AllocationtType
7c81a393 8d85c8fdffff lea eax,[ebp-238h]
7c81a399 50 push eax ; RegionSize
7c81a39a 52 push edx ; ZeroBits
7c81a39b 8d85c4fdffff lea eax,[ebp-23Ch]
7c81a3a1 50 push eax ; BaseAddress
7c81a3a2 ffb580fdffff push dword ptr [ebp-280h] ; hNewProcessHandle
7c81a3a8 8b3d9011807c mov edi,dword ptr [kernel32!_imp__NtAllocateVirtualMemory (7c801190)]
7c81a3ae ffd7 call edi
7c81a3b0 898574fdffff mov dword ptr [ebp-28Ch],eax
7c81a3b6 8b85c8fdffff mov eax,dword ptr [ebp-238h]
7c81a3bc 898558fdffff mov dword ptr [ebp-2A8h],eax
7c81a3c2 83bd74fdffff00 cmp dword ptr [ebp-28Ch],0
7c81a3c9 0f8c04950200 jl kernel32!BasePushProcessParameters+0x504 (7c8438d3)
7c81a3cf 8b8dccfdffff mov ecx,dword ptr [ebp-234h]
7c81a3d5 8901 mov dword ptr [ecx],eax
7c81a3d7 f6452b10 test byte ptr [ebp+2Bh],10h
7c81a3db 0f85fa940200 jne kernel32!BasePushProcessParameters+0x51d (7c8438db)
7c81a3e1 f6452b20 test byte ptr [ebp+2Bh],20h
7c81a3e5 0f85ff940200 jne kernel32!BasePushProcessParameters+0x52d (7c8438ea)
7c81a3eb f6452b40 test byte ptr [ebp+2Bh],40h
7c81a3ef 0f8504950200 jne kernel32!BasePushProcessParameters+0x53d (7c8438f9)
7c81a3f5 6a00 push 0 ; nBytesWritten
7c81a3f7 8b85ccfdffff mov eax,dword ptr [ebp-234h]
7c81a3fd ff7004 push dword ptr [eax+4] ; nBytesToWrite : pProcParameters->Length
7c81a400 50 push eax ; Buffer : pProcParameters
7c81a401 ffb5c4fdffff push dword ptr [ebp-23Ch] ; BaseAddress :
7c81a407 ffb580fdffff push dword ptr [ebp-280h] ; hNewProcessHandle:
; NtWriteVirtualMemory( *(ebp-280h), *(ebp-23Ch), eax, ???, 0 )
7c81a40d ffd6 call esi ; _imp__NtWriteVirtualMemory
網路上可以找到人家 reverse 過的 RtlCreateProcessParameters() prototype 可以加速 trace 的速度:
NTSTATUS RtlCreateProcessParameters(
PRTL_USER_PROCESS_PARAMETERS *ProcessParameters,
PUNICODE_STRING ImagePathName,
PUNICODE_STRING DllPath,
PUNICODE_STRING CurrentDirectory,
PUNICODE_STRING CommandLine,
PWSTR Environment, // Not sured
PUNICODE_STRING WindowTitle,
PUNICODE_STRING DesktopInfo,
PUNICODE_STRING ShellInfo,
PUNICODE_STRING RuntimeData );
不過如果想要硬派的自己來也是可以的,可以試試看這篇文章的作法 : )
一般來說,MiniFilter 的 InstanceSetupCallback 會在 filter manager 把 minifilter attache 到 volume 後呼叫。如果沒有的話,可以檢查一下 minifilter 的 INF 是否把 instance fla...