Ein Blog

Posts mit Tag "writeup"

BND-CTF: RSA Key Generation

Der BND hat mal wieder einen öffentlichen Einstellungstest, dem wir uns erfreuen dürfen. Ideal, um sich mal etwas in CTF-Writeups zu probieren!

Aus der Kategorie Cryptography schauen wir uns mal die (einzige) Challenge an: Analysieren einer Backdoor in einem RSA-Schlüsselerzeugungsalgorithmus.

Folgender Code wurde uns bereitgestellt:

SageMath-Code {%- highlight c linenos -%} ### Excerpt from utils.py (used to encode / decode the message) def encode(msg): """ Convert a message to a number.
Example:
>>> hex(encode(b'ABCD'))
'0x41424344'
"""
return int.from_bytes(msg, byteorder='big')

def decode(msg): """ Convert a number back to a message.

Example:
>>> decode(0x41424344)
b'ABCD'
"""
return int(msg).to_bytes(length=len(hex(msg))//2-1, byteorder='big')

Excerpt from RSA.py

x = 0xbb31781a2436fd6833597b61f91b94fba8cc5be702c7084de28625d96823102daf48dd84244fe41d180452a900388d1666ff59981f0912c6640977684c20bcfdcbf365dfcb68c0c5a9fd02576134a0e94ab9e20bbacffb4df5c9c27ae7f5022f6609aefeb9f5249387925ad13ce80a13

def rsa_keygen(): def a(x, y): z = 1 while True: if is_prime(xz + y): return xz + y z += 1

p = a(x, random_prime(2^128, None, 2^127))
q = a(x, random_prime(2^128, None, 2^127))

N = p * q
e = 65537

d = inverse_mod(e, (p-1) * (q-1))

return e, d, N

def rsa_crypt(m, k, n): return lift(Mod(m, n) ^ k) {%- endhighlight -%}

(wer kein SageMath installiert hat, kann diese Online-Version verwenden)

Zu dem Code haben wir folgende Zahlen enthalten, die wir entschlüsseln sollen:

BND-CTF: 86-GBE Message Service

Der nächste Writeup aus den BND-CTFs. Nach der Backdoor in der RSA-Schlüsselerzeugung, dieses Mal aus der Kategorie Binary Exploitation.

Das Ziel:

Exploit a binary to gain a remote shell.

Wir bekommen die server.c, sowie die auf dem Server genutzte libc.

Server-Code {%- highlight c linenos -%} // gcc server.c -o server -g -ggdb -Wl,-z,norelro -fstack-protector-all

#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h>

int my_read(FILE *fd, char *buf, size_t max) { int read = 0; while (read < max && !feof(fd)) { int ch = getc(fd); if (ch == ‘\n’) { break; } else { buf[read] = ch; read++; } } buf[read] = 0;

return read;

}

char rot13(char ch) { if (ch >= ‘a’ && ch <= ‘z’) { return ((ch - ‘a’ + 13) % 26) + ‘a’; } else if (ch >= ‘A’ && ch <= ‘Z’) { return ((ch - ‘A’ + 13) % 26) + ‘A’; } else if (ch >= ‘0’ && ch <= ‘9’) { return ((ch - ‘0’ + 5) % 10) + ‘0’; } else { return ch; } }

int main(int argc, char** argv) { setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0);

char inp[0x100];
char cc;

puts("Welcome to our 86-GBE service!");
puts("Enter your message and press return.");
puts("Enter quit to exit.");

unsigned char len;
for (;;) {
	len = my_read(stdin, inp, sizeof (inp) - 1);
	if (strcmp(inp, "quit") == 0) {
		exit(0);
	}

	for (int i = 0; i < len; i++) {
		inp[i] = rot13(inp[i]);
	}

	for (int i = 0; i < len / 2; i++) {
		cc = inp[i];
		inp[i] = inp[len -i -1];
		inp[len - i - 1] = cc;
	}

	printf(inp);
	putc('\n', stdout);
}

exit(0);

} {%- endhighlight -%}

Nehmt Euch am Besten etwas Zeit, um den Code erstmal anzuschauen.

Falls Ihr das nachstellen wollt, die Sha1-Hashes der beiden Shared-Objects und der bereitgestellten server-Executable sind:

Für ein CTF musste ich letztens einen Netfilter bauen, der ein- und ausgehende IP-Pakete patcht. Genauer gesagt mussten Daten in TCP-Header geändert werden (mehrere Reserved-Bits mussten gesetzt werden).

Nachdem ich hoffnungslos versucht hab, irgendwelche random TCP/IP-Userspace-Stacks mit Raw-Sockets oder TUN/TAP-Devices zum Laufen zu bringen, war die “einfache” Lösung ein Netfilter-Kernelmodul. Da man dazu immer mal wieder verschiedene Varianten findet und 90% der Tech-Blogs da draußen nicht in der Lage sind, Zeilenumbrüche und Quotes ordentlich zu formatieren, hier eine “einfache” Lösung (basiert teilweise auf dieser):

ultra_hack.c:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/ip.h>
#include <linux/tcp.h>

MODULE_LICENSE("GPL-3.0");
MODULE_AUTHOR("Ultra Hacker 3000");
MODULE_DESCRIPTION("Super-TCP-Hack");

static unsigned int hook_func(void *priv, struct sk_buff *skb, const struct nf_hook_state *state)
{
	if (!skb)
		return NF_ACCEPT;

	struct iphdr *iph = ip_hdr(skb);
	if (iph->protocol != IPPROTO_TCP)
		return NF_ACCEPT;

	struct tcphdr *tcp_header = tcp_hdr(skb);

	tcp_header->res1 = 0b1010;

	printk(KERN_DEBUG "ultra hack is hacking.\n");

	return NF_ACCEPT;
}

static struct nf_hook_ops nf_hook_data = {
	.hook = hook_func,
	.hooknum = NF_INET_POST_ROUTING,
	.pf = PF_INET,
	.priority = NF_IP_PRI_FIRST,
};

static int __init init_function(void)
{
	printk(KERN_INFO "ultra hack module is loading.\n");
	nf_register_net_hook(&init_net, &nf_hook_data);
	return 0;
}

static void __exit exit_function(void)
{
	printk(KERN_INFO "ultra hack module is unloading.\n");
	nf_unregister_net_hook(&init_net, &nf_hook_data);
}

module_init(init_function);
module_exit(exit_function);

Ein paar Anmerkungen:

  • PF_INET steht für IPv4
  • die hooknum gibt die Stelle an, an der der Filter eingesetzt wird (das kann man noch Pre-Routing machen etc, einfach nach anderen Konstanten schauen).
  • Die Signatur der hook_func ändert sich gerne mal bei Kernel-Updates. Die aus dem Beispiel funktioniert für 5.11.0.

Hier noch die Makefile dazu:

obj-m := ultra_hack.o
LKM-objs += ultra_hack.o

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) modules
	rm -r -f *.mod.c .*.cmd *.symvers *.o
clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) clean

Zum Laden, Entfernen etc:

# kompilieren
make all

# laden
sudo insmod ultra_hack.ko

# schauen, ob es da ist
lsmod

# schauen, ob printk-Nachrichten auftauchen
sudo dmesg -w

# entfernen
sudo rmmod ultra_hack

Man kann das auch mit nfqueue und Python machen.