Week 4 Challenge Writeups

~/ 30 Sep 2024

Add To Cartel

Description: Whispers in the digital underground hint at a startling revelation about NO_NO_NO’s elusive funding source. Buried in encrypted data streams, links to a cryptic online storefront have surfaced.

Could this innocuous merch shop be the key to NO_NO_NO’s financial puzzle? Stranger still, the store is offering free merchandise as part of a limited time promotion. Might as well grab some free merch while it’s available? http://chals.secedu.site:5016

On the website there is a form to request some merch, capturing the POST request in burp reveals there is a role attribute to the form. Changing this to member and sending the request reveals the flag.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
POST /submit HTTP/1.1
...

------WebKitFormBoundary6PnbLsvvrInhikEu
Content-Disposition: form-data; name="name"

ad
------WebKitFormBoundary6PnbLsvvrInhikEu
Content-Disposition: form-data; name="email"

da@da.com
------WebKitFormBoundary6PnbLsvvrInhikEu
Content-Disposition: form-data; name="address"

da
------WebKitFormBoundary6PnbLsvvrInhikEu
Content-Disposition: form-data; name="role"

member
------WebKitFormBoundary6PnbLsvvrInhikEu--
->
Congratulations! Not only do you get a free Hoodie, you also get a flag: SECEDU{yes_yes_yes_we_want_free_merch}

Coded Conspiracy

Description: Our team has discovered a series of binaries on a seized web server associated with NO_NO_NO. These binaries seem to communicate extensively. Your task is to find a way to analyse these messages. nc chals.secedu.site 5018

As part of the challenge we are provided with a binary and something to netcat to, we also know these programs are communicating with each other.

Running file on the provided binary we see it’s stripped, meaning we won’t have any debugging symbols like function names to help us analyse the program.

1
2
file mystery
mystery: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, stripped

Running the strings command we can see the file is presumably written in the go programming language.

1
2
3
4
5
6
7
8
9
10
11
strings mystery | tail
.noptrbss
.go.fuzzcntrs
.go.buildinfo
.elfdata
.rodata
.typelink
.itablink
.gosymtab
.gopclntab
.shstrtab

Running the binary, we get what looks to be gibberish or encoded data:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
./mystery
Received: bhOz*
               ;
                *
rt^RGLwOQ~DH~N1iSu^Yu^OSL~oODoToGP tDH~DH6fYuMHs(
                                                   tDR~IHrER!
_wEO~'6_KH~VER7
+
o~Z)/
+	+
{V~1yYi\Yi^ip]
hK_4	(k 1cGP;\YiYUtD9~D_tNUuM9COt#6$6'
                                         xTihBzy;BHvFK~Wc4k(i4nh_
dS~qW
5OX]uYUoCSuKP4y6;
;
;
6^NzDOr^UtD]wXoN% s^Qw*xOncBHvF
DvFRhs^Hk4]KlK(SiM*"Ds^QcGP!F]uM9OR9
;UEH;cQkFYvORoOX'Hr^P~6sO]6^tNE% 5T*	+6
% 5'^tNE% 4BHvFB

For those wondering what this says, you’ll want to investigate the while loop at the end of the main function

We get some more gibberish when we connect via nc:

1
2
nc chals.secedu.site 5018
DS;LPzM}EN;SSn

After doing a quick google on reversing Go binaries it looks like you can get most of the function names back in debuggers like IDA and Ghidra using some scripts. I decided to use Ghidra and GoReSym to get the function names back.

After running GoReSym we can see a main.main function is identified in the functions window. There is a lot of stuff going on but right near the top of the function is a for loop which is XOR’ing some variable.

1
2
3
4
5
for (lVar4 = 0; lVar4 < 0x29; lVar4 = lVar4 + 1) {
    uVar2 = lVar4 + (SUB168(SEXT816(-0x5555555555555555) * SEXT816(lVar4),8) + lVar4 >> 1) * -3;
    if (2 < uVar2) goto LAB_004d2ea5;
    *(byte *)(lVar1 + lVar4) = *(byte *)((long)&local_7b + uVar2) ^ *(byte *)(lVar4 + local_18);
  }

Let’s investigate, click on the beginning of the for loop and take a look at the listing window in ghidra and find the memory address for the instruction. Start up gdb and set a breakpoint:

1
2
3
4
5
gdb mystery
(gdb) break *0x004d2be4
Breakpoint 1 at 0x4d2be4
(gdb) r
Thread 1 "mystery" hit Breakpoint 1, 0x00000000004d2be4 in ?? ()

After taking a look at the registries, I didn’t find much. Let’s see what they look like after an iteration of the loop.

1
2
3
4
5
6
7
8
9
(gdb) c
Continuing.
Thread 1 "mystery" hit Breakpoint 1, 0x00000000004d2be4 in ?? ()
(gdb) i r
...
(gdb) x/s $rsi
0xc0000b6000:	"Hello, Server! Can I get the flag please?"
(gdb) x/s $rax
0xc00001c0f0:	"b"

After inspecting all the registries I found rsi and rax were storing strings, presumably the data being XOR’d in rsi and the result of the XOR in rax. Let’s find out, back in Ghidra find the ^ in the for loop’s body and set another breakpoint in gdb at this memory address.

1
2
3
4
5
6
7
(gdb) break *0x004d2bcc
Breakpoint 2 at 0x4d2bcc
(gdb) r
(gdb) c
(gdb) c
Continuing.
Thread 1 "mystery" hit Breakpoint 2, 0x00000000004d2bcc in ?? ()

Analysing the instruction pointer we can see edx and edi are being XOR’d

1
2
3
4
5
6
7
(gdb) x/i $rip
=> 0x4d2bcc:	xor    %edx,%edi
(gdb) i r
...
rdx            0x48                72
rdi            0x2a                42
...

We see rdx is storing 0x48 which corresponds to the ASCII value of ‘H’ and rdi is storing 0x2a. 0x48 ^ 0x2a = 0x62 aka ‘b’, terrific news we’ll call it a day and skip to the end of the for loop…

1
2
3
4
(gdb) x/s $rsi
0xc0000b2000:	"Hello, Server! Can I get the flag please?"
(gdb) x/s $rax
0xc0000b2030:	"bYwFS7\no~XJ~X\035;i]u\nu;MYo\nHsO\034}F]|\nLwO]hO\003"

Ah, not so fast rsi ^ 0x2a is not what’s ended up in rax, maybe the key is different for every character…

Let’s head back to breakpoint 2 and continue through the loop, examining the contents of the XOR key in rdi each time.

1
2
3
4
5
6
7
8
9
10
11
12
13
// First iteration
rdi = 0x2a
// Second Iteration
rdi = 0x3c
// Third Iteration
rdi = 0x1b
// Fourth iteration
rdi = 0x2a
// Fifth Iteration
rdi = 0x3c
// Sixth Iteration
rdi = 0x1b
...

From this we can determine the XOR key is 2a3c1b. We can also confirm this by looking at the value of rax which increments a counter to 2 on each iteration of the loop before resetting to 0.

Let’s send the string in rax to the server and see what we get:

1
2
3
4
5
echo -ne "bYwFS7\no~XJ~X\035;i]u\nu;MYo\nHsO\034}F]|\nLwO]hO\003" | nc chals.secedu.site 5018
bYwFS7
_wCYu^;O~
HsCO;^S;XYj_Yh^oBY;LPzM;DSuERt
                              #ZNZz#(

We get a different response this time, let’s decode this with our new found XOR key:

1
Hello, client! Use this to request the flag: nonono5783fddfa9383839

Take the nonono5783fddfa9383839 and XOR that with the key and convert the result to hex with delimiter \x using cyberchef. Let’s send that back to the server like it asked:

1
echo -ne "\x44\x53\x75\x45\x52\x74\x1f\x0b\x23\x19\x5a\x7f\x4e\x5a\x7a\x13\x0f\x23\x19\x04\x28\x13" | nc chals.secedu.site 5018 | xxd

We get some non-ascii characters in the response so I will take a hexdump instead and again XOR the result in cyberchef for the flag: SECEDU{X0R_Y0UR_3Y3S_0NLY}

Compiled Chaos

Description: The next target has been identified, and agents have cleared the cars of the CL-2384 train, and found an unknown implant device attached to the speed controllers.

We were able to produce a firmware dump from the device. We need you to find out what this device is doing, and what information it is collecting or sending!

Opening up the dump in ghidra, let’s take a look at the defined strings, we see what looks to be HTTP request. Let’s take a look there as we know from the description the firmware was meant to be sending some data.

1
2
3
4
5
6
7
POST %s HTTP/1.1
Host: %s
Connection: close
Content-Type: %s
Content-Length: %d

%s

Looking at the XREF for the string reveals a function which looks to be constructing a HTTP request from several variables.

1
2
3
4
5
6
7
8
9
10
11
12
13
  do {
    pbVar1 = pbVar1 + 1;
    *pbVar5 = abStack_120[uVar3];
    uVar3 = (uint)*pbVar1;
    pbVar5 = pbVar5 + 1;
  } while (uVar3 != 0);
  *pbVar5 = *pbVar1;
  uVar2 = FUN_10030cb0(&DAT_20002858);
  FUN_1002e510(&DAT_2000ceb8,0x800,
               "POST %s HTTP/1.1\r\nHost: %s\r\nConnection: close\r\nContent-Type: %s\r\nContent-Len gth: %d\r\n\r\n%s"
               ,&DAT_20002c58,&DAT_20002d58,&DAT_20002758,uVar2,&DAT_20002858);
  return &DAT_2000ceb8;
}

Towards the top of the function we can see an array of characters being constructed as abStack_120 from two strings.

1
2
3
4
5
6
7
DAT_100385c0 = "?q2AwgR+$&ials-IG5T<}(;cz#S)*mhUb[H4>\\uXpFKd,E`!^9~L\"x8_tVJQ6Beo|]PnZ0=kj.O3{%M7rY'N@fyC1D/W:"
DAT_10034a99 = "23456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
  do {
    abStack_120[(byte)(&DAT_100385c0)[iVar6]] = bVar4;
    bVar4 = (&DAT_10034a99)[iVar6];
    iVar6 = iVar6 + 1;
  } while (bVar4 != 0);

We see from the loop the byte value of each character from DAT_100385c0 is being used as an index for abStack_120 and that characters inserted into abStack_120 are being drawn from DAT_10034a99. Let’s reconstruct abStack_120 with some python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
chars_into_abstack = "23456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
char_to_index = "?q2AwgR+$&ials-IG5T<}(;cz#S)*mhUb[H4>\\uXpFKd,E`!^9~L\"x8_tVJQ6Beo|]PnZ0=kj.O3{%M7rY'N@fyC1D/W:"

indexes = [ord(c) for c in char_to_index]
abstack_120 = ['*'] * 256 # '*' is a placeholder for no value
bVar4 = '0'
iVar6 = 0

# First iteration outside the loop
abstack_120[indexes[iVar6]] = bVar4
iVar6 += 1

# Rest of the iterations
for i in range(len(chars_into_abstack)):
	bVar4 = chars_into_abstack[i]
	abstack_120[indexes[iVar6]] = bVar4
	iVar6 += 1
abstack_120 = "".join(abstack_120)
print(abstack_120)
->
# After removing the '*'
MRq9;a@mst8Jf-|)`3/AiZ=TO~nk*B0\4!_{KGhzgXHQ<[.&Y7rjwW}E?(yC%NULcxoI"]6vb,+du'#F2>eVD*5S^p:$lP

Further down the function we see abStack_120 is being used to create a new string pbVar5:

1
2
3
4
5
6
7
uVar3 = "<s[)*Sc<s)e{e-}&ID;v*CTmw*C)*S?;IwC*vlA[WeW"
do {
	pbVar1 = pbVar1 + 1;
	*pbVar5 = abStack_120[uVar3];
	uVar3 = (uint)*pbVar1;
	pbVar5 = pbVar5 + 1;
} while (uVar3 != 0);

This time, the characters in uVar3 determine the index of abStack_120 and the value stored at that index will be added to pbVar5. Let’s create a python script to recreate the new string

1
2
3
4
5
6
7
8
9
10
11
pbVar1 = "<s[)*Sc<s)e{e-}&ID;v*CTmw*C)*S?;IwC*vlA[WeW"
uVar3 = 'D'  # 0x44
request_body = [abstack_120[ord(uVar3)]]
for char in pbVar1:
	uVar3 = char
	request_body.append(abstack_120[ord(uVar3)])
	if abstack_120[ord(uVar3)] == '*':
		print(ord(uVar3))
DAT_20002858 = "".join(request_body)
->
{keystrokes":"flag{n*t_ju5t_str0ng5_t*d4y}"}

Not quite the string, but close enough to fill in the blanks: flag{not_ju5t_string5_today}