<style> img[alt=chall-sc] { display: block; margin: 0 auto; width: 100em; } p { text-align: justify; } p::first-line { text-indent: 0; } </style> # ARA CTF 6.0 QUALIFICATION WRITEUP ## Sanity Check ![chall-sc](https://hackmd.io/_uploads/BkcI3gIFyg.png) Free flag `Flag: ARA6{apakah_kalian_akan_memasak_atau_dimasak?????}` ## Simple Math ![chall-sc](https://hackmd.io/_uploads/BJ2aTxLYJe.png) Given a disassembled Python bytecode and the output ```python 0 0 RESUME 0 2 2 LOAD_CONST 9 ((5,)) 4 LOAD_CONST 1 (<code object conv at 0x000001D2B5453870, file "<string>", line 2>) 6 MAKE_FUNCTION 1 (defaults) 8 STORE_NAME 0 (conv) 6 10 PUSH_NULL 12 LOAD_NAME 1 (open) 14 LOAD_CONST 2 ('flag.txt') 16 CALL 1 24 LOAD_ATTR 5 (NULL|self + read) 44 CALL 0 52 STORE_NAME 3 (flag) 7 54 BUILD_LIST 0 56 STORE_NAME 4 (flags) 8 58 BUILD_LIST 0 60 LOAD_CONST 3 ((412881107802, 397653008560, 378475773842, 412107467700, 410815948500, 424198405792, 379554633200, 404975010927, 419449858501, 383875726561)) 62 LIST_EXTEND 1 64 STORE_NAME 5 (N) 9 66 PUSH_NULL 68 LOAD_NAME 6 (reversed) 70 LOAD_NAME 5 (N) 72 CALL 1 80 STORE_NAME 7 (NR) 11 82 PUSH_NULL 84 LOAD_NAME 8 (len) 86 LOAD_NAME 3 (flag) 88 CALL 1 96 LOAD_CONST 0 (5) 98 BINARY_OP 6 (%) 102 LOAD_CONST 4 (0) 104 COMPARE_OP 40 (==) 108 POP_JUMP_IF_TRUE 2 (to 114) 110 LOAD_ASSERTION_ERROR 112 RAISE_VARARGS 1 13 >> 114 PUSH_NULL 116 LOAD_NAME 9 (zip) 118 PUSH_NULL 120 LOAD_NAME 0 (conv) 122 LOAD_NAME 3 (flag) 124 CALL 1 132 LOAD_NAME 5 (N) 134 LOAD_NAME 7 (NR) 136 CALL 3 144 GET_ITER >> 146 FOR_ITER 71 (to 292) 150 UNPACK_SEQUENCE 3 154 STORE_NAME 10 (i) 156 STORE_NAME 11 (j) 158 STORE_NAME 12 (k) 14 160 LOAD_NAME 13 (int) 162 LOAD_ATTR 29 (NULL|self + from_bytes) 182 LOAD_NAME 10 (i) 184 LOAD_ATTR 31 (NULL|self + encode) 204 CALL 0 212 LOAD_CONST 5 ('big') 214 CALL 2 222 STORE_NAME 16 (x) 15 224 LOAD_NAME 16 (x) 226 LOAD_NAME 11 (j) 228 BINARY_OP 0 (+) 232 LOAD_CONST 6 (1337) 234 BINARY_OP 5 (*) 238 LOAD_NAME 12 (k) 240 BINARY_OP 12 (^) 244 STORE_NAME 17 (y) 16 246 LOAD_NAME 17 (y) 248 LOAD_CONST 7 (871366131) 250 BINARY_OP 23 (-=) 254 STORE_NAME 17 (y) 17 256 LOAD_NAME 4 (flags) 258 LOAD_ATTR 37 (NULL|self + append) 278 LOAD_NAME 17 (y) 280 CALL 1 288 POP_TOP 290 JUMP_BACKWARD 73 (to 146) 13 >> 292 END_FOR 19 294 PUSH_NULL 296 LOAD_NAME 19 (print) 298 LOAD_NAME 4 (flags) 300 CALL 1 308 POP_TOP 310 RETURN_CONST 8 (None) Disassembly of <code object conv at 0x000001D2B5453870, file "<string>", line 2>: 2 0 RETURN_GENERATOR 2 POP_TOP 4 RESUME 0 3 6 LOAD_GLOBAL 1 (NULL + range) 16 LOAD_CONST 1 (0) 18 LOAD_GLOBAL 3 (NULL + len) 28 LOAD_FAST 0 (str) 30 CALL 1 38 LOAD_FAST 1 (l) 40 CALL 3 48 GET_ITER >> 50 FOR_ITER 12 (to 78) 54 STORE_FAST 2 (i) 4 56 LOAD_FAST 0 (str) 58 LOAD_FAST 2 (i) 60 LOAD_FAST 2 (i) 62 LOAD_FAST 1 (l) 64 BINARY_OP 0 (+) 68 BINARY_SLICE 70 YIELD_VALUE 1 72 RESUME 1 74 POP_TOP 76 JUMP_BACKWARD 14 (to 50) 3 >> 78 END_FOR 80 RETURN_CONST 0 (None) >> 82 CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) 84 RERAISE 1 ``` ### Disassembled Bytecode Analysis The provided disassembled bytecode indicates the following operations: 1. **Reading `flag.txt`**: The script opens and reads the contents of `flag.txt` into a variable named `flag`. 2. **Initialization of Lists**: - `N`: A list containing ten large integers. - `NR`: A reversed version of the list `N`. 3. **Assertion**: The script checks that the length of `flag` is a multiple of 5. 4. **Processing Loop**: The script iterates over the zipped result of a function `conv(flag)`, `N`, and `NR`. For each tuple `(i, j, k)`: - Converts the string `i` to an integer `x` using `int.from_bytes(i.encode(), 'big')`. - Applies the following transformation to compute `y`: $$ y = ((x + j) \times 1337) \oplus k - 871366131 $$ - Appends `y` to a list named `flags`. 5. **Output**: Finally, the script prints the `flags` list. ### Reversing the Transformation To reconstruct the original content of `flag.txt`, we need to reverse the transformation applied to each character. Given the transformation: $$ y = ((x + j) \times 1337) \oplus k - 871366131 $$ We can solve for `x` as follows: $$ x = \left( \frac{(y + 871366131) \oplus k}{1337} \right) - j $$ ### Solving It This is the script that that I made to reverse the transformation: ```python #!/usr/bin/env python3 N = [412881107802, 397653008560, 378475773842, 412107467700, 410815948500, 424198405792, 379554633200, 404975010927, 419449858501, 383875726561] NR = list(reversed(N)) ct = [927365724618649, 855544946535839, 1075456339888851, 1051300489856216, 854566738228717, 862564607600557, 1107196607637040, 835104762026329, 1108826984434051, 843310935687105] pt = b"" for y, j, k in zip(ct, N, NR): x = ((y + 871366131) ^ k) // 1337 - j pt += x.to_bytes((x.bit_length() + 7) // 8, 'big') print(pt) ``` ![chall-sc](https://hackmd.io/_uploads/HyCHWZ8KJe.png) `Flag: ARA6{8yT3_c0d3_W1Th_51MPl3_m4th_15_345Y____R19ht?}` ## memory ![chall-sc](https://hackmd.io/_uploads/Sk-dZb8Yyx.png) Given two ELF (Executable and Linkable Format) 64-bit files: `memory` and `memory-v2`. I used `diff` to compare the symbol tables of two ELF binaries (`memory` and `memory-v2`) and show their differences ![chall-sc](https://hackmd.io/_uploads/rJ0wGZIYkl.png) `memory-v2` includes a reference to String::len() from Rust's alloc crate, whereas `memory` does not. I decompiled the `memory-v2` using IDA. This is the result: ### main ```c int __fastcall main(int argc, const char **argv, const char **envp) { return std::rt::lang_start(memory::main, argc, (u8 **)argv, 0); } ``` ### memory::main ```c void __cdecl memory::main() { __int64 v0; // rax void *v1; // rax __int64 v2; // rax __int64 v3; // rax __int64 v4; // rdx _str v5; // rdi _str v6; // rdx _str v7; // rdi core::str::iter::Chars v8; // rax unsigned __int16 v9; // ax __int16 v10; // ax _str v11; // rdi core::str::iter::Chars v12; // rax usize v13; // [rsp+10h] [rbp-218h] unsigned __int8 v14; // [rsp+59h] [rbp-1CFh] unsigned __int8 v15; // [rsp+5Ah] [rbp-1CEh] core::fmt::Arguments v16; // [rsp+E8h] [rbp-140h] BYREF core::fmt::Arguments v17; // [rsp+118h] [rbp-110h] BYREF alloc::string::String self; // [rsp+148h] [rbp-E0h] BYREF core::fmt::Arguments v19; // [rsp+160h] [rbp-C8h] BYREF __int64 v20; // [rsp+190h] [rbp-98h] core::result::Result<usize,std::io::error::Error> v21; // [rsp+198h] [rbp-90h] BYREF __int64 v22; // [rsp+1A8h] [rbp-80h] core::option::Option<char> v23; // [rsp+1B4h] [rbp-74h] BYREF core::option::Option<char> v24[2]; // [rsp+1B8h] [rbp-70h] BYREF u8 *end_or_len; // [rsp+1C0h] [rbp-68h] unsigned int v26; // [rsp+1CCh] [rbp-5Ch] char v27; // [rsp+1D2h] [rbp-56h] char v28; // [rsp+1D3h] [rbp-55h] core::option::Option<char> v29; // [rsp+1D4h] [rbp-54h] BYREF core::option::Option<char> v30[2]; // [rsp+1D8h] [rbp-50h] BYREF u8 *v31; // [rsp+1E0h] [rbp-48h] alloc::string::String other; // [rsp+1E8h] [rbp-40h] BYREF char v33; // [rsp+203h] [rbp-25h] int v34; // [rsp+204h] [rbp-24h] void *v35; // [rsp+208h] [rbp-20h] std::io::error::Error v36; // [rsp+210h] [rbp-18h] core::fmt::Arguments::new_const(&v16, (_str (*)[1])off_5A2A8); std::io::stdio::_print(); core::fmt::Arguments::new_const(&v17, (_str (*)[1])off_5A2B8); ((void (__fastcall *)(core::fmt::Arguments *))std::io::stdio::_print)(&v17); alloc::string::String::new(&self); core::fmt::Arguments::new_const(&v19, (_str (*)[1])off_5A2C8); std::io::stdio::_print(); std::io::stdio::stdout(); v20 = v0; <std::io::stdio::Stdout as std::io::Write>::flush(); v35 = v1; if ( v1 ) { v36.repr.__0.pointer = v35; core::result::unwrap_failed(); } std::io::stdio::stdin(); v22 = v2; std::io::stdio::Stdin::read_line(); *(_QWORD *)v21.gap0 = v3; *(_QWORD *)&v21.gap0[8] = v4; core::ptr::drop_in_place<core::result::Result<usize,std::io::error::Error>>(&v21); alloc::string::String::pop((core::option::Option<char> *)&self, &self); v5 = <alloc::string::String as core::ops::deref::Deref>::deref(&self); v6.data_ptr = (u8 *)"ARA6{"; v6.length = 4LL; if ( core::str::<impl str>::starts_with(v5, v6) ) { v7 = <alloc::string::String as core::ops::deref::Deref>::deref(&self); v8 = core::str::<impl str>::chars(v7); *(_QWORD *)v24[0].gap0 = v8.iter.ptr.pointer; end_or_len = v8.iter.end_or_len; v23 = (core::option::Option<char>)core::iter::traits::iterator::Iterator::nth( v24, (core::str::iter::Chars *)&::self, (usize)v8.iter.end_or_len); if ( <core::option::Option<T> as core::cmp::PartialEq>::eq(&v23, (core::option::Option<char> *)"{") ) { v26 = 5; v27 = 0; LABEL_9: v28 = 0; while ( 1 ) { v15 = v28 + v27; if ( __CFADD__(v28, v27) ) core::panicking::panic_const::panic_const_add_overflow(); LOBYTE(v9) = v15 / 0x1Au; HIBYTE(v9) = v15 % 0x1Au; v10 = HIBYTE(v9); v14 = v10 + 64; if ( __CFADD__((_BYTE)v10, 64) ) core::panicking::panic_const::panic_const_add_overflow(); v33 = v10 + 64; v34 = v14; v11 = <alloc::string::String as core::ops::deref::Deref>::deref(&self); v12 = core::str::<impl str>::chars(v11); *(_QWORD *)v30[0].gap0 = v12.iter.ptr.pointer; v31 = v12.iter.end_or_len; v29 = (core::option::Option<char>)core::iter::traits::iterator::Iterator::nth( v30, (core::str::iter::Chars *)v26, (usize)v12.iter.end_or_len); LODWORD(other.vec.buf.inner.cap.__0) = v14; if ( core::cmp::PartialEq::ne(&v29, (core::option::Option<char> *)&other) ) break; if ( v28 == -1 ) core::panicking::panic_const::panic_const_add_overflow(); ++v28; if ( v26 == -1 ) core::panicking::panic_const::panic_const_add_overflow(); ++v26; if ( v28 == 15 ) { if ( v27 == -1 ) core::panicking::panic_const::panic_const_add_overflow(); ++v27; goto LABEL_9; } if ( v27 == 4 ) { HIDWORD(other.vec.buf.inner.cap.__0) = (unsigned int)alloc::string::String::pop( (core::option::Option<char> *)&self, &other); if ( <core::option::Option<T> as core::cmp::PartialEq>::eq( (core::option::Option<char> *)&other.vec.buf.inner.cap.__0 + 1, (core::option::Option<char> *)&::other) ) { v13 = alloc::string::String::len(&self); if ( v13 == -1LL ) core::panicking::panic_const::panic_const_add_overflow(); if ( v13 == 66 ) { memory::yes(); core::ptr::drop_in_place<alloc::string::String>(&self); return; } } break; } } } } memory::no(); core::ptr::drop_in_place<alloc::string::String>(&self); } ``` ### memory::yes ```c void __cdecl memory::yes() { core::fmt::Arguments v0; // [rsp+8h] [rbp-30h] BYREF core::fmt::Arguments::new_const(&v0, (_str (*)[1])off_5A298); std::io::stdio::_print(); } ``` ### memory::no ```c void __cdecl memory::no() { core::fmt::Arguments v0; // [rsp+8h] [rbp-30h] BYREF core::fmt::Arguments::new_const(&v0, (_str (*)[1])pieces); std::io::stdio::_print(); } ``` This is a flag checker challenge. If we input the correct flag, the result is `Yes! This one you got it right!`, and if we input the incorrect flag, the result is `That is... wrong`. ### Observing the Flag Pattern The flag follows this structure: - **Prefix**: `"ARA6{"` - **Grid-based character sequence**: (60 characters) - Generated using an indexed ASCII transformation. - **Suffix**: `"}"` ### Solving It The flag is compared character by character at elf_base + 0x94be against our input using `<core::option::Option<T> as core::cmp::PartialEq>::eq(&v23, (core::option::Option<char> *)"{")`. ![chall-sc](https://hackmd.io/_uploads/ryTXJX8Y1g.png) I manually checked each of the compared character to retrieve the full flag. ![chall-sc](https://hackmd.io/_uploads/SJKmgm8Fye.png) `Flag: ARA6{@ABCDEFGHIJKLMNABCDEFGHIJKLMNOBCDEFGHIJKLMNOPCDEFGHIJKLMNOPQD}`