Challenge

vってどんな意味? • chal.c で用いられている関数の詳細は intrinsics guide で確認できます.

// aarch64-linux-gnu-gcc -march=armv8-a+sve -static -O0 -o chal chal.c
// qemu-aarch64 -cpu max,sve=on,sve128=on ./chal

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

#define FLG_LEN 0x40

unsigned long flag[] = {0x9beff28796ecf3e9, 0x2335ae47c5b3ea6a,
                        0x7bd30354a9dfecfe, 0x3243804702b92b8c,
                        0x7caad2839ae4bf07, 0x2749c14807c2e873,
                        0xbcd9c683a3ebf11c, 0x4119a527d9aa0a73};

void micro_kernelA(short *array, svbool_t active) {
    svuint16_t op1 = svdup_n_u16(0x1dea);
    svuint16_t op2 = svdup_n_u16(0xcafe);
    svuint16_t data = svld1_u16(svptrue_b16(), (uint16_t *)array);
    data = svmla_u16_m(active, data, op1, op2);
    svst1_u16(svptrue_b16(), (uint16_t *)array, data);
}

int main(void) {
    char buf[0x100] = {0};
    printf("flag>");
    fgets(buf, sizeof(buf), stdin);
    if(strlen(buf) != FLG_LEN + 1) {
        puts("wrong");
        exit(1);
    }

    unsigned long vlen = svcntd();
    for(unsigned long i = 0; i < FLG_LEN; i += vlen * 8) {
        for(int j = 0; j < 0x100; j++) {
            uint16_t active_array[8];
            for(int k = 0; k < 8; k++) {
                active_array[k] = j > (1 << k);
            }
            svuint16_t active_vec = svld1_u16(svptrue_b16(), active_array);
            svbool_t active = svcmpne_n_u16(svptrue_b16(), active_vec, 0);
            micro_kernelA((short *)(buf + i), active);
        }
    }

    svbool_t ok = svptrue_b64();
    for(unsigned long i = 0; i < FLG_LEN; i += vlen * 8) {
        svuint64_t sv_buf = svld1_u64(svptrue_b64(), (uint64_t *)(buf + i));
        svuint64_t sv_flg = svld1_u64(svptrue_b64(), (uint64_t *)((char *)flag + i));
        ok = svand_b_z(svptrue_b64(), ok, svcmpeq_u64(svptrue_b64(), sv_buf, sv_flg));
    }

    if(svptest_any(svptrue_b64(), svnot_b_z(svptrue_b64(), ok))) {
        puts("wrong");
        exit(1);
    }

    puts("correct!");
    return 0;
}

Solution

前置き

SIMDの入門としておすすめしたい内容でした。

<aside> 💡

SIMD

「1命令で複数の値をまとめて計算する」仕組み

</aside>

今回のバイナリは

qemu-aarch64 -cpu max,sve=on,sve128=on ./chalで動かすことからも分かる通り、

ARMで命令セットはaarch64となっています。そして、SVEを128bitとして実行しています。

aarch64では汎用レジスタとしてx0~x30(64bit),w0~w30(32bit)を使いますがSIMDでは異なるレジスタを使います。(aarch32ではr0~r15)

また、アセンブリのオペコード名もx86-64と違い、 メモリの参照や書き込みにload,store jmp,callなど移動系がb になっていたりするので初めて知った方はアーキごとの違いから学ぶと良いかもしれません。

<aside> 💡

NEON

固定128bitのSIMD

V0~V31レジスタを使う

</aside>

<aside> 💡

SVE

128~2048bitで可変のSIMD

Z0~Z31レジスタを使う

また、マスク用にP0^P15レジスタも使う

</aside>

問題解説

入力は #define FLG_LEN 0x40 から64byteで、bufに格納されます。

フラグの暗号化処理