使用免费工具进行逆向和利用:第13部分
原作者:Ricardo Narvaja
翻译作者:梦幻的彼岸
更新日期:2022年1月21日
64位练习的解决方案
今天我开始了第13部分,添加了shellcode,分析并调整了一个64位的解决方案。
这个shellcode不是我的,我打算解释一下,但它是很公开的,它适合于本案例。
完整解决方案脚本
以下是运行NOTEPAD的完整解决方案,只需稍作更改。
让我们解释一下它的工作原理:
在这里,我们看到了迄今为止存在的部分解决方案的补充部分。
上面是带有解析器的shellcode,我们将在后面解释。
有一个gadget可以改变
rop+=struct.pack("<Q",0x1400060b7)# ADD RAX, 0x20 # CHANGED to have more space
这个gadget在跳转运行前给RAX增加了0x28,我把它改成了一个类似的gadget,但增加了0x20,以免浪费空间,因为空间很小。
请记住,memcpy不会将我们发送的所有内容复制到VirtualAlloc堆栈中的保留空间。
我们看到它只分配和复制了0x64字节,所以最好是避免浪费空间。
我们看到一个由我制作的代码,memmove将复制在堆中创建的保留区中,它将为执行最终的shellcode准备一切,我们将在执行时看到它。
而shellcode在堆栈中的ROP下面没有执行权限,你能告诉我为什么不把shellcode放在堆里,而直接把那段代码放在堆里执行。
答案是,堆中的空间不适合,我们已经看到它只保留0x64字节,最重要的是,我们跳过时丢失了前0x20字节,剩下的0x44字节不属于这个小空间。
让我们运行它并解释一下。
我运行脚本,它在我放置的断点处停止。
在那里我分配0x64字节。
我们看到它实际上分配了超过0x64个字节,问题是它严格地只复制0x64个字节。
在那里,我们不要忘记,rop从一开始就中断了几个字节,要跳转我必须避开它们,我唯一找到的gadget是add rax, 20,这给我留下了很少的shellcode空间。
跟踪shellcode直到它到达VirtualAlloc。
我带着正确的参数到达那里,我可以转到 RET,把光标放在那里,按F4键,这样我就不会跟踪太多了。
这就是GADGET,在RAX中加入0x20来跳转执行,避免了开始时被中断的字节。
然后我们跳转到执行。
你可以看到我做的代码,它的作用是在堆栈中寻找shellcode,并在下面复制它,因为有地方,memmove在复制时没有完整地复制它,因为size=0x64的限制,但我是可以完整地复制它。
RSI tendr el source
RDI el destino
RCX el size a copiar en dwords
当我到达REP时,MOVSD复制了shellcode,我做了一个PUSH RDI来保存我复制的目的地的地址,这个地址被留在RDI中。
我可以把光标放在那里按F4。
我们看到,它将以NOTEPAD字符串作为参数调用WinExec,该参数将执行NOTEPAD。
好吧,我有shellcode可以运行,我只是需要解释一下,在x64dbg中不是很好,所以我将运行脚本,并用Windbg给我附上,这样我可以看到需要的结构和符号,即使在我给它RUN之前,我验证它运行一个NOTEPAD。
我们看到,NOTEPAD运行后进入ExitProcess并关闭。
如果我在没有调试的情况下运行它,我看到它运行NOTEPAD并正确关闭。
好吧,我打算用Windbg运行它来追踪RESOLVER。
在那之后
.reload /f
并在它完成下载所有的符号后。
lm
好
吧,我有了这些符号。
我在那里设置了断点。
现在要追溯到shellcode,我可以在VirtualAlloc中设置一个断点
bp KERNELBASE!VirtualAlloc
我用G运行并接受MessageBoxA,到VirtualAlloc在启动时被程序调用时,我继续用G。
再一次是 VirtualAlloc,但我们必须首先在 RET 停止,值得一提的是 VirtualAlloc 的下一站,我们继续,
我就停在那儿
再次按g键,现在如果在VirtualAlloc中,shift+F11是STEP OUT,则在RET之后立即退出函数。
用f11进行追踪。
我们到达了我的代码。
我一直用F10追踪,以传递REP MOVS,这样它就不会重复。
这里开始了SHELLCODE的解决办法,
通过找到KERNEL32的基地址来解决64位问题
CDQ:如果SF符号标志为零,则将RDX设为零,可能是XOR RDX, RDX
它是零,所以说RDX=0。
回顾一下,在32位中,TEB或TIB是由FS指向的。
64位中,GS寄存器被用于TEB。
在32位中,我们能够使用dg fs命令来查看FS值。
它对GS不起作用。
也许我们有更多的窍门,嘿嘿,还有"!teb"命令。
在这里我们看到TEB的内容和它在我的机器上的基数0x21f000和我的机器上的PEB的地址0x21e000。
如果我追踪第一条指令,它正在从字段0x60读取PEB。
mov rax, qword ptr gs:[rdx+60h]
由于我知道我的机器上的TEB地址是0x21f000,我可以使用dt命令并更好地看到它。
dt nt!_TEB 0x21f000
另外,我还有一个链接显示我需要的GDP。
第二条指令是:
读取PEB偏移量0x18中的字段,因为我可以单击链接并查看PEB列表。
Lee PEB->Ldr
好吧,让我们继续。
我们可以单击LDR或listar _ peb _ ldr _ data
请注意,在0x20偏移处,inmemoryordermodulelist加载
Microsoft 上面写着
在一些网页中(以及我们做32位RESOLVER部分时),LDR_DATA_TABLE_ENTRY也被称为LDR_MODULE,它更短,但相同。
叫它LDR_DATA_TABLE_ENTRY还是很方便的,因为这样它就被列在Windbg中。
正如我们所看到的,第一个字段的类型是TYPE_LIST_ENTRY,正如文档所说,它具有指向与下一个模块相对应的类似结构的Flink,即一个链表。
因此,正如我们在图片中所看到的,这些结构是通过FLINK和BLINK相互连接的,因为FLINK是一个指向下一个结构的指针,只有找到它的内容,我们才能得到下一个结构的FLINK。
mov rsi, [rax + 0x20]
所以在这条指令中,它加载了RSI=InMemoryOrderModuleList,正如我们看到的,它是链接列表的开始,反过来属于LDR_DATA_TABLE_ENTRY链的第一个模块。
那些学习了32位练习教程的人应该记得,第一个InLoadOrderLinks字段是在那里使用的,两个列表都有关于模块的相同信息,它只会改变它们所在的顺序,在本例中,我们不是像InLoadOrderLinks那样位于结构的偏移量0x0,而是我们的Flink始终位于偏移量0x10。
RSI在第一个LDR_DATA_TABLE_ENTRY的0x10偏移处,在我的机器上是0x046262fa0。
我可以在Windbg中列出。
在这里我们看到,我们在偏移量0x10处,所以我们必须减去0x10来列出结构。
dt LDR_DATA_TABLE_ENTRY (0x0462fa0 -0x10)
我们看到它对应的是可执行模块,它总是在链中的第一个,在那里我们看到它的基地址和它的名字,我们也看到FLINK到第二个模块的结构。
这是通过查找ESI内容以编程方式完成的,因为LODS语句读取ESI内容并将其移动到EAX。
lodsqword ptr [rsi] ds:0x00462fa0=0x0462e10
EAX再次位于第二个结构的0x10偏移处,我们可以看一下对应的模块。
我们看到第二个LDR_DATA_TABLE_ENTRY对应的是ntdll.dll,第三个被FLINK指向的将是0x463460。
然后用XCHG把它从EAX移到ESI。
然后用LODS再次找到内容,当然它匹配的将是0x463460。
它对应于kernel32.dll,由于EAX被定位在偏移量0x10,要读取kernel32.dll的基数,你必须加上0x20,才能到达它所在的0x30。
有了这个,他已经找到了 kernel32.dll基数 ,这是第一个要寻找的目标。
查找WINEXEC的地址
一旦找到 Kernel32.dll 的基数 ,在 kernel32 中找到 WinExec 或我们想要的函数的步骤如下。
这是我们要追踪的代码的一部分,以检查一切是否匹配。
标头部开始的结构,当然是在我们找到的kernel32.dll图像库的地址处,被称为_IMAGE_DOS_HEADER,在那里我们看到了MZ的特征,这两个字节是DOS可执行文件的开头。
请注意,shellcode读取0x3c偏移字段
值为232十进制或0x 8是_IMAGE_NT_HEADERS64的偏移量
将基地址相加以获得地址。
RDX中的地址为_IMAGE_NT_HEADERS64
然后寻找字段0x88,我们看到它在OptionalHeader里面,它的位置是0x18。
在0x70是_IMAGE_DATA_DIRECTORY64加上0x18的_IMAGE_OPTIONAL_HEADER64,我们在0x88的时候,shellcode读到了。
这是一个_IMAGE_DATA_DIRECTORY的ARRAY。
第一个是到EXPORT表的偏移量,其第一个字段是到地址的偏移量。
然后加上基数,得到EXPORT TABLE的地址。
读取0x20偏移地址。
因此,在0x1c中有三个addressofnames数组,在0x20中有addressofnames数组,在0x24中有AddressOfNames序数数组。
所以RSI有命名表或数组。
为了找到表中的每一个条目,你必须加上基数,这就是它所做的,它在表中读取偏移量并加上基数,然后与WinExec进行比较(如果你想要另一个函数,你必须在这里改成你想找到的那个函数的名称)。
让我们来看看表中的第一个指向什么。
因此,这些偏移中的每一个加上base都指向一个导出函数的名称,因此它循环遍历表,将每个字符串与winexec进行比较。
我们可以在JNE后面加一个BREAKPOINT,当它找到这个名字时按RUN停止。
当它停止时。
我们看到,它正在增加RCX,这是表的索引,所以WinExec在表中的位置是RCX=0x60e。
回顾一下,我们在r8上加上0x20,然后再加上基数,就可以找到名字表,如果我们加上0x24,再加上基数,就可以找到序数表。
在非循环序数表中,使用名称表位置的RCX中的索引值,我们从该表中读取函数的编号。
它也是0x60e,在最后一个表格中用于查找WinExec函数的偏移。
因此,RSI+RCX*4为我们提供了WinExec偏移量。
我们在其中加上基数,我们就有了WinExec的虚拟地址。
调用winexec
就这样,然后设置一个NOTEPAD字符串来传递,跳转到运行WinExec的 "NOTEPAD "参数。
然后,如果我们用f10传递调用并继续,我们就调用ExitProcess来关闭它。
在那里,NOTEPAD一直在运行。
好了,64位解决方案的情况到此为止。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论