(author) Winter HackingCamp CTF 2023 - syscall (SROP)
올해도 어김없이 해킹캠프 문제 출제를 하였다. 대회 시간이 꽤 짧고 다른 어려운 포너블 문제가 있기 때문에 문제 풀이 해주신 분들은 풀이가 상대적으로 쉬운 Sigreturn-oriented programming(SROP)
를 이용하여 문제를 풀이해주셨을 것이라 생각된다. 나는 문제 출제하면서 두 가지 방법으로 풀이하였고 정작 SROP
는 생각이 나지 않아 다른 방법으로 푼 후에 SROP
로 풀이하였다.
롸업을 작성하면서 생각든 건데 seccomp과 같은 것을 두지 않았으니 서버에 존재하는 flag 파일의 이름을 알아낸 다음, openat, read, write
해서 문제를 풀이할 수 있을 것 같다. 해당 방법은 언인텐이 아니며 초심자를 위한 해킹캠프 취지에 맞게 다양한 풀이가 가능하도록 보호 기법을 강하게 제한을 두지 않았다. 개인적으로 flag 값을 읽어오는 것보단 쉘 따는 것이 더 재밌기 때문에.. 해당 풀이는 소개하지 않는다.
원하는 system call을 실행시켜보자!
https://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/
tar -zxvf syscall-public.tar.gz
- [ 3 solves / 428 points]
Analysis
1 | int __cdecl main(int argc, const char **argv, const char **envp) |
전역변수에 적힌 값을 출력하고, 지역변수에 값을 입력 받는데 여기서 overflow가 대놓고 발생한다. 주어진 코드는 이게 다고, 내가 작성한 것을 출력해 주는 기능이 현재로선 존재하지 않기 때문에 뭔갈 leak할 수도 없어 보인다. 그러나 함수 목록을 보면 아래와 같은 hint 함수가 존재하고 유용한 가젯이 보인다.
보기 쉽게 나타내면 아래 두 가젯이 눈에 보인다.
1 | add rax, 0x1 ; ret ; |
이것을 이용하여 우리는 원하는 system call을 실행시킬 수 있다. 문제 바이너리는 64bit 전용 ELF이니 문제 설명에 나와있던 사이트를 접속해보자.
overflow도 발생하겠다.. rax
레지스터를 조작하여 우리는 저 사이트에 있는 system call들을 실행시킬 수 있다.
Solve 1 (Sigreturn-oriented programming(SROP))
- Sigreturn system call을 사용하는 ROP 기법
프로그램은 보안, 자원 관리 등의 이유로 user mode와 kernel mode를 왔다갔다 하면서 실행되는데 이 때 자원 공유가 필요하다. kernel mode에서 user mode로 갈 때 Sigreturn system call
을 이용하여 user mode의 레지스터를 세팅한다. 저 시스템 콜을 사용하여 레지스터를 세팅할 때 값에 대한 검증이 이루어지지 않는다. 그래서 우리는 Sigreturn system call
을 강제로 호출하여 레지스터를 마음대로 바꿀 수 있다.
pwntools
의 frame = SigreturnFrame()
을 이용하여 매우 편리하게 코드를 작성할 수 있다.
- exploit 시나리오
- 전역변수에
/bin/sh\x00
작성 ->read(0, 0x404028, 8)
- main으로 돌림
SigreturnFrame()
을 이용하여 편리하게 레지스터 세팅 ->execve('/bin/sh\x00', 0, 0)
을 실행시키기 위함execve('/bin/sh\x00', 0, 0)
실행
Exploit Code
1 | from pwn import * |
Solve 2 (run execute(‘/bin/sh\x00’, 0, 0))
SROP 기법을 이용하지 않고 execute('/bin/sh\x00', 0, 0)
을 실행시키는 것을 목표로 두는 풀이이다.
write(1, stack주소, ?)
호출
main 함수에서sys_read(0, buf, 0x250uLL);
실행하고 난 뒤기 때문에rax
레지스터와rdi
레지스터만 수정해서write
함수를 호출하여 stack 주소를 leak할 수 있다. (stack에 쓰여진 건 모두 leak할 수 있고 stack에는 stack 주소도 대부분 적혀있다.)read(0, 0x404028, 8)
호출
bss 영역에 ‘/bin/sh\x00’을 쓸 것이다. 쓰고 난 후execute('/bin/sh\x00', 0, 0)
을 실행할 예정이다.execute('/bin/sh\x00', 0, 0)
호출
인자 세 개를 조절해야 한다. 이 때rdx
를 조절하기 적당한 가젯이 없어서csu gadget
을 이용하였다.
이 때 [r15 + rbx*8]
에는 현재 작성한 payload의 아래 부분을 작성해주는 것이 유리하다. 1번 과정에서 stack 주소를 leak했으니 우리 payload가 어느 주소에 적힌지도 구할 수 있다. 익스 코드 주석을 보면 나는 rdi
레지스터를 세팅해주는 곳을 가리켰다. 정리하면 csu
가젯으로 edi
, rsi
, rdx
모두 세팅할 수 있는데 다 0으로 해놓고 후에 rdi
를 다시 세팅해줬다.
편리하게 write, read, execute 함수 자체를 호출하는 것처럼 시나리오를 작성하였지만 호출해야할 것은 system call이라는 것을 잊지 말자. system call이니 만큼 함수를 호출하면 안되고 문제 설명의 사이트를 참고하여 올바른 값으로 rax
레지스터를 세팅한 다음 syscall
가젯을 호출해야 한다. 그리고 현재 레지스터의 상황에 맞게 add rax, 0x1 ; ret ;
가젯을 호출하자. 0x3b로 세팅해줘야 한다고 무작정 0x3b번 호출하면 안된다는 뜻이다. 왜냐하면 그 당시 rax
레지스터가 0이란 보장을 할 수 없기 때문이다.
Exploit Code
1 | from pwn import * |
Flag
1 | HCAMP{cf518127a07c471e00dcfca20a5ca08f} |