以前玩游戲遇到一些實在過不去的管卡,經常會找一些游戲修改軟件來修改游戲,讓自己變得無比強大,將boss一路砍瓜切菜過足游戲癮。其實游戲修改軟件的功能大多都比較簡單,我們可以通過windbg的一些簡單命令同樣也可以實現。
在這片文章中我們會通過一個簡單的winform登錄程序來演示將一系列簡單調試命令聯合運用,破解登錄程序。
測試程序
登錄程序界面
登錄驗證代碼
private void btnLogin_Click(object sender, EventArgs e) { if (txtUser.Text == "test" && txtPassword.Text == "abc123") { MessageBox.Show("login successfully"); } else { MessageBox.Show("login failed"); } }
調試步驟
運行程序,將windbg attach到登錄程序。輸入用戶名和密碼(這里我們讓用戶名比較特殊,這樣方便我們在內存中查找,密碼隨便輸入,我們裝作不知道)。
在windbg中通過內存查找命令搜索我們輸入的用戶名,
這里解釋下這個查找命令,s是search memory命令符,-u代表搜索的目標類型為unicode,另外還支持-a ascii,-d dword等。0x00000000 L?0x7fffffff為搜索范圍參數,這里我們的目標空間為2G所有的user mode內存空間。
0:005> s-u 0x00000000 L?0x7fffffff mockuser 005f0178 006d 006f 0063 006b 0075 0073 0065 0072 m.o.c.k.u.s.e.r.
運氣很好,只有一個匹配,那就是這個地址了。接下來我們要找到驗證用戶名和密碼的方法,這個如何找到呢?
靠猜!我猜這個方法調用過程中會訪問我們找到的用戶名地址從而讀取用戶名。我們來試一下,我在這個地址上設置一個訪問斷點,
ba是訪問斷點設置命令(break on access),r4代表讀取前4位的時候觸發斷點。
0:005> ba r4 005f0178 0:005> bl 0 e 005f0178 r 4 0001 (0001) 0:****
點login按鈕,果然斷點被觸發了,接下來我們來看一下調用棧,這里看到與程序自定義代碼相關的方法就是這個btnLogin_Click事件了。如果你調試的程序為非托管的話,那這個部分需要很下翻功夫,因為很可能看不到程序方法名。
Breakpoint 0 hit eax=006f006d ebx=00611050 ecx=00000004 edx=00000000 esi=005f0178 edi=0044ed1c eip=75c7980a esp=0044e90c ebp=0044e914 iopl=0 nv up ei ng nz ac pe cy cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000297 msvcrt!UnwindUpVec+0x3c: 75c7980a 89448ff0 mov dword ptr [edi+ecx*4-10h],eax ds:002b:0044ed1c=00004c00 0:000> kL ChildEBP RetAddr 0044e914 725f9f09 msvcrt!UnwindUpVec+0x3c 0044e934 725f9af7 comctl32_725f0000!Edit_GetTextHandler+0x3a ... 0044edec 63e422a5 System_Windows_Forms_ni!System.Windows.Forms.Control.get_WindowText()+0xac 0044edfc 63e3316b System_Windows_Forms_ni!System.Windows.Forms.TextBoxBase.get_WindowText()+0x5 0044edfc 63e422b5 System_Windows_Forms_ni!System.Windows.Forms.Control.get_Text()+0x1b 0044ee38 00210845 System_Windows_Forms_ni!System.Windows.Forms.TextBox.get_Text()+0x5 0044ee38 63ddd522 MemoryEdit!MemoryEdit.SampleLoginForm.btnLogin_Click(System.Object, System.EventArgs)+0x45 0044ee50 63ddf920 System_Windows_Forms_ni!System.Windows.Forms.Control.OnClick(System.EventArgs)+0x62 0044ee60 643a3f58 System_Windows_Forms_ni!System.Windows.Forms.Button.OnClick(System.EventArgs)+0x80 0044ee7c 6437b7ec System_Windows_Forms_ni!System.Windows.Forms.Button.OnMouseUp(System.Windows.Forms.MouseEventArgs)+0xac 0044eed4 646e9a72 System_Windows_Forms_ni!System.Windows.Forms.Control.WmMouseUp(System.Windows.Forms.Message ByRef, System.Windows.Forms.MouseButtons, Int32)+0x274 0044ef28 646f0821 System_Windows_Forms_ni!System.Windows.Forms.Control.WndProc(System.Windows.Forms.Message ByRef)+0x8ba812 ...
0044f27c 00210093 System_Windows_Forms_ni!System.Windows.Forms.Application.Run(System.Windows.Forms.Form)+0x31 0044f288 68842952 MemoryEdit!MemoryEdit.Program.Main()+0x43 ...
這里我們需要反編譯btnLogin_Click的代碼,這個方法接下來調用了TextBox.get_Text方法,那get_Text的返回地址00210845就在btnLogin_Click中,所以我們可以方編譯這個地址來查看login_click方法。
我們可以看到這個方法邏輯比較簡單,其中調用了兩個String.op_Equality方法來比較字符串,那我們要做的就是讓它的比較的結果為True。
在比較之前有兩個匯編操作,分別將兩個地址mov到了edx和ecx,猜測應該是op_Equality的兩個字符串參數。
0:000> uf 00210845 MemoryEdit!MemoryEdit.SampleLoginForm.btnLogin_Click(System.Object, System.EventArgs)+0x45 : 22 00210845 8945d4 mov dword ptr [ebp-2Ch],eax 22 00210848 8b1560217f03 mov edx,dword ptr ds:[37F2160h] 22 0021084e 8b4dd4 mov ecx,dword ptr [ebp-2Ch] 22 00210851 e89a228566 call mscorlib_ni!System.String.op_Equality(System.String, System.String) (66a62af0) 22 00210856 8945ec mov dword ptr [ebp-14h],eax 22 00210859 837dec00 cmp dword ptr [ebp-14h],0 22 0021085d 7435 je MemoryEdit!MemoryEdit.SampleLoginForm.btnLogin_Click(System.Object, System.EventArgs)+0x94 (00210894) MemoryEdit!MemoryEdit.SampleLoginForm.btnLogin_Click(System.Object, System.EventArgs)+0x5f : 22 0021085f 8b45d8 mov eax,dword ptr [ebp-28h] 22 00210862 8b8848010000 mov ecx,dword ptr [eax+148h] 22 00210868 8b01 mov eax,dword ptr [ecx] 22 0021086a 8b404c mov eax,dword ptr [eax+4Ch] 22 0021086d ff501c call dword ptr [eax+1Ch] 22 00210870 8945d0 mov dword ptr [ebp-30h],eax 22 00210873 8b1564217f03 mov edx,dword ptr ds:[37F2164h] 22 00210879 8b4dd0 mov ecx,dword ptr [ebp-30h] 22 0021087c e86f228566 call mscorlib_ni!System.String.op_Equality(System.String, System.String) (66a62af0) 22 00210881 8945dc mov dword ptr [ebp-24h],eax 22 00210884 90 nop 22 00210885 837ddc00 cmp dword ptr [ebp-24h],0 22 00210889 0f94c0 sete al 22 0021088c 0fb6c0 movzx eax,al 22 0021088f 8945e8 mov dword ptr [ebp-18h],eax 22 00210892 eb07 jmp MemoryEdit!MemoryEdit.SampleLoginForm.btnLogin_Click(System.Object, System.EventArgs)+0x9b (0021089b) MemoryEdit!MemoryEdit.SampleLoginForm.btnLogin_Click(System.Object, System.EventArgs)+0x94 : 22 00210894 c745e801000000 mov dword ptr [ebp-18h],1 ... MemoryEdit!MemoryEdit.SampleLoginForm.btnLogin_Click(System.Object, System.EventArgs)+0xce : 30 002108ce 90 nop 30 002108cf 8d65f8 lea esp,[ebp-8] 30 002108d2 5e pop esi 30 002108d3 5f pop edi 30 002108d4 5d pop ebp 30 002108d5 c20400 ret 4
我們在op_Equality方法上設置斷點。然后繼續運行程序,斷點觸發。
為了驗證我們的猜想,我們來通過sos managed debug extension來驗證下我們的猜想,果然ecx,edx中存的是我們輸入的用戶名和校驗方法中比對的用戶名。到這里其實我們已經知道登錄代碼要比對的用戶名是test。不過這次為了演示,要更改內存讓本次登錄成功。
0:000> bp 00210851 0:000> bp 0021087c 0:000> bl 0 e 005f0178 r 4 0001 (0001) 0:**** 1 e 00210851 0001 (0001) 0:**** MemoryEdit!MemoryEdit.SampleLoginForm.btnLogin_Click(System.Object, System.EventArgs)+0x51 2 e 0021087c 0001 (0001) 0:**** MemoryEdit!MemoryEdit.SampleLoginForm.btnLogin_Click(System.Object, System.EventArgs)+0x7c 0:000> g Breakpoint 1 hit eax=0283140c ebx=02818468 ecx=0283140c edx=028312f4 esi=027f36ec edi=0044ee28 eip=00210851 esp=0044ee08 ebp=0044ee38 iopl=0 nv up ei pl zr na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246 MemoryEdit!MemoryEdit.SampleLoginForm.btnLogin_Click(System.Object, System.EventArgs)+0x51: 00210851 e89a228566 call mscorlib_ni!System.String.op_Equality(System.String, System.String) (66a62af0) 0:000> .loadby sos clr 0:000> !do 0283140c Name: System.String MethodTable: 66b8acc0 EEClass: 6679486c Size: 30(0x1e) bytes File: C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll String: mockuser Fields: MT Field Offset Type VT Attr Value Name 66b8c480 40000aa 4 System.Int32 1 instance 8 m_stringLength 66b8b6b8 40000ab 8 System.Char 1 instance 6d m_firstChar 66b8acc0 40000ac c System.String 0 shared static Empty >> Domain:Value 0057ae08:NotInit << 0:000> !do 028312f4 Name: System.String MethodTable: 66b8acc0 EEClass: 6679486c Size: 22(0x16) bytes File: C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll String: test Fields: MT Field Offset Type VT Attr Value Name 66b8c480 40000aa 4 System.Int32 1 instance 4 m_stringLength 66b8b6b8 40000ab 8 System.Char 1 instance 74 m_firstChar 66b8acc0 40000ac c System.String 0 shared static Empty >> Domain:Value 0057ae08:NotInit <<
這里我們通過內存查看命令來看一下這兩個字符串的數據結構是如何在呈現的。
可以看到黃色的為string類型的方法表(method table),綠色的為該字符串的長度,再接下來的內容為字符串內容。
我們要做的是要將字符串大小從8改為4,然后將字符串內容替換為test。
0:000> dc 0283140c 0283140c 66b8acc0 00000008 006f006d 006b0063 ...f....m.o.c.k. 0283141c 00730075 00720065 00000000 00000000 u.s.e.r......... 0:000> dc 028312f4 028312f4 66b8acc0 00000004 00650074 00740073 ...f....t.e.s.t. 02831304 00000000 80000000 66b8acc0 00000006 ...........f....
通過內存更改命令eb和eu來完成。這里我們看到更改之后字符串貌似變成了testuser,沒關系,因為我們更改了字符串長度,所以程序只會比較前四個字符。
0:000> eb 0283140c+4 4 0:000> eu 0283140c+8 "test" 0:000> dc 0283140c 0283140c 66b8acc0 00000004 00650074 00740073 ...f....t.e.s.t. 0283141c 00730075 00720065 00000000 00000000 u.s.e.r.........
接下來我們要運行了,如果運氣好我們可以運行到下一個密碼比較的斷點。
當然我們運氣一項不錯,第二個斷點如約觸發了。還是做同樣的事情,將需要比較的密碼改掉。
0:000> g Breakpoint 2 hit eax=028314a4 ebx=02818468 ecx=028314a4 edx=0283130c esi=027f36ec edi=0044ee28 eip=0021087c esp=0044ee08 ebp=0044ee38 iopl=0 nv up ei pl zr na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246 MemoryEdit!MemoryEdit.SampleLoginForm.btnLogin_Click(System.Object, System.EventArgs)+0x7c: 0021087c e86f228566 call mscorlib_ni!System.String.op_Equality(System.String, System.String) (66a62af0) 0:000> !do 028314a4 Name: System.String MethodTable: 66b8acc0 EEClass: 6679486c Size: 26(0x1a) bytes File: C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll String: 123456 Fields: MT Field Offset Type VT Attr Value Name 66b8c480 40000aa 4 System.Int32 1 instance 6 m_stringLength 66b8b6b8 40000ab 8 System.Char 1 instance 31 m_firstChar 66b8acc0 40000ac c System.String 0 shared static Empty >> Domain:Value 0057ae08:NotInit << 0:000> !do 0283130c Name: System.String MethodTable: 66b8acc0 EEClass: 6679486c Size: 26(0x1a) bytes File: C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll String: abc123 Fields: MT Field Offset Type VT Attr Value Name 66b8c480 40000aa 4 System.Int32 1 instance 6 m_stringLength 66b8b6b8 40000ab 8 System.Char 1 instance 61 m_firstChar 66b8acc0 40000ac c System.String 0 shared static Empty >> Domain:Value 0057ae08:NotInit <<
0:000> eu 028314a4+8 "abc123"
0:000> dc 028314a4
028314a4 66b8acc0 00000006 00620061 00310063 ...f....a.b.c.1.
028314b4 00330032 00000000 00000000 00000000 2.3.............
看看最終的運行結果吧,雖然用戶名和密碼都不正確,但還是越獄成功了。
總結
本文使用的都是最基礎的調試命令,但也是最重要的命令。
- 內存搜索: s -a/-u/-d
- 訪問斷點: ba r/w
- 查看調用棧: kv/kL/kp
- 查看內存: dc/du/da/dd
- 修改內存: ed/eu/ea
- 反編譯代碼: uf/ub
在真實的調試中情況不會這么簡單,需要耐心分析,不斷嘗試。
可以粗略了解些匯編語言,明白常用命令的作用,寄存器的用途,和方法調用規范(calling convention x86 x64)。
發揮想象力,想象力比知識更重要。
I am enough of an artist to draw freely upon my imagination. Imagination is more important than knowledge. Knowledge is limited. Imagination encircles the world.
Albert Einstein
Aaron Zhang