以前玩游戏遇到一些实在过不去的管卡,经常会找一些游戏修改软件来修改游戏,让自己变得无比强大,将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

posted on 2013-08-05 21:22  微软互联网开发支持  阅读(8139)  评论(25编辑  收藏  举报