相信各位读者随着上一节学习已经可以顺利找出程序的main函数了,既然如此,那么接下来我们是不是应该做点什么呢?嗯,让我好好想想,我们一起来玩一个小游戏吧,看看我们是否可以让目标程序显示“Hello everybody”,而不是毫无个性的“Hello World”。

      这里我先放出源代码:

int _tmain(int argc, _TCHAR* argv[])
{
    if (argc)
    {
        printf("Hello world!\r\n");
    }
    else
    {
        printf("Hello everybody!\r\n");
    }
  return 0;
}

      各位读者应该都知道程序在执行时会将自己的路径保存到argv字符数组里的,因此argc的值肯定都是始终大于1的,所以程序如果未经处理总是会显示“Hello world!”,现在就让我们给他做一个微型外科手术吧。

      为了更接近实战,所以这次我们要分析Release版的,找到main函数后其内容如下:

00401000  CMP DWORD PTR SS:[ESP+4], 0
00401005  JE SHORT Test_0.00401018
00401007  PUSH Test_0.004020F4                     ; /format = "Hello world!
0040100C  CALL DWORD PTR DS:[<&MSVCR90.printf>]    ; \printf
00401012  ADD ESP, 4
00401015  XOR EAX, EAX
00401017  RETN
00401018  PUSH Test_0.00402104                     ; /format = "Hello everybody!
0040101D  CALL DWORD PTR DS:[<&MSVCR90.printf>]    ; \printf
00401023  ADD ESP, 4
00401026  XOR EAX, EAX
00401028  RETN

      看到这些有些读者应该已经快大叫起来了“这个我知道,改关键跳!”但是请注意,我们现在不是在学爆破,而是在学习逆向,因此
还请各位读者静下心来好好看看这段代码的意思。

      由于ESP是栈指针,ESP的值也就是当前栈的所在位置,而[ESP+4]的意思就是在当前堆栈+4的地方取其内容,那么这句话的意思也就是在当前堆栈+4的地方取其内容,然后与0比较。
      第二句话的意思是检查其比较结果,我们都知道其实cmp指令的作用其实就是对着两个操作数做减法操作,只不过不保存操作结果,仅影响标志位。因此结合起来看可以用C语言描述为如下形式:

    if([ESP+4]!=0)
    { …… }
    else
    { …… }

      但是[ESP+4]我们又怎样理解呢?下面我就带领大家顺便再温习一下栈的结构,先看图:

        ESP = 0012FFE0
      栈地址    栈内容
    ┏━━━━┳━━━━┓
    ┃0012FFE8┃00000000┃▲
    ┣━━━━╋━━━━┫┃
    ┃0012FFEC┃00000000┃┃由
    ┣━━━━╋━━━━┫┃高
  ->┃0012FFF0┃00000000┃┃向
    ┣━━━━╋━━━━┫┃低
    ┃0012FFF4┃00000000┃┃增
    ┣━━━━╋━━━━┫┃长
    ┃0012FFF8┃00000000┃┃
    ┣━━━━╋━━━━┫┃
    ┃0012FFFC┃00000000┃┃
    ┗━━━━┻━━━━┛


    我们应该还记得在进入一个CALL后,其ESP指向的地址为这个CALL的返回地址,如果在调用这个CALL之前压入了一个参数,那么要找到它的方法很定是将当前ESP加上4在取内容,为了更利于各位读者理解这里我为各位举一个比较典型的例子:

…………  …………
00400010  jmp 00400200h
…………  …………
00400100  mov eax,[esp+4] 
00400104  mov [esp-4],eax  ; 将参数2的值传给局部变量1
00400108  mov eax,[esp+8]
0040010C  mov [esp-8],eax  ; 将参数1的值传给局部变量2
00400110  retn             ; 假如我们此时停到这里!EIP = 00400110
…………  …………
00400200  push 11          ; 参数1
00400202  push 22          ; 参数2
00400204  call 00400100
00400209  …………

    它的栈结构大致如下:

        ESP = 0012FFE4
      栈地址    栈内容
    ┏━━━━┳━━━━┓
    ┃0012FFE8┃00000000┃▲
    ┣━━━━╋━━━━┫┃
    ┃0012FFEC┃00000011┃┃由  <- 局部变量2
    ┣━━━━╋━━━━┫┃高
    ┃0012FFF0┃00000022┃┃向  <- 局部变量1
    ┣━━━━╋━━━━┫┃低
  ->┃0012FFF4┃00400209┃┃增  <- 返回地址
    ┣━━━━╋━━━━┫┃长
    ┃0012FFF8┃00000022┃┃    <- 参数2
    ┣━━━━╋━━━━┫┃
    ┃0012FFFC┃00000011┃┃    <- 参数1
    ┗━━━━┻━━━━┛

      本小节就算是又带领大家又大致温习了一下一些基础知识,现在我们回到主题,看看怎样达到我们的目的,我想这应该是很简单的事情了,我目前想出了以下方案:

(1) 将00401005处的JZ改为JMP
(2) 将004020F4处的文本信息改为Hello everybody!
(3) 交给各位完成(提示:与本小节所讲内容相关)【注:谢绝各位大牛们公布答案-_-!】

      我将在发布下一节教程时公布答案……

答案:
     各位回答的都很不错,不管用的是什么方法,只要解决问题了就是好方法。我在这里就在公布另一个方法,此方法与本节课程联系较大:
第一步、00401007至00401012填充为nop
第二步、在00401007处加上一句push 00401018即可,这样当它执行retn指令时就会返回到00401018处。


【返回到目录】:http://bbs.pediy.com/showthread.php?t=113689

  • 标 题:答复
  • 作 者:lonkil
  • 时 间:2010-05-27 13:29:28

跟着楼主教程步伐学习一下。

我的解法

方法一:
在CRT调用main入口处将最后一个压栈值改成0,也就是ESP+4那个参数从1改成0压入。

;修改前
00401103  |.  50            push    eax
00401104  |.  FF35 30994000 push    dword ptr [409930]
0040110A      FF35 2C994000 push    dword ptr [40992C]
00401110  |.  E8 EBFEFFFF   call    00401000;main 函数

;修改后
00401103  |.  50            push    eax
00401104  |.  FF35 30994000 push    dword ptr [409930]
0040110A      6A 00         push    0
0040110C      90            nop
0040110D      90            nop
0040110E      90            nop
0040110F      90            nop
00401110  |.  E8 EBFEFFFF   call    00401000;main函数


方法二:

调用此程序时加一个任意参数,哈哈。

另,楼主在画栈示意图的时候,可能由于从栈底复制上的去的原因,笔误了。

   栈地址    栈内容
    ┏━━━━┳━━━━┓
    ┃0012FFE8┃00000000┃▲
    ┣━━━━╋━━━━┫┃
    ┃0012FFEC┃00000011┃┃由  <- 局部变量2
    ┣━━━━╋━━━━┫┃高
    ┃0012FFE0┃00000022┃┃向  <- 局部变量1
    ┣━━━━╋━━━━┫┃低
  ->┃0012FFF4┃00400209┃┃增  <- 返回地址
    ┣━━━━╋━━━━┫┃长
    ┃0012FFF8┃00000022┃┃    <- 参数2
    ┣━━━━╋━━━━┫┃
    ┃0012FFFC┃00000011┃┃    <- 参数1
    ┗━━━━┻━━━━┛

最顶层的两个0012FFE8 和 0012FFEC 不是递减的了。


期待其他同学的新奇答案。

  • 标 题:答复
  • 作 者:yasm
  • 时 间:2010-05-27 21:40:31

我也是小菜,借此机会,刚好拾起汇编看看。我这个main跟你的有一些区别,额。

代码:
00401000  mov     eax, dword ptr [esp+4]
00401004  test    eax, eax
00401006  je      short 00401018
00401008  push    00407044                         ;  ASCII "Hello world!",CR,LF
0040100D  call    00401030
00401012  add     esp, 4
00401015  xor     eax, eax
00401017  retn
00401018  push    00407030                         ;  ASCII "Hello everybody!",CR,LF
0040101D  call    00401030
00401022  add     esp, 4
00401025  xor     eax, eax
00401027  retn
方法一:00401004  test    eax, eax 修改为sbb eax,eax
方法二:00401006  je      short 00401018 修改为 jne short 00401018
方法三:push    00407044 和 push    00407030这两句对调

都比较傻,不要鄙视。

  • 标 题:答复
  • 作 者:zhchchqihu
  • 时 间:2010-05-28 11:30:25

我是小菜菜~!借此机会跟着楼主学习,我的解法是:
方法一:直接将内存00407044 的Hello world!改为Hello everybody!

00401000  /$  8B4424 04     MOV EAX,DWORD PTR SS:[ESP+4]     //当前ESP=0012FF84
00401004  |.  85C0          TEST EAX,EAX
00401006  |.  74 10         JE SHORT reales.00401018
00401008      68 44704000   PUSH reales.00407044                     ;  ASCII "Hello world!
"
0040100D  |.  E8 1E000000   CALL reales.00401030
00401012  |.  83C4 04       ADD ESP,4
00401015  |.  33C0          XOR EAX,EAX
00401017  |.  C3            RETN
00401018  |>  68 30704000   PUSH reales.00407030                     ;  ASCII "Hello everybody!
"
0040101D  |.  E8 0E000000   CALL reales.00401030
00401022  |.  83C4 04       ADD ESP,4
00401025  |.  33C0          XOR EAX,EAX
00401027  \.  C3            RETN

方法二:将ESP+4(ESP=0012FF84)处的内容改为0(程序在执行时会将自己的路径保存到argv字符数组里的,因此argc的值肯定都是始终大于1,那么argc参数的值改为0,也达到同样的效果)

各位见笑了~!

  • 标 题:答复
  • 作 者:wswm
  • 时 间:2010-05-30 09:38:46

整理整理  只是为了理清下思路:
原程序:
00401000  CMP DWORD PTR SS:[ESP+4], 0
00401005  JE SHORT Test_0.00401018
00401007  PUSH Test_0.004020F4                     ; /format = "Hello world!
0040100C  CALL DWORD PTR DS:[<&MSVCR90.printf>]    ; \printf
00401012  ADD ESP, 4
00401015  XOR EAX, EAX
00401017  RETN
00401018  PUSH Test_0.00402104                     ; /format = "Hello everybody!
0040101D  CALL DWORD PTR DS:[<&MSVCR90.printf>]    ; \printf
00401023  ADD ESP, 4
00401026  XOR EAX, EAX
00401028  RETN
修改方法:
        1.如lz所说 JE SHORT Test_0.00401018// 改jmp .......
            2.修改内存004020F4地址的文本信息为“Hello everybody!”
        3.00401007至00401012填充为nop 然后在00401007处加上一句mov [esp],00401018即可,这样当它执行retn指令时就会返回到00401018处(这个有点巧妙!)
             4.就像 yasm  所说 PUSH Test_0.004020F4 与PUSH Test_0.00402104    对换
不知道还有什么巧妙的方法没?

  • 标 题:答复
  • 作 者:zzycqok
  • 时 间:2010-06-01 15:49:06

和楼主学习了很多知识,谢谢。支持楼主再写。
ps:作者的答案,有一点不解
答案:
     各位回答的都很不错,不管用的是什么方法,只要解决问题了就是好方法。我在这里就在公布另一个方法,此方法与本节课程联系较大:
第一步、00401007至00401012填充为nop
第二步、在00401007处加上一句mov [esp],00401018即可,这样当它执行retn指令时就会返回到00401018处。

在这个第二步中,在00401007处加上一句mov [esp],00401018,确实可以执行到00401018处,但是从00401018调用处返回时,即在00401028处返回时,栈已经是乱的了,个人想法是在00401007这句指令是否应该改成push    401018更好,这样在00401028返回时可以再返回原来调用main函数的地方,即401192处,再执行exit。
个人见解,请各位老鸟指正。

  • 标 题:答复
  • 作 者:AOnePass
  • 时 间:2010-06-02 00:57:32

引用:
最初由 zzycqok发布 查看帖子
和楼主学习了很多知识,谢谢。支持楼主再写。
ps:作者的答案,有一点不解
答案:
     各位回答的都很不错,不管用的是什么方法,只要解决问题了就是好方法。我在这里就在公布另一个方法,此方法与本节课程联系较大:
第一步、00401007至00401012填充为nop
第二步、在00401...
我们先看原来的反汇编代码:
00401007  PUSH Test_0.004020F4                     ; /format = "Hello world!
0040100C  CALL DWORD PTR DS:[<&MSVCR90.printf>]    ; \printf
00401012  ADD ESP, 4

我们如果将其nop掉之后,main函数内的唯一一个影响堆栈的操作就已经没有了,所以我们此时只需要修改一下返回地址就可以。

如果按照你的方法,会在返回地址前压入一个你想要的返回地址,此时的栈才真正的乱了。

  • 标 题:答复
  • 作 者:zzycqok
  • 时 间:2010-06-02 09:37:05

感谢你的回帖。
我还是没有明白。
我贴一下我改完后的代码,加了点注释,请指教。
00401000    837C24 04 00    cmp     dword ptr [esp+4], 0
00401005    74 11           je      short 00401018
00401007    68 18104000     push    00401018   //从这里开始改的,push前栈顶是main函数调用后要返回的地址00401192,再压入一个00401018 
0040100C    90              nop
0040100D    90              nop
0040100E    90              nop
0040100F    90              nop
00401010    90              nop
00401011    90              nop
00401012    90              nop
00401013    90              nop
00401014    90              nop
00401015    33C0            xor     eax, eax
00401017    C3              retn    //这里的retn,返回调用00401018处代码,栈顶再变成00401192
00401018    68 04214000     push    00402104                         ; ASCII "Hello everybody!",CR,LF
0040101D    FF15 A0204000   call    dword ptr [<&MSVCR90.printf>]    ; MSVCR90.printf
00401023    83C4 04         add     esp, 4
00401026    33C0            xor     eax, eax
00401028    C3              retn  //根据栈顶,返回00401192,调用main函数的后一句代码