Write-ups/pwnable.xyz
Free Spirit - pwnable.xyz
dolphinlmg
2020. 2. 12. 02:46
Prob Info
int __cdecl main(int argc, const char **argv, const char **envp)
{
char *v3; // rdi
signed __int64 i; // rcx
int v5; // eax
char v7[8]; // [rsp+8h] [rbp-60h]
char *buf; // [rsp+10h] [rbp-58h]
char nptr[48]; // [rsp+18h] [rbp-50h]
unsigned __int64 v10; // [rsp+48h] [rbp-20h]
v10 = __readfsqword(0x28u);
setup();
buf = (char *)malloc(0x40uLL);
while ( 1 )
{
while ( 1 )
{
_printf_chk(1LL, "> ");
v3 = nptr;
for ( i = 12LL; i; --i )
{
*(_DWORD *)v3 = 0;
v3 += 4;
}
read(0, nptr, 0x30uLL);
v5 = atoi(nptr);
if ( v5 != 1 )
break;
__asm { syscall; LINUX - sys_read }
}
if ( v5 <= 1 )
break;
if ( v5 == 2 )
{
_printf_chk(1LL, "%p\n");
}
else if ( v5 == 3 )
{
if ( (unsigned int)limit <= 1 )
_mm_storeu_si128((__m128i *)v7, _mm_loadu_si128((const __m128i *)buf));
}
else
{
LABEL_16:
puts("Invalid");
}
}
if ( v5 )
goto LABEL_16;
if ( !buf )
exit(1);
free(buf);
return 0;
}
psuedocode에 나오지 않은 부분이 여럿 있다. 먼저 1번 메뉴는 syscall read인데 어셈블리어로 보면 아래와 같다.
.text:0000000000400849 mov rsi, [rsp+68h+buf] ; buf
.text:000000000040084E xor rax, rax
.text:0000000000400851 xor rdi, rdi ; fd
.text:0000000000400854 mov rdx, 20h ; count
.text:000000000040085B syscall ; LINUX - sys_read
buf에 0x20바이트 만큼 read를 한다.
2번 메뉴는 _printf_chk인데 어셈블리어로 보면 아래와 같다.
.text:000000000040085F loc_40085F: ; CODE XREF: main+80↑j
.text:000000000040085F lea rsi, aP ; "%p\n"
.text:0000000000400866 mov rdx, r12
.text:0000000000400869 mov edi, 1
.text:000000000040086E xor eax, eax
.text:0000000000400870 call __printf_chk
.text:0000000000400875 jmp short loc_4007F8
세번째 인자로 r12의 값을 주며 이전에 마지막으로 사용된 r12의 값을 찾아보면 아래와 같다.
.text:00000000004007C0 push r12
.text:00000000004007C2 push rbp
.text:00000000004007C3 xor ebp, ebp
.text:00000000004007C5 push rbx
.text:00000000004007C6 sub rsp, 50h
.text:00000000004007CA mov rax, fs:28h
.text:00000000004007D3 mov [rsp+68h+var_20], rax
.text:00000000004007D8 xor eax, eax
.text:00000000004007DA lea rbx, [rsp+68h+nptr]
.text:00000000004007DF lea r12, [rsp+68h+buf]
.text:00000000004007E4 call setup
.text:00000000004007E9 mov edi, 40h ; size
.text:00000000004007EE call malloc
.text:00000000004007F3 mov [rsp+68h+buf], rax
이 부분에서 r12의 값이 buf의 주소를 담고 있음을 알 수 있다. 1번 메뉴를 선택하면 buf의 주소를 릭 할 수 있다.
3번 메뉴도 뭔가 이상한 함수로 나와있는데 어셈블리어로 보면 다음과 같다.
.text:0000000000400884 mov rax, [rsp+68h+buf]
.text:0000000000400889 movdqu xmm0, xmmword ptr [rax]
.text:000000000040088D movdqu xmmword ptr [rsp+8], xmm0
movdqe는 16바이트를 그대로 mov하는 명령어이다. 즉 buf에 있는 16바이트를 v7에 덮어 쓴다는 것이다. v7을 16바이트 덮게 되면 그 아래에 존재하는 buf를 덮게 된다. 이후 2번 메뉴를 통해 덮은 주소에 값을 쓸 수 있다. Full RELRO가 적용되어있기 때문에 GOT는 덮지 못하지만 buf의 주소를 릭할 수 있기 때문에 그대로 ret을 덮어버리면 된다.
하지만 ret을 덮고 프로그램을 종료시키려고 하면 아래 코드에 의해 세그폴트가 뜬다.
free(buf);
buf를 ret 주소로 덮은 상태에서 종료시키면 free를 진행하면서 청크를 fastbin에 넣게 되는데 이때 청크 구조가 맞아야 한다. 이를 통과하기 위해 쓰기 가능한 영역에 fake chunk를 만들어주고 그 buf를 그 주소로 덮어주면 된다.