先来解释一下,Windbg的脚本是什么?你可以理解为脚本就是一种语言,就像c或者汇编,但是他不需要编译器将其编译为可执行文件,而是由解释器将其内容翻译为对应的动作。而Windbg的脚本就是利用Windbg作为解释器,将脚本内容翻译为实际的动作。也许这个解释还是有些晦涩,那让我们跳过这些晦涩的概念,来一个简单的例子:
3. 别名
别名和变量还有些区别,变量是在执行过程中取他的值,而别名更像是宏,在解释时直接用内容替换原始操作数。别名有两种,一种是固定名字的,一种是自定义的。
● 固定名字别名
固定名字别名和伪寄存器很类似,Windbg提供了10个,$u0-$u9。使用的时候依然是r命令,不过要在“u”前面加个“.”,像下面这样:
从上面的例子可以看出一旦别名被定义了,到使用他的时候,Windbg会把别名替换为内容。
● 自定义别名
自定义别名会复杂一些,但是,有了它的存在,我们才可以为内存中的一些字符串定义别名。操作自定义别名有3个命令: as,ad,al 。
As 定义一个别名,其强大之处在于,可以指定一个内存地址,然后将内存中的内容定义为别名。也可以定义为一个( 伪 )寄存器 。
al显示已经定义的别名,ad删除已经定义的别名,接着刚才的例子继续输入以下命令:
可以看的很明显吧。
现在我们来解释一下例子里那个长得很奇怪的${},这个东西叫别名解释器,把别名放在后面的大括号里面,Windbg就知道里面是个别名,需要被翻译。其实不用这个符号也可以,不过写到复杂脚本的时候就可能出问题,谁用谁知道,我就不再发散了,建议是最好用。这个解释器也有选项,上面的/v:就是一个。
/v: 保持别名原样,不翻译,在定义和删除的时候用。
/n: 如果别名定义就翻译为内容,否则不做任何翻译。
/f: 如果别名定义就翻译为内容,否则翻译为空。
/d: 如果别名被定义,翻译为1,否则翻译为0,相当于#ifdef。
4. 表达式
Windbg提供了两种表达式:汇编表达式和C++表达式。两种表达式的操作符和操作数都略有区别。
默认是汇编表达式,求汇编表达式的值用?,求C++表达式的值用??。
汇编表达式里能用的操作符除了+、-、*、/这些算数运算符以外还有一些类似转型运算符,比如poi,有时候大家断到一个函数,第一参数是个字符串指针,想打印这个字符串怎么办?可以这样 dd esp+4,然后再从结果中da一次,有了poi,一行命令就可以做到,dd poi(esp+4)。
C++表达式就更加丰富了,几乎所有的C++表达式都可以用,包括.和->操作符,想让Windbg将表达式按C++方式解释,需要在表达式前面加@@c++()。
5. 语句
都说了脚本要按照编程的思想来写,既然是编程,怎么能少得了流程控制语句呢?Windbg支持以下流程控制语句。
我觉得都可以不用解释,看名字就应该知道是什么,大家都是写程序的嘛,对吧。
另外还有几个比较有用的语句
这里面,.block要单独说说,所谓语句块,其实就是用{}括起开的一堆语句,包括.if、.else后面的语句其实都是语句块,语句块内部的别名(还记得吗)在进入块的时候会被翻译,进入块以后,如果修改了别名的定义,那么在本块内的后续语句中是无效的(还记得别名是原样替换吗),所以,如果需要在后续语句中生效,需要把后面的语句放到一个单独的语句块里,也就是用{}把他们包含起来,但是Windbg又不能识别直接用{}包含起来的东西,于是就出现了.block,看到这里,请切记,如果需要别名被翻译,一定要把他放到语句块里。
6. 内建函数
这里只讲两个内建函数$scmp和$sicmp都是字符串比较,一个区分大小写,一个不区分大小写。这两个函数有一个毛病,那就是参数只接受字符串字面量,就是说,你只能写$scmp(“123”,”123”),不能写$scmp(poi(esp+4),”123”),好了,有人急了,不能这样写,要这两个函数有什么用?不急,我们可以利用别名(这就是别名最有用的地方),还是接着刚才那个例子:
这样就可以比较变量字符串了。
好了,有了以上知识,写一个windbg脚本应该就有基础了,剩下的就是要看大家知道多少“API”了,更详细的信息需要在Windbg的帮助里挖掘了。
最后贴一个完整的例子,利用脚Hook CreateFileW,这个例子虽然不长,但是都是精华啊,哈哈。
稍微解释一下,一开始分配了一段内存,选了一个几乎不会被用到的地址,然后填充为
之后设置一个条件断点,断到以后判断参数中的文件名,如果文件是c:\1.txt就将执行流程转移到分配的指令处,相当于直接返回,于是打开文件失败。
.echo "hello world!"
这条命令会显示“hello world!”这个字符串,把它保存到c:\1.txt文件,然后在Windbg的命令窗口里输入:$$><c:\1.txt回车,看看屏幕上出现了什么?没错,Windbg将1.txt里的内容当做一条Windbg的命令执行了。这就是一个简单的脚本。
也许有人说,这确实是一个脚本,但是他太弱了,只能打印字符串而已。别急,饭要一口一口吃,脚本要一点一点扩展。先来看看这个$$><,根据前面的例子,很容易看出他的作用是将脚本文件交给Windbg解释,由他完成了将一个txt变成Windbg命令的关键转换。其实你知道了这个,Windbg脚本就算入门了,因为你可以把很多命令写在这个文件里,然后用$$><装载执行。这应该能完成一些功能,不过,这样的用法充其量应该叫做batch,而不是script,因为他只能批量执行命令。那么怎么才能升级到script呢?接下来我们一步一步分解,不过在此之前,还是先把$$><了解透彻。$$>< 其实有5个孪生兄弟,在windbg中给出的形式和用法如下:
也许有人说,这确实是一个脚本,但是他太弱了,只能打印字符串而已。别急,饭要一口一口吃,脚本要一点一点扩展。先来看看这个$$><,根据前面的例子,很容易看出他的作用是将脚本文件交给Windbg解释,由他完成了将一个txt变成Windbg命令的关键转换。其实你知道了这个,Windbg脚本就算入门了,因为你可以把很多命令写在这个文件里,然后用$$><装载执行。这应该能完成一些功能,不过,这样的用法充其量应该叫做batch,而不是script,因为他只能批量执行命令。那么怎么才能升级到script呢?接下来我们一步一步分解,不过在此之前,还是先把$$><了解透彻。$$>< 其实有5个孪生兄弟,在windbg中给出的形式和用法如下:
- $<FileName
- $><FileName
- $$< FileName
- $$>< FileName
- $$>a< FileName [arg1 arg2 arg3 ...]
妈呀,眼都花了,看上去长得都很像。别急,他们是有规律的,归纳一下:
1.'$'的表示'<'和脚本名之间不可以有空格。
2.'$$'的表示可以有空格(其实我有点不太理解这个操蛋设定,为什么不能自动检测)。
3.'<'表示不会自动把脚本文件压缩为一行。
4.'><'表示会把他们压缩为一行,并将原来的换行变成';'。
5.最后一个表示可以给脚本传递参数。
为什么要压缩成一行?问的好,Windbg执行某些命令的时候需要他们是一行,比如bp后面可以添加其他命令,但是所有命令写一行又太长了,不容易阅读,于是帮你压缩一下。一般我们用$$><就够了。
好了,接下来是脚本的时刻了。要用好脚本,先要转变自己的态度,要像学习一门编程语言一样学习他,像写代码一样写他,总之,你的思路应该和编程的思路一样。写Windows的应用程序需要哪些知识?首先需要一门编程语言,比如c,另外需要了解Windows的API。好了,我们现在对应到Windbg的脚本。Windbg提供了一些脚本的语法,相当于一门编程语言,而脚本里用到的那些命令相当于系统的API。要学好开发,先要学好一门编程语言,而语言学好以后,API就是现用现查的,所以我们就主要从语法入手。
新学一门编程语言,入门的时候都会学以下几个方面:数据类型,变量,表达式,语句,内建函数,我们也从这几个方面来了解Windbg的脚本。
1. 数据类型:
关于数据类型,Windbg的帮助里没有明确列举,但是,在使用时一般会遇到,数值和字符串这两种。
● 数值
数值没有太多需要解释的,和所有编程语言里的整数含义一样,在表示的时候有进制之分。 Windbg 默认的数值进制一般是 16 , 可以通过 n 命令查看和设置当前进制,所以我们一般在数值里带上进制 , 0n( 十进制 ) , 0x( 十六进制 ) , 0t(8 进制 ) , 0y(2 进制 ) , 比如 0n20 表示 20 , 0x14 表示 20 等。
1.'$'的表示'<'和脚本名之间不可以有空格。
2.'$$'的表示可以有空格(其实我有点不太理解这个操蛋设定,为什么不能自动检测)。
3.'<'表示不会自动把脚本文件压缩为一行。
4.'><'表示会把他们压缩为一行,并将原来的换行变成';'。
5.最后一个表示可以给脚本传递参数。
为什么要压缩成一行?问的好,Windbg执行某些命令的时候需要他们是一行,比如bp后面可以添加其他命令,但是所有命令写一行又太长了,不容易阅读,于是帮你压缩一下。一般我们用$$><就够了。
好了,接下来是脚本的时刻了。要用好脚本,先要转变自己的态度,要像学习一门编程语言一样学习他,像写代码一样写他,总之,你的思路应该和编程的思路一样。写Windows的应用程序需要哪些知识?首先需要一门编程语言,比如c,另外需要了解Windows的API。好了,我们现在对应到Windbg的脚本。Windbg提供了一些脚本的语法,相当于一门编程语言,而脚本里用到的那些命令相当于系统的API。要学好开发,先要学好一门编程语言,而语言学好以后,API就是现用现查的,所以我们就主要从语法入手。
新学一门编程语言,入门的时候都会学以下几个方面:数据类型,变量,表达式,语句,内建函数,我们也从这几个方面来了解Windbg的脚本。
1. 数据类型:
关于数据类型,Windbg的帮助里没有明确列举,但是,在使用时一般会遇到,数值和字符串这两种。
● 数值
数值没有太多需要解释的,和所有编程语言里的整数含义一样,在表示的时候有进制之分。 Windbg 默认的数值进制一般是 16 , 可以通过 n 命令查看和设置当前进制,所以我们一般在数值里带上进制 , 0n( 十进制 ) , 0x( 十六进制 ) , 0t(8 进制 ) , 0y(2 进制 ) , 比如 0n20 表示 20 , 0x14 表示 20 等。
● 字符串
字符串用一对 ” 括起来。比如上面的 ”hello windbg”。
2. 变量:
在windbg中变量的定义很特别,实际上,他并没有变量这个概念,所以,你学习的时候会觉得很别扭。不过,我们换个思路就容易了,变量实际上就是为了保存临时结果, 如果你只想保存一些数值,那么伪寄存器应该是比较好的选择,windbg提供了20个伪寄存器$t0-$t19,供命令保存临时数值变量。称他们为伪寄存器是有原因的,首先对他们的操作和寄存器一样,都是使用r命令,在C++表达式里都前面需要加@符,但是他们又不是真正的寄存器,只是windbg定义的名字而已。使用这些伪寄存器也是很方便的:
字符串用一对 ” 括起来。比如上面的 ”hello windbg”。
2. 变量:
在windbg中变量的定义很特别,实际上,他并没有变量这个概念,所以,你学习的时候会觉得很别扭。不过,我们换个思路就容易了,变量实际上就是为了保存临时结果, 如果你只想保存一些数值,那么伪寄存器应该是比较好的选择,windbg提供了20个伪寄存器$t0-$t19,供命令保存临时数值变量。称他们为伪寄存器是有原因的,首先对他们的操作和寄存器一样,都是使用r命令,在C++表达式里都前面需要加@符,但是他们又不是真正的寄存器,只是windbg定义的名字而已。使用这些伪寄存器也是很方便的:
1
2
3
4
5
6
7
8
9
10
|
0:000>
r $t0=0x123
0:000>
r $t0
$t0=00000123
0:000>
r eax
eax=004c1b89
0:000>
r $t0=@eax
0:000>
r $t0
$t0=004c1b89
|
从上面的例子也可以看出r命令后面的@是可以省略的。
3. 别名
别名和变量还有些区别,变量是在执行过程中取他的值,而别名更像是宏,在解释时直接用内容替换原始操作数。别名有两种,一种是固定名字的,一种是自定义的。
● 固定名字别名
固定名字别名和伪寄存器很类似,Windbg提供了10个,$u0-$u9。使用的时候依然是r命令,不过要在“u”前面加个“.”,像下面这样:
1
2
3
|
0:000>
r $.u0 =
"123"
0:000>
.
echo
$u0
123
|
● 自定义别名
自定义别名会复杂一些,但是,有了它的存在,我们才可以为内存中的一些字符串定义别名。操作自定义别名有3个命令: as,ad,al 。
As 定义一个别名,其强大之处在于,可以指定一个内存地址,然后将内存中的内容定义为别名。也可以定义为一个( 伪 )寄存器 。
1
2
3
4
5
6
|
0:000>
.dvalloc 10
Allocated 1000 bytes starting at 00010000
0:000>
ea 00010000
"123456"
0:000>
as
/ma
${
/v
:
test
} 0x00010000
0:000>
.
echo
test
123456
|
其中 ".dvalloc" 是申请内存命令,ea是给这块内存去赋值。 "as /ma ${/v:test} 0x00010000" 命令是将 0x00010000这块地址当做ANSII字符串,给别名test。后面的".echo test"就是显示test这个别名的值。
上面的命令将0x00010000地址的定义为一个别名,由于as使用了/ma选项,所以将内容当做一个’\0’结尾的ASCII字符串来解析,${}是别名解释器,后面再讲。除了/ma选项以外as还有一些其他强大的选项:
1
2
3
4
5
6
|
/ma
参数指定的内存地址当做ASCII字符串。
/mu
参数指定的内存地址当做Unicode字符串。
/msa
参数指定的内存地址当做ANSI_STRING字符串。
/msu
参数指定的内存地址当做UNICODE_STRING字符串。
/f
别名等于参数指定文件的内容。
/e
别名等于参数指定的环境变量。
|
1
2
3
4
5
6
7
|
0:000>
al
Alias Value
------- -------
test
123456
0:000>
ad ${
/v
:
test
}
0:000>
al
No aliases
|
现在我们来解释一下例子里那个长得很奇怪的${},这个东西叫别名解释器,把别名放在后面的大括号里面,Windbg就知道里面是个别名,需要被翻译。其实不用这个符号也可以,不过写到复杂脚本的时候就可能出问题,谁用谁知道,我就不再发散了,建议是最好用。这个解释器也有选项,上面的/v:就是一个。
/v: 保持别名原样,不翻译,在定义和删除的时候用。
/n: 如果别名定义就翻译为内容,否则不做任何翻译。
/f: 如果别名定义就翻译为内容,否则翻译为空。
/d: 如果别名被定义,翻译为1,否则翻译为0,相当于#ifdef。
4. 表达式
Windbg提供了两种表达式:汇编表达式和C++表达式。两种表达式的操作符和操作数都略有区别。
默认是汇编表达式,求汇编表达式的值用?,求C++表达式的值用??。
汇编表达式里能用的操作符除了+、-、*、/这些算数运算符以外还有一些类似转型运算符,比如poi,有时候大家断到一个函数,第一参数是个字符串指针,想打印这个字符串怎么办?可以这样 dd esp+4,然后再从结果中da一次,有了poi,一行命令就可以做到,dd poi(esp+4)。
C++表达式就更加丰富了,几乎所有的C++表达式都可以用,包括.和->操作符,想让Windbg将表达式按C++方式解释,需要在表达式前面加@@c++()。
5. 语句
都说了脚本要按照编程的思想来写,既然是编程,怎么能少得了流程控制语句呢?Windbg支持以下流程控制语句。
1
2
3
4
5
6
7
8
|
.
if
.
else
.
elif
.
for
.
while
.
break
.
continue
.
do
|
另外还有几个比较有用的语句
1
2
3
|
.
printf
格式化输出,熟悉吧。
.block 语句块
$$ 注释,长得好奇怪
|
6. 内建函数
这里只讲两个内建函数$scmp和$sicmp都是字符串比较,一个区分大小写,一个不区分大小写。这两个函数有一个毛病,那就是参数只接受字符串字面量,就是说,你只能写$scmp(“123”,”123”),不能写$scmp(poi(esp+4),”123”),好了,有人急了,不能这样写,要这两个函数有什么用?不急,我们可以利用别名(这就是别名最有用的地方),还是接着刚才那个例子:
1
2
3
4
5
|
0:000>
as
/ma
${
/v
:
test
} 00010000
0:000>
? $scmp(
"${test}"
,
"123456"
)
Evaluate expression: 0 = 00000000
0:000>
? $scmp(
"${test}"
,
"123457"
)
Evaluate expression: -1 = ffffffff
|
好了,有了以上知识,写一个windbg脚本应该就有基础了,剩下的就是要看大家知道多少“API”了,更详细的信息需要在Windbg的帮助里挖掘了。
最后贴一个完整的例子,利用脚Hook CreateFileW,这个例子虽然不长,但是都是精华啊,哈哈。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
.dvalloc
/b
0x79990000 30
ew 0x79990000 0xc033
ed 0x79990002 0x00001cc2
bp kernel32!CreateFileW "
as
/mu
${
/v
:filename} poi(esp+4);
.block{
.
if
($sicmp(\"${filename}\", \"c:\\1.txt\") == 0){
.
echo
\"
open
1.txt\";
r eip=0x79990000
}
}
ad ${
/v
:filename};
gc;
"
|
1
2
|
xor eax,eax
ret 0x1c
|
最后贴几个小脚本。
bu RegSetValueExW ".if(poi(esp+8)>0) {r $t0=poi(esp+8);as /mu ${/v:valuename} @$t0;du @$t0;.block{.if($spat(\"${valuename}\",\"ProgId*\")){kbn};.else{ad ${/v:valuename};g}}};.else{g; dd poi(esp+8)}"
bm RegOpenKeyExW ".if(poi(esp+14)>0) {r $t0=poi(esp+14);as /mu ${/v:valuename} @$t0;du @$t0;.block{.if($spat(\"${valuename}\",\"*UserChoice*\")){kbn};.else{ad ${/v:valuename};g}}};.else{g; dd poi(esp+14)}"
bm RegCreateKeyExW ".if(poi(esp+8)>0) {r $t0=poi(esp+8);as /mu ${/v:valuename} @$t0;du @$t0;.block{.if($spat(\"${valuename}\",\"*UserChoice*\")){kbn};.else{ad ${/v:valuename};g}}};.else{g; dd poi(esp+8)}"
bu KERNELBASE!RegSetValueExW ".if(poi(esp+8)>0) {r $t0=poi(esp+8);as /mu ${/v:valuename} @$t0;du @$t0;.block{.if($spat(\"${valuename}\",\"ProgId*\")){kbn};.else{ad ${/v:valuename};g}}};.else{g; dd poi(esp+8)}"
bm RegCreateKeyExW ".if(poi(esp+8)>0) {du poi(esp+8)}"
bm RegCreateKeyExW "du poi(esp+8)"