CCSC 2023 Shellhunting

21/07/2023 - 5 minutes

2023 angr assembly ccsc ctfs hacking rc4 reverse wireshark
  1. 1 Description
  2. 2 Reversing
    1. 2.1 Running the binary for the first time
    2. 2.2 Firing up Ghidra
    3. 2.3 The key holds the power and you hold the key!
    4. 2.4 Stracing our way to understanding
    5. 2.5 Dynamically analyzing the shellcode
    6. 2.6 Viewing the network traffic with wireshark
    7. 2.7 Statically analyzing the shellcode
      1. 2.7.1 Reading the shellcode from the binary
      2. 2.7.2 Decrypting the shellcode using RC4 and the found key
      3. 2.7.3 Disassemble the shellcode
    8. 2.8 Reading assembly...
    9. 2.9 Solving plan
    10. 2.10 Solution script

# Description

One of the generals of the AI-sponsored government left his house unlocked while he was going to the beach. Ava spotted the powered on computer, and managed to intercept the traffic from an unknown application.

Help her to understand the contents of the communication

Author: icyDux

# Reversing

Ok so we are given an application of some kind(the binary named chall) which supposingly has made a some kind of network communication(hence the provided pcap file) and we must try to see what that traffic is and how it was generated

# Running the binary for the first time

image_1_f079b323.png The binary seems to expect some kind of input(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa in the example) and then it tells us if we are using a correct key or not.

# Firing up Ghidra

If we fire up ghidra and perform a default analysis the decompiled main function is the below

undefined4 main(void)

{
 FILE *__stream;
 int iVar1;
 undefined (*__s) [16];
 char *pcVar2;
 
 __s = (undefined (*) [16])malloc(0x11);
 printf("%s \n","I love shells");
 if (__s != (undefined (*) [16])0x0) {
   __s[1][0] = 0;
   __stream = stdin;
   *__s = (undefined  [16])0x0;
   pcVar2 = fgets((char *)__s,0x11,__stream);
   if (pcVar2 == (char *)0x0) {
	 free(__s);
   }
   else {
	 __s[1][0] = 0;
	 iVar1 = parse(__s);
	 if (iVar1 == 0) {
	   puts("You found the correct key, let\'s move on...");
	   run(__s);
	   return 0;
	 }
	 puts("You did not found the correct key :(");
	 free(__s);
   }
 }
 return 1;
}

On binaries like this I like to try and cheese them using angr and symbolic execution, which worked this time, but getting the key was the easy part :<

import angr
import claripy

elf = ELF('./chall')
shellcode = elf.sym['shellcode']
shellcode = elf.read(shellcode, 0x100)

key = claripy.BVS('key', 16*8) # we can see that the key is 16 char(s) long and each char needs 8 bits to be represented in C
p = angr.Project('./chall', load_options={'auto_load_libs': False})
state = p.factory.entry_state(args=['./chall'], stdin=key)
simgr = p.factory.simulation_manager(state)

def find(state):
output = state.posix.dumps(sys.stdout.fileno())
if b"You found the correct key, let\'s move on..." in output:
	return True
return False

def avoid(state):
output = state.posix.dumps(sys.stdout.fileno())
if b"You did not found the correct key :(" in output:
	return True
return False

simgr.explore(find=find, avoid=avoid)

key = simgr.found[0].solver.eval(key, cast_to=bytes).decode()
print(key) #I_am_w3LL_h1dd3n

ok now we have the key so what?

# The key holds the power and you hold the key!

We have the correct key but this is where easy mode ends...

puts("You found the correct key, let\'s move on...");
run(__s);

let's see what run is doing

void run(uchar *param_1)

{
 undefined4 uVar1;
 undefined4 uVar2;
 undefined4 uVar3;
 undefined4 *outdata;
 undefined4 *__addr;
 long in_FS_OFFSET;
 RC4_KEY RStack_428;
 long local_20;
 
 local_20 = *(long *)(in_FS_OFFSET + 0x28);
 outdata = (undefined4 *)malloc(0x100);
 RC4_set_key(&RStack_428,0x10,param_1);
 RC4(&RStack_428,0x100,shellcode,(uchar *)outdata);
 __addr = (undefined4 *)mmap((void *)0x0,0x100,3,0x21,-1,0);
 uVar1 = outdata[1];
 uVar2 = outdata[2];
 uVar3 = outdata[3];
 *__addr = *outdata;
 __addr[1] = uVar1;
 __addr[2] = uVar2;
 __addr[3] = uVar3;
 uVar1 = outdata[5];
 uVar2 = outdata[6];
 uVar3 = outdata[7];
 __addr[4] = outdata[4];
 __addr[5] = uVar1;
 __addr[6] = uVar2;
 __addr[7] = uVar3;
 uVar1 = outdata[9];
 uVar2 = outdata[10];
 uVar3 = outdata[0xb];
 __addr[8] = outdata[8];
 __addr[9] = uVar1;
 __addr[10] = uVar2;
 __addr[0xb] = uVar3;
 uVar1 = outdata[0xd];
 uVar2 = outdata[0xe];
 uVar3 = outdata[0xf];
 __addr[0xc] = outdata[0xc];
 __addr[0xd] = uVar1;
 __addr[0xe] = uVar2;
 __addr[0xf] = uVar3;
 uVar1 = outdata[0x11];
 uVar2 = outdata[0x12];
 uVar3 = outdata[0x13];
 __addr[0x10] = outdata[0x10];
 __addr[0x11] = uVar1;
 __addr[0x12] = uVar2;
 __addr[0x13] = uVar3;
 uVar1 = outdata[0x15];
 uVar2 = outdata[0x16];
 uVar3 = outdata[0x17];
 __addr[0x14] = outdata[0x14];
 __addr[0x15] = uVar1;
 __addr[0x16] = uVar2;
 __addr[0x17] = uVar3;
 uVar1 = outdata[0x19];
 uVar2 = outdata[0x1a];
 uVar3 = outdata[0x1b];
 __addr[0x18] = outdata[0x18];
 __addr[0x19] = uVar1;
 __addr[0x1a] = uVar2;
 __addr[0x1b] = uVar3;
 uVar1 = outdata[0x1d];
 uVar2 = outdata[0x1e];
 uVar3 = outdata[0x1f];
 __addr[0x1c] = outdata[0x1c];
 __addr[0x1d] = uVar1;
 __addr[0x1e] = uVar2;
 __addr[0x1f] = uVar3;
 uVar1 = outdata[0x21];
 uVar2 = outdata[0x22];
 uVar3 = outdata[0x23];
 __addr[0x20] = outdata[0x20];
 __addr[0x21] = uVar1;
 __addr[0x22] = uVar2;
 __addr[0x23] = uVar3;
 uVar1 = outdata[0x25];
 uVar2 = outdata[0x26];
 uVar3 = outdata[0x27];
 __addr[0x24] = outdata[0x24];
 __addr[0x25] = uVar1;
 __addr[0x26] = uVar2;
 __addr[0x27] = uVar3;
 uVar1 = outdata[0x29];
 uVar2 = outdata[0x2a];
 uVar3 = outdata[0x2b];
 __addr[0x28] = outdata[0x28];
 __addr[0x29] = uVar1;
 __addr[0x2a] = uVar2;
 __addr[0x2b] = uVar3;
 uVar1 = outdata[0x2d];
 uVar2 = outdata[0x2e];
 uVar3 = outdata[0x2f];
 __addr[0x2c] = outdata[0x2c];
 __addr[0x2d] = uVar1;
 __addr[0x2e] = uVar2;
 __addr[0x2f] = uVar3;
 uVar1 = outdata[0x31];
 uVar2 = outdata[0x32];
 uVar3 = outdata[0x33];
 __addr[0x30] = outdata[0x30];
 __addr[0x31] = uVar1;
 __addr[0x32] = uVar2;
 __addr[0x33] = uVar3;
 uVar1 = outdata[0x35];
 uVar2 = outdata[0x36];
 uVar3 = outdata[0x37];
 __addr[0x34] = outdata[0x34];
 __addr[0x35] = uVar1;
 __addr[0x36] = uVar2;
 __addr[0x37] = uVar3;
 uVar1 = outdata[0x39];
 uVar2 = outdata[0x3a];
 uVar3 = outdata[0x3b];
 __addr[0x38] = outdata[0x38];
 __addr[0x39] = uVar1;
 __addr[0x3a] = uVar2;
 __addr[0x3b] = uVar3;
 uVar1 = outdata[0x3d];
 uVar2 = outdata[0x3e];
 uVar3 = outdata[0x3f];
 __addr[0x3c] = outdata[0x3c];
 __addr[0x3d] = uVar1;
 __addr[0x3e] = uVar2;
 __addr[0x3f] = uVar3;
 mprotect(__addr,0x100,7);
 (*(code *)__addr)();
 if (local_20 == *(long *)(in_FS_OFFSET + 0x28)) {
   return;
 }
				   /* WARNING: Subroutine does not return */
 __stack_chk_fail();
}

Well what a mess... This all may seem complicated at first but upon careful inspection we can see that all that happens here is: The symbol at the address shellcode is decrypted using the RC4 cipher(the one that was used to at the very early days of wifi) Then it is copied in memory and protected using mprotect Then it is executed on the line (*(code *)__addr)();, which essentially dereferences the address and calls it to execute

# Stracing our way to understanding

Lets run the binary with strace to see what really happens behind the scenes

sometimes we will get

open("flag.txtX\336\207\177", O_RDONLY) = -1 ENOENT (No such file or directory)

I will be honest i still don't know why this happens, but if we keep running it eventually it will read the test flag.txt I have placed in the same directory

# Dynamically analyzing the shellcode

Ok so it seems that the binary reads the flag and then opens a socket to a random address 127.1.128.63 for example Hmm lets see the wireshark capture

# Viewing the network traffic with wireshark

Well kind of disappointing, there are no protocols involved, just raw dog water tcp and the data transferred is of course unreadable...

# Statically analyzing the shellcode

Well as now we know how to decrypt the shellcode lets try to dissasemble it using pwntools.

# Reading the shellcode from the binary

from pwn import *
elf = ELF('./chall')
shellcode = elf.sym['shellcode']
shellcode = elf.read(shellcode, 255) # 255 as the list has 255 according to ghidra
"""0010217f 06              undefined106h                     [255]"""
print(shellcode)

Output

b'\xf5\x90\x13zj\xc1y\xd8\x13\xf5\xa0\xfa5\xfe\xf2\xc2\x8e\x15&@\x8e\x02\xf9\x12\x84\x9a\xbdu{_\xc9/\x9bKm\xf13\x8f\xc0\x90K$6\xf03\xda\x9fU\xbe\x99x\xc8\xdf\xa6\x14\xda\xa2\xfe"\xd7c\xda\xd27\x0e+\xc9+\r\xd8|+`?\x82xM@w\x9f|\x9f\x9cf(aH\x9e\x02[.\xa5\x1b\x98\x01\r\xa6\x8e\xde\x85\xe7\xbet\x96\xba,`\x1e\xfb\x1a\xf3F+\x02>\x83\xb9\xb0\xe8\x1c&\x10\x98N\x0b%%\xc6/\xb2Q\xfd\x12hy\x06Mk\xac\x88,\x0c\x9c3\x89Y\xed\xf8\xc8\x90\xee\xe2\x88H\x9b}W\xe4\x99y\xa22Z\x17\xc7\xa5\x9au$\x18&\xd9\xe9\xca\x8f6\xde<\xb1ym\xe75\xc5\xa1\xcbu\xd1\xb0n\x1eu%\x03\xf5\xb0\xff\x1d\x9b"<:\xc7\xc8\xf6C\x11\xd7\xc8\x07\x0cr\xf2SBl\x98S\x93"\x16!cC\xb2\xcfk\x9eA\x9d%\xef\xd4{#\x17\x04\x93n\x1e\x00Uy7\xcc|\xc7\xa0\xfa/\xc3\x1a\r\x0f\\\x06'

# Decrypting the shellcode using RC4 and the found key

from Crypto.Cipher import ARC4
key = "I_am_w3LL_h1dd3n"

# RC4 cipher with the key
enc = shellcode
print(f"Encrypted data: {enc}")
print(f"Length of encrypted data: {len(enc)}")
# key = [ord(i) for i in key]
key = bytes(key, 'utf-8')
rc4 = ARC4.new(key)
shellcode = rc4.decrypt(enc)
print(f"Shellcode: {shellcode}")

Output:

Encrypted data: b'\xf5\x90\x13zj\xc1y\xd8\x13\xf5\xa0\xfa5\xfe\xf2\xc2\x8e\x15&@\x8e\x02\xf9\x12\x84\x9a\xbdu{_\xc9/\x9bKm\xf13\x8f\xc0\x90K$6\xf03\xda\x9fU\xbe\x99x\xc8\xdf\xa6\x14\xda\xa2\xfe"\xd7c\xda\xd27\x0e+\xc9+\r\xd8|+`?\x82xM@w\x9f|\x9f\x9cf(aH\x9e\x02[.\xa5\x1b\x98\x01\r\xa6\x8e\xde\x85\xe7\xbet\x96\xba,`\x1e\xfb\x1a\xf3F+\x02>\x83\xb9\xb0\xe8\x1c&\x10\x98N\x0b%%\xc6/\xb2Q\xfd\x12hy\x06Mk\xac\x88,\x0c\x9c3\x89Y\xed\xf8\xc8\x90\xee\xe2\x88H\x9b}W\xe4\x99y\xa22Z\x17\xc7\xa5\x9au$\x18&\xd9\xe9\xca\x8f6\xde<\xb1ym\xe75\xc5\xa1\xcbu\xd1\xb0n\x1eu%\x03\xf5\xb0\xff\x1d\x9b"<:\xc7\xc8\xf6C\x11\xd7\xc8\x07\x0cr\xf2SBl\x98S\x93"\x16!cC\xb2\xcfk\x9eA\x9d%\xef\xd4{#\x17\x04\x93n\x1e\x00Uy7\xcc|\xc7\xa0\xfa/\xc3\x1a\r\x0f\\\x06'
Length of encrypted data: 256
Shellcode: b'\xeb\x00H\x81\xec\xfe\x00\x00\x00H1\xf6H1\xffH1\xc0H\xb8flag.txtPH\x8d<$\xb8\x02\x00\x00\x00\x0f\x05H\x83\xf8\x00\x0f\x8e\xc1\x00\x00\x00P\xba \x00\x00\x00H\x8dt$\x10H\x8b<$\xb8\x00\x00\x00\x00\x0f\x05H\x83\xf8\x00\x0f\x8e\x95\x00\x00\x00H\x89\xc3H1\xd2H9\xda\x7f$H\x0f\xb6D\x14\x10A\x88\xd0A\xf6\xc0\x01u\x10H\xd1\xc0H\x83\xf03\x88D\x14\x10H\xff\xc2\xeb\xdcH\xd1\xc8\xeb\xeeH1\xd2\xbe\x01\x00\x00\x00\xbf\x02\x00\x00\x00\xb8)\x00\x00\x00\x0f\x05\x88D$\x04\xc6D$8\x02f\xc7D$:\x059f\xc7D$<\x7f\x01\xba\x10\x00\x00\x00H\x8dt$8H\x0f\xb6|$\x04\xb8*\x00\x00\x00\x0f\x05H\x83\xf8\x00u\x11H\x89\xdaH\x8dt$\x10\xb8\x01\x00\x00\x00\x0f\x05\xeb\x00H\x0f\xb6|$\x04\xb8\x03\x00\x00\x00\x0f\x05H\x0f\xb6<$\xb8\x03\x00\x00\x00\x0f\x05H1\xffH1\xc0\xb8<\x00\x00\x00\x0f\x05'

# Disassemble the shellcode

disasm = disasm(shellcode, arch='amd64')
print(f"Disassembly:")
print(disasm)

Output:

Disassembly:
	 0:   eb 00                   jmp    0x2
	 2:   48 81 ec fe 00 00 00    sub    rsp, 0xfe
	 9:   48 31 f6                xor    rsi, rsi
	 c:   48 31 ff                xor    rdi, rdi
	 f:   48 31 c0                xor    rax, rax
	12:   48 b8 66 6c 61 67 2e 74 78 74   movabs rax, 0x7478742e67616c66
	1c:   50                      push   rax
	1d:   48 8d 3c 24             lea    rdi, [rsp]
	21:   b8 02 00 00 00          mov    eax, 0x2
	26:   0f 05                   syscall
	28:   48 83 f8 00             cmp    rax, 0x0
	2c:   0f 8e c1 00 00 00       jle    0xf3
	32:   50                      push   rax
	33:   ba 20 00 00 00          mov    edx, 0x20
	38:   48 8d 74 24 10          lea    rsi, [rsp+0x10]
	3d:   48 8b 3c 24             mov    rdi, QWORD PTR [rsp]
	41:   b8 00 00 00 00          mov    eax, 0x0
	46:   0f 05                   syscall
	48:   48 83 f8 00             cmp    rax, 0x0
	4c:   0f 8e 95 00 00 00       jle    0xe7
	52:   48 89 c3                mov    rbx, rax
	55:   48 31 d2                xor    rdx, rdx
	58:   48 39 da                cmp    rdx, rbx
	5b:   7f 24                   jg     0x81
	5d:   48 0f b6 44 14 10       movzx  rax, BYTE PTR [rsp+rdx*1+0x10]
	63:   41 88 d0                mov    r8b, dl
	66:   41 f6 c0 01             test   r8b, 0x1
	6a:   75 10                   jne    0x7c
	6c:   48 d1 c0                rol    rax, 1
	6f:   48 83 f0 33             xor    rax, 0x33
	73:   88 44 14 10             mov    BYTE PTR [rsp+rdx*1+0x10], al
	77:   48 ff c2                inc    rdx
	7a:   eb dc                   jmp    0x58
	7c:   48 d1 c8                ror    rax, 1
	7f:   eb ee                   jmp    0x6f
	81:   48 31 d2                xor    rdx, rdx
	84:   be 01 00 00 00          mov    esi, 0x1
	89:   bf 02 00 00 00          mov    edi, 0x2
	8e:   b8 29 00 00 00          mov    eax, 0x29
	93:   0f 05                   syscall
	95:   88 44 24 04             mov    BYTE PTR [rsp+0x4], al
	99:   c6 44 24 38 02          mov    BYTE PTR [rsp+0x38], 0x2
	9e:   66 c7 44 24 3a 05 39    mov    WORD PTR [rsp+0x3a], 0x3905
	a5:   66 c7 44 24 3c 7f 01    mov    WORD PTR [rsp+0x3c], 0x17f
	ac:   ba 10 00 00 00          mov    edx, 0x10
	b1:   48 8d 74 24 38          lea    rsi, [rsp+0x38]
	b6:   48 0f b6 7c 24 04       movzx  rdi, BYTE PTR [rsp+0x4]
	bc:   b8 2a 00 00 00          mov    eax, 0x2a
	c1:   0f 05                   syscall
	c3:   48 83 f8 00             cmp    rax, 0x0
	c7:   75 11                   jne    0xda
	c9:   48 89 da                mov    rdx, rbx
	cc:   48 8d 74 24 10          lea    rsi, [rsp+0x10]
	d1:   b8 01 00 00 00          mov    eax, 0x1
	d6:   0f 05                   syscall
	d8:   eb 00                   jmp    0xda
	da:   48 0f b6 7c 24 04       movzx  rdi, BYTE PTR [rsp+0x4]
	e0:   b8 03 00 00 00          mov    eax, 0x3
	e5:   0f 05                   syscall
	e7:   48 0f b6 3c 24          movzx  rdi, BYTE PTR [rsp]
	ec:   b8 03 00 00 00          mov    eax, 0x3
	f1:   0f 05                   syscall
	f3:   48 31 ff                xor    rdi, rdi
	f6:   48 31 c0                xor    rax, rax
	f9:   b8 3c 00 00 00          mov    eax, 0x3c
	fe:   0f 05                   syscall
   100:   a4                      movs   BYTE PTR es:[rdi], BYTE PTR ds:[rsi]
   101:   e8 04 4c 77 7b          call   0x7b774d0a
   106:   1b f3                   sbb    esi, ebx
   108:   57                      push   rdi
   109:   36 4c 74 3c             ss rex.WR je 0x149
   10d:   91                      xchg   ecx, eax
   10e:   2c f1                   sub    al, 0xf1
   110:   0d 6b 04 d7 40          or     eax, 0x40d7046b
   115:   52                      push   rdx
   116:   f6 f4                   div    ah
   118:   63 90 ea 56 f1 c4       movsxd edx, DWORD PTR [rax-0x3b0ea916]
   11e:   f1                      int1
   11f:   c3                      ret
   120:   67 f8                   addr32 clc
   122:   24 60                   and    al, 0x60
   124:   84 8e 0e b8 2b 08       test   BYTE PTR [rsi+0x82bb80e], cl
   12a:   8d                      (bad)
   12b:   e5 97                   in     eax, 0x97
   12d:   2c 6f                   sub    al, 0x6f
   12f:   25 3b 7d e4 d1          and    eax, 0xd1e47d3b
   134:   fb                      sti
   135:   31 0c 99                xor    DWORD PTR [rcx+rbx*4], ecx
   138:   da b8 08 1b 2b 9a       fidivr DWORD PTR [rax-0x65d4e4f8]
   13e:   58                      pop    rax
   13f:   8d                      (bad)
   140:   d2 af 3c f6 3f d6       shr    BYTE PTR [rdi-0x29c009c4], cl
   146:   fd                      std
   147:   e3 2c                   jrcxz  0x175
   149:   43 7b f6                rex.XB jnp 0x142
   14c:   3d 45 00 a4 d3          cmp    eax, 0xd3a40045
   151:   00 23                   add    BYTE PTR [rbx], ah
   153:   85 01                   test   DWORD PTR [rcx], eax
   155:   6c                      ins    BYTE PTR es:[rdi], dx
   156:   65 db 21                (bad)  gs:[rcx]
   159:   8c 62 d4                mov    WORD PTR [rdx-0x2c], fs
   15c:   f0 04 71                lock add al, 0x71
   15f:   e6 c8                   out    0xc8, al
   161:   a6                      cmps   BYTE PTR ds:[rsi], BYTE PTR es:[rdi]
   162:   98                      cwde
   163:   f4                      hlt
   164:   3a a2 e3 f1 a7 89       cmp    ah, BYTE PTR [rdx-0x76580e1d]
   16a:   b1 d5                   mov    cl, 0xd5
   16c:   84 7e f9                test   BYTE PTR [rsi-0x7], bh
   16f:   18 03                   sbb    BYTE PTR [rbx], al
   171:   ef                      out    dx, eax
   172:   b4 c9                   mov    ah, 0xc9
   174:   0c ad                   or     al, 0xad
   176:   85 8e 5c 3e 44 90       test   DWORD PTR [rsi-0x6fbbc1a4], ecx
   17c:   f3 68 59 dd 28 ff       repz push 0xffffffffff28dd59
   182:   12 58 07                adc    bl, BYTE PTR [rax+0x7]
   185:   bf 22 de 81 2b          mov    edi, 0x2b81de22
   18a:   28 36                   sub    BYTE PTR [rsi], dh
   18c:   ae                      scas   al, BYTE PTR es:[rdi]
   18d:   e2 1f                   loop   0x1ae
   18f:   85 d7                   test   edi, edx
   191:   39 cf                   cmp    edi, ecx
   193:   a2 b1 56 12 27 17 7e 22 2b      movabs ds:0x2b227e17271256b1, al
   19c:   45 78 ea                rex.RB js 0x189
   19f:   5e                      pop    rsi
   1a0:   aa                      stos   BYTE PTR es:[rdi], al
   1a1:   83 91 98 60 1e f4 f2    adc    DWORD PTR [rcx-0xbe19f68], 0xfffffff2
   1a8:   fd                      std
   1a9:   4f ca eb ce             rex.WRXB retfq 0xceeb
   1ad:   9e                      sahf
   1ae:   0e                      (bad)
   1af:   36 47 e8 21 e7 65 03    ss rex.RXB call 0x365e8d7
   1b6:   13 d0                   adc    edx, eax
   1b8:   67 03 52 e7             add    edx, DWORD PTR [edx-0x19]
   1bc:   14 48                   adc    al, 0x48
   1be:   eb 32                   jmp    0x1f2
   1c0:   04 32                   add    al, 0x32
   1c2:   15 9e 2c b6 f2          adc    eax, 0xf2b62c9e
   1c7:   8f                      (bad)
   1c8:   f5                      cmc
   1c9:   a2 c8 68 6c de 6a 9b e3 b0      movabs ds:0xb0e39b6ade6c68c8, al
   1d2:   5f                      pop    rdi
   1d3:   a4                      movs   BYTE PTR es:[rdi], BYTE PTR ds:[rsi]
   1d4:   e3 27                   jrcxz  0x1fd
   1d6:   6a d3                   push   0xffffffffffffffd3
   1d8:   42 9e                   rex.X sahf
   1da:   23 a7 0f 5f 96 47       and    esp, DWORD PTR [rdi+0x47965f0f]
   1e0:   3a c3                   cmp    al, bl
   1e2:   65 ca b9 0b             gs retf 0xbb9
   1e6:   d5                      (bad)
   1e7:   da b1 d3 e3 64 b2       fidiv  DWORD PTR [rcx-0x4d9b1c2d]
   1ed:   1a a7 e1 46 78 71       sbb    ah, BYTE PTR [rdi+0x717846e1]
   1f3:   c5 da 92                (bad)
   1f6:   63 b1 64 16 55 a0       movsxd esi, DWORD PTR [rcx-0x5faae99c]
   1fc:   b9 b5 be 15 8a          mov    ecx, 0x8a15beb5
   201:   4d                      rex.WRB
   202:   43 f8                   rex.XB clc
   204:   73 95                   jae    0x19b
   206:   ed                      in     eax, dx
   207:   15 84 34 17 95          adc    eax, 0x95173484
   20c:   8a 3e                   mov    bh, BYTE PTR [rsi]
   20e:   76 4f                   jbe    0x25f
   210:   bf 96 e4 e6 cb          mov    edi, 0xcbe6e496
   215:   89                      .byte 0x89
   216:   79                      .byte 0x79
  

# Reading assembly...

Well as you may have guessed this is where I struggled a lot as I couldn't find of a way to decompile the given assembly code to a more readable language like C, I had to work with what I was given though...

Let's try to understand what is going on using our knowledge of what happened during dynamic analysis.

First of all the 0x7478742e67616c66 must be flag.txt, let's confirm image_3_f079b323.png Then the syscall at line 26 must be the syscall to open the flag.txt file Then on line 46, there is the syscall to read the content

Then here comes the part of intrest

	66:   41 f6 c0 01             test   r8b, 0x1
	6a:   75 10                   jne    0x7c
	6c:   48 d1 c0                rol    rax, 1
	6f:   48 83 f0 33             xor    rax, 0x33
	73:   88 44 14 10             mov    BYTE PTR [rsp+rdx*1+0x10], al
	77:   48 ff c2                inc    rdx
	7a:   eb dc                   jmp    0x58
	7c:   48 d1 c8                ror    rax, 1
	7f:   eb ee                   jmp    0x6f
	81:   48 31 d2                xor    rdx, rdx
	84:   be 01 00 00 00          mov    esi, 0x1
	89:   bf 02 00 00 00          mov    edi, 0x2
	8e:   b8 29 00 00 00          mov    eax, 0x29

The first line performs a btiwise AND and if the result is 1 then the number is odd else the number is even and accordingly it is either rol(ed) or ror(ed) and then xored with 0x33. To be more exact if the number is even then it is rol(ed) and xored with 0x33 with a flow of 66->6a->7c->7f->6f and if it is odd it is ror(ed) and xored with 0x33 with a flow of 66->6c->6f. So to decrypt this we must do the exact opposite.

# Solving plan

  1. Parse the tcp transfers
  2. Decrypt them by the above rules

# Solution script

from scapy.all import *

# Read all the tcp data using scapy
pcap = rdpcap('capture.pcap')
allTCPData = b''
for pkt in pcap:
if pkt.haslayer(TCP):
	allTCPData += bytes(pkt[TCP].payload)
allTCPData = [int(i) for i in allTCPData]
print(allTCPData)

# the ror and rol functions are from https://gist.github.com/trietptm/5cd60ed6add5adad6a34098ce255949a

rol = lambda val, r_bits, max_bits: \
   (val << r_bits%max_bits) & (2**max_bits-1) | \
   ((val & (2**max_bits-1)) >> (max_bits-(r_bits%max_bits)))

ror = lambda val, r_bits, max_bits: \
   ((val & (2**max_bits-1)) >> r_bits%max_bits) | \
   (val << (max_bits-(r_bits%max_bits)) & (2**max_bits-1))


def decrypt_input(buffer):
   result = []
   for i, value in enumerate(buffer):
	   temp = (value ^ 0x33)
	   # print(temp)
	   if i % 2 == 0:
		   result.append(ror(temp, 1, 16))
	   else:
		   result.append(rol(temp, 1, 16))

   return result

dec = decrypt_input(allTCPData)
print("".join([chr(c) for c in dec]))

Output:

CBSB{x0t_4r2_th2_rh2LL_l4rt2r }

Ok this seems very weird... we know that it must start with CCSC{ but every second letter is off by one, but then only sometimes... I don't really know why this is happening, my guess is python was never made to ror and rol... But adding a +1 to the odds gives us:

CCSC{y0u_5r3_uh3_sh3LM_m4st3r!}

Well this is much closer to the actual flag we can guess the correct values by combining it with the previous output and voila!

CCSC{y0u_4r3_th3_sh3LL_m4st3r!}

And that is how you become the shell master!!!

This was my favorite challenge out of the whole of the competition as it forced me to get out of my shell of being scared to read binary, but not only understand it but even reverse it and retrieve the flag, a big thanks to the author icyDux for creating something so amazing!