使用免费工具进行逆向和利用:第15部分
原作者:Ricardo Narvaja
翻译作者:梦幻的彼岸
更新日期:2022年2月1日
正如我们在第14部分中所看到的,这个rop将不会像我们以前看到的那样简单。
使用RADARE进行逆向分析练习2--32位
我们打开我们保存的项目,每个命令的解释在第14部分。
打开已保存的项目 (po)
r2 ConsoleApplication9.exe
Po ROP32
afl ~main
s pdb._main
pdf
重命名功能(AFN)
现在我们又到了main,我将重新命名pdb._main和pdb._fcp,p main和f,这样更容易访问。
对命令的疑问(?)
如果你对某个命令有任何疑问,在它后面加上"?",我们就能得到帮助。
用afn我将重命名我所在的函数,因为我在pdb.main中用afn main它将被重命名。
然后我将用s pdb._f进入pdb._f,用afn f重命名。
我们看到你将其重命名为fcn.f.
我们以v进入视觉模式
我们看到我们通过按键: 按Ps ROP32保存的重命名的函数。
浏览菜单 (M)
我们看到,通过M键和方向键,我们可以浏览菜单
我所选择的是标有颜色的,但还有很多
我们可以在可视模式下配置要显示的面板以及大小,例如:
我发现在我的例子中,功能面板和符号太大了,我们可以添加和删除面板,扩大我们想要的面板,并保存布局。
例如,要放大反汇编面板,我们可以使用w键进入窗口模式。
窗口模式(w)
让我们知道我们处于窗口模式。
用突出显示的箭头显示我想要此资源的面板,我可以更改它的大小。
然后用Shift+H,J,K或L的组合,我可以放大或缩小,在我的例子中,我想把它放大到右边,所以我会重复用Shift+L。
那里看起来更好,我可以看看我可以添加哪些窗口。
我用w退出WINDOW模式,用M进入菜单,转到VIEW。
我可以添加一个反编译窗口
我只能按ENTER键聚焦此面板
如果我把main放在反编译器的列表视图中,main也会显示在反编译器中。
如果我想把它从面板上移走,我会用W进入窗口模式,然后用SHIFT+X。
搜索
如果我们回到菜单,我们会看到一个字符串的搜索。
让我们看看他找到了什么。
在这里我发现了一个可以克服的错误,但是当你重新打开一个项目的时候,有些东西和你第一次运行的时候不一样,我们看到的是字符串的地址,你必须把基地址加到其中才能得到真正的地址。
Python>hex(0x510000+ 0x1a008)
'0x52a008'
而如果我在另一个控制台中打开它而不加载项目,我就会加载符号,进行解析,然后去同一个菜单中寻找字符串。
在将项目主题固定到搜索中之前,我们可以看到比较,基本图像必须添加到搜索中,而直接打开并分析它是不必要的,引用和标记也可以很好地工作。
所以我再次打开它,我可以复制并粘贴所有后续的命令到一个控制台上,然后一次执行所有后续的命令,所以如果我复制所有我执行的命令(但是看看项目的rc文件,它们就保存在那里了)
C:\Users\XXXXX\.local\share\radare2\projects\\ROP32
还有一个命令可以查看您运行的命令的历史记录。
让我们假设,如果我复制重要的命令并将它们粘贴到控制台中,如果我按当时执行它们的顺序放置它们,它们就会一个接一个地执行,而不提示,一切都是一样的,所以在它们修复项目主题之前,我们必须保存我们执行的命令列表。
r2 ConsoleApplication9.exe
idp ConsoleApplication9.pdb
aaa
afvb -1032 buffer int32_t @ 0x511040
afvb -8 pbuffer int32_t @ 0x511040
afvb -1 temp_char char @ 0x511040
s pdb._main
afn main
s pdb._f
afn f
eco bright
pdf
我们看到,它被留在以前,现在我可以用v进入。
现在菜单中的一切匹配也要搜索ROP GADGETS、HEXA CHAINS等。现在从控制台搜索有很好的机会。
找到引用
我们看到各种各样的搜索,尽管对于找到引用来说,/命令不是那么快,如果我输入/calc,它就会找到它。
我们看到它显示了字符串calc的地址,它位于整个字符串 “A ejecutar la calculadora”的中间,问题是如果我寻找calc的引用,它不会有,而整个字符串的起始地址却有,使用这个其他命令izz。
我们可以看到,即使我搜索的是calc,整个字符串也被设置了,并返回起始地址0x52a008,从这里我可以很容易地用axt命令找到代码中的引用。
在这里,我们看到了找到引用的所有可能性。
在那里我们看到在main的地址0x51109a,它使用了这个字符串,我可以在listing中看到它,方法是进入v,然后突出listing面板,按g并输入地址。
并显示列表中的字符串。
如果我们想搜索十六进制字节序列,我们进行如下操作
这里不需要搜索引用,结果在代码中,我使用v,然后g,在偏移量中输入我想要去的地址
可以使用冒号作为通配符进行搜索。
我们看到我可以用地址或标签进入序列
寻找GADGET
我可以搜索gadgets来创建ROP。
在我的案例中,我设置了gadget的长度为2。
e.rop.len
然后是命令
/R comando1 ; comando2; etc ~..
任何列表末尾的修饰符(~...)使得用回车键和pg向上、pg向下滚动成为可能,避免了长的无法阅读的列表。
我可以用ENTER或PG DOWN继续向下列举。
将长度改为3
e.rop.len =3
你可以用/R/命令使用regexp来查找gadget
/R/ pop e..; ret ~..
将搜索所有以e开头的pops gadget
您可以找到并搜索多个组合,我们当然会使用它们来解决本练习中的rop。
我们已经知道如何搜索gadget了,现在让我们来分析一下以创建脚本。
在此,我们可以通过按/键并键入pbuffer变量的名称来查看其使用位置。
另外,在视觉模式下,当我在图形模式下反复按p键时,我可以改变视觉化的方式,我最喜欢的是这个方式。
更加清晰了。
如果你继续按p,还有其他显示,你可以在某些时候使用。
它显示一个方框图,只标记CALL指令。
静态逆向
正如我们在下面分析的那样,buffer是变量pbuffer,它存储了缓冲区的地址,它是一个指针类型的变量。
我们可以通过命令t看到类型,我们添加了~。才能滚动查看结果。
Pbuffer变量是char类型*,我们可以用以下命令更改它
afvt pbuffer char *
或者如果我们更喜欢PCHAR
afvt pbuffer PCHAR
由于缓冲区的长度是0x400,因为缓冲区和下面的变量pbuffer相减后得到了这个距离,我们可以把它改名为
afvt buffer char[1024]
现在它更类似于每个变量的实际内容。
afvf命令向我显示了堆栈的静态位置,我们知道保存的ebp有4个字节长,返回地址也是4个字节长。(我需要在变量前面加上减号,我会加上的)。
-0x00000408buffer: char[1024]
-0x00000008pbuffer: char *
-0x00000001temp_char: char
0 stored ebp
+4 return address
因此我们看到,从缓冲区到返回地址的距离,我们有一个1024字节的缓冲区变量,然后是一个4字节的buffer变量,3个空字节,1字节的temp_char和4个存储的ebp。
1024(buffer) + 4 (pbuffer) + 3 (vacio) +1(temp_char) + 4 (stored ebp)= 1036
这就是我们应该在返回地址之前填充缓冲区的内容,所以我们的脚本应该是这样的。
但我们再看一下,问题是
1)我们要用这个来单步到返回地址吗?
2)我们在没有破坏任何东西的情况下到达retn ?
好吧,我们看到有一个无限循环,它有某种形式的输出。
在getchar里面读取一个字符并保存在temp_char中,与0x40进行比较,如果相等,则按绿色箭头的true退出循环,如果不等于0x40,则继续与0x10进行比较,如果不相等,则继续循环,这就等于说,如果等于0x10,则退出
如果它仍然在循环内(它不允许循环内的字符是0x40或0x10,正如我们所看到的),它将其保存在ECX的内容中,ECX有缓冲区的pbuffer地址,并指向缓冲区的开始。
保存后,它增加了buffer,以便在下一个循环保存下一个字符。
这似乎一切都是正确的,这意味着如果我们在你点击后加上一个0x40,返回的地址就会出来。
请记住,由于我们在Python 3中将b放在每个字节字符串的前面,在这种情况下,它将是b“\ x40”
我们可以用0x41424344单步到返回地址,然后放入一些字节,然后用0x40退出。
但是,它们并不都是flores ,还有一些问题。
回顾一下,在buffer下面,有一个变量pbuffer,它将在步入返回地址之前被步入。
这意味着如果我以0x41414141为例对其进行单步,例如,在下一个循环中,我将尝试写入[0x41414141]
如果pbuffer使用0x4141414,我会在那里写mov byte[ECX],DL,我们就不能再使用返回地址了。
现实情况是,buffer是一次步进1个字节,所以我们可以步进最低的字节,并让它稍后写入,以跳过写入地址的其他三个字节。
让我们运行脚本并附加它以在 x64dbg 中查看它
让我们转到f函数,然后用g看到图形模式。
在这里我们有一个pbuffer的块,如果我们把硬件断点放在写上,每次递增它都会停止,所以在它缓冲的地方放一个断点没有什么意义。
演示运行并接受MessageBox。
我们可以通过ECX-FOLLOW IN DUMP观察缓冲区在每个周期内的填充情况。
找到pbuffer的位置,它应该在下面。
在下一行中,从0x19ff14读取,在那里变量保存从1递增的缓冲区地址。
所以当你单步到0x19ff14时,我们将改变它在下一个循环中复制的地址。
我们可以将硬件按上述方向写入,这样它就不会在每个周期停止,就像我们将硬件放在同一个pbuffer中一样。
我们移除之前设置的断点,只留下这个断点,这样当缓冲区满了时,它就会停止,而这个断点正好要到达指针,让我们运行。
在这里,它将跳过前面的4个字节,然后它将跳过pbuffer的最后一个字节,如果我们让最后一个字节为0x18,下一次它将写入0x19ff18,并跳过顶部的3个字节。
pbuffer下面有多少字节?
也就是说,我们应该写1024字节,然后是0x18,然后我们将停留在返回地址,因为我们继续写在pbuffer下面,我们有3个空字节的temp_char字节,和4个存储的ebp。
3 + 1+ 4 = 8 bytes
让我们看看情况是否如此
它到达地址的低字节,因此在下一个循环中它应该写入 0x19ff18 并且不触及地址的剩余 3 个字节让我们继续循环跟踪。
我在纠结一个问题
在步过它之后增加指针,所以我们要么用0x17步过它,要么我们运行一个完整的字节,我最好用 0x17 来步过它。
让我们再试一次。
复制0x17让我们继续用F8跟踪循环。
现在,如果你从0x19FF18开始写0x24,让我们通过删除所有断点来到达RET。
开始ropear的基本脚本。
```python
!/usr/bin/env python
-- coding: utf-8 --
import sys
from subprocess import Popen, PIPE
import struct
import sys
import codecs
import random
import string
payload = b"A" * 1024 + b"\x17" + 8* b"B"+ struct.pack("<L", 0x41424344) + "ABCDEFG" + b"\x40"
p1 = Popen(r"ConsoleApplication9.exe", stdin=PIPE)
print("PID: %s" %hex(p1.pid))
print("Enter para continuar")
p1.communicate(payload)
p1.wait()
```
现在我们已经准备好了,我们将在下一个部分进行。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论