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

windbg 基础命令实战 - 简单程序破解的更多相关文章

  1. Windbg 基础命令 《第一篇》

    Windbg.exe是Windows的一个调试工具,它支持两种调试模式,即“实时调试模式(Living)”和“事后调试模式(Postmortem)”. 实时模式:被调试的程序正在运行当中,调试器可以实 ...

  2. Linux课程实践三:简单程序破解

    一.基本知识 1. 常用指令机器码 不同版本对应机器码不同,这里以我做实验的kali(Intel 80386)为例. 指令 作用 机器码 nop 无作用(no operation) 90 call 调 ...

  3. linux实践——简单程序破解

    一.运行login可执行程序,屏幕显示需要输入密码,随便输入一串字符,结果是Drop dead! 二.objdump -d login,对login进行反汇编,找到main函数,找到含有scanf的那 ...

  4. python基础入门学习简单程序练习

    1.简单的乘法程序 i = 256*256 print('The value of i is', i) 运行结果: The value of i is 65536 2.执行python脚本的两种方式 ...

  5. linux学习9 运维基本功-Linux常用基础命令实战应用

    一.文件系统知识回顾 1.Linux文件系统: a.文件名称严格区分字符大小写 b.文件可以使用除/以外任意字符 c.文件名长度不能超过255个字符 d.以.开头的文件为隐藏文件: . :当前目录 . ...

  6. Android简单应用程序破解——runtime.apk

    对于<Debugging Android Application>一文中最后附上的练习,我采用了另一种静态方法绕开原有的逻辑去破解.主要的过程如下: 利用apktool将练习的runtim ...

  7. 某客的《微信小程序》从基础到实战视频教程

    第 1 部分 微信小程序从基础到实战课程概要   第 1 节 微信小程序从基础到实战课程概要   1.1微信小程序从基础到实战课程概要   第 2 部分 初识微信小程序    第 1 节 微信小程序简 ...

  8. 零基础入门 实战mpvue2.0多端小程序框架

    第1章 课程快速预览(必看!!!)在这一章节中,老师讲带领你快速预览课程整体.其中,涉及到为什么要做这么一门实战课程.制作一个小程序的完整流程是怎么样的,以及如何做项目的技术选型. 第2章 30 分钟 ...

  9. 如何通过命令行简单的执行C程序

    如何通过命令行简单的执行C语言编写的程序 ​ 首先,我们知道C语言程序都是以xxx.c结尾的,这在Windows系统和Linux系统都是一样的.其次,C程序的执行过程为四步:预处理--编译--汇编-- ...

随机推荐

  1. jQuery操作Table tr td常用的方法

    虽然现在DIV+CSS进行页的布局大行其道,但是很多地方使用table还是有很多优势,用table展示数据是比较方便的,下面汇总了jQuery操作Table tr td常用的方法,熟记这些操作技巧,下 ...

  2. Cassandra-几个基本测试常识

    一 使用ycsb进行装载,每次装载都不会删除以前装载过的. 因此如果想在空数据库中装载,需要先情况数据表. drop命令删除整个表,因此需要重建标头. truncate命令删除表的所有行,只留下表头, ...

  3. mac 日式键盘反斜线\

    日式键盘 没有反斜线\, 但是有快捷键可以输入:alt+¥  (英文模式下)

  4. python中的enumerate

    读取一个iter,返回[index,内容]的一个list

  5. Java 第27章 JDBC

    JDBC 模版 JDBC 的工作原理 JDBC API 提供者:Sun公司 内容:供程序员调用的接口与类,集成在java.sql 和javax.sql 包中,如: DriverManager 类 Co ...

  6. Raab判别法确定级数是否收敛

  7. C.C++把整个文件内容读进一个buffer中

    原创文章,未经本人允许禁止转载. //C方式, 调用的函数繁多 //fopen,fseek,ftell,fseek,malloc,fread,fclose,free. void foo() { FIL ...

  8. javaWeb开发小工具---MailUtils及其单元测试

    本次介绍的是,在javaWeb开发中,我们不免会遇到发送邮件的需求,比如:用户注册账号,需要激活登录,以及服务器定期向会员发送礼品信息等.所以参考有关资料,写了这个MailUtils工具类. 1.Ma ...

  9. 【记忆化搜索】bzoj1652 [Usaco2006 Feb]Treats for the Cows

    跟某NOIP的<矩阵取数游戏>很像. f(i,j)表示从左边取i个,从右边取j个的答案. f[x][y]=max(dp(x-1,y)+a[x]*(x+y),dp(x,y-1)+a[n-y+ ...

  10. eclipse/myeclipse sublime 实时更新文件改变

    情形: 在使用eclipse/myeclipse开发的时候, 像JS 或者HTML 以及一些操作时,sublime 的效率比eclipse/myeclipse要快,所以我们就可以使用这两者一起开发. ...