-
Game - pwnable.xyzWrite-ups/pwnable.xyz 2020. 2. 12. 21:33
Prob Info
Prob Info Checksec int __cdecl __noreturn main(int argc, const char **argv, const char **envp) { const char *v3; // rdi signed int v4; // eax setup(); v3 = "Shell we play a game?"; puts("Shell we play a game?"); init_game(); while ( 1 ) { while ( 1 ) { print_menu(v3, argv); v3 = "> "; printf("> "); v4 = read_int32(); if ( v4 != 1 ) break; (*((void (**)(void))cur + 3))(); } if ( v4 > 1 ) { if ( v4 == 2 ) { save_game(); } else { if ( v4 != 3 ) goto LABEL_13; edit_name(); } } else { if ( !v4 ) exit(1); LABEL_13: v3 = "Invalid"; puts("Invalid"); } } }
init_game()char *init_game() { char *result; // rax saves = (__int64)malloc(0x20uLL); cur = (char *)find_last_save(32LL); printf("Name: "); read(0, cur, 0x10uLL); result = cur; *((_QWORD *)cur + 3) = play_game; return result; }
saves에 malloc으로 힙을 할당하고 find_last_save함수로 마지막 힙을 cur에 저장한다. 이후 cur에 이름을 0x10바이트 입력받고 힙의 마지막 8바이트에 play_game함수의 주소를 넣는다.
bss
.bss:0000000000602100 ; __int64 saves[5] .bss:0000000000602100 saves dq ? ; DATA XREF: find_last_save+1E↑o .bss:0000000000602100 ; find_last_save+3E↑o ... .bss:0000000000602108 dq ? .bss:0000000000602110 dq ? .bss:0000000000602118 dq ? .bss:0000000000602120 dq ?
init_game 함수가 끝나면 아래와 같다.
pwndbg> x/10gx &cur 0x6020e0 <cur>: 0x0000000000603010 0x0000000000000000 0x6020f0: 0x0000000000000000 0x0000000000000000 0x602100 <saves>: 0x0000000000603010 0x0000000000000000 0x602110 <saves+16>: 0x0000000000000000 0x0000000000000000 0x602120 <saves+32>: 0x0000000000000000 0x0000000000000000
pwndbg> x/6gx 0x603000 0x603000: 0x0000000000000000 0x0000000000000031 0x603010: 0x6161616161616161 0x6161616161616161 0x603020: 0x0000000000000000 0x0000000000400aca
이를 기준으로 간단하게 구조체를 만들었다.
00000000 game struc ; (sizeof=0x20, align=0x8, copyof_6) 00000000 name db 16 dup(?) 00000010 score dq ? 00000018 f_play dq ? ; offset 00000020 game ends
구조체에 함수 포인터가 있으므로 이것을 덮으면 된다.
ssize_t edit_name() { size_t v0; // rax v0 = strlen(cur->name); return read(0, cur, v0); }
현재 게임의 이름 부분을 strlen으로 길이를 구해 그만큼 read로 입력받는다. 함수포인터를 덮어야 하는데, 함수 포인터와 name 사이에 socre가 존재해 strlen을 이용하면 함수포인터를 덮을 수 없다. 일단 play_game함수를 보자.
unsigned __int64 play_game() { __int16 v0; // dx __int16 v1; // dx __int16 v2; // dx __int16 v3; // dx int fd; // [rsp+Ch] [rbp-124h] int v6; // [rsp+10h] [rbp-120h] unsigned int buf; // [rsp+14h] [rbp-11Ch] unsigned int v8; // [rsp+18h] [rbp-118h] unsigned __int8 v9; // [rsp+1Ch] [rbp-114h] char s; // [rsp+20h] [rbp-110h] unsigned __int64 v11; // [rsp+128h] [rbp-8h] v11 = __readfsqword(0x28u); fd = open("/dev/urandom", 0); if ( fd == -1 ) { puts("Can't open /dev/urandom"); exit(1); } read(fd, &buf, 0xCuLL); close(fd); v9 &= 3u; memset(&s, 0, 0x100uLL); snprintf(&s, 0x100uLL, "%u %c %u = ", buf, ops[v9], v8); printf("%s", &s); v6 = read_int32(); if ( v9 == 1 ) { if ( buf - v8 == v6 ) v1 = LOWORD(cur->score) + 1; else v1 = LOWORD(cur->score) - 1; LOWORD(cur->score) = v1; } else if ( v9 > 1 ) { if ( v9 == 2 ) { if ( buf / v8 == v6 ) v2 = LOWORD(cur->score) + 1; else v2 = LOWORD(cur->score) - 1; LOWORD(cur->score) = v2; } else if ( v9 == 3 ) { if ( v8 * buf == v6 ) v3 = LOWORD(cur->score) + 1; else v3 = LOWORD(cur->score) - 1; LOWORD(cur->score) = v3; } } else if ( !v9 ) { if ( v8 + buf == v6 ) v0 = LOWORD(cur->score) + 1; else v0 = LOWORD(cur->score) - 1; LOWORD(cur->score) = v0; } return __readfsqword(0x28u) ^ v11; }
내용이 많은데, 이중에서 그냥 score가 -1점이 되는 부분이 존재하는것만 확인했다. 0점에서 -1점을 하면 0xffffffffffffffff이 될 줄 알았는데 잘 보니 LOWORD, 즉 하위 2바이트만 사용해 0xffff가 된다. 이 함수 외에는 score를 건들 수 없으니 다음 함수를 살펴봤다.
int save_game() { game *v0; // rcx __int64 v1; // rdx __int64 v2; // rdx game *v3; // rax signed int i; // [rsp+Ch] [rbp-4h] for ( i = 1; i <= 4; ++i ) { if ( !saves[i] ) { saves[i] = malloc(0x20uLL); v0 = saves[i]; v1 = *&cur->name[8]; *v0->name = *cur->name; *&v0->name[8] = v1; saves[i]->score = SLOWORD(cur->score); saves[i]->f_play = play_game; v2 = i; v3 = saves[v2]; cur = saves[v2]; return v3; } } LODWORD(v3) = puts("Not enough space."); return v3; }
saves에서 빈칸을 찾고 그곳에 새로운 힙을 저장하고 새로은 힙에 cur의 내용을 복사한다. score를 복사하는 부분을 어셈블리어로 다시 봐봤다.
mov rax, cs:cur movzx eax, word ptr [rax+10h] movsx rax, ax mov [rdx], rax
cur->score를 eax에 넣고 (rax = 0x000000000000ffff) movsx rax, ax를 통해 rax가 0xffffffffffffffff이 된다. 이 값을 새로운 힙에 저장해 원래 원하던 값으로 만들 수 있다.
이후 edit_name을 진행하면 총 0x18 바이트 + 함수포인터 길이 만큼 덮을 수 있으므로 win 함수의 주소로 덮어주면 된다.
'Write-ups > pwnable.xyz' 카테고리의 다른 글
SUS - pwnable.xyz (0) 2020.02.13 fspoo - pwnable.xyz (0) 2020.02.13 l33t-ness - pwnable.xyz (0) 2020.02.12 Jmp table - pwnable.xyz (0) 2020.02.12 TLSv00 - pwnable.xyz (0) 2020.02.12