windbg 基礎命令實戰 - 簡單程序破解


以前玩游戲遇到一些實在過不去的管卡,經常會找一些游戲修改軟件來修改游戲,讓自己變得無比強大,將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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM