]> git.somenet.org - pub/jan/ctf-seminar.git/blob - writeups/hah/hxp36c3.md
Add hxp 36c3 writeup
[pub/jan/ctf-seminar.git] / writeups / hah / hxp36c3.md
1 # hxp 36c3
2 ## Retrospective
3 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.
4 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.
5
6 ## Challenges
7 ### flag concat
8 * Category: pwn
9 * 32 solves / 244 points (easy difficulty)
10 * Time spent: ~ 3 hours
11
12 ```
13 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.
14 ```
15
16 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):
17
18 ``` c
19 // gcc -no-pie -o vuln vuln.c
20
21 #include <stdint.h>
22 #include <string.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25
26 typedef struct{
27         char s1[0x400];
28         char s2[0x200];
29         char *concatenated_s3;
30 } packed_strings;
31
32 packed_strings strings;
33
34 void win(){
35         printf("Debug mode activated!\n");
36         system("cat flag.txt");
37 }
38
39 void do_strncat(){
40         int output_len = 0;
41         char *start_s1 = NULL;
42         char *start_s2 = NULL;
43
44         printf("First Flag:\n");
45         fgets(strings.s1, 0x100, stdin); // Null-terminates, s1[0x100] = \00
46         printf("Second Flag:\n");
47         fgets(strings.s2, 0x100, stdin); // Null-terminates, s2[0x100] = \00
48
49         output_len = strlen(strings.s1) + strlen(strings.s2); // Amount of bytes excl. \00 (max: 2 * 0ff?)
50         char s3[output_len+1];
51         strings.concatenated_s3 = s3;
52
53         printf("Going to output %i bytes max!\n", output_len);
54
55         start_s1 = strstr(strings.s1, "hxp{");
56         start_s2 = strstr(strings.s2, "hxp{");
57
58         if(!start_s1){
59                 start_s1 = strings.s1;
60         }
61         if(!start_s2){
62                 start_s2 = strings.s2;
63         }
64
65         strncat(start_s1, start_s2, SIZE_MAX); // Probably needs to be manipulated already
66         strcpy(strings.concatenated_s3, start_s1); // Ignores length, s1 must be manipulated already
67
68         printf("%s\n", strings.concatenated_s3);
69 }
70
71 int main(){
72         setbuf(stdout, NULL);
73         setbuf(stdin, NULL);
74         printf("Welcome to the hxp flag concat protocol server!\n");
75         do_strncat();
76         return 0;
77 }
78 ```
79
80 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:
81
82 * 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
83 * The output length is correctly calculated, and the array used to store the concatenated was appropriately sized
84 * All string handling functions checked for and added terminating null bytes if necessary
85 * 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
86
87 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:
88
89 ``` python
90 #!/usr/bin/env python
91
92 from pwn import *
93
94 # sym.win: 0x4007b6
95 # sym.do_strncat: 0x4007d5
96
97 first_payload = "AAAA" + "BBBB" + "hxp{" + "CCC"
98 second_payload = "XXXX" + "\x00" + "YYYY" + "hxp{" + "ZZZ"
99 first_payload = "A" * 40
100 second_payload = "C" * 35
101
102 for i in range(0,0x100):
103     second_payload = "B" * i + "hxp{" + "C" * 35
104     p = process('./vuln')
105     p.recvuntil('First Flag:\n')
106     p.sendline(first_payload)
107     p.recvuntil('Second Flag:\n')
108     p.sendline(second_payload)
109     bytesline = p.recvline()
110     first_payload_line = p.recvline(keepends=False)
111     second_payload_line = p.recvline(keepends=False)
112     if (first_payload != first_payload_line or second_payload[i:] != second_payload_line):
113         print(i)
114         print(first_payload_line)
115         print(second_payload_line)
116         print(second_payload[i:])
117     p.close()
118 ```
119
120 Sebastian however also unearthed (another bug report)[https://sourceware.org/bugzilla/show_bug.cgi?id=19390] and ultimately found the correct input strings:
121
122 ```
123 \x00-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\xb6\x07\x40\x00----------------\n.............................hxp{ABCDEFGHIJKLMNOPQRTSUWVXYZ1234567890\n
124 ```
125
126 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.
127
128
129 ### splitcode
130 * Category: pwn
131 * 30 solves / 256 points (medium difficulty)
132 * Time spent: ~ 6 hours
133
134 ```
135 We proudly present: Code Execution as a Service (CEaaS), now on x86-64!
136
137 (Flag filename is randomized.)
138 ```
139
140 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.
141 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:
142
143 ```
144 # Input: ABCDEFGHIJKLMNOPQRSTUVWZYXabcdefghijklmno
145 #:> px 0x240 @r12
146 #- offset -     | 0 1  2 3  4 5  6 7  8 9  A B  C D  E F| 0123456789ABCDEF
147 #0x7ffb1d3e5000 |4889 fc58 595a 5b5d 5e5f 4158 4159 415a| H..XYZ[]^_AXAYAZ
148 #0x7ffb1d3e5010 |415b 415c 415d 415e 415f 9d5c eb02 f4f4| A[A\A]A^A_.\....
149 #0x7ffb1d3e5020 |4142 eb1e f4f4 f4f4 f4f4 f4f4 f4f4 f4f4| AB..............
150 #0x7ffb1d3e5030 |f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4| ................
151 #0x7ffb1d3e5040 |f4f4 4344 eb10 f4f4 f4f4 f4f4 f4f4 f4f4| ..CD............
152 #0x7ffb1d3e5050 |f4f4 f4f4 f4f4 4546 eb1a f4f4 f4f4 f4f4| ......EF........
153 #0x7ffb1d3e5060 |f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4| ................
154 #0x7ffb1d3e5070 |f4f4 f4f4 4748 eb1e f4f4 f4f4 f4f4 f4f4| ....GH..........
155 #0x7ffb1d3e5080 |f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4| ................
156 #0x7ffb1d3e5090 |f4f4 f4f4 f4f4 494a eb10 f4f4 f4f4 f4f4| ......IJ........
157 #0x7ffb1d3e50a0 |f4f4 f4f4 f4f4 f4f4 f4f4 4b4c eb12 f4f4| ..........KL....
158 #0x7ffb1d3e50b0 |f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4| ................
159 #0x7ffb1d3e50c0 |4d4e eb1e f4f4 f4f4 f4f4 f4f4 f4f4 f4f4| MN..............
160 #0x7ffb1d3e50d0 |f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4| ................
161 #0x7ffb1d3e50e0 |f4f4 4f50 eb10 f4f4 f4f4 f4f4 f4f4 f4f4| ..OP............
162 #0x7ffb1d3e50f0 |f4f4 f4f4 f4f4 5152 eb12 f4f4 f4f4 f4f4| ......QR........
163 #0x7ffb1d3e5100 |f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 5354 eb18| ............ST..
164 #0x7ffb1d3e5110 |f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4| ................
165 #0x7ffb1d3e5120 |f4f4 f4f4 f4f4 f4f4 5556 eb18 f4f4 f4f4| ........UV......
166 #0x7ffb1d3e5130 |f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4| ................
167 #0x7ffb1d3e5140 |f4f4 f4f4 575a eb12 f4f4 f4f4 f4f4 f4f4| ....WZ..........
168 #0x7ffb1d3e5150 |f4f4 f4f4 f4f4 f4f4 f4f4 5958 eb1e f4f4| ..........YX....
169 #0x7ffb1d3e5160 |f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4| ................
170 #0x7ffb1d3e5170 |f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 6162 eb1a| ............ab..
171 #0x7ffb1d3e5180 |f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4| ................
172 #0x7ffb1d3e5190 |f4f4 f4f4 f4f4 f4f4 f4f4 6364 eb1a f4f4| ..........cd....
173 #0x7ffb1d3e51a0 |f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4| ................
174 #0x7ffb1d3e51b0 |f4f4 f4f4 f4f4 f4f4 6566 eb1e f4f4 f4f4| ........ef......
175 #0x7ffb1d3e51c0 |f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4| ................
176 #0x7ffb1d3e51d0 |f4f4 f4f4 f4f4 f4f4 f4f4 6768 eb10 f4f4| ..........gh....
177 #0x7ffb1d3e51e0 |f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 696a| ..............ij
178 #0x7ffb1d3e51f0 |eb16 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4| ................
179 #0x7ffb1d3e5200 |f4f4 f4f4 f4f4 f4f4 6b6c eb10 f4f4 f4f4| ........kl......
180 #0x7ffb1d3e5210 |f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 6d6e eb18| ............mn..
181 #0x7ffb1d3e5220 |f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4 f4f4| ................
182 #0x7ffb1d3e5230 |f4f4 f4f4 f4f4 f4f4 6f0a eb1a f4f4 f4f4| ........o.......
183 ```
184
185 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```).
186 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.