标 题: 【原创】WINDBG Script简易教程{看雪学院2006金秋读书季}
作 者: 笨笨雄
时 间: 2006-10-22,17:03
链 接: http://bbs.pediy.com/showthread.php?t=33663
【文章标题】: WINDBG Script简易教程
【文章作者】: 笨笨雄
【作者邮箱】:
[email protected]
【工具名称】: WINDBG
【下载地址】: http://www.microsoft.com/whdc/devtools/debugging/installx86.mspx
【作者声明】: 希望更多人使用WINDBG,然后大概就能看到debugging tools for windows的帮助文件的中文翻译了吧。花了几天时间才在翻译软件的帮助下看完debugger commands部分,痛苦死了。也找不到WINDBG的插件,还是WINDBG没插件功能?为了简化调试过程,只有学习使用SCRIPT了,现在把这几天的经验跟大家分享。附件中所有代码经NOTEPAD,REGEDIT等调试,花了几小时,基本通过。
废话多了,现在是正文。WINDBG的指令比较多,还是英文的,所以我只挑了一部分经常会用到的,并通过实例去告诉大家那些指令的作用和格式。正如大多数高级语言教程一样,我们先来看看如何写一个HELLO WORLD的程序。
如果使用.echo "HELLO WORLD"作为例程就太简单了,我希望介绍更多的指令。所以我在OD直接用汇编写了个程序:
PUSH 0
PUSH 12345678 ;TITLE跟显示内容都在这个跟下一个PUSH
PUSH 12345678 ;我比较懒。。。就用一样的字符了
PUSH 0
MOV EAX,OFFSET MESSAGEBOXW
CALL EAX
先用EAX保存MESSAGEBOXW的指针,然后再CALL。这是为了你在任何一个程序下都能用使用这个SCRIPT。如果直接CALL MESSAGBOXW的指针,翻译成机器码是相对于当前位置的偏移,这样写出来的SCRIPT文件在这个程序能用,别的程序就不能用了。
机器码 6A 00 68 78 56 34 12 68 78 56 34 12 6A 00 b8 68 3d e2 77 ff d0
我虚拟机使用的是WIN 2000 连SP1都不是。。所以我不保证你的机器仍然能正常运行这个程序。为了能正常使用,你可以随便找一个程序,然后BP MESSAGEBOXW,中断之后当前的EIP就是了,把ff d0前面的68 3d e2 77换掉了就可以了。我的目的并不是介绍如何写一个兼容性差的程序,重点是学会如何写SCRIPT。
准备工作做好了,在看代码之前,先解释一些指令:
$exentry伪寄存器,数值上等于EP
$t0-$t19,WINDBG为我们提供了20个自定义的伪寄存器
R指令能改变几乎所有寄存器的值,包括EAX等
.dvalloc [/b] size 申请内存空间,带/b 地址,可在指定地址申请空间,不带则自动分配,指定地址时不一定成功,暂时的经验指定地址越大越容易成功。
e* 地址 在指定内存中写入数据,EW 写入WORD,EB写入BYTE,ED写入DWORD
注意: EW 00400000 12345会产生溢出错误,同理EB 00400000 123也是错的,正确的例子可见后面的代码
f 地址 L长度 BYTE 在长度的地址写入数据,你可以在示例中看到效果。同样BYTE的位置只能是BYTE,多于8位的数据都会造成溢出错误。
m 源地址 L源地址长度 目的地址 复制内存区域
d* 地址 显示地址中的数据,其中db的效果可在示例中看到。
.dvfree /d 地址 size 释放指定地址的内存,这里指定地址用的是/d要与 .dvalloc的/b相区别。
附件中helloworld.txt的代码:
---------------------------------------helloworld.txt--------------------------------
g $exentry
r $t0=00ff0000; $$ $t0:源内存基址
r $t2=@$exentry; $$ $t2:目的内存基址
r $t1=@$t0; $$ $t1:当前指针
.dvalloc /b $t0 1000; $$在00b90000申请1000BYTE的内存空间
ew $t1 006A 0068
db $t0
.echo "ew指令的效果"
r $t3 = @$t1 + 3; $$PUSH DWORD的机器码=68 DWORD
$$这里应该输入字符串的首址
r $t1 = @$t1 + 7; $$懒得计算,所以用$t3存起DWORD的指针
$$输入字符串的时候一起搞定
eb $t1 68 12 34
db $t0
.echo "eb指令的效果"
r $t4 = @$t1 + 1; $$同上
r $t1 = @$t1 + 5
f $t1 l20 6A 00 b8 68 3d e2 77 ff d0
db $t0
.echo "f指令的效果"
r $t1 = @$t1 + 9
r $t5 =$t1 - $t0 + $t2
ed $t3 $t5; $$ 添加字符串的指针回去
ed $t4 $t5
f $t1 l20 'h' 'e' 'l' 'l' 'o' ' ' 'w' 'o' 'r' 'l' 'd' '!' 00 00
$$ 把字符串写进内存中
m $t0 l30 $t2
r $ip=$t2; $$ 修改EIP
.dvfree /d $t0 1000; $$ 释放内存
g
---------------------------------------helloworld.txt完结的分割线--------------------------------
为了你的速度,请保证symbol path为空,只有在你有源代码或者系统核心的时候它的存在才有意义,否则你会发现它会非常费时且毫无意义,尤其是你得连上网络下载symbol资源的时候。
使用SCRIPT文件的命令有4个"$<","$$<","$><","$$><",他们的区别就是有没有空格或者换行符的限制。使用$$><没有任何限制,这样可使代码更具可读性。要使用附件中的SCRIPT请使用$$><指令。
例如你可以用下面指令访问在D盘下的helloworld.txt。
$$><d:/helloworld.txt
如果你把helloworld.txt放在WINDBG的安装目录,那么你可以使用下面指令:
$$><helloworld.txt
运行完helloworld.txt后你会发现报错了,因为我的代码覆盖了EP。通过上面的例子,我们能用SCRIPT做什么呢?在合适的时机,把没加密的IAT或者其他什么的,暂存到内存中,在脱壳完毕的时候再自动用正确的部分把加密部分覆盖掉。
也许在未来,也会遇到这样的需要,程序运行到某部分的时候,中断,然后运行我们自己的代码,运行完毕之后,我们需要返回到程序原来的流程。为此我把上面的SCRIPT修改了一下,写成BACKTOCODE.TXT。
--------------------------------------------backtocode.txt--------------------------------------
r $t0=00ff0000
r $t1=@$t0
r $t18=$ip ;$$ 用$t18暂存当前EIP,显然$ip=EIP
.dvalloc /b $t0 1000
ew $t1 006A 0068
r $t3 = @$t1 + 3
r $t1 = @$t1 + 7
eb $t1 68 12 34
r $t4 = @$t1 + 1
r $t1 = @$t1 + 5
f $t1 l20 6A 00 b8 68 3d e2 77 ff d0
r $t1 = @$t1 + 9
ed $t3 $t1
ed $t4 $t1
ba e1 $t1 ;$$ 内存运行断点,E代表运行1是长度,在E后面通常是1,断在最后一个指令后的第一个地址
f $t1 l20 'h' 'e' 'l' 'l' 'o' ' ' 'w' 'o' 'r' 'l' 'd' '!' 00 00
r $ip=$t0 ;$$ 在分配的内存中直接运行我们自己的代码
g
.dvfree /d $t0 1000
r $ip=$t18 ;$$ 把EIP设置为原来的
.cls ;$$ 当前命令行窗口清屏
p ;$$ 单步步过
-------------------------------------------backtocode.txt结束-----------------------------------
为了anti anti debug我们需要隐藏标题,虽然WINDBG本身有.wtitle指令,可是那个指令会把它后面的所有内容当作字符串输入,而且windbg+版本号是无论怎么改都会被默认添加到最后的。这样上面那个SCRIPT就有用了,就差CODE了。
为了anti anti debug,每次DEBUG的时候,我们都要做一些准备工作,用SCRIPT文件,我们可以自动完成这些操作,下面是我DEBUG之前都会用到的SCRIPT文件
-----------------------------------------------start.txt----------------------------------------
r $t0 = 00
eb 7FFDF002 $t0 ;$$去除DEBUG标志
.pcmd -s ".if(eax<70000000 and eax>00120000){da eax;du eax}; .if(edx<70000000 and edx>00120000){da edx;du edx}"
g $exentry ;$$入口点
-------------------------------------------start.txt结束----------------------------------------
如你所见的,这有点少。没办法水平有限,而且写这篇文章的时候我才勉强说是学会用,还是那句重点是教会大家用WINDBG。WINDBG的初始断点并不是入口点所以得自己用指令让它自动停在入口点,有的程序是有TLS表的,对着PE格式的介绍文章,写一个SCRIPT在有TLSCALLBACK的情况下自动停在TLSCALLBACK入口是有可能的,你会在文章的最后部分得到相关指令的介绍。现在来说说START.TXT中没有注释的指令。
; 分号,多条命令的分隔符。从左到右运行。
下面例子中,对MESSAGEBOXW下断后运行,中断之后便会运行r $t0=esp+8指令
bp messageboxw;g;r $t0=esp+8
注意:如果你使用CRTL+BREAK快捷键在中断之前暂停调试也会导致r $t0=esp+8的运行。
.if(条件表达式){命令} 跟C语言中的用法一样。
.pcmd 不带参数则显示每条指令之后自动使用的指令。-s "命令" 设置命令。-c 清除命令。
da 以ASCII显示内存地址,du以UNICODE显示内存地址
在示例中,整条指令的效果表现为,每单步一个指令,便会当EAX,EDX指向的是一个合法地址的时候,便以ASCII和UNICODE的方式分别显示它的值,就象OD那样。如果熟悉ASCII和UNICODE字符集的范围还能设置仅当有效字符时才显示结果。