Reverse Engineering

ChallengeLink

beginners_rev_2023 (189 pts)

T the weakest (215 pts)

Conduit (470 pts) UPSOLVE

Natural Flag Processing 2 (470 pts) UPSOLVE

Nets (500 pts) UPSOLVE

mimetic_cycle (500 pts) UPSOLVE

Frictionless (500 pts) UPSOLVE

beginners_rev_2023 (189 pts)

Description

Let's just deal with them one by one, shall we?

Solution

Given PE32+ executable, open it using IDA

We can see that there are some unknown function such as sub_140001020 and sub_140001000. Take a look on each function we can know what is those function do

To help us carry out static analysis, we rename those function.

We can see the program receive our input on line 28 using scanf function, but there is a function that executed before our input received which is sub_140001480.

Function sub_140001480 is custom function and the result from that function is static.

So basically we can dump the values and use it for next step without reimplement the function. After receiving our input, the program processed our input with custom algorithm again.

----SNIPPET----
  v7 = input + 3;
  v8 = 2i64;
  do
  {
    *(v7 - 3) ^= *(v7 - 3) >> 12;
    *(v7 - 2) ^= *(v7 - 2) >> 12;
    *(v7 - 1) ^= *(v7 - 1) >> 12;
    *v7 ^= *v7 >> 12;
    v7[1] ^= v7[1] >> 12;
    v7[2] ^= v7[2] >> 12;
    v7[3] ^= v7[3] >> 12;
    v7[4] ^= v7[4] >> 12;
    v7[5] ^= v7[5] >> 12;
    v7[6] ^= v7[6] >> 12;
    v7[7] ^= v7[7] >> 12;
    v7[8] ^= v7[8] >> 12;
    v7[9] ^= v7[9] >> 12;
    v7[10] ^= v7[10] >> 12;
    v7[11] ^= v7[11] >> 12;
    v7[12] ^= v7[12] >> 12;
    v7[13] ^= v7[13] >> 12;
    v7[14] ^= v7[14] >> 12;
    v7[15] ^= v7[15] >> 12;
    v7[16] ^= v7[16] >> 12;
    v7[17] ^= v7[17] >> 12;
    v7[18] ^= v7[18] >> 12;
    v7[19] ^= v7[19] >> 12;
    v7[20] ^= v7[20] >> 12;
    v9 = v7[21];
    v7 += 32;
    *(v7 - 11) = v9 ^ (v9 >> 12);
    *(v7 - 10) ^= *(v7 - 10) >> 12;
    *(v7 - 9) ^= *(v7 - 9) >> 12;
    *(v7 - 8) ^= *(v7 - 8) >> 12;
    *(v7 - 7) ^= *(v7 - 7) >> 12;
    *(v7 - 6) ^= *(v7 - 6) >> 12;
    *(v7 - 5) ^= *(v7 - 5) >> 12;
    *(v7 - 4) ^= *(v7 - 4) >> 12;
    --v8;
  }
----SNIPPET----

So there is little obfuscation regarding the index of input, but we can easily analyze it

v7 = input + 3;

*(v7 - 3) ^= *(v7 - 3) >> 12; // (input + 3 - 3) = input + 0
*(v7 - 2) ^= *(v7 - 2) >> 12; // (input + 3 - 2) = input + 1
*(v7 - 1) ^= *(v7 - 1) >> 12; // (input + 3 - 2) = input + 2
...
v9 = v7[21];
v7 += 32;
*(v7 - 11) = v9 ^ (v9 >> 12); // input + 3 + 32 - 11 = input + 24
*(v7 - 10) ^= *(v7 - 10) >> 12; // input + 3 + 32 - 10 = input + 25

So basically it loop from index 0 until index 31 (32 iteration), but since v8 value is 2 so it do iteration for index 0 until index 63 (64 iteration). Here is the implementation on python.

for i in range(0, len(v7), 32):
	for j in range(32):
		v7[i+j] ^= v7[i+j] >> 12

Next, we take a look on function sub_1400010E0.

sub_1400010E0(v3, (__int64)v7, (__int64)input, (__int64)Buf1);
  LODWORD(v4) = *a1;
  v5 = (_BYTE *)(a4 + 2);
  LODWORD(v6) = a1[1];
  v7 = (_BYTE *)(a3 + 2);
  v8 = a4 - a3;
  v10 = 32i64;
  do
  {
    v11 = (unsigned __int8)(v4 + 1);
    v12 = a1[v11 + 2];
    v13 = (unsigned __int8)(v12 + v6);
    v14 = a1[v13 + 2];
    a1[v11 + 2] = v14;
    a1[v13 + 2] = v12;
    v7[v8 - 2] = *(v7 - 2) ^ LOBYTE(a1[(unsigned __int8)(v12 + v14) + 2]);
    v15 = (unsigned __int8)(v11 + 1);
    v16 = a1[v15 + 2];
    v17 = (unsigned __int8)(v13 + v16);
    v18 = a1[v17 + 2];
    a1[v15 + 2] = v18;
    a1[v17 + 2] = v16;
    *(v5 - 1) = *(v7 - 1) ^ LOBYTE(a1[(unsigned __int8)(v16 + v18) + 2]);
    v19 = (unsigned __int8)(v15 + 1);
    v20 = a1[v19 + 2];
    v21 = (unsigned __int8)(v17 + v20);
    v22 = a1[v21 + 2];
    a1[v19 + 2] = v22;
    a1[v21 + 2] = v20;
    *v5 = *v7 ^ LOBYTE(a1[(unsigned __int8)(v20 + v22) + 2]);
    v23 = (unsigned __int8)(v19 + 1);
    v24 = a1[v23 + 2];
    v25 = (unsigned __int8)(v21 + v24);
    v26 = a1[v25 + 2];
    a1[v23 + 2] = v26;
    a1[v25 + 2] = v24;
    v5[1] = v7[1] ^ LOBYTE(a1[(unsigned __int8)(v24 + v26) + 2]);
    v27 = (unsigned __int8)(v23 + 1);
    v28 = a1[v27 + 2];
    v29 = (unsigned __int8)(v25 + v28);
    v30 = a1[v29 + 2];
    a1[v27 + 2] = v30;
    a1[v29 + 2] = v28;
    v5[2] = v7[2] ^ LOBYTE(a1[(unsigned __int8)(v28 + v30) + 2]);
    v31 = (unsigned __int8)(v27 + 1);
    v32 = a1[v31 + 2];
    v33 = (unsigned __int8)(v29 + v32);
    v34 = a1[v33 + 2];
    a1[v31 + 2] = v34;
    a1[v33 + 2] = v32;
    v5[3] = v7[3] ^ LOBYTE(a1[(unsigned __int8)(v32 + v34) + 2]);
    v35 = (unsigned __int8)(v31 + 1);
    v36 = a1[v35 + 2];
    v37 = (unsigned __int8)(v33 + v36);
    v38 = a1[v37 + 2];
    a1[v35 + 2] = v38;
    a1[v37 + 2] = v36;
    v5[4] = v7[4] ^ LOBYTE(a1[(unsigned __int8)(v36 + v38) + 2]);
    v39 = (unsigned __int8)(v35 + 1);
    v40 = a1[v39 + 2];
    v41 = (unsigned __int8)(v37 + v40);
    v42 = a1[v41 + 2];
    a1[v39 + 2] = v42;
    a1[v41 + 2] = v40;
    v5[5] = v7[5] ^ LOBYTE(a1[(unsigned __int8)(v40 + v42) + 2]);
    v43 = (unsigned __int8)(v39 + 1);
    v44 = a1[v43 + 2];
    v45 = (unsigned __int8)(v41 + v44);
    v46 = a1[v45 + 2];
    a1[v43 + 2] = v46;
    a1[v45 + 2] = v44;
    v5[6] = v7[6] ^ LOBYTE(a1[(unsigned __int8)(v44 + v46) + 2]);
    v47 = (unsigned __int8)(v43 + 1);
    v48 = a1[v47 + 2];
    v49 = (unsigned __int8)(v45 + v48);
    v50 = a1[v49 + 2];
    a1[v47 + 2] = v50;
    a1[v49 + 2] = v48;
    v5[7] = v7[7] ^ LOBYTE(a1[(unsigned __int8)(v48 + v50) + 2]);
    v51 = (unsigned __int8)(v47 + 1);
    v52 = a1[v51 + 2];
    v53 = (unsigned __int8)(v49 + v52);
    v54 = a1[v53 + 2];
    a1[v51 + 2] = v54;
    a1[v53 + 2] = v52;
    v5[8] = v7[8] ^ LOBYTE(a1[(unsigned __int8)(v52 + v54) + 2]);
    v55 = (unsigned __int8)(v51 + 1);
    v56 = a1[v55 + 2];
    v57 = (unsigned __int8)(v53 + v56);
    v58 = a1[v57 + 2];
    a1[v55 + 2] = v58;
    a1[v57 + 2] = v56;
    v5[9] = v7[9] ^ LOBYTE(a1[(unsigned __int8)(v56 + v58) + 2]);
    v59 = (unsigned __int8)(v55 + 1);
    v60 = a1[v59 + 2];
    v61 = (unsigned __int8)(v57 + v60);
    v62 = a1[v61 + 2];
    a1[v59 + 2] = v62;
    v5 += 16;
    a1[v61 + 2] = v60;
    v7 += 16;
    *(v5 - 6) = *(v7 - 6) ^ LOBYTE(a1[(unsigned __int8)(v60 + v62) + 2]);
    v63 = (unsigned __int8)(v59 + 1);
    v64 = a1[v63 + 2];
    v65 = (unsigned __int8)(v61 + v64);
    v66 = a1[v65 + 2];
    a1[v63 + 2] = v66;
    a1[v65 + 2] = v64;
    *(v5 - 5) = *(v7 - 5) ^ LOBYTE(a1[(unsigned __int8)(v64 + v66) + 2]);
    v67 = (unsigned __int8)(v63 + 1);
    v68 = a1[v67 + 2];
    v69 = (unsigned __int8)(v65 + v68);
    v70 = a1[v69 + 2];
    a1[v67 + 2] = v70;
    a1[v69 + 2] = v68;
    *(v5 - 4) = *(v7 - 4) ^ LOBYTE(a1[(unsigned __int8)(v68 + v70) + 2]);
    v4 = (unsigned __int8)(v67 + 1);
    v71 = a1[v4 + 2];
    v6 = (unsigned __int8)(v69 + v71);
    v72 = a1[v6 + 2];
    a1[v4 + 2] = v72;
    a1[v6 + 2] = v71;
    result = *(v7 - 3) ^ LOBYTE(a1[(unsigned __int8)(v71 + v72) + 2]);
    *(v5 - 3) = result;
    --v10;
  }
  while ( v10 );
  a1[1] = v6;
  *a1 = v4;
  return result;

The algorithm has pattern, so we just need to deobfuscate the first part then just loop the rest.

// a1 = generated_values from sub_140001480

LODWORD(v4) = *a1; // a1[0] == 0
v5 = (_BYTE *)(a4 + 2); // result_address (stored processed input)
LODWORD(v6) = a1[1]; // a1[1] == 0
v7 = (_BYTE *)(a3 + 2);
v8 = a4 - a3;
v10 = 32i64;
do {  
    v11 = (unsigned __int8)(v4 + 1); // 0 + 1 = 1
    v12 = a1[v11 + 2]; // a1[1+2] = a1[3]
    v13 = (unsigned __int8)(v12 + v6); // a1[3] + 0 = a1[3]
    v14 = a1[v13 + 2]; // a1[a1[3] + 2]
    a1[v11 + 2] = v14; // a1[1] = v14
    a1[v13 + 2] = v12; // a1[a1[3]] = v12
    v7[v8 - 2] = *(v7 - 2) ^ LOBYTE(a1[(unsigned __int8)(v12 + v14) + 2]); // result_address[0] = processed_input[0] ^ a1[a1[3] + a1[a1[3] + 2] + 2]
...

To validate the last part (xor part), we can debug the executable and breakpoint on instruction below

00007FF7293A113C: xor     al, [r11-2]

After we know how the algorithm processed our input and static values now we just need to reimplement it on python

def sub_7FF671F610E0(a1, a2):
	v4 = a1[0]
	v6 = a1[1]
	v5 = []
	for i in range(32):
		for j in range(16):
			v4 += 1
			v4 &= 0xff
			v12 = a1[v4+2]
			v6 += v12
			v6 &= 0xff
			v14 = a1[v6+2]
			a1[v4+2] = v14
			a1[v6+2] = v12
			v5.append(a2[i][j] ^ a1[((v12 + v14)&0xff) + 2])
	return v5

Back to main function, returned values from sub_7FF7293A10E0 processed with the same algorithm like previous part.

----SNIPPET----
    *((_QWORD *)v10 - 3) ^= *((_QWORD *)v10 - 3) >> 12;
    *((_QWORD *)v10 - 2) ^= *((_QWORD *)v10 - 2) >> 12;
    *((_QWORD *)v10 - 1) ^= *((_QWORD *)v10 - 1) >> 12;
    *(_QWORD *)v10 ^= *(_QWORD *)v10 >> 12;
    *((_QWORD *)v10 + 1) ^= *((_QWORD *)v10 + 1) >> 12;
    *((_QWORD *)v10 + 2) ^= *((_QWORD *)v10 + 2) >> 12;
    *((_QWORD *)v10 + 3) ^= *((_QWORD *)v10 + 3) >> 12;
    *((_QWORD *)v10 + 4) ^= *((_QWORD *)v10 + 4) >> 12;
    *((_QWORD *)v10 + 5) ^= *((_QWORD *)v10 + 5) >> 12;
    *((_QWORD *)v10 + 6) ^= *((_QWORD *)v10 + 6) >> 12;
    *((_QWORD *)v10 + 7) ^= *((_QWORD *)v10 + 7) >> 12;
    *((_QWORD *)v10 + 8) ^= *((_QWORD *)v10 + 8) >> 12;
    *((_QWORD *)v10 + 9) ^= *((_QWORD *)v10 + 9) >> 12;
    *((_QWORD *)v10 + 10) ^= *((_QWORD *)v10 + 10) >> 12;
    *((_QWORD *)v10 + 11) ^= *((_QWORD *)v10 + 11) >> 12;
    *((_QWORD *)v10 + 12) ^= *((_QWORD *)v10 + 12) >> 12;
----SNIPPET----

Since we've reimplement it on python we can reuse the code. Here is the final script to implement the whole algorithm.

from Crypto.Util.number import *
import string

def print_hex(inp):
	arr = []
	for i in inp:
		arr.append(hex(i))
	print(arr)

def make_block(inp):
	blocks = []
	for i in range(0, len(inp), 8):
		blocks.append(bytes_to_long(inp[i:i+8]))
	return blocks

def make_byte(inp):
	arr = []
	for i in range(len(inp)):
		tmp = long_to_bytes(inp[i])[::-1]
		for j in range(len(tmp)):
			arr.append(tmp[j])
	return arr

def combine_16(inp):
	arr = []
	for i in range(0, len(inp), 2):
		tmp = long_to_bytes(inp[i])[::-1]
		tmp += long_to_bytes(inp[i+1])[::-1]
		arr.append(tmp)
	return arr

def combine_8(inp):
	arr = []
	for i in range(0, len(inp), 8):
		arr.append(bytes_to_long(bytes(inp[i:i+8])[::-1]))
	return arr

def sub_7FF671F610E0(a1, a2):
	v4 = a1[0]
	v6 = a1[1]
	v5 = []
	for i in range(32):
		for j in range(16):
			v4 += 1
			v4 &= 0xff
			v12 = a1[v4+2]
			v6 += v12
			v6 &= 0xff
			v14 = a1[v6+2]
			a1[v4+2] = v14
			a1[v6+2] = v12
			v5.append(a2[i][j] ^ a1[((v12 + v14)&0xff) + 2])
	return v5

dk = [0x0,0x0,0x32,0x63,0x4F,0x6A,0x61,0x0B,0x0D7,0x31,0x76,0x29,0x0C,0x69,0x21,0x93,0x1C,0x2B,0x0E9,0x0B6,0x0AA,0x3C,0x0CA,0x7,0x9B,0x54,0x58,0x6,0x0ED,0x96,0x89,0x0C7,0x0F9,0x66,0x0B8,0x92,0x82,0x17,0x19,0x1D,0x0A9,0x30,0x0FC,0x0E4,0x0F7,0x33,0x5C,0x0BB,0x8C,0x7A,0x0DD,0x38,0x48,0x0CD,0x1,0x1B,0x0B3,0x0DE,0x0,0x15,0x0CE,0x43,0x3,0x7E,0x36,0x7D,0x23,0x73,0x6B,0x0B1,0x46,0x52,0x59,0x3D,0x7F,0x5B,0x78,0x9F,0x85,0x4A,0x20,0x97,0x9A,0x1F,0x77,0x0AB,0x28,0x72,0x0CB,0x81,0x0CC,0x0EB,0x0D2,0x0B9,0x2D,0x12,0x13,0x0C0,0x0A6,0x25,0x71,0x0A,0x88,0x9E,0x37,0x22,0x24,0x47,0x42,0x0A5,0x0FA,0x2A,0x53,0x8A,0x6C,0x99,0x9,0x0E3,0x0EF,0x0EC,0x0E0,0x0A0,0x51,0x0F3,0x1E,0x4,0x8F,0x0E7,0x2,0x0A1,0x90,0x60,0x0A4,0x0AD,0x0FD,0x5F,0x79,0x44,0x6E,0x39,0x34,0x4C,0x0A3,0x0D4,0x74,0x0B4,0x9C,0x8E,0x83,0x0E6,0x4E,0x0D5,0x3A,0x0F1,0x0BE,0x6D,0x5,0x0BA,0x84,0x0B2,0x87,0x10,0x0F4,0x0BC,0x0D8,0x0FE,0x0AE,0x0BD,0x7C,0x0C3,0x0E8,0x0E5,0x4B,0x0C9,0x2C,0x0B5,0x3F,0x4D,0x50,0x0BF,0x8D,0x45,0x0C8,0x18,0x0F0,0x0DA,0x8B,0x0DB,0x0C4,0x16,0x8,0x0F,0x62,0x0D6,0x91,0x1A,0x0A8,0x9D,0x0D1,0x98,0x86,0x67,0x0C5,0x68,0x0C6,0x35,0x0AF,0x0EA,0x0F8,0x0C2,0x0D0,0x56,0x94,0x40,0x0FF,0x26,0x65,0x2E,0x0D9,0x49,0x57,0x5D,0x0AC,0x0A2,0x0B0,0x0D,0x0F6,0x0C1,0x0EE,0x0FB,0x55,0x6F,0x0A7,0x0DC,0x75,0x0E,0x64,0x14,0x0DF,0x95,0x0CF,0x3E,0x0F2,0x0F5,0x41,0x0D3,0x0E2,0x0B7,0x80,0x3B,0x27,0x7B,0x5E,0x0E1,0x2F,0x5A,0x70,0x11,0x0ABABABAB,0x0ABABABAB,0x0ABABABAB,0x0ABABABAB,0x0FEEEFEEE,0x0FEEEFEEE,0x0,0x0,0x0,0x0]
cmp_val = [  0x07, 0x56, 0xE5, 0x58, 0x71, 0x89, 0x9A, 0xCA, 0xF0, 0x67, 
  0x03, 0x2D, 0x49, 0xFB, 0x6E, 0x86, 0xC2, 0xF7, 0x48, 0xCA, 
  0x3C, 0x43, 0xDB, 0x8E, 0x04, 0x2A, 0x56, 0x4A, 0x97, 0x33, 
  0xA1, 0xA2, 0x07, 0x83, 0xF0, 0x89, 0x19, 0x13, 0x77, 0xB4, 
  0x9F, 0x7D, 0x7B, 0x9C, 0xDD, 0x8E, 0xFD, 0xAD, 0xB5, 0xE2, 
  0x28, 0x0E, 0x06, 0xAF, 0xE5, 0xE3, 0x86, 0xC3, 0x08, 0xAD, 
  0xE6, 0x4C, 0xDE, 0x63, 0xA3, 0x5F, 0x1E, 0x96, 0x34, 0x7D, 
  0x9D, 0x19, 0xF5, 0xC8, 0x84, 0x7F, 0x7B, 0x62, 0x2A, 0x6B, 
  0xC1, 0x28, 0x3B, 0x6D, 0x09, 0xEF, 0xFC, 0xCB, 0xA0, 0x90, 
  0x9A, 0x3E, 0x66, 0xA2, 0x4E, 0x06, 0x90, 0x2C, 0x9D, 0xAE, 
  0x3C, 0x99, 0x40, 0x53, 0x4C, 0x69, 0x63, 0xE7, 0xB9, 0xA8, 
  0xB3, 0x87, 0xA5, 0x97, 0x98, 0xFE, 0x1F, 0x20, 0x51, 0xA7, 
  0xAE, 0x0D, 0x00, 0xAB, 0x16, 0x35, 0x59, 0x3D, 0x08, 0x1B, 
  0x1C, 0x92, 0xE2, 0x4F, 0x1D, 0x86, 0xA5, 0x6E, 0x0A, 0x14, 
  0x45, 0x4D, 0x61, 0x08, 0x69, 0xC3, 0x12, 0xA2, 0xEB, 0x50, 
  0x13, 0x93, 0x22, 0xE2, 0xC4, 0x10, 0xCA, 0x5F, 0xB2, 0x0B, 
  0xA2, 0x30, 0xC8, 0x54, 0x91, 0x3A, 0x37, 0xFD, 0xD2, 0x10, 
  0xAB, 0x5A, 0xF8, 0x38, 0xF3, 0xD3, 0xD5, 0x85, 0x58, 0xDE, 
  0xDF, 0xC0, 0xF4, 0x17, 0x4E, 0xF7, 0x31, 0x79, 0xDD, 0x41, 
  0x2F, 0xB3, 0x20, 0xC7, 0xEC, 0x98, 0x5E, 0xAE, 0xF7, 0xA9, 
  0xCB, 0x27, 0x13, 0x72, 0xFE, 0xCA, 0x64, 0xFF, 0x43, 0x93, 
  0x80, 0x3E, 0x1E, 0xE5, 0x99, 0xBF, 0x41, 0x4B, 0x9D, 0x85, 
  0x4E, 0x0F, 0x99, 0x94, 0x57, 0xE1, 0x63, 0xD9, 0x01, 0x85, 
  0x78, 0x8A, 0x06, 0xFE, 0x9D, 0x41, 0x32, 0x74, 0x55, 0x83, 
  0xB2, 0x85, 0xE9, 0x9F, 0xC6, 0x2C, 0x4B, 0x62, 0x8F, 0xBF, 
  0x7D, 0x57, 0xC8, 0x76, 0x3B, 0x31, 0x5E, 0x87, 0x60, 0x89, 
  0x35, 0x41, 0xC1, 0x52, 0x6C, 0xD0, 0x0B, 0x7D, 0xCA, 0x60, 
  0x5D, 0x82, 0x19, 0xB0, 0x96, 0x5E, 0x16, 0xE7, 0x9B, 0x2F, 
  0x37, 0x5F, 0xC9, 0xC5, 0xF3, 0x20, 0xC3, 0x45, 0xCB, 0x47, 
  0xA1, 0xCC, 0x79, 0xE5, 0xB6, 0xFB, 0xD4, 0x55, 0xDB, 0xC1, 
  0x35, 0x9B, 0x8B, 0xFA, 0x38, 0xD5, 0xB2, 0xB5, 0xE0, 0x4F, 
  0x4D, 0x6C, 0x4F, 0x8C, 0x0C, 0x42, 0xBC, 0x8E, 0xB3, 0x78, 
  0x48, 0xE4, 0x87, 0x8E, 0x34, 0xA3, 0x1D, 0x01, 0x53, 0x98, 
  0x71, 0xFA, 0x8F, 0x2F, 0xE3, 0x7A, 0x6B, 0xB9, 0x1B, 0xB6, 
  0x7E, 0x34, 0x7F, 0xC8, 0xC4, 0x6C, 0xAB, 0x45, 0x4D, 0x81, 
  0xEF, 0xEE, 0xC3, 0xD9, 0xDB, 0x13, 0x5B, 0x63, 0x90, 0xFC, 
  0x34, 0x18, 0x81, 0xBC, 0xD1, 0x18, 0x48, 0xBB, 0x7C, 0x24, 
  0x5B, 0x56, 0x2B, 0x35, 0x6B, 0xD7, 0xF9, 0xD3, 0xD5, 0x2B, 
  0xE2, 0x24, 0xD8, 0x50, 0xF1, 0xEC, 0xD5, 0xE6, 0x29, 0x55, 
  0x66, 0xF2, 0xF7, 0x28, 0x20, 0x7D, 0xF3, 0x47, 0x40, 0x03, 
  0x11, 0x4A, 0x47, 0xA5, 0xB4, 0x74, 0x15, 0x35, 0xD0, 0xF0, 
  0xE5, 0x4C, 0x04, 0xB5, 0x59, 0xFE, 0xFC, 0x45, 0x9D, 0x3A, 
  0xA1, 0x3F, 0x1A, 0xA7, 0xA8, 0x51, 0xE5, 0x65, 0xF1, 0x56, 
  0xEE, 0xDE, 0xFC, 0xC4, 0x87, 0xF5, 0xFA, 0x79, 0x31, 0x07, 
  0x0A, 0x3F, 0x41, 0x28, 0xD1, 0x59, 0x17, 0x4D, 0x02, 0xE4, 
  0x5A, 0x22, 0x3A, 0xBC, 0xD2, 0xCD, 0x80, 0xBC, 0x2A, 0x49, 
  0xF0, 0x7F, 0x97, 0xA1, 0x90, 0x59, 0x01, 0x8D, 0x25, 0x43, 
  0xD8, 0x00, 0xEA, 0xD8, 0x4F, 0xE2, 0x4E, 0x2B, 0x06, 0xFD, 
  0x7E, 0x16, 0xA9, 0x92, 0xC4, 0xFD, 0xB5, 0x6A, 0x82, 0x06, 
  0x18, 0x0C, 0x0A, 0xB7, 0xB8, 0x29, 0x8F, 0x87, 0x63, 0x65, 
  0x25, 0xB9, 0x7A, 0xD0, 0x6E, 0x30, 0x3C, 0xF2, 0xF7, 0xC2, 
  0x30, 0x86]

inp = b""
for i in string.printable[:64]:
	inp += (i*8).encode()

print(inp)
v7 = make_block(inp)

for i in range(0, len(v7), 32):
	for j in range(32):
		v7[i+j] ^= v7[i+j] >> 12

v7_16 = combine_16(v7)
v10 = sub_7FF671F610E0(dk, v7_16)
v10_8 = combine_8(v10)
for i in range(0, len(v10_8), 32):
	for j in range(32):
		v10_8[i+j] ^= v10_8[i+j] >> 12

res = make_byte(v10_8)

print_hex(res)

Now, we assume that those algorhtm encrypt our input. So the next step we do is figuring out how to implement the decryption routine.

  • sub_7FF671F610E0

    • Encryption routine : xor processed input (a2) with static values (a1/SBOX)

    • Decyption routine : xor processed input (a2) with static values (a1/SBOX)

  • Xor shift part

Last step just implement decryption routine with above information then decrypt ciphertext of flag. Here is the implementation in python.

from Crypto.Util.number import *
import string

def print_hex(inp):
	arr = []
	for i in inp:
		arr.append(hex(i))
	print(arr)

def make_block(inp):
	blocks = []
	for i in range(0, len(inp), 8):
		blocks.append(bytes_to_long(inp[i:i+8]))
	return blocks

def make_byte(inp):
	arr = []
	for i in range(len(inp)):
		tmp = long_to_bytes(inp[i])[::-1]
		for j in range(len(tmp)):
			arr.append(tmp[j])
	return arr

def combine_16(inp):
	arr = []
	for i in range(0, len(inp), 2):
		tmp = long_to_bytes(inp[i]).rjust(8, b"\x00")[::-1]
		tmp += long_to_bytes(inp[i+1]).rjust(8, b"\x00")[::-1]
		arr.append(tmp)
	return arr

def combine_8(inp):
	arr = []
	for i in range(0, len(inp), 8):
		arr.append(bytes_to_long(bytes(inp[i:i+8])[::-1]))
	return arr

def sub_7FF671F610E0(a1, a2):
	v4 = a1[0]
	v6 = a1[1]
	v5 = []
	for i in range(32):
		for j in range(16):
			v4 += 1
			v4 &= 0xff
			v12 = a1[v4+2]
			v6 += v12
			v6 &= 0xff
			v14 = a1[v6+2]
			a1[v4+2] = v14
			a1[v6+2] = v12
			v5.append(a2[i][j] ^ a1[((v12 + v14)&0xff) + 2])
	return v5

def rev_shift(g):
    n = 0
    while g:
        n ^= g
        g >>= 12
    return n

res = [  0x07, 0x56, 0xE5, 0x58, 0x71, 0x89, 0x9A, 0xCA, 0xF0, 0x67, 
  0x03, 0x2D, 0x49, 0xFB, 0x6E, 0x86, 0xC2, 0xF7, 0x48, 0xCA, 
  0x3C, 0x43, 0xDB, 0x8E, 0x04, 0x2A, 0x56, 0x4A, 0x97, 0x33, 
  0xA1, 0xA2, 0x07, 0x83, 0xF0, 0x89, 0x19, 0x13, 0x77, 0xB4, 
  0x9F, 0x7D, 0x7B, 0x9C, 0xDD, 0x8E, 0xFD, 0xAD, 0xB5, 0xE2, 
  0x28, 0x0E, 0x06, 0xAF, 0xE5, 0xE3, 0x86, 0xC3, 0x08, 0xAD, 
  0xE6, 0x4C, 0xDE, 0x63, 0xA3, 0x5F, 0x1E, 0x96, 0x34, 0x7D, 
  0x9D, 0x19, 0xF5, 0xC8, 0x84, 0x7F, 0x7B, 0x62, 0x2A, 0x6B, 
  0xC1, 0x28, 0x3B, 0x6D, 0x09, 0xEF, 0xFC, 0xCB, 0xA0, 0x90, 
  0x9A, 0x3E, 0x66, 0xA2, 0x4E, 0x06, 0x90, 0x2C, 0x9D, 0xAE, 
  0x3C, 0x99, 0x40, 0x53, 0x4C, 0x69, 0x63, 0xE7, 0xB9, 0xA8, 
  0xB3, 0x87, 0xA5, 0x97, 0x98, 0xFE, 0x1F, 0x20, 0x51, 0xA7, 
  0xAE, 0x0D, 0x00, 0xAB, 0x16, 0x35, 0x59, 0x3D, 0x08, 0x1B, 
  0x1C, 0x92, 0xE2, 0x4F, 0x1D, 0x86, 0xA5, 0x6E, 0x0A, 0x14, 
  0x45, 0x4D, 0x61, 0x08, 0x69, 0xC3, 0x12, 0xA2, 0xEB, 0x50, 
  0x13, 0x93, 0x22, 0xE2, 0xC4, 0x10, 0xCA, 0x5F, 0xB2, 0x0B, 
  0xA2, 0x30, 0xC8, 0x54, 0x91, 0x3A, 0x37, 0xFD, 0xD2, 0x10, 
  0xAB, 0x5A, 0xF8, 0x38, 0xF3, 0xD3, 0xD5, 0x85, 0x58, 0xDE, 
  0xDF, 0xC0, 0xF4, 0x17, 0x4E, 0xF7, 0x31, 0x79, 0xDD, 0x41, 
  0x2F, 0xB3, 0x20, 0xC7, 0xEC, 0x98, 0x5E, 0xAE, 0xF7, 0xA9, 
  0xCB, 0x27, 0x13, 0x72, 0xFE, 0xCA, 0x64, 0xFF, 0x43, 0x93, 
  0x80, 0x3E, 0x1E, 0xE5, 0x99, 0xBF, 0x41, 0x4B, 0x9D, 0x85, 
  0x4E, 0x0F, 0x99, 0x94, 0x57, 0xE1, 0x63, 0xD9, 0x01, 0x85, 
  0x78, 0x8A, 0x06, 0xFE, 0x9D, 0x41, 0x32, 0x74, 0x55, 0x83, 
  0xB2, 0x85, 0xE9, 0x9F, 0xC6, 0x2C, 0x4B, 0x62, 0x8F, 0xBF, 
  0x7D, 0x57, 0xC8, 0x76, 0x3B, 0x31, 0x5E, 0x87, 0x60, 0x89, 
  0x35, 0x41, 0xC1, 0x52, 0x6C, 0xD0, 0x0B, 0x7D, 0xCA, 0x60, 
  0x5D, 0x82, 0x19, 0xB0, 0x96, 0x5E, 0x16, 0xE7, 0x9B, 0x2F, 
  0x37, 0x5F, 0xC9, 0xC5, 0xF3, 0x20, 0xC3, 0x45, 0xCB, 0x47, 
  0xA1, 0xCC, 0x79, 0xE5, 0xB6, 0xFB, 0xD4, 0x55, 0xDB, 0xC1, 
  0x35, 0x9B, 0x8B, 0xFA, 0x38, 0xD5, 0xB2, 0xB5, 0xE0, 0x4F, 
  0x4D, 0x6C, 0x4F, 0x8C, 0x0C, 0x42, 0xBC, 0x8E, 0xB3, 0x78, 
  0x48, 0xE4, 0x87, 0x8E, 0x34, 0xA3, 0x1D, 0x01, 0x53, 0x98, 
  0x71, 0xFA, 0x8F, 0x2F, 0xE3, 0x7A, 0x6B, 0xB9, 0x1B, 0xB6, 
  0x7E, 0x34, 0x7F, 0xC8, 0xC4, 0x6C, 0xAB, 0x45, 0x4D, 0x81, 
  0xEF, 0xEE, 0xC3, 0xD9, 0xDB, 0x13, 0x5B, 0x63, 0x90, 0xFC, 
  0x34, 0x18, 0x81, 0xBC, 0xD1, 0x18, 0x48, 0xBB, 0x7C, 0x24, 
  0x5B, 0x56, 0x2B, 0x35, 0x6B, 0xD7, 0xF9, 0xD3, 0xD5, 0x2B, 
  0xE2, 0x24, 0xD8, 0x50, 0xF1, 0xEC, 0xD5, 0xE6, 0x29, 0x55, 
  0x66, 0xF2, 0xF7, 0x28, 0x20, 0x7D, 0xF3, 0x47, 0x40, 0x03, 
  0x11, 0x4A, 0x47, 0xA5, 0xB4, 0x74, 0x15, 0x35, 0xD0, 0xF0, 
  0xE5, 0x4C, 0x04, 0xB5, 0x59, 0xFE, 0xFC, 0x45, 0x9D, 0x3A, 
  0xA1, 0x3F, 0x1A, 0xA7, 0xA8, 0x51, 0xE5, 0x65, 0xF1, 0x56, 
  0xEE, 0xDE, 0xFC, 0xC4, 0x87, 0xF5, 0xFA, 0x79, 0x31, 0x07, 
  0x0A, 0x3F, 0x41, 0x28, 0xD1, 0x59, 0x17, 0x4D, 0x02, 0xE4, 
  0x5A, 0x22, 0x3A, 0xBC, 0xD2, 0xCD, 0x80, 0xBC, 0x2A, 0x49, 
  0xF0, 0x7F, 0x97, 0xA1, 0x90, 0x59, 0x01, 0x8D, 0x25, 0x43, 
  0xD8, 0x00, 0xEA, 0xD8, 0x4F, 0xE2, 0x4E, 0x2B, 0x06, 0xFD, 
  0x7E, 0x16, 0xA9, 0x92, 0xC4, 0xFD, 0xB5, 0x6A, 0x82, 0x06, 
  0x18, 0x0C, 0x0A, 0xB7, 0xB8, 0x29, 0x8F, 0x87, 0x63, 0x65, 
  0x25, 0xB9, 0x7A, 0xD0, 0x6E, 0x30, 0x3C, 0xF2, 0xF7, 0xC2, 
  0x30, 0x86]

print(len(res))

v10_8 = combine_8(res)

for i in range(0, len(v10_8),  32):
	for j in range(31, -1, -1):
		v10_8[i+j] = rev_shift(v10_8[i+j])

v10 = combine_16(v10_8)
dk = [0x0,0x0,0x32,0x63,0x4F,0x6A,0x61,0x0B,0x0D7,0x31,0x76,0x29,0x0C,0x69,0x21,0x93,0x1C,0x2B,0x0E9,0x0B6,0x0AA,0x3C,0x0CA,0x7,0x9B,0x54,0x58,0x6,0x0ED,0x96,0x89,0x0C7,0x0F9,0x66,0x0B8,0x92,0x82,0x17,0x19,0x1D,0x0A9,0x30,0x0FC,0x0E4,0x0F7,0x33,0x5C,0x0BB,0x8C,0x7A,0x0DD,0x38,0x48,0x0CD,0x1,0x1B,0x0B3,0x0DE,0x0,0x15,0x0CE,0x43,0x3,0x7E,0x36,0x7D,0x23,0x73,0x6B,0x0B1,0x46,0x52,0x59,0x3D,0x7F,0x5B,0x78,0x9F,0x85,0x4A,0x20,0x97,0x9A,0x1F,0x77,0x0AB,0x28,0x72,0x0CB,0x81,0x0CC,0x0EB,0x0D2,0x0B9,0x2D,0x12,0x13,0x0C0,0x0A6,0x25,0x71,0x0A,0x88,0x9E,0x37,0x22,0x24,0x47,0x42,0x0A5,0x0FA,0x2A,0x53,0x8A,0x6C,0x99,0x9,0x0E3,0x0EF,0x0EC,0x0E0,0x0A0,0x51,0x0F3,0x1E,0x4,0x8F,0x0E7,0x2,0x0A1,0x90,0x60,0x0A4,0x0AD,0x0FD,0x5F,0x79,0x44,0x6E,0x39,0x34,0x4C,0x0A3,0x0D4,0x74,0x0B4,0x9C,0x8E,0x83,0x0E6,0x4E,0x0D5,0x3A,0x0F1,0x0BE,0x6D,0x5,0x0BA,0x84,0x0B2,0x87,0x10,0x0F4,0x0BC,0x0D8,0x0FE,0x0AE,0x0BD,0x7C,0x0C3,0x0E8,0x0E5,0x4B,0x0C9,0x2C,0x0B5,0x3F,0x4D,0x50,0x0BF,0x8D,0x45,0x0C8,0x18,0x0F0,0x0DA,0x8B,0x0DB,0x0C4,0x16,0x8,0x0F,0x62,0x0D6,0x91,0x1A,0x0A8,0x9D,0x0D1,0x98,0x86,0x67,0x0C5,0x68,0x0C6,0x35,0x0AF,0x0EA,0x0F8,0x0C2,0x0D0,0x56,0x94,0x40,0x0FF,0x26,0x65,0x2E,0x0D9,0x49,0x57,0x5D,0x0AC,0x0A2,0x0B0,0x0D,0x0F6,0x0C1,0x0EE,0x0FB,0x55,0x6F,0x0A7,0x0DC,0x75,0x0E,0x64,0x14,0x0DF,0x95,0x0CF,0x3E,0x0F2,0x0F5,0x41,0x0D3,0x0E2,0x0B7,0x80,0x3B,0x27,0x7B,0x5E,0x0E1,0x2F,0x5A,0x70,0x11,0x0ABABABAB,0x0ABABABAB,0x0ABABABAB,0x0ABABABAB,0x0FEEEFEEE,0x0FEEEFEEE,0x0,0x0,0x0,0x0]
v7_16 = sub_7FF671F610E0(dk, v10)
v7 = combine_8(v7_16)

for i in range(0, len(v7),  32):
	for j in range(31, -1, -1):
		v7[i+j] = rev_shift(v7[i+j])

print(bytes(make_byte(v7)))

Flag : TSGCTF{y0u_w0uld_und3r57and_h0w_70_d3cryp7_arc4_and_h0w_70_d3cryp7_7h3_l3ak3d_5af3_l1nk1ng_p01n73r}

T the weakest (215 pts)

Description

T「AHHHHHH!」 S「LOOKS LIKE T IS DEFEATED」 G「HEH. HE'S ALWAYS THE WEAKEST OF THE BIG ONE HUNDRED.」 C「LOSING TO A MERE MORTAL. WHAT A DISGRANCE TO TSG-ER.」

Solution

Given ELF 64 bit, open it using IDA

So IDA cant decompile it and when i try to edit the stack IDA still cant decompile it. Next, i try to open it using ghidra. Looking at start function, we found the main address which is LAB_001010a0.

From pseudocode we can see the big picture of the program but there still some part that easier to analyze on assembly part. Here is the big picture of the whole executable

  • FUN_00101255(), print "ng" then exit

  • If there is no argument provided, the program will exit by calling FUN_00101255

  • If argv[1] (first argument) is not "T" the program will exit by calling FUN_00101255.

  • Program will call memfd_create using syscall then write data to it. After write done, execute the written data on fd/x using execv function with the next byte of our first argument (argv[1] + 1).

From above information, i tried to implement automation using gdb scripting. In this case i try to implement flow below automatically with my script.

  • Check the main address is correct by analyzing the first instruction

  • Set pie breakpoint and delete pie breakpoint

  • If input is correct, it will call write function that has known pattern in assembly. So dump the new executable if it call write function then move to new executable.

  • Check if char has been found or not, if char not found so there is something different with the new executable

#!/usr/bin/python3
import string
import os

class SolverEquation(gdb.Command):
    def __init__ (self):
        super (SolverEquation, self).__init__ ("solve-equation",gdb.COMMAND_OBSCURE)
    
    def invoke (self, arg, from_tty):
        flag = ""
        list_chr = '_0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ{}'
        poss_main = ["0x10a0"]
        cnt = 0
        while "}" not in flag:
            i = len(flag)
            os.system(f"chmod +x t{i}")
            base_main = poss_main[cnt%len(poss_main)]
            print(f"base_main: {base_main}")
            
            for z in list_chr:
                inp = flag + z
                gdb.execute("del")
                gdb.execute("pie del")
                gdb.execute(f"pie b {base_main}")
                
                gdb.execute(f"file t{i}")
                gdb.execute(f"pie run {inp[i]}")
                
                try:
                    arch = gdb.selected_frame().architecture()
                except Exception as e:
                    break

                current_pc = addr2num(gdb.selected_frame().read_register("pc"))
                disa = arch.disassemble(current_pc)[0]

                if("push" in disa["asm"]):
                    tmp = gdb.execute("x/150i $pc", to_string=True)
                    dict = parse_and_break(tmp)
                
                    print(i, inp)
                    gdb.execute("c")

                    try:
                        rip = addr2num(gdb.selected_frame().read_register("pc"))
                        flag = inp
                        gdb.execute(f"dump binary memory t{i+1} $rsi $rsi+$rdx")
                        gdb.execute("pie run")
                        break
                    except Exception as e:
                        gdb.execute("pie run")
                        continue
                else:
                    gdb.execute("pie run")
                    break
            if(len(flag) == i):
                print("Something different")
                break
            cnt += 1
            print("flag", i, flag)

def parse_and_break(a1):
    zz = a1.split("\n")[:-1]
    dict = {}
    for i in range(3, len(zz)):
        tmp4 = zz[i-3].split(":\t")
        tmp3 = zz[i-2].split(":\t")
        tmp2 = zz[i-1].split(":\t")
        tmp = zz[i].split(":\t")
        if(("mov    rbx,rax" == tmp3[1] or "mov    rbp,rax" == tmp3[1]) and "mov    edi,eax" == tmp2[1] and "call" in tmp[1]):
            dict["rip"] = tmp[0][-14:]
            gdb.execute(f"b *{tmp[0][-14:]}")

    return dict

        
def addr2num(addr):
    try:
        return int(addr)  # Python 3
    except:
        return long(addr) # Python 2

SolverEquation()

Last flag we got is TSGCTF{hint_do_script, so check the newest executable then decompile it using ghidra.

main function on t21 is on address 001010b0.

t21 check environment variable LINES and COLUMNS, if there is environment variable LINES and COLUMNS it will automatically exit the program by calling FUN_0010285 function. So to bypass this validation we need to add unset environment command in our automation script. Besides that we need to add address 0x001010b0 in list of main function. Here is the modified script

#!/usr/bin/python3
import string
import os

class SolverEquation(gdb.Command):
    def __init__ (self):
        super (SolverEquation, self).__init__ ("solve-equation",gdb.COMMAND_OBSCURE)
    
    def invoke (self, arg, from_tty):
        flag = "TSGCTF{hint_do_script"
        list_chr = '_0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ{}'
        poss_main = ["0x10a0", "0x10b0"]
        cnt = 0
        while "}" not in flag:
            i = len(flag)
            os.system(f"chmod +x t{i}")
            base_main = poss_main[cnt%len(poss_main)]
            print(f"base_main: {base_main}")
            
            for z in list_chr:
                inp = flag + z
                gdb.execute("del")
                gdb.execute("pie del")
                gdb.execute(f"pie b {base_main}")
                gdb.execute("unset environment LINES")
                gdb.execute("unset environment COLUMNS")
                
                gdb.execute(f"file t{i}")
                gdb.execute(f"pie run {inp[i]}")
                
                try:
                    arch = gdb.selected_frame().architecture()
                except Exception as e:
                    break

                current_pc = addr2num(gdb.selected_frame().read_register("pc"))
                disa = arch.disassemble(current_pc)[0]

                if("push" in disa["asm"]):
                    tmp = gdb.execute("x/150i $pc", to_string=True)
                    dict = parse_and_break(tmp)
                
                    print(i, inp)
                    gdb.execute("c")

                    try:
                        rip = addr2num(gdb.selected_frame().read_register("pc"))
                        flag = inp
                        gdb.execute(f"dump binary memory t{i+1} $rsi $rsi+$rdx")
                        gdb.execute("pie run")
                        break
                    except Exception as e:
                        gdb.execute("pie run")
                        continue
                else:
                    gdb.execute("pie run")
                    break
            
            print("flag", i, flag)
            
            if(len(flag) == i):
                cnt += 1
                if(cnt == len(poss_main)):
                    print("Something different")
                    break
            else:
                cnt = 0

def parse_and_break(a1):
    zz = a1.split("\n")[:-1]
    dict = {}
    for i in range(3, len(zz)):
        tmp4 = zz[i-3].split(":\t")
        tmp3 = zz[i-2].split(":\t")
        tmp2 = zz[i-1].split(":\t")
        tmp = zz[i].split(":\t")
        if(("mov    rbx,rax" == tmp3[1] or "mov    rbp,rax" == tmp3[1]) and "mov    edi,eax" == tmp2[1] and "call" in tmp[1]):
            dict["rip"] = tmp[0][-14:]
            gdb.execute(f"b *{tmp[0][-14:]}")

    return dict

        
def addr2num(addr):
    try:
        return int(addr)  # Python 3
    except:
        return long(addr) # Python 2

SolverEquation()

Okay, t26 is different. Open it using ghidra

There is new validation which compare (returned values from malloc) - rdx with 0x20000. To bypass this we just need to set rax value to > 0x20000 on address 0x1010d8. Here is the modified script

#!/usr/bin/python3
import string
import os

class SolverEquation(gdb.Command):
    def __init__ (self):
        super (SolverEquation, self).__init__ ("solve-equation",gdb.COMMAND_OBSCURE)
    
    def invoke (self, arg, from_tty):
        flag = "TSGCTF{hint_do_scripting_R"
        list_chr = '_0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ{}'
        poss_main = ["0x10a0", "0x10b0"]
        cnt = 0
        while "}" not in flag:
            i = len(flag)
            os.system(f"chmod +x t{i}")
            base_main = poss_main[cnt%len(poss_main)]
            print(f"base_main: {base_main}")
            
            for z in list_chr:
                inp = flag + z
                gdb.execute("del")
                gdb.execute("pie del")
                gdb.execute(f"pie b {base_main}")
                gdb.execute("unset environment LINES")
                gdb.execute("unset environment COLUMNS")
                
                gdb.execute(f"file t{i}")
                gdb.execute(f"pie run {inp[i]}")
                
                try:
                    arch = gdb.selected_frame().architecture()
                except Exception as e:
                    break

                current_pc = addr2num(gdb.selected_frame().read_register("pc"))
                disa = arch.disassemble(current_pc)[0]

                if("push" in disa["asm"]):
                    tmp = gdb.execute("x/150i $pc", to_string=True)
                    dict = parse_and_break(tmp)
                
                    print(i, inp)
                    gdb.execute("c")

                    if("jg" in dict):
                        gdb.execute("set $rax=0x20001")
                        gdb.execute("c")

                    try:
                        rip = addr2num(gdb.selected_frame().read_register("pc"))
                        flag = inp
                        gdb.execute(f"dump binary memory t{i+1} $rsi $rsi+$rdx")
                        gdb.execute("pie run")
                        break
                    except Exception as e:
                        gdb.execute("pie run")
                        continue
                else:
                    gdb.execute("pie run")
                    break
            
            print("flag", i, flag)
            
            if(len(flag) == i):
                cnt += 1
                if(cnt == len(poss_main)):
                    print("Something different")
                    break
            else:
                cnt = 0

def parse_and_break(a1):
    zz = a1.split("\n")[:-1]
    dict = {}
    for i in range(3, len(zz)):
        tmp4 = zz[i-3].split(":\t")
        tmp3 = zz[i-2].split(":\t")
        tmp2 = zz[i-1].split(":\t")
        tmp = zz[i].split(":\t")
        
        if(("mov    rbx,rax" == tmp3[1] or "mov    rbp,rax" == tmp3[1]) and "mov    edi,eax" == tmp2[1] and "call" in tmp[1]):
            dict["rip"] = tmp[0][-14:]
            gdb.execute(f"b *{tmp[0][-14:]}")

        if("cmp    rax,0x20000"  == tmp[1]):
            dict["jg"] = tmp[0][-14:]
            gdb.execute(f"b *{tmp[0][-14:]}")

    return dict

        
def addr2num(addr):
    try:
        return int(addr)  # Python 3
    except:
        return long(addr) # Python 2

SolverEquation()

Open t41 using ghidra.

It use LAB_001010c0 as main address, open main function

It calls signal function that will exit program in particular time. So to bypass this we can just skip the call process of signal function. We can skip it by set the rip register to next instruction which has different 0x5 with call signal address.

#!/usr/bin/python3
import string
import os

class SolverEquation(gdb.Command):
    def __init__ (self):
        super (SolverEquation, self).__init__ ("solve-equation",gdb.COMMAND_OBSCURE)
    
    def invoke (self, arg, from_tty):
        flag = "TSGCTF{hint_do_scripting_RdJ5GNjKkUidxjcG"
        list_chr = '_0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ{}'
        poss_main = ["0x10a0", "0x10b0", "0x10c0"]
        cnt = 0
        while "}" not in flag:
            i = len(flag)
            os.system(f"chmod +x t{i}")
            base_main = poss_main[cnt%len(poss_main)]
            print(f"base_main: {base_main}")
            
            for z in list_chr:
                inp = flag + z
                gdb.execute("del")
                gdb.execute("pie del")
                gdb.execute(f"pie b {base_main}")
                gdb.execute("unset environment LINES")
                gdb.execute("unset environment COLUMNS")
                
                gdb.execute(f"file t{i}")
                gdb.execute(f"pie run {inp[i]}")
                
                try:
                    arch = gdb.selected_frame().architecture()
                except Exception as e:
                    break

                current_pc = addr2num(gdb.selected_frame().read_register("pc"))
                disa = arch.disassemble(current_pc)[0]

                if("push" in disa["asm"]):
                    tmp = gdb.execute("x/150i $pc", to_string=True)
                    dict = parse_and_break(tmp)
                
                    print(i, inp)
                    gdb.execute("c")

                    if("jg" in dict):
                        gdb.execute("set $rax=0x20001")
                        gdb.execute("c")

                    if("signal1" in dict and "signal2" in dict):
                        gdb.execute(f"set $rip={dict['signal1']}+0x5")
                        gdb.execute("c")
                        gdb.execute(f"set $rip={dict['signal2']}+0x5")
                        gdb.execute("c")

                    try:
                        rip = addr2num(gdb.selected_frame().read_register("pc"))
                        flag = inp
                        gdb.execute(f"dump binary memory t{i+1} $rsi $rsi+$rdx")
                        gdb.execute("pie run")
                        break
                    except Exception as e:
                        gdb.execute("pie run")
                        continue
                else:
                    gdb.execute("pie run")
                    break
            
            print("flag", i, flag)
            
            if(len(flag) == i):
                cnt += 1
                if(cnt == len(poss_main)):
                    print("Something different")
                    break
            else:
                cnt = 0

def parse_and_break(a1):
    zz = a1.split("\n")[:-1]
    dict = {}
    for i in range(3, len(zz)):
        tmp4 = zz[i-3].split(":\t")
        tmp3 = zz[i-2].split(":\t")
        tmp2 = zz[i-1].split(":\t")
        tmp = zz[i].split(":\t")
        
        if(("mov    rbx,rax" == tmp3[1] or "mov    rbp,rax" == tmp3[1]) and "mov    edi,eax" == tmp2[1] and "call" in tmp[1]):
            dict["rip"] = tmp[0][-14:]
            gdb.execute(f"b *{tmp[0][-14:]}")

        if("cmp    rax,0x20000"  == tmp[1]):
            dict["jg"] = tmp[0][-14:]
            gdb.execute(f"b *{tmp[0][-14:]}")

        if("call" in tmp[1] and tmp4[1] == tmp[1] and "mov    edi,0x4" == tmp2[1] and "lea    rsi" in tmp3[1]):
            dict["signal2"] = tmp[0][-14:]
            dict["signal1"] = tmp4[0][-14:]
            gdb.execute(f"b *{tmp4[0][-14:]}")
            gdb.execute(f"b *{tmp[0][-14:]}")

    return dict

        
def addr2num(addr):
    try:
        return int(addr)  # Python 3
    except:
        return long(addr) # Python 2

SolverEquation()

Open t59 using ghidra.

t59 use LAB_001010e0 as main function, open main function.

It compare the return of ptrace function with 0 after incremented it. So to bypass it we just need to set the rax value to valid value, such as for INC RAX -> JNZ we can set RAX to 0xffffffffffffffff, for INC RAX -> JZ we can set RAX to 0x0. Here is the modified script

#!/usr/bin/python3
import string
import os

class SolverEquation(gdb.Command):
    def __init__ (self):
        super (SolverEquation, self).__init__ ("solve-equation",gdb.COMMAND_OBSCURE)
    
    def invoke (self, arg, from_tty):
        flag = "TSGCTF{hint_do_scripting_RdJ5GNjKkUidxjcGN4o7j5Wxz1Feo19Q0_"
        list_chr = '_0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ{}'
        poss_main = ["0x10a0", "0x10b0", "0x10c0", "0x10e0"]
        cnt = 0
        while "}" not in flag:
            i = len(flag)
            os.system(f"chmod +x t{i}")
            base_main = poss_main[cnt%len(poss_main)]
            print(f"base_main: {base_main}")
            
            for z in list_chr:
                inp = flag + z
                gdb.execute("del")
                gdb.execute("pie del")
                gdb.execute(f"pie b {base_main}")
                gdb.execute("unset environment LINES")
                gdb.execute("unset environment COLUMNS")
                
                gdb.execute(f"file t{i}")
                gdb.execute(f"pie run {inp[i]}")
                
                try:
                    arch = gdb.selected_frame().architecture()
                except Exception as e:
                    gdb.execute("pie run")
                    break

                current_pc = addr2num(gdb.selected_frame().read_register("pc"))
                disa = arch.disassemble(current_pc)[0]
                
                if("push" in disa["asm"]):
                  
                    tmp = gdb.execute("x/150i $pc", to_string=True)
                    dict = parse_and_break(tmp)
                
                    print(i, inp)
                    gdb.execute("c")

                    if("jg" in dict):
                        gdb.execute("set $rax=0x20001")
                        gdb.execute("c")

                    if("signal1" in dict and "signal2" in dict):
                        gdb.execute(f"set $rip={dict['signal1']}+0x5")
                        gdb.execute("c")
                        gdb.execute(f"set $rip={dict['signal2']}+0x5")
                        gdb.execute("c")

                    if("ptrace" in dict):
                        try:
                            gdb.execute("set $rax = 0xffffffffffffffff")
                            gdb.execute("c")
                            gdb.execute("set $rax = 0x0")
                            gdb.execute("c")
                            gdb.execute("set $rax = 0xffffffffffffffff")
                            gdb.execute("c")
                            gdb.execute("set $rax = 0xffffffffffffffff")
                            gdb.execute("c")
                        except Exception as e:
                            gdb.execute("pie run")
                            continue

                    try:
                        rip = addr2num(gdb.selected_frame().read_register("pc"))
                        flag = inp
                        gdb.execute(f"dump binary memory t{i+1} $rsi $rsi+$rdx")
                        gdb.execute("pie run")
                        break
                    except Exception as e:
                        gdb.execute("pie run")
                        continue
                else:
                    gdb.execute("pie run")
                    break
            
            print("flag", i, flag)
            
            if(len(flag) == i):
                cnt += 1
                if(cnt == len(poss_main)):
                    print("Something different")
                    break
            else:
                cnt = 0

def parse_and_break(a1):
    zz = a1.split("\n")[:-1]
    dict = {}
    for i in range(3, len(zz)):
        tmp4 = zz[i-3].split(":\t")
        tmp3 = zz[i-2].split(":\t")
        tmp2 = zz[i-1].split(":\t")
        tmp = zz[i].split(":\t")
        
        if(("mov    rbx,rax" == tmp3[1] or "mov    rbp,rax" == tmp3[1]) and "mov    edi,eax" == tmp2[1] and "call" in tmp[1]):
            dict["rip"] = tmp[0][-14:]
            gdb.execute(f"b *{tmp[0][-14:]}")

        if("cmp    rax,0x20000"  == tmp[1]):
            dict["jg"] = tmp[0][-14:]
            gdb.execute(f"b *{tmp[0][-14:]}")

        if("call" in tmp[1] and tmp4[1] == tmp[1] and "mov    edi,0x4" == tmp2[1] and "lea    rsi" in tmp3[1]):
            dict["signal2"] = tmp[0][-14:]
            dict["signal1"] = tmp4[0][-14:]
            gdb.execute(f"b *{tmp4[0][-14:]}")
            gdb.execute(f"b *{tmp[0][-14:]}")

        if(("jne" in tmp[1] or "je" in tmp[1]) and "inc" in tmp2[1] and "call" in tmp3[1]):
            dict["ptrace"] = [tmp2[0][-14:]]
            gdb.execute(f"b *{tmp2[0][-14:]}")

    return dict

        
def addr2num(addr):
    try:
        return int(addr)  # Python 3
    except:
        return long(addr) # Python 2

SolverEquation()

Open t60 using ghidra

t60 only has diffeerent on main address. So just add 0x1100 in poss_main array.

t64 also has different in main address, so just add 0x10f0 in poss_main array.

Got sigsegv on t84, open it using ghidra

It use 0x1010d0 as main address, so add it to poss_main array.

Here is the final script

#!/usr/bin/python3
import string
import os

class SolverEquation(gdb.Command):
    def __init__ (self):
        super (SolverEquation, self).__init__ ("solve-equation",gdb.COMMAND_OBSCURE)
    
    def invoke (self, arg, from_tty):
        flag = "TSGCTF{hint_do_scripting_RdJ5GNjKkUidxjcGN4o7j5Wxz1Feo19Q0_hop3_you_did_no7_s0lve_ma"
        list_chr = '_0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ{}'
        poss_main = ["0x10a0", "0x10b0", "0x10c0", "0x10d0", "0x10e0", "0x10f0", "0x1100"]
        cnt = 0
        while "}" not in flag:
            i = len(flag)
            os.system(f"chmod +x t{i}")
            base_main = poss_main[cnt%len(poss_main)]
            print(f"base_main: {base_main}")
            
            for z in list_chr:
                inp = flag + z
                gdb.execute("del")
                gdb.execute("pie del")
                gdb.execute(f"pie b {base_main}")
                gdb.execute("unset environment LINES")
                gdb.execute("unset environment COLUMNS")
                
                gdb.execute(f"file t{i}")
                gdb.execute(f"pie run {inp[i]}")
                
                try:
                    arch = gdb.selected_frame().architecture()
                except Exception as e:
                    gdb.execute("pie run")
                    break

                current_pc = addr2num(gdb.selected_frame().read_register("pc"))
                disa = arch.disassemble(current_pc)[0]
                
                if("push" in disa["asm"]):
                    tmp = gdb.execute("x/150i $pc", to_string=True)
                    dict = parse_and_break(tmp)
                
                    print(i, inp)
                    gdb.execute("c")

                    if("jg" in dict):
                        gdb.execute("set $rax=0x20001")
                        gdb.execute("c")

                    if("signal1" in dict and "signal2" in dict):
                        gdb.execute(f"set $rip={dict['signal1']}+0x5")
                        gdb.execute("c")
                        gdb.execute(f"set $rip={dict['signal2']}+0x5")
                        gdb.execute("c")

                    if("ptrace" in dict):
                        try:
                            gdb.execute("set $rax = 0xffffffffffffffff")
                            gdb.execute("c")
                            gdb.execute("set $rax = 0x0")
                            gdb.execute("c")
                            gdb.execute("set $rax = 0xffffffffffffffff")
                            gdb.execute("c")
                            gdb.execute("set $rax = 0xffffffffffffffff")
                            gdb.execute("c")
                        except Exception as e:
                            gdb.execute("pie run")
                            continue

                    try:
                        rip = addr2num(gdb.selected_frame().read_register("pc"))
                        flag = inp
                        gdb.execute(f"dump binary memory t{i+1} $rsi $rsi+$rdx")
                        gdb.execute("pie run")
                        break
                    except Exception as e:
                        gdb.execute("pie run")
                        continue
                else:
                    gdb.execute("pie run")
                    break
            
            print("flag", i, flag)
            
            if(len(flag) == i):
                cnt += 1
                if(cnt == len(poss_main)):
                    print("Something different")
                    break
            else:
                cnt = 0

def parse_and_break(a1):
    zz = a1.split("\n")[:-1]
    dict = {}
    for i in range(3, len(zz)):
        tmp4 = zz[i-3].split(":\t")
        tmp3 = zz[i-2].split(":\t")
        tmp2 = zz[i-1].split(":\t")
        tmp = zz[i].split(":\t")
        
        if(("mov    rbx,rax" == tmp3[1] or "mov    rbp,rax" == tmp3[1]) and "mov    edi,eax" == tmp2[1] and "call" in tmp[1]):
            dict["rip"] = tmp[0][-14:]
            gdb.execute(f"b *{tmp[0][-14:]}")

        if("cmp    rax,0x20000"  == tmp[1]):
            dict["jg"] = tmp[0][-14:]
            gdb.execute(f"b *{tmp[0][-14:]}")

        if("call" in tmp[1] and tmp4[1] == tmp[1] and "mov    edi,0x4" == tmp2[1] and "lea    rsi" in tmp3[1]):
            dict["signal2"] = tmp[0][-14:]
            dict["signal1"] = tmp4[0][-14:]
            gdb.execute(f"b *{tmp4[0][-14:]}")
            gdb.execute(f"b *{tmp[0][-14:]}")

        if(("jne" in tmp[1] or "je" in tmp[1]) and "inc" in tmp2[1] and "call" in tmp3[1]):
            dict["ptrace"] = [tmp2[0][-14:]]
            gdb.execute(f"b *{tmp2[0][-14:]}")

    return dict

        
def addr2num(addr):
    try:
        return int(addr)  # Python 3
    except:
        return long(addr) # Python 2

SolverEquation()

Flag : TSGCTF{hint_do_scripting_RdJ5GNjKkUidxjcGN4o7j5Wxz1Feo19Q0_hop3_you_did_no7_s0lve_manu4l1y_vNbwVTKw}

Conduit (470 pts)

Description

Solution

Natural Flag Processing 2 (470 pts)

Description

Solution

Nets (500 pts)

Description

Solution

mimetic_cycle (500 pts)

Description

Solution

Frictionless (500 pts)

Description

Solution

Last updated