Win7系统中使用WinDbg与虚拟机调试驱动程序的方法和要点(3)

编辑:quers 2016-06-06 13:56:59 来源于:系统城 1. 扫描二维码随时看资讯 2. 请使用手机浏览器访问: http://wap.xtcheng.cc/xtjc/11729.html 手机查看

  ---------------------------------------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字符集的范围还能设置仅当有效字符时才显示结果。

  在调试的过程中,有时我们希望自动化解决一些问题。例如调试使用了UnhandledExceptionFilter的SEH,我们需要自动修改 ZwQueryInformationProcess的返回值。或者对于某些API的ANTI DEBUG,如果我们修改了输入参数,同样不能返回应该返回的值。学破解不久,一下子要找用了UnhandledExceptionFilter的软件还真不容易,用别的API代替了。我用OD把NOTEPAD 修改一下,改名为TEST放在附件中。

  流程MESSAGEBOXW,GETCOMMANDLINEW,MESSAGEBOXW输出COMMANDLINE,最后EXITPROCESS。

  现在我要做的是改变GETCOMMANDLINEW的输出,和第二个MESSAGEBOXW的输入。现在让我们看看test2.txt

  -------------------------------------------test2.txt-----------------------------------------------

  g $exentry

  r $t0=0

  bp messageboxw “r $t0=$t0+1;j($t0=2)‘r $t1=poi(esp+8);f $t1 l4 45;g’;g”

  bp getcommandlinew “g poi(esp);r $t1=eax+5;f $t1 l4 55;g”

  g

  ------------------------------------------test2.txt完结---------------------------------------------

  首先对相关指令作一些介绍

  BP 地址或者函数名 “命令” 命令参数是可选的,存在的情况下,中断的同时会先运行那些命令。

  J(条件表达式)‘命令1’;命令2 相当于.if但是又有点不同命令2只能是1个,后面所有命令会被忽略。

  POI() 返回指针的指向位置的内容。

  != 不等于

  这里用了条件中断的方法实现,第一个条件中断指令用$t0作为计数器,第二次中断的时候变修改堆栈中指针指向位置的内存区域。注意到调用API的返回地址在ESP中,直接跳出去,然后修改EAX就可以达到修改函数输出参数的效果了。

  这里提供第二种可行的方法,并且更有可扩展性,现在看看test.txt中的代码。

  -------------------------------------------test.txt------------------------------------------------

  g $exentry

  r $t0=0

  bp messageboxw

  bp getcommandlinew

  bp exitprocess

  .while (eip!=77e7b0bb){

  g

  .if($ip=77e116cc){

  r $t0=$t0+1

  .if($t0=2){

  r $t1=poi(esp+8)

  f $t1 l4 45

  }

  }

  .if($ip=77e7c693){

  g poi(esp)

  r $t1=eax+5

  f $t1 l4 55

  }

  .elsif($ip=77e7b0bb){

  .break

  }

  }

  g

  ------------------------------------------test.txt完结----------------------------------------------

  仍然先介绍一些指令:

  .while(条件表达式){} 跟C语言中的一样,循环结构,直到条件表示式为真

  .elsif(){} 跟前面的.if用法一样,它的作用如字面上意思,只是小心别拼错为ELSEIF

  .break 跟C语言中的一样,跳出循环。

  如果在条件为真的时候不用.break跳出循环就会出错,这点要注意。

  这里构造了一个循环结构,并且通过对比EIP的方法来识别函数,同样地因为我的虚拟机是WIN 2000 连SP1都不是,所以我不肯定该地址在你的机器中仍然可用。不过这里提供了一个思路,你可以用这个方法构造一个SCRIPT来加强WINDBG的功能,例如象OD一样中断的时候自动显示所有参数,并且带上英文提示那是什么参数。同样地,我们可以做一个自动化分析SCRIPT,分析每个CALL中包含了什么API,并且列出输入和输出参数,CALL的深度还指令数,并且自动生成报告文件,假如有人开发出这样一个SCRIPT,调试分析将会变得容易。WINDBG里面有个相似功能的指令。

  WT 自动跟踪并生成报告,几乎跟我上面说的一样。带/l参数的时候可以设置深度,不过很多时候,我们看到一个CALL并不知道里面究竟有多深,但是我们希望得到一些关于那个CALL的详细信息来判断是否值得跟进。

  两个问题:

  1、递归,那这个指令不知道运行多久。

  2、大量NATIVE API调用,显然大多数情况下,我们并不关心。

  比起1,2更加常见,/i参数是用来避开指定模块的,不会用,帮助文件里也没提。。。。希望有大大能答我这个问题

  WINDBG提供了下面3个指令用于保存分析过程进文件,通过适当的开关可以过滤一些无意义的信息,使分析过程易于观看。

  .logopen 文件路径带/U参数则以UNICODE方式输入文本。重写整个文件,并记录当前命令窗口在使用该指令之后的所有内容。

  .logclose 文件路径 停止记录并关闭文件。

  .logappend 文件路径带/U参数则以UNICODE方式写文件。记录当前命令窗口在使用该指令之后的所有内容,并添加进文件。

  提到了功能强化,大家都知道OD里面有个命令是运行到RET处吧,在WINDBG中似乎没有这样的指令,类似的有PC,即运行到CALL。我写了一个SCRIPT来模拟OD中的那个指令。现在我们来看看goret.txt

  -------------------------------------------goret.txt------------------------------------------------

  r $t0=0

  .while(@$t0!=c3){

  p

  r $t0=by(eip)

  .if(@$t0=c3){

  .break

  }

  }

  -----------------------------------------goret.txt完结----------------------------------------------
 

  这里是最后一个示例分析,所以除了解释上面的指令之外也给出一些有价值的指令

  not 非 and或者& 与

  hi() 取高16位 or或者^ 或

  low() 取低16位 xor或者| 异或

  by() 取低8位 gu 步出,不知道具体原理,有时会出错

  wo() 取低16位 t 步入

  mod或者% 模运算

  这个SCRIPT使用了一个循环,通过EIP取得当前指令的机器码,低8位既为指令,然后把指令存进$t0作比较。C3是RET的机器码,等于则跳出循环,否则一直步过。

  这个示例表明,我们可以在SCRIPT里分析每一条指令。我们可以在WINDBG中进行2次开发,动态将那些简单使用JMP+内存指针或者寄存器作为跳转的乱序的程序重新排序,使花指令失效,并且实现自动清除垃圾指令,最后生成优化后的汇编代码文件。本论坛翻译区里的变形多态中的关于收缩器的理论已经为我们奠定了理论基础。

  你可能会需要用到反汇编指令

  u 起始地址 l长度 L代表的不是地址长度而是指令的个数
 

  ---------------------------------后续讨论,用SCRIPT把WINDBG变成脱壳机------------------------------

  在准备写这篇文章的时候,我又把DEBUGGER COMMANDS看了一次,发现了这个指令

  .writemen filename range 将目标内存区域写进文件。RANGE的格式为 地址 l长度

  我没试过L后面是否接受寄存器作为参数。也没实际测试过这个指令的具体操作是怎么样的,无论如何,有这个可能存在。当然我们也可以申请内存区域以程序的方式来完成这个工作,不过我希望它仅用SCRIPT完成。

  假如这的确可行,可以通过下面指令组合来自动寻找文件头,当然也能确定文件大小。

  $p 伪寄存器,它将返回前一次用d*指令所显示的内存的内容。

  假设00100000 01 02 03 04 05 06 07 08

  我使用dd 00100000,那么$p = 04030201

  显然我们可以通过这个方法来访问内存。

  dw 取一个WORD; dd取DWORD; dw取qword

  能访问内存也代表说我们在调试程序中插入的代码也能跟SCRIPT通信,并且把一些SCRIPT无法完成的工作交给程序执行,然后把结果返回给SCRIPT。

  假如l的参数无法通过寄存器来传递,只能依靠用户按照提示进行操作,那么我们有更简单的方法

  .imgscan 它将返回所有模块MZ的地址和它的SIZE
 

  ----------------------------------这里给出一些可能的疑问和解答--------------------------------------

  Q:在调试SCRIPT文件的时候,我该如何知道寄存器跟内存的变化?

  A:我们可以用下面的指令来观察寄存器跟内存的变化

  d* 用于显示内存,之前已经提到就不详细说明了

  ? 寄存器 显示寄存器的值,例如

  ? poi(esp); ? $t0

  这将先显示ESP指向的值,然后显示$t0的值

  除了可以使用.echo命令对显示参数作说明之前,也可以使用.printf作格式化输出,它的用法跟C语言中的printf是一样的

  Q:我写的SCRIPT文件出错了,语法跟参数都没错,为什么我找不到出错原因?

  A:有的指令要注意的,BA只能在进入程序区域之后才能用。.dvalloc申请过的内存,即使用.dvfree释放了,也无法在同样的位置再申请,可能是BUG。

  *是一个注释命令,它后面所有的内容都会被当作字符

  $$则是以分号为结束

  .restart指令跟.wtitle指令,不知道为什么不能放在SCRIPT中使用。

  还有就是@这个标记,这个标记是告诉WINDBG后面的是一个伪寄存器而不是程序里的某一个变量的符号。有的指令在没有@标记的时候会报错,例如。 while括号里的条件表达式,如果你用了伪寄存器,一定要在前面加上@否则一定报错。此外用帮助文件里的话来说,使用@,可以让SCRIPT文件运行得更快,因为在解读这个代码的时候不需要先搜索一次SYMBOL记录。
 

  Q:我能把功能模块化然后在其他SCRIPT文件中使用吗?

  A:我已经测试过$$》《指令也能在SCRIPT里面使用

  Q:我写的SCRIPT FILE能在64位系统中用吗?

  A:如果你仅使用SCRIPT来实现功能,那么很可能与64位兼容。尽量使用伪寄存器。

  $ip,$retreg,$csp在32位系统中分别表示EIP,EAX,ESP,而在64位系统中则表示RIP,RAX,RSP也分别对应Itanuim处理器中的相关寄存器。

  通过函数名获取不同版本下的地址,可以通过下面代码:

  bp messageboxw ; $$第一个断点,断点ID为0

  bp getcommandlinew; $$第二个断点,断点ID为1

  r $t10 = $bp0; $$将第一个断点的地址转存$t10

  r $t11 = $bp1; $$将第二个断点的地址转存$t11

  bc *; $$清除所有断点。

  这段代码运行之后MESSAGEBOXW的地址便存于$t10中,而$t11里面的则是getcommandlinew的地址。这里要说明,断点用完要释放,否则不好估计断点的ID,此外在内核模式中,最多只允许32个断点。

分享到:

热门图文

热门搜索

返回顶部