ASCWG Reverse Engineering challenges

S3dny
10 min readOct 19, 2024

--

Hello everybody, Hope you all feel good,

Here we are with a new challenges and new experience to get. I have joined the Arab Cybersecurity wargames CTf and I really enjoyed the the reverse engineering challenges. So go and bring your cup of coffee and let’s discuss how could we solve the reverse engineering challenges.

You will find the challenges here:
https://drive.google.com/drive/folders/1Mp0xA6oP3ru9Hcg4H-L9tMjDD70GBm6z?usp=sharing

1- Trst

when I run the challenge it doesn’t take any input from the user. It just prints this words.

Hello World!
Hello World!22222222222

So let’s open it on IDA and see what we have. At the first time we will recognize that there is anti-disassemble technique used here and there is a large part of byte-code that isn’t recognized nor analyzed well.

At the first time I tried to write a code that replaces the jz & jnz with nops but really it was useless . So I decided to debug it and see what we will get.

While debugging I found that there is a lot of mov instructions like this so seems that I’m going in the right way. By single stepping on the instructions , here is our flag.

ASCWG{0eac0677e1c47d5412dd16a84d8ebff8}

2–10100111

This challenge is an ELF file that takes a flag as an input and validate it. here is the decompilation of the main function.

 byte_633C = 0;
v27 = 0;
v25 = &v27;
dword_6340 = 6;
v26 = 0LL;
std::__ostream_insert<char,std::char_traits<char>>(&std::cout, "Enter the flag: ", 16LL);
std::operator>><char,std::char_traits<char>,std::allocator<char>>(&std::cin, &v25);
if ( v26 != 43 )
{
std::__ostream_insert<char,std::char_traits<char>>(&std::cout, "Wrong flag :(", 13LL);
sub_2950((std::ostream *)&std::cout);
return std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::_M_dispose(&v25);
}
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::substr(v28, &v25, 0LL, dword_6340);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::substr(
&src,
&v25,
dword_6340,
v26 + ~(__int64)dword_6340);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::substr(v31, &v25, v26 - 1, -1LL);
if ( !(unsigned __int8)sub_2B20(v28, "ASCWG{") || !(unsigned __int8)sub_2B20(v31, "}") )
goto LABEL_18;
v1 = n;
if ( byte_633C )
{
v2 = src;
v20 = 0LL;
v19 = 0LL;
if ( (n & 0x8000000000000000LL) != 0LL )
{
std::__throw_length_error("cannot create std::vector larger than max_size()");
goto LABEL_22;
}
}
else
{
if ( dword_6340 * dword_6340 != n )
{
exception = (std::runtime_error *)__cxa_allocate_exception(0x10uLL);
std::runtime_error::runtime_error(exception, "Assertion failed: substring2 length doesn't match");
__cxa_throw(
exception,
(struct type_info *)&`typeinfo for'std::runtime_error,
(void (__fastcall *)(void *))&std::runtime_error::~runtime_error);
}
v2 = src;
v20 = 0LL;
v19 = 0LL;
}
if ( !n )
{
LABEL_22:
v4 = 0LL;
*(_QWORD *)&v19 = 0LL;
v20 = 0LL;
goto LABEL_11;
}
v3 = (_BYTE *)operator new(n);
v4 = &v3[v1];
*(_QWORD *)&v19 = v3;
v20 = &v3[v1];
if ( v1 <= 1 )
{
*v3 = *v2;
}
else
{
v18 = &v3[v1];
memmove(v3, v2, v1);
v4 = v18;
}
LABEL_11:
*((_QWORD *)&v19 + 1) = v4;
sub_32E0(v21, &v19, (unsigned int)dword_6340);
v5 = dword_6340;
if ( dword_6340 >= -1 )
{
v6 = v21[0];
v7 = 0LL;
v8 = (_QWORD *)v21[0];
do
{
v9 = v8;
v10 = 0LL;
if ( v5 - 2 * (int)v7 > 1 )
{
do
{
v11 = (char *)(*v8 + v10 + v7);
v9 += 3;
v12 = *v11;
*v11 = *(_BYTE *)(*(_QWORD *)(v6 + 24LL * (v5 - 1 - (int)v7 - (int)v10)) + v7);
*(_BYTE *)(*(_QWORD *)(v6 + 24LL * (dword_6340 + ~(_DWORD)v7 - (int)v10)) + v7) = *(_BYTE *)(*(_QWORD *)(v6 + 24LL * (dword_6340 + ~(_DWORD)v7)) + dword_6340 + ~(_DWORD)v7 - (int)v10);
v13 = dword_6340 + ~(_DWORD)v7;
v14 = v13;
v15 = v13 - v10++;
*(_BYTE *)(*(_QWORD *)(v6 + 24 * v14) + v15) = *(_BYTE *)(*(v9 - 3) + v14);
*(_BYTE *)(*(v9 - 3) + dword_6340 + ~(_DWORD)v7) = v12;
v5 = dword_6340;
}
while ( dword_6340 - 2 * (int)v7 - 1 > (int)v10 );
}
++v7;
v8 += 3;
}
while ( v5 / 2 >= (int)v7 );
}
sub_29B0(v22, v21, 2LL, 3LL);
sub_2BA0(v33, v22, 0LL);
sub_29B0(v23, v21, 1LL, 4LL);
sub_2BA0(v35, v23, 1LL);
sub_29B0(v24, v21, 0LL, 5LL);
sub_2BA0(v36, v24, 2LL);
sub_2FC0(v34, v36, v35);
sub_2FC0(v32, v34, v33);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::_M_dispose(v34);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::_M_dispose(v36);
sub_2B70(v24);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::_M_dispose(v35);
sub_2B70(v23);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::_M_dispose(v33);
sub_2B70(v22);
v16 = sub_2B20(v32, ";6da;4644g3`6d5273237847317831062724");
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::_M_dispose(v32);
sub_2F50(v21);
sub_2B70(&v19);
if ( !v16 )
{
LABEL_18:
std::__ostream_insert<char,std::char_traits<char>>(&std::cout, "Wrong flag :(", 13LL);
goto LABEL_19;
}
std::__ostream_insert<char,std::char_traits<char>>(&std::cout, "Correct flag :)", 15LL);

By analyzing the code at the main we will recognize some checks which are :-

  • Check flag length == 43
  • check flag start == “ASCWG{“
  • flag end ==”}”
  • The value between the parenthesis after encryption == “;6da;4644g3`6d5273237847317831062724”

At the first time I thought it’s just a simple bruteforcing challenge using gdb in python . But by some tries I found that the input character at index x doesn’t affect the character at the same index in the output. So there is a pattern we have to know before we bruteforce on the right value. By changing character by character in the input and check the output change I found that this is the right pattern.

pattern = [0, 12, 24, 25, 13, 1, 2, 14, 26, 27, 15, 3, 4, 16, 28, 29, 17, 5, 6, 18, 30, 31, 19, 7, 8, 20, 32, 33, 21, 9, 10, 22, 34, 35, 23, 11, 12, 22]

But we still forget something to handle before start writing the script.look here in the main function graph.

If u put a breakpoint after this instruction and run the program with gdb you will find that rdi dosen’t hold the address of the encrypted input. But It holds an address that points to an address that points to the start of the encrypted input🫠. Very bothering situation🥲.

If you tried to access the encrypted value gdb will return an error.

So let’s go ahead and continue our analysis. By debugging the validating_function we will find that the memcmp is hitted three times which are :-

  • First time : validate flag start → “ASCWG{“
  • Second time : Validate flag end → “}”
  • Third time : Validate the between parenthesis value

what is nice here is that we will find that the rdi register holds the address that points directly to the encrypted value start which is the thing we are working on.

So let’s rock and write the Python GDB script.

import gdb
from string import printable

final_values = [0x3b, 0x36, 0x64, 0x61, 0x3b, 0x34, 0x36, 0x34, 0x34, 0x67, 0x33, 0x60,
0x36, 0x64, 0x35, 0x32, 0x37, 0x33, 0x32, 0x33, 0x37, 0x38, 0x34, 0x37,
0x33, 0x31, 0x37, 0x38, 0x33, 0x31, 0x30, 0x36, 0x32, 0x37, 0x32, 0x34]

pattern = [0, 12, 24, 25, 13, 1, 2, 14, 26, 27, 15, 3, 4, 16, 28, 29, 17, 5,
6, 18, 30, 31, 19, 7, 8, 20, 32, 33, 21, 9, 10, 22, 34, 35, 23, 11]

gdb.execute("file ./chall")
gdb.execute("start")
gdb.execute("b *0x555555554000+0x2B56")
flag_start = "ASCWG{"
for i in range(36):
true_index =pattern[i]
for chaar in printable:
tmp_flag = flag_start + chaar + "_"*(35-i)+"}"
with open("tmpfile", "w") as tmp:
tmp.write(tmp_flag)
gdb.execute("r < tmpfile")
for _ in range(2):
gdb.execute("c") #We have to continue 2 times to reach the compare between parenthesis validation
rdi = gdb.execute(f"x/bx $rdi+{true_index}", to_string= True).split()[-1]
encrypted_tst = int(rdi, 16)
print(f"The value that I need is {final_values[true_index]} , while the value I got is {encrypted_tst}")
if final_values[true_index] == encrypted_tst:
print(f"Here I got one right value whic is {chaar} in indx{i}")
flag_start = flag_start+chaar
break
print(f"final flag is {flag_start}" + "}")

finally , Here is our flag .

ASCWG{9731e4f4783c96312643062666279e15246b}

3-Tr4p

This is an exe file that doesn’t take an input or print an output and it deletes itself after running it 😢. So it’s a debugging game. When I opened the executable with IDA I didn’t find anything interesting . But the executable seems to be packed. I uploaded it on unpac.me and got the unpacked version. By looking at the main function I recognized that the executable is full of anti-debugging techniques applied here . This moment is the best to use x32dbg with scyllahide plugin 🫡🫶.

Before that we need to analyze the program to know where to put a breakpoint and where will the flag be calculated. By analyzing the program , we will find that it’s calculating a key based on the anti-debugging techniques checks and pass it to RC4 function that decrypt an encrypted string in memory.

Here we will put a breakpoint at the jump before the call and change the sf to jmp to the call. This function makes some initialization for the RC4 then goes to a function in it that makes xor and swap operations .

Here will be our flag saved letter by letter. Here is a small video showing how to use x32dbg to in solving it after applying scyllahide.

Note : Don’t forget to check the used anti-debugging techniques in the program and check that they are applied in scyllahide. If u are lazy u can apply all the options in scyllahide😅.

ASCWG{a456e8edc6f689211713d65a53b7650e4a402a94b}

4-R4nd0m

Here we are with the last challenge . When you open the executable at the first time you will see this🥲.

So close the challenge and sleep🫠🥱. Nothing deserve to take care from more than your health😅.

Come back bro we are going to solve this one 🫡. when I ran the challenge I found that it doesn’t take input in the cli and it returns no output.It’s time to analyze it block by block. By debugging the code I found that the first block the program go through is this one.

file_handle:
mov [rsp+arg_0], rbx
mov [rsp+arg_8], rdi
push rbp
lea rbp, [rsp+8+var_2D8]
sub rsp, 3D0h
mov rax, cs:__security_cookie
xor rax, rsp
mov [rbp+2C0h], rax
xor edx, edx ; lpWindowName
lea rcx, cs:7FF7EB5636C9h
pushf
sub rcx, 5ACC0409h ; lpClassName
popf
call cs:FindWindowA
mov rcx, rax ; hWnd
xor edx, edx ; nCmdShow
call cs:ShowWindow
xorps xmm0, xmm0
mov dword ptr [rbp+118h], 747874h
xor eax, eax
mov dword ptr [rbp+110h], 665C3A43h
xorps xmm1, xmm1
mov dword ptr [rbp+114h], 2E67616Ch
xor edi, edi
mov [rbp+150h], al
mov [rsp+3D8h+hTemplateFile], rdi ; hTemplateFile
lea rcx, [rbp+110h] ; lpFileName
lea r8d, [rax+1] ; dwShareMode
mov [rsp+3D8h+dwFlagsAndAttributes], 40000080h ; dwFlagsAndAttributes
xor r9d, r9d ; lpSecurityAttributes
mov [rbp+178h], al
mov edx, 5FC5C89Bh
pushf
not edx
add edx, 0D86480E8h
xor edx, 8F925E53h
rol edx, 3Eh
popf
pushf
not edx
add edx, 96525FFCh
xor edx, 988F0674h
rol edx, 92h ; dwDesiredAccess
popf
mov [rbp+1B0h], al
movups xmmword ptr [rbp+120h], xmm0
mov [rsp+3D8h+dwCreationDisposition], 3 ; dwCreationDisposition
movups xmmword ptr [rbp+130h], xmm0
movups xmmword ptr [rbp+140h], xmm0
movups [rsp+3D8h+var_388], xmm0
movups [rsp+3D8h+var_378], xmm0
movups xmmword ptr [rbp+158h], xmm1
movups xmmword ptr [rbp+168h], xmm1
movups xmmword ptr [rbp+180h], xmm0
movups xmmword ptr [rbp+190h], xmm0
movups xmmword ptr [rbp+1A0h], xmm0
call cs:CreateFileA
cmp rax, 0FFFFFFFFFFFFFFFFh
jz loc_7FF7908AA334

Which is a file handle that is checking for this file ‘C:\flag.txt’ and returns a handle to access this file. So the input here should be written in this file. after that the program execute this block of code.

read_file:
lea rcx, cs:7FF7FA4208A9h
pushf
sub rcx, 69B7F8A9h
popf
mov [rsp+lpCompletionRoutine], rcx ; lpCompletionRoutine
lea r9, [rsp+Overlapped] ; lpOverlapped
mov rcx, rax ; hFile
lea r8d, [rdi+30h] ; nNumberOfBytesToRead
lea rdx, [rbp+120h] ; lpBuffer
call cs:ReadFileEx
movdqa xmm3, cs:xmmword_7FF7908A32F0
xorps xmm2, xmm2
xorps xmm1, xmm1
mov eax, edi
xchg ax, ax
push rax
pushf
mov eax, 2E8676BDh
pushf
not eax
add eax, 0E940A86Fh
xor eax, 0AB8F18EDh
rol eax, 57h
popf
pushf
not eax
add eax, 0A97BE10Bh
xor eax, 0BC78EA1Ah
rol eax, 47h
popf
pushf
not eax
add eax, 9390B6F9h
xor eax, 0FBB16380h
rol eax, 11h
popf
pushf
not eax
add eax, 0BBAB8A43h
xor eax, 0F4FF9F2Bh
rol eax, 19h
popf
jmp loc_7FF7908AA054

This block of code reads the file content and stores it in a buffer. After this, the program reads the input 8 characters at a time, for a total of 6 times, which tells us that the flag length is 48. While reading the flag, the program applies some operations using the xmm registers, as we can see in this block.

prepare_seed:
movd xmm0, dword ptr [rbp+rax+120h]
punpcklbw xmm0, xmm0
punpcklwd xmm0, xmm0
psrad xmm0, 18h
pxor xmm0, xmm3
paddd xmm2, xmm0
movd xmm0, dword ptr [rbp+rax+124h]
punpcklbw xmm0, xmm0
add rax, 8
punpcklwd xmm0, xmm0
psrad xmm0, 18h
pxor xmm0, xmm3
paddd xmm1, xmm0
cmp rax, 30h ; '0'
jl short loc_7FF7908AA4B4

After that the program is passing a value to srand depending on the input data using the value stored in the xmm1 register .

seed:
paddd xmm1, xmm2
movdqa xmm0, xmm1
psrldq xmm0, 8
paddd xmm1, xmm0
movdqa xmm0, xmm1
psrldq xmm0, 4
paddd xmm1, xmm0
movd ecx, xmm1 ; Seed
call cs:srand
mov rbx, rdi
push rax
pushf
mov eax, 704193F5h
pushf
not eax
add eax, 0A78A0D7Ch
xor eax, 0C1306AD1h
rol eax, 24h
popf
pushf
not eax
add eax, 0F09870F6h
xor eax, 88173B76h
rol eax, 0AAh
popf
jmp loc_7FF7908AA054

After that the program is going in a loop of 0x20 iterations to create the key which is calculated using this equation.

key = (rand & 0xff) ^ 0x9d

After this step, the program performs some key initialization operations for the RC4 encryption algorithm. From the above, we understand that the solution of this challenge depends on obtaining the correct seed to decrypt the encrypted flag. Here is my code for that.

from ctypes import CDLL
from Crypto.Cipher import ARC4
from tqdm import tqdm
lib = CDLL('msvcrt.dll')

ct = open("flag.txt.enc","rb").read()
progress = tqdm(total=0x10000, desc="Cracking", unit="probaple")
for seed in range(0x10000):
key = b""
lib.srand(seed)

for _ in range(0x20):
rand = lib.rand() & 0xff
key += bytes([rand ^ 0x9d])
assert len(key) == 0x20
try:
progress.update(1)
cipher = ARC4.new(key)
decrypted = cipher.decrypt(ct)
if b"ASCWG{" in decrypted:
print("\n", decrypted.decode())
print(seed)
break
except Exception as e:
continue
progress.close()

# 8444

Finally the flag:-

ASCWG{a519f481fde462ff603b98bb092a25a91789def88}

Finally, I hope my write-up met your expectations. See you soon in future write-ups.😊💖.

--

--