Forensic

ChallengeLink

Baby SoC (256 pts) 🥈

Budget SoC (363 pts) 🥈

Baby SoC (256 pts)

Description

We found really funny device. It was broken from the beginning, trust us! Can you help with recovering the truth?

Solution

Given flashdump.bin, parse the executable using this parser. There will be issue if we use the newer version of esptool library, use this patch to solve the issue. First, take a look on available partition on the flashdump.bin

python3 esp32_image_parser.py show_partitions flashdump.bin

Dump app0 executable using create_elf argument.

python esp32_image_parser.py create_elf flashdump.bin -partition app0 -output app.elf

Use ghidra to decompile the ELF file. Looking at available strings that there is "Flag" string.

FUN_400d2964
---snippet---
  memw();
  memw();
  iStack_24 = _DAT_3ffc4170;
  FUN_400d8108(0x3ffc3f3c,&DAT_3f400120);
  memcpy_(auStack_7c,s__<!DOCTYPE_html>_<html>_<head>_<_3f400125);
  if (DAT_3ffc3ce8 != '\0') {
    FUN_400d8108(0x3ffc3f3c,s_here2_3f4002d0);
    FUN_400d293c(auStack_49);
    memcpy_(auStack_6c,auStack_49);
    memcpy_(auStack_5c,s_<h2>Flag:_3f4002d6);
    uVar2 = FUN_400d87ec(auStack_5c,auStack_6c);
    uVar2 = FUN_400d881c(uVar2,s_</h2>_3f4002e1);
    FUN_400d8708(auStack_7c,uVar2);
    FUN_400d82dc(auStack_5c);
    FUN_400d80f0(0x3ffc3f3c,auStack_6c);
    FUN_400d82dc(auStack_6c);
  }
  FUN_400d87b8(auStack_7c,s_</body></html>_3f4002e7);
  FUN_400d7334(0x3ffc3cec,200,s_text/html_3f4002f6,auStack_7c);
  FUN_400d82dc(auStack_7c);
---snippet---

As we can see on line 12 there is code like an string builder that result <h2>Flag: ??? </h2>. So we can assume that auStack_6c is the variable that store the flag. auStack_6c copied from auStack_49 that constructed from FUN_400d293c. Take a look on FUN_400d293c.

void FUN_400d293c(byte *param_1)

{
  *param_1 = DAT_3ffbdb68 ^ DAT_3ffbdb8d;
  return;
}

FUN_400d293c do xor for two static values which are DAT_3ffbdb68 and DAT_3ffbdb8d. To get the flag we just need to do xor for those static values.

a = [0xEE,0xCE,0x48,0x21,0x63,0xA8,0x1C,0xB8,0xA8,0x61,0x61,0x0D,0x8B,0x36,0xF0,0x07,0x25,0x85,0x5C,0xB0,0x6A,0x4B,0xC6,0xEF,0xBB,0x74,0x80,0x06,0x67,0x44,0xED,0x2A,0xD3,0x26,0xF7, 0xC6]
b = [0x84,0xBB,0x3B,0x55,0x20,0xFC,0x5A,0xC3,0xD1,0x0E,0x14,0x52,0xF3,0x06,0x82,0x58,0x48,0xE0,0x03,0xC2,0x5B,0x2C,0xAE,0x9B,0xE4,0x06,0xB0,0x73,0x09,0x20,0xB2,0x48,0xE7,0x44,0x8E,0xBB]

flag = b""
for i in range(len(a)):
    flag += bytes([a[i] ^ b[i]])
print(flag)

Flag: justCTF{you_x0r_me_r1ght_r0und_b4by}

Budget SoC (363 pts)

Description

We've obtained a mysterious device. Our forensic team tried to retrieve the original source code, but something went wrong. Fortunately, we managed to dump the memory into a file. Can you find what we need?

Solution

Given flashdump.bin, parse the executable using this parser. There will be issue if we use the newer version of esptool library, use this patch to solve the issue. First, take a look on available partition on the flashdump.bin.

python3 esp32_image_parser.py show_partitions flashdump.bin

Dump app0 executable using create_elf argument.

python esp32_image_parser.py create_elf flashdump.bin -partition app0 -output app0.elf

Open it using ghidra and look at string "flag" we will found reference to the function that will produce flag like in previous SOC challenge.

Rename some variable and function to make it easier to understand.

FUN_400d29b
---snippet--- 
memw();
  memw();
  iStack_24 = _DAT_3ffc4120;
  FUN_400d88c4(0x3ffc3eec,&DAT_3f400120);
  memcpy_(auStack_94,s__<!DOCTYPE_html>_<html>_<head>_<_3f400125);
  if (DAT_3ffc3ca8 != '\0') {
    FUN_400d88c4(0x3ffc3eec,s_here2_3f4002d0);
    if (0x83 < _DAT_3ffc3e3c) {
      allocation_(ciphertext,_DAT_3ffc3e38 + 100,0x20);
    }
    FUN_400d296c(ciphertext,decrypted,0x20);
    memcpy_(auStack_84,decrypted);
    memcpy_(auStack_74,s_<h2>Flag:_3f4002d6);
    uVar2 = FUN_400d8fa8(auStack_74,auStack_84);
    uVar2 = FUN_400d8fd8(uVar2,s_</h2>_3f4002e1);
    FUN_400d8ec4(auStack_94,uVar2);
    FUN_400d8a98(auStack_74);
    FUN_400d88ac(0x3ffc3eec,auStack_84);
    FUN_400d8a98(auStack_84);
  }
---snippet---

So the ciphertext are processed on function FUN_400d296c, next take a look on function FUN_400d296c. There are some constant in the function so it nice to search it on github.

Search for the constant in 4 bytes format, https://github.com/search?q=0x52096ad5&type=code and i found this.

From above code we can see that the constant is actually from aes decrypt process. Looking at another function looks like it is same like in the app0.elf function. So the last step is basically finding the key and the ciphertext used by the function in app0.elf.

undefined4
FUN_400d82e4(int instance,undefined4 ciphertext,int param_3,undefined4 param_4,undefined4 key,
            undefined2 length,undefined4 param_7)

{
  undefined4 uVar1;
  int iVar2;
  
  *(instance + 0xfc) = param_3;
  aes_key_expand(instance,key,length);
  iVar2 = param_3 + 0xf;
  if (-1 < param_3) {
    iVar2 = param_3;
  }
  aes_(instance,ciphertext,param_4,iVar2 >> 4,param_7);
  uVar1 = FUN_40173bc4(instance,param_4,param_3);
  return uVar1;
}

From the caller function we get the key, which is on the fifth argument (DAT_3ffbdb68). The ciphertext is on second argument and it allocated from function allocation_ that we assume the data is from _DAT_3ffc3e38.

FUN_400d831c(auStack_134,ciphertext,param_3,output,&DAT_3ffbdb68,0x10,uVar1);

Looking at ELF file, we know that _DAT_3ffc3e38 is not stored on it.

So we assume that the data is maybe on runtime memory. Because we have the flashdump.bin we try to directly find the ciphertext by bruteforcing all 32 bytes value in the flashdump.bin. Below is our script to do bruteforce.

from Crypto.Cipher import AES

f = open("flashdump.bin", "rb").read()
key = [0x33,0xBD,0xFB,0x72,0x4C,0x22,0x87,0x33,0x62,0xFF,0x75,0x41,0xD5,0x14,0xF6,0xFD]
bytes_key = bytes(key)

for i in range(0, len(f) - 32):
	cipher = AES.new(bytes_key, AES.MODE_ECB)
	tmp = f[i:i+32]
	res = cipher.decrypt(tmp)
	if b"just" in res:
		print(i, res)

Looks like we got partial flag, so the mode should be not ECB. The next step we do is trying to use AES CBC with iv null bytes, because the first block is already correct plaintext.

from Crypto.Cipher import AES

f = open("flashdump.bin", "rb").read()
key = [0x33,0xBD,0xFB,0x72,0x4C,0x22,0x87,0x33,0x62,0xFF,0x75,0x41,0xD5,0x14,0xF6,0xFD]
bytes_key = bytes(key)
iv = b"\x00"*16
i = 42308
cipher = AES.new(bytes_key, AES.MODE_CBC, iv)
tmp = f[i:i+32]
print(cipher.decrypt(tmp))

Flag: justCTF{dUmp3d_r3v3rs3d_h4ck3d}

Last updated