[Write-up]SECCON 2022 Quals babyfile write-up - 1
안녕하세요 오늘은 SECCON 2022 Quals의 Pwn 문제인 babyfile을 가져왔습니다.
해당 문제는 아래의 깃허브 주소에서 다운로드 받았습니다.
https://github.com/SECCON/SECCON2022_online_CTF
GitHub - SECCON/SECCON2022_online_CTF
Contribute to SECCON/SECCON2022_online_CTF development by creating an account on GitHub.
github.com
조금 난이도가 높은 FSOP문제라고 해서 천천히 살펴보도록 하겠습니다.
먼저 환경구성을 하겠습니다. 문제 파일 안에 docker파일과 libc파일이 모두 들어있기 때문에
docker compose up -d --build
로 파일을 빌드했고, 포트는 3157:9999로 연결되어 있습니다.

연결이 잘 되는걸 확인했습니다.
문제의 보안 옵션을 확인해보겠습니다.

FSOP문제의 대부분은 보안기법이 다 적용되어있는듯 합니다.
이제 소스코드를 확인해보겠습니다.
해당 코드는 Heap 메모리 영역에 File 구조체 (약 0xe 크기)를 할당합니다.
이 구조체 안에서는 파일 입출력을 관리하는 수많은 변수가 들어있습니다. 해당 할당된 구조체를 통해 익스플로잇을 하면 될 것 같습니다.
그 다음 메뉴 부분입니다.
case 2: 부분을 주의깊게 살펴봐야 하는데 일단 fp 구조체의 특정 offset(ofs)에 있는 값을 사용자가 입력한 값으로 덮어씁니다. 이때 주의할 점은 char형식이기 때문에 1바이트씩만 접근이 가능합니다.
그리고 0x80~0xBF인 경우 접근이 불가하게 막아두었고 강제로 0xC0부분으로 튕겨냅니다.
이것도 문제 제작자의 의도로 중간 영역에 접근을 못하게 막아뒀고 해당 바이트에 해당하는 부분은 _lock(파일 잠금 포인터)와 _wide_data입니다.
아까 위에서도 _wide_data를 NULL로 초기해뒀는데, 이건 최근 해킹 기법인 House of Kiwi와 같이 _wide_vtable을 건드려서 우회하는 걸 막기 위해서인 듯 싶습니다.
익스플로잇 세부 목표
- Libc Leak : fp 구조체를 조작하여 fflush를 호출했을 때, 구조체 안에 들어있는 libc 주소를(vtable)을 화면에 출력하도록 만듭니다.
- 해당 주소를 바탕으로 system, /bin/sh, 공격에 사용할 주소를 계산합니다.
- fp 구조체를 한번 더 조작하여 fflush 호출시 system("/bin/sh")가 실행되도록 합니다.
여기서 핵심적인 역할을 하는 fflush가 파일 구조체 포인터에 대해 어떻게 작동하는지 확인해보았습니다.
fflush(fp)가 호출되었을 때 어떤 과정을 거쳐 실행되는지 분석입니다.
- _lock 포인터 확인 : fp 구조체 내부의 _lock 포인터를 확인하여 충돌을 방지합니다. 해당 포인터는 저희가 쓰기 불가능한 영역이기 때문에 신경을 안써도 됩니다.
- 상태 및 버퍼 검사 : fp 내부의 변수들을 읽어서 지금 데이터를 써야하는 상황인지 판단합니다. 이때 확인하는 값은 _flags와 _IO_write_ptr, _IO_write_base입니다. 해당 변수들은 저번에 House of orange문제를 풀때 확인했어서 익숙하네요.
- Vtable 호출 : 데이터를 써야한다고 생각하면 _IO_file_jumps를 찾고 그 안의 _IO_file_sync라는 함수를 호출합니다., 이 때 vtable 호출은 원하는 목적에 맞게 조작을 해서 leak과 exploit을 실행하면 될 거 같습니다.
1. Libc Leak 상세 과정
fopen 직후 FILE 구조체가 힙에 생성됩니다. 하지만 실제로 데이터를 담아두는 공간인 버퍼는 아직 할당되지 않았는데 이건 fp의 주소는 존재하지만 fp->_IO_write_base와 fp->_IO_buf_base가 NULL로 채워져 있음을 의미합니다. 이 상태에서 fflush를 돌리면 주소를 바꾸고 fflush를 돌리면 프로그램이 주소를 인식하지 못해 죽어버립니다.
그래서 먼저 버퍼를 생성하고, 해당 버퍼에 쓰기로 libc 주소를 쓰게끔 유도한 뒤, 해당 버퍼에 써진 값을 stdout으로 출력하는 과정이 필요합니다. 먼저 trick에 대한 편의함수를 다음과 같이 작성해줍니다.
그 뒤 flag값에 1880을 넣어 write가 가능하게 flag값을 넣습니다.
이 다음 과정이 중요한데 먼저 fflush가 파일 버퍼를 정리할 때 호출하는 sync주소에 다른 vtable값을 넣어야하는데, 버퍼를 생성해야하므로 파일의 커서를 이동시키는 함수이면서, 버퍼가 없을경우 버퍼를 생성하는 seekoff 함수를 사용해야합니다.
해당 함수 주소값들끼리의 오프셋 차이를 계산해주고, 그 값을 vtable의 시작주소를 담고 있는 target_lsb에 넣어줍니다.

(*참고로 0x88이 나오는 이유는 0xA0에서(원래의 sync가 들어있는 하위2비트)에서 offset만큼인 0x18을 뺐기 때문입니다)
그 뒤 Flush 메뉴를 선택하면 버퍼를 할당시킬 수 있습니다.
그 다음으로는 오프셋을 다시 overflow함수의 주소로 설정해줘야합니다.
overflow는 버퍼가 있으면 포인터를 세팅하기 때문에 현재 NULL값이 들어가있는 _IO_write_base에 아까 생성한 버퍼의 주소(_IO_buf_base)값을 넣어줍니다.
여기까지 오면 write_base에 아까 할당된 버퍼의 주소가 들어가있습니다.
이 write_base의 하위 2비트는 80인데, 해당 2비트를 80이 아닌 70으로 바꿔줍니다.
원래 0x55....70에 fd/bk포인터와 버퍼의 크기값이 들어있는 헤더가 있고 그 다음 0x55...80주소에 버퍼의 내용이 담기는데 0x70으로 바꿨으므로 트리거를 해주면 fd/bk포인터(libc내 주소)를 Leak할 수 있게됩니다.
여기서 중요한점은 _fileno값에 1을 넣어서 stdout으로 변경해둬야 화면상 출력이 가능해집니다.
해당 과정을 통해 _fflush -> _IO_new_file_sync ->_IO_do_write -> _IO_SYSWRITE 순으로 연계가 됩니다.

사진의 0x1e0사이즈의 힙은 처음 파일 구조체를 할당받을때 받은 힙이고, 그 뒤 0x1010은 seekoff에 의해 생성된 heap입니다.


위와 같이 leak 된 주소를 통해 libc base를 구하면 됩니다.
2. 공격할 주소 구하기
이건 굉장히 쉽습니다. libc_base에서 오프셋만 더하면 되고, 그것도 귀찮으면 pwntools에서 제공하는 가젯들을 쓰면 됩니다.
이렇게 설정해뒀고

이렇게 출력이 잘 됩니다.

이렇게 두번째 단계까지 잘 작성이 되었습니다.
마지막 단계(사실 제일 어려움)은 fflush가 호출될 때 system이 호출되도록 FSOP를 일으키는 과정입니다.
다른 write-up을 참고해보니 아마 fflush를 여러번 호출해서 버퍼를 할당하고 해제하고를 반복하는 것 같은데, 다음 포스팅에서 이어서 작성해보도록 하겠습니다.
읽어주셔서 감사합니다~
방학에는 열심히 포스팅 해보겠습니다!.!