# Aero CTF 2021 - Dummyper (454 pts)
This the first challenge I did. The challenge's task was:> This stupid program has encrypted our flag.>> We only have a dump left.
With a mysterious "dump.7z" that contains a "dump" file. This file is an ELF binary, so we load it in IDA.
## Overview
IDA complains about broken section table, but still succeeds to load the bin. We get classical glibc's `__libc_start_main`, and the "main" function which looks like this:```; int __fastcall main(int, char **, char **)main proc nearendbr64push    rbpmov     rbp, rspcall    loc_1691call    sub_172Acall    sub_188Bmov     eax, 0pop     rbpretnmain endp
```The first function contains garbage, such as:
```
loc_1691:                               ; CODE XREF: main+8↓pLOAD:0000000000001691                 out     53h, alLOAD:0000000000001693                 pop     rbpLOAD:0000000000001694                 out     1, eax          ; DMA controller, 8237A-5.LOAD:0000000000001694                                         ; channel 0 base address and word countLOAD:0000000000001696                 stdLOAD:0000000000001697                 adc     [rdi+65h], clLOAD:000000000000169A                 movsbLOAD:000000000000169B                 mov     ebx, 58D66E0AhLOAD:000000000000169B ; ---------------------------------------------------------------------------LOAD:00000000000016A0                 dq 91870DBC4BC97160h, 1FEC1165698C4247h, 26B5D424EA599C8Ah
```which probably means that the binary was edited before the dump was taken. A quick look to the next function (`sub_172a`) confirms this:
```c
_BYTE *sub_172A(){  _BYTE *result; // rax  int i; // [rsp+Ch] [rbp-34h]  int j; // [rsp+10h] [rbp-30h]  int v3; // [rsp+14h] [rbp-2Ch]  int v4; // [rsp+1Ch] [rbp-24h]  void *ptr; // [rsp+28h] [rbp-18h]  void *v6; // [rsp+38h] [rbp-8h]
  v3 = getpagesize();  mprotect((char *)&loc_13A9 - (unsigned __int64)&loc_13A9 % v3, v3, 7);  ptr = (void *)((__int64 (__fastcall *)(__int64))loc_13A9)(32LL);  fread(ptr, 0x20uLL, 1uLL, stream);  for ( i = 0; i <= 63; ++i )  {    v4 = rand() % 2047;    v6 = (void *)((__int64 (__fastcall *)(_QWORD))loc_13A9)(v4);    fread(v6, v4, 1uLL, stream);  }  result = &loc_13A9;  for ( j = 0; j <= 895; ++j )  {    result = (char *)&loc_13A9 + j;    *result ^= *((_BYTE *)ptr + j % 32);  }  return result;}
```It's obvious to see that the function calls mprotect to allow writing on the .text session, then call `loc_13a9`, which also contains garbage. Then, some part of the text section is xored with a random key. So to analyze further the binary, we need to find the decryption key, which is a 32-byte key.
Luckily, we have an amd64 binary, and gcc puts an "endbr64" instruction at the beginning of each section, before the habitual prologue `push rbp, mov rbp, rsp`. So we can recover 8 bytes of the key with partial known plaintext.
We have two encrypted functions, and luckily, the first function is at position 0 mod 32, and the second function at pos 8 mod 32, which means we know the first 16 bytes of the key, we have to xor the 8 first bytes of the function with "f30f1efa554889e5" (our prologue).
Then, we can manage to "guess" some bytes looking at the partial disassembly, which gives this decoding script:```python
#!/usr/bin/python
f = open("dump", "rb")buf = f.read()modbuf = bytearray(buf)
# Offset of the two first functionsoffset = 0x13a9offset2 = 0x1691
# endbr64; push rbp; mov rbp; rspendbr64 = bytes.fromhex("f30f1efa554889e5")
# Get the first 8 bytes of the keyfunc = buf[offset:offset+len(endbr64)]key1 = bytes([x ^ y for x,y in zip(func, endbr64)])
# Get the next 8 bytes of the keyfunc2 = buf[offset2:offset2+len(endbr64)]key2 = bytes([x ^ y for x,y in zip(func2, endbr64)])
# Some guessed bytes according to the disasskey = key1 + key2 + b"\x2d\x27\x57" + b"\x1a\x26"
# Luckily we have a function here too, so 8 bytes for freefunc3 = buf[0x13fe:0x13fe+len(endbr64)]key4 = bytes([x ^ y for x,y in zip(func3, endbr64)])
# Guessed bytes againkey = key + key4 + b"\xba\xca\x5e"
# Now let's decrypt the encrypted functionsfor i in range(0, 896):    modbuf[offset + i] = modbuf[offset + i] ^ key[i % 32]
out = open("dump2", "wb")out.write(modbuf)out.close()
```Now we have recovered the full code, we can analyze the dump.
## Analysis of the decrypted dumpThe main function didn't change a lot, but now the `sub_1691` function disassembles correctly. Let's have a look to the decompiled function:```c
__int64 sub_1691(){  unsigned int v0; // eax  FILE *stream; // [rsp+0h] [rbp-10h]  void *ptr; // [rsp+8h] [rbp-8h]
  randomfile = fopen("/dev/urandom", "r");  v0 = time(0LL);  srand(v0);  stream = fopen("./flag.txt", "r");  ptr = (void *)alloc_mem(128LL);  fread(ptr, 0x80uLL, 1uLL, stream);  fclose(stream);  return cryptostuff(ptr);}
```The function calls `alloc_mem` which looks like this:```c
char *__fastcall alloc_mem(size_t a1){  char *s; // [rsp+18h] [rbp-8h]
  s = (char *)&unk_5060 + count;  memset((char *)&unk_5060 + count, 204, a1);  count += a1;  return s;}
```to write the flag on it. Then the function calls `cryptostuff`, we'll look into.
```c  for ( i = 0; i <= 63; ++i )  {    v9 = rand() % 2047;    ptr = alloc_mem(v9);    fread(ptr, v9, 1uLL, randomfile);  }  aeskey = alloc_mem(0x20uLL);  for ( j = 0; j <= 63; ++j )  {    v8 = rand() % 2047;    v15 = alloc_mem(v8);    fread(v15, v8, 1uLL, randomfile);  }  aesiv = alloc_mem(0x10uLL);  for ( k = 0; k <= 63; ++k )  {    v7 = rand() % 2047;    v14 = alloc_mem(v7);    fread(v14, v7, 1uLL, randomfile);  }  fread(aeskey, 1uLL, 0x20uLL, randomfile);  fread(aesiv, 1uLL, 0x10uLL, randomfile);  aes_ctx = alloc_mem(0xC0uLL);  for ( l = 0; l <= 63; ++l )  {    v6 = rand() % 2047;    v13 = alloc_mem(v6);    fread(v13, v6, 1uLL, randomfile);  }  aes_setup_key((__int64)aes_ctx, (__int64)aeskey);  aes_setup_iv(aes_ctx, aesiv);  return aes_encrypt(aes_ctx, flag, 128LL);
```  The function calls the custom "malloc" to get the AES key and IV (the AES function could be identified thanks to its S-Box which begins with `63h, 7Ch, 77h, 7Bh, 0F2h, 6Bh, 6Fh, 0C5h, 30h, 1, 67h`). To avoid being found easily, a lot of junk allocation with random sizes are performed. Fortunately, those random allocation are performed with `rand`, which is initialized with `srand(time(NULL))`. So we just need to know when the program was run, you can know thanks to the modification date of the "dump" file in the archive.
  So, the solve script to fetch the flag (I use the offset of the xor key as a check to determine if we got the right seed):```python
#!/usr/bin/pythonfrom ctypes import CDLLfrom Crypto.Cipher import AESlibc = CDLL("libc.so.6")
# Offset where blocks are allocatedblockoff = 0x5060
f = open("dump", "rb")buf = f.read()
# Get the encrypted flagencflag = buf[blockoff:blockoff + 0x80]
# Bruteforce timestamps for the 25 Feb. to get the correct seedts = 1614211200for i in range(0, 24*3660):    blockpos = 0x80    libc.srand(ts + i)    for _ in range(0, 64):        blockpos += libc.rand() % 2047    aeskey = buf[blockoff + blockpos:blockoff + blockpos + 0x10]    blockpos += 0x20    for _ in range(0, 64):        blockpos += libc.rand() % 2047    aesiv = buf[blockoff + blockpos:blockoff + blockpos + 0x10]    blockpos += 0x10    for _ in range(0, 64):        blockpos += libc.rand() % 2047    blockpos += 0xc0    for _ in range(0, 64):        blockpos += libc.rand() % 2047    # If the computed offset is the offset of the xor key in the dump, we won    if(blockoff + blockpos == 0x4ba74):        print("Found candidate %d" % i)        break
c = AES.new(aeskey, AES.MODE_CBC, aesiv)print(i)print(c.decrypt(encflag))
```And the flag we got is `Aero{d37fd6db2f8d562422aaf2a83dc62043}`