はじめに
この記事は 1日1CTF Advent Calendar 2024 の 6 日目の記事です。
問題
former-seccomp (問題出典: SECCON Beginners CTF 2024)
フラグチェック用のシステムコールを自作してみました
リポジトリ: https://github.com/SECCON/SECCON_Beginners_CTF_2024/tree/main/reversing/former-seccomp
考察
ghidra でデコンパイルして、本質を頑張って探すと、この関数っぽい。
undefined8 FUN_00101737(char *param_1)
{
size_t sVar1;
undefined8 uVar2;
long lVar3;
ulong local_28;
long local_20;
sVar1 = strlen(param_1);
if (sVar1 == 0x1a) {
for (local_28 = 0; sVar1 = strlen(&DAT_00104030), local_28 < sVar1; local_28 = local_28 + 1) {
(&DAT_00104030)[local_28] = (char)local_28 + 0x20U ^ (&DAT_00104030)[local_28];
}
lVar3 = FUN_00101497(&DAT_00104010,&DAT_00104030);
for (local_20 = 0; (param_1[local_20] != '\0' && (*(char *)(local_20 + lVar3) != '\0'));
local_20 = local_20 + 1) {
if (param_1[local_20] != *(char *)(local_20 + lVar3)) {
return 0;
}
}
uVar2 = 1;
}
else {
uVar2 = 0;
}
return uVar2;
}
頑張って人力で読みやすくすると、こうなる。
int check(char *inp){
int inp_len = strlen(inp);
if(inp_len == 0x1a) {
for(byte i = 0;i < strlen(key); i++) {
key[i] = (i + 0x20U) ^ key[i];
}
byte *res = FUN_00101463();
for(int i = 0; (inp[i] != '\0' && (res[i] != '\0')); i++) {
if(inp[i] != res[i]) {
return 0;
}
}
return 1;
}
else {
return 0;
}
}
入力の長さが 0x1a
か確認して、 FUN_00101497()
関数でフラグを生成して、入力と比較している。
FUN_00101497()
関数も見てみる。
void * FUN_00101497(char *param_1,char *param_2)
{
size_t __size;
void *pvVar1;
size_t sVar2;
long in_FS_OFFSET;
ulong local_158;
ulong local_150;
ulong local_148;
ulong local_140;
ulong local_138;
ulong local_130;
byte local_118 [264];
long local_10;
local_10 = *(long *)(in_FS_OFFSET + 0x28);
__size = strlen(param_1);
pvVar1 = malloc(__size);
sVar2 = strlen(param_2);
for (local_158 = 0; local_158 < 0x100; local_158 = local_158 + 1) {
local_118[local_158] = (byte)local_158;
}
local_148 = 0;
for (local_150 = 0; local_150 < 0x100; local_150 = local_150 + 1) {
local_148 = (ulong)((int)param_2[local_150 % (ulong)(long)(int)sVar2] +
(uint)local_118[local_150] + (int)local_148 & 0xff);
FUN_00101463(local_118 + local_150,local_118 + local_148);
}
local_140 = 0;
local_138 = 0;
for (local_130 = 0; local_130 < __size; local_130 = local_130 + 1) {
local_140 = (ulong)((int)local_140 + 1U & 0xff);
local_138 = (ulong)((int)local_138 + (uint)local_118[local_140] & 0xff);
FUN_00101463(local_118 + local_140,local_118 + local_138);
*(byte *)(local_130 + (long)pvVar1) =
local_118[(int)(uint)(byte)(local_118[local_138] + local_118[local_140])] ^
param_1[local_130];
}
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return pvVar1;
}
これも頑張って人力で直す。
byte * sub_check(){
byte buf[264];
long local_10;
int dat_len = strlen(dat);
byte *res = malloc(dat_len);
int key_len = strlen(key);
for(int i = 0; i < 0x100; i++) {
buf[i] = i;
}
byte t = 0;
for(int i = 0; i < 0x100; i++) {
t = (key[i % key_len] + buf[i] + t) & 0xff;
FUN_00101463(&buf[i],&buf[t]);
}
byte x = 0,y = 0;
for(int i = 0; i < dat_len; i++) {
x = (x + 1) & 0xff;
y = (y + buf[x]) & 0xff;
FUN_00101463(&buf[x],&buf[y]);
res[i] = buf[(buf[y] + buf[x]) & 0xff] ^ dat[i];
}
return res;
}
途中で FUN_00101463()
関数が呼ばれているが、これは swap してるだけっぽい。
void FUN_00101463(undefined *param_1,undefined *param_2)
{
undefined uVar1;
uVar1 = *param_1;
*param_1 = *param_2;
*param_2 = uVar1;
return;
}
ということで、すべてを繋いで、sub_check()
の結果を出力すればいい。
solve.c
...
int check(char *inp){
...
byte *res = sub_check();
printf("%s\n",res); // 追加
...
}
int main(){
check("AAAAAAAAAAAAAAAAAAAAAAAAAA"); // "A" * 0x1a
}
solver
ここまでをまとめてコードを書く。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define byte unsigned char
byte dat[] = {0xa5, 0xd2, 0xbc, 0x02, 0xb2, 0x7c, 0x86, 0x38, 0x17, 0xb1, 0x38, 0xc6, 0xe4, 0x5c, 0x1f, 0xa0, 0x9d, 0x96, 0xd1, 0xf0, 0x4b, 0xa6, 0xa6, 0x5c, 0x64, 0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
byte key[] = {0x43, 0x55, 0x44, 0x17, 0x46, 0x1f, 0x14, 0x17, 0x1a, 0x1d, 0x00};
void swap(byte *a,byte *b){
byte tmp = *a;
*a = *b;
*b = tmp;
}
byte * sub_check(){
byte buf[264];
long local_10;
int dat_len = strlen(dat);
byte *res = malloc(dat_len);
int key_len = strlen(key);
for(int i = 0; i < 0x100; i++) {
buf[i] = i;
}
byte t = 0;
for(int i = 0; i < 0x100; i++) {
t = (key[i % key_len] + buf[i] + t) & 0xff;
swap(&buf[i],&buf[t]);
}
byte x = 0,y = 0;
for(int i = 0; i < dat_len; i++) {
x = (x + 1) & 0xff;
y = (y + buf[x]) & 0xff;
swap(&buf[x],&buf[y]);
res[i] = buf[(buf[y] + buf[x]) & 0xff] ^ dat[i];
}
return res;
}
int check(char *inp){
int inp_len = strlen(inp);
if(inp_len == 0x1a) {
for(byte i = 0;i < strlen(key); i++) {
key[i] = (i + 0x20U) ^ key[i];
}
byte *res = sub_check();
printf("%s\n",res);
for(int i = 0; (inp[i] != '\0' && (res[i] != '\0')); i++) {
if(inp[i] != res[i]) {
return 0;
}
}
return 1;
}
else {
return 0;
}
}
int main(){
check("AAAAAAAAAAAAAAAAAAAAAAAAAA"); // "A" * 0x1a
}
flag: ctf4b{p7r4c3_c4n_3mul4t3_sysc4ll}
おわりに
Reversing 問題、「頑張る」以外に解説できることが本当にない…