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;
}
前置き
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に格納されます。
フラグの暗号化処理