1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Daily Alpacahack 8/18-20 Let's analyze system callsのwriteup

1
Posted at

Let's analyze system calls

x86-64版Linuxのシステムコールを解析してみましょう!

初心者向けヒント1: システムコールの呼び出し規約とgccの拡張インラインアセンブラ
x86-64版Linuxにおけるシステムコールの呼び出し規約は、 https://gitlab.com/x86-psABIs/x86-64-ABI からダウンロードできる System V Application Binary Interface AMD64 Architecture Processor Supplement 資料中の A.2.1 Calling Conventions や、 https://man7.org/linux/man-pages/man2/syscall.2.html に記述があります。
システムコールを実行するには、システムコール番号を rax レジスタに、最大6個までの引数を順に rdi, rsi, rdx, r10, r8, r9 レジスタに格納してから syscall 命令を実行します。
システムコール実行結果は rax レジスタに格納されます。
gccの拡張インラインアセンブラ構文は、 https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.htmlhttps://gcc.gnu.org/onlinedocs/gcc/Machine-Constraints.html に記述があります。
本問題で使用している1つ目の syscall 命令用の拡張インラインアセンブラでは、次のことを指定しています。
OutputOperands パラメーターは未指定です。
InputOperands パラメーターで、拡張インラインアセンブラを実行する前に、rax レジスタに 318 を、rdi レジスタに &result_1 を、rsi レジスタに sizeof(result_1) を、rdx レジスタに 0 を設定しています。
Clobbers パラメーターで、拡張インラインアセンブラの実行中に rcx レジスタと r11 レジスタ、メモリが変更されることを指定しています。
なお、syscall 命令そのものが rcx レジスタと r11 レジスタを変更します。
本問題で使用している2つ目の syscall 命令用の拡張インラインアセンブラでは、次のことを指定しています。
OutputOperands パラメーターで、拡張インラインアセンブラの実行後に、rax レジスタの内容をresult_2 へ設定しています。
InputOperands パラメーターで、拡張インラインアセンブラを実行する前に、rax レジスタに 102 を設定しています。
Clobbers パラメーターで、拡張インラインアセンブラの実行中に rcx レジスタと r11 レジスタが変更されることを指定しています。
初心者向けヒント2: 問題へのアプローチ
本問題を解くアプローチはいくつかあります。
1つの方法は、x86-64版Linuxのシステムコール番号を調査して、対応するシステムコールの引数や戻り値を確認する方法です。
別の方法は、逆コンパイラを使用する方法です。
逆コンパイラによって、システムコール呼び出しを最初から完璧に逆コンパイルできるものもあれば、初期状態では逆コンパイル結果が貧弱なものの追加操作で完璧に逆コンパイルできるものもあります。

やったーsyscall!私syscall大好き!ということでwriteupを書きます。
writeupを書かなくてもいいくらいヒントが充実していますが…

配布されるのはchal.cchalDockerfilecompose.yamlの4つです。
てっきりELFだけかと思っていたので意外ですね。chal.cを読んでみます。

#include <stdio.h>
#include <stdlib.h>

int main() {
    unsigned long result_1, result_2, sum, input;

    asm("syscall"
        : /* No outputs. */
        : "a"(318), "D"(&result_1), "S"(sizeof(result_1)), "d"(0)
        : "rcx", "r11", "memory");

    asm("syscall"
        : "=a"(result_2)
        : "a"(102)
        : "rcx", "r11");

    sum = result_1 + result_2;

    printf("result_1: %lu\n", result_1);
    printf("What will be the sum? ");
    fflush(stdout);
    scanf("%lu", &input);
  
    if (input == sum) {
        const char *env_flag = getenv("FLAG");
        printf("Correct! FLAG: %s\n", env_flag ? env_flag : "Alpaca{DUMMY}");
    }
    else {
        puts("Incorrect...");
    }
}

直接アセンブラ書いてる…

何かと何かの値を足したものを答えるとflagがもらえるみたいですね。
見た感じ、result_1は教えてくれそうなので、result_2が分かれば正解できそう。

syscallというのは、writeとかopenとかを書くとlinuxカーネルがそれを実行してくれる機能です。(素人の理解です)

ディスアセンブラすると関数名ではなくsyscallとかswiなどで出てきて一見中身を追うことはできなさそうですが、呼び出しに規則があって、「どのレジスタに何の値が入っているとこのsystem call」というのが決まっています。

man syscallするとどのアーキテクチャがどんな方法でsyscallを実行するかが分かります。

       Arch/ABI    Instruction           System  Ret  Ret  Error    Notes
                                         call #  val  val2
       ───────────────────────────────────────────────────────────────────
       alpha       callsys               v0      v0   a4   a3       1, 6
       arc         trap0                 r8      r0   -    -
       arm/OABI    swi NR                -       r0   -    -        2
       arm/EABI    swi 0x0               r7      r0   r1   -
       arm64       svc #0                w8      x0   x1   -
       blackfin    excpt 0x0             P0      R0   -    -
       i386        int $0x80             eax     eax  edx  -
       ia64        break 0x100000        r15     r8   r9   r10      1, 6
       loongarch   syscall 0             a7      a0   -    -
       m68k        trap #0               d0      d0   -    -
       microblaze  brki r14,8            r12     r3   -    -
       mips        syscall               v0      v0   v1   a3       1, 6
       nios2       trap                  r2      r2   -    r7
       parisc      ble 0x100(%sr2, %r0)  r20     r28  -    -
       powerpc     sc                    r0      r3   -    r0       1
       powerpc64   sc                    r0      r3   -    cr0.SO   1
       riscv       ecall                 a7      a0   a1   -
       s390        svc 0                 r1      r2   r3   -        3
       s390x       svc 0                 r1      r2   r3   -        3
       superh      trapa #31             r3      r0   r1   -        4, 6
       sparc/32    t 0x10                g1      o0   o1   psr/csr  1, 6
       sparc/64    t 0x6d                g1      o0   o1   psr/csr  1, 6
       tile        swint1                R10     R00  -    R01      1
       x86-64      syscall               rax     rax  rdx  -        5
       x32         syscall               rax     rax  rdx  -        5
       xtensa      syscall               a2      a2   -    -

今回はx86-64なので、オペコードはsyscall、何を呼び出すかはraxの値によることが分かりました。
chalのディスアセンブル結果を見てみます。chal.cを見てももちろん大丈夫ですが、私はsyscallについてはアセンブリを読む方が分かりやすいと思っているのでそうします。

00000000000011e9 <main>:
    11e9:       f3 0f 1e fa             endbr64
    11ed:       55                      push   %rbp
    11ee:       48 89 e5                mov    %rsp,%rbp
    11f1:       48 83 ec 30             sub    $0x30,%rsp
    11f5:       64 48 8b 04 25 28 00    mov    %fs:0x28,%rax
    11fc:       00 00
    11fe:       48 89 45 f8             mov    %rax,-0x8(%rbp)
    1202:       31 c0                   xor    %eax,%eax
    1204:       b8 3e 01 00 00          mov    $0x13e,%eax
    1209:       48 8d 7d d0             lea    -0x30(%rbp),%rdi
    120d:       be 08 00 00 00          mov    $0x8,%esi
    1212:       ba 00 00 00 00          mov    $0x0,%edx
    1217:       0f 05                   syscall
    1219:       b8 66 00 00 00          mov    $0x66,%eax
    121e:       0f 05                   syscall
    1220:       48 89 45 e0             mov    %rax,-0x20(%rbp)
    1224:       48 8b 55 d0             mov    -0x30(%rbp),%rdx

syscallが二回登場します。上がresult_1、下がresult_2を出すためのsyscallです。
ここでraxの値に何が入っているか確認します。objdumpではeaxとなっていますが、eaxはraxの下位バイトなのでeaxを見れば大丈夫です。

    1204:       b8 3e 01 00 00          mov    $0x13e,%eax

最初のsyscallのraxはこれです。次は

    1219:       b8 66 00 00 00          mov    $0x66,%eax

これですね。
というわけで、次はこれが何を呼ぶかについて確認します。
代表的なものはChromiumのドキュメントに一覧が載っているので、私はよくここを見てます。
x86-64のtableを見てみると、%raxの値に何が入るとどのsyscallを呼び出すかが分かります。今回はresult_1が0x13e、result_2が0x66なので、result_1はgetrandom、result_2はgetuidであることが分かりました。
getuidは実行しているユーザのIDを教えてくれるsyscallです。このchalだけだと分からないので、配布ファイルを探したところ、Dockerfileの中に

# Sets UID and GID
USER 404:404

という親切な記述がありました。
というわけで、ncでつないでresult_1の値と404を足した数字を入力してFlagをもらえました。

syscall、CTFではそんなに出てこないんですが、私はこれをGhidraでちまちま探して関数名を変えてくところがGhidraでいちばん楽しいと思っています。面白かったです。ありがとうございました。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?