Playing Hacks and Stuffs!
Hello 👋, I participated in this CTF with team !ethical
as 0x1337
I’ll be giving writeups to some of the challenges which I solved
Not pretty much so let’s get to it 😉
It’s clearly referring to the main page of the site, so I went over there, viewed page source and got the flag
Flag: ACTF{dont_skip_cutscenes}
Head over to their accounts, follow them go back to the ctf platform then submit Yes
:)
Flag: Yes
Just submit that
Flag: actf{i_did_not_skip_this_cutscene}
Ohh this challenge was pretty easy but I spent some time on it
First I downloaded the attached file and on checking it’s content I got this
6182665f351415600b57005b5f80fd
I started thinking this was some sort of hex value 💀
Well after trying I got back to the challenge and read the “description”
Our contact says Nuk deployed a contract using EIP-3855 .
The hexcode calldata of a transaction that will not revert is said to be the key.
I am not a Web3 person but from reading this I knew it was related to Web3
Ok time for some research, first thing I had to search what was “EIP-3855” meant
Most of what I was seeing was just “PUSH0 instruction”
I tried to learn about “EIP” but didn’t succeed as I could not find any where to get a general basic of what it is
Now i decided to work back with what I have
From reading this it made me conclude that the hex value given as the challenge is a bytecode because the links based on “EIP-3855” was referencing opcode (operational code) and instructions, you can also use the challenge name to conclude your assumption is right (Byteops
)
Now that I know this I had to find a way to decompile the bytecode
Searching it up on google gave this and trying to use it worked
contract Contract {
function main() {
var var0 = 0x8266;
// Unhandled termination
}
}
label_0000:
0000 61 PUSH2 0x8266
0003 5F 5F
// Stack delta = +1
// Outputs[1] { @0000 stack[0] = 0x8266 }
// Block terminates
0004 35 CALLDATALOAD
0005 14 EQ
0006 15 ISZERO
0007 60 PUSH1 0x0b
0009 57 *JUMPI
000A 00 *STOP
000B 5B JUMPDEST
000C 5F 5F
000D 80 DUP1
000E FD *REVERT
Ok good now we decompiled it what next?
Well we need to understand what it does and each opcode has the operation it performs so I searched for the opcode list and got this
Before diving into the bytecode we can see that some portion was decompiled correctly and it defines a contract Contract
where the main
function stores a value 0x8266
in variable var0
At this point of seeing it I went back to the challenge and read the description
The hexcode calldata of a transaction that will not revert is said to be the key.
This looks like the right input to this challenge so I tried it but unfortunately it didn’t work
I did spam lot of it though 💀
Now let’s understand what the bytecode does
First I’ll write what each opcode translates too
PUSH2 - Pushes a 2-byte value onto the stack
CALLDATALOAD - Reads a (u)int256 from message data
EQ - (u)int256 equality
ISZERO - (u)int256 is zero
PUSH1 - Pushes a 1-byte value onto the stack
JUMPI - conditional jump if condition is truthy
STOP - Halts execution of the contract
JUMPDEST - Metadata to annotate possible jump destinations
PUSH0 - Shanghai hardfork, EIP-3855: pushes 0 onto the stack
DUP1 - clones the last value on the stack
REVERT - Byzantium hardfork, EIP-140: reverts with return data
Harmed with this instruction let’s translate what it does
At this point let’s visualize the stack as an array, then it should hold this:
[0x8266, 0x0]
Ok pretty straight forward we just need to provide the value that would make the contract not to revert, and for that to happen the value oughts to be 0x8266
But when I submitted that as flag it wasn’t working
I then decided to work with this dynamically, basically seeing how it works during runtime
Searching for how I could achieve that lead me to evm_playground
This is how it looks like
So I change the default bytecode there to the one we have
One thing to note there is that the Calldata
form is where we pass in input as hex in this case the value we want to check
First i tried using the value it’s compared to which is 0x8266
Starting it’s execution shows this
We can click on next instruction
After the CALLDATALOAD
instruction we see our given value has multiple 0’s
I don’t really know how i can say this but i think it’s just like how memory address are when it uses msb to represent data
Like in this case CALLDATALOAD
reads an unsigned 256 bits int
When I checked the size of (u)int256
in solidity I got it to be 2^256-1
If you compute that and compare it with the length of the value we have you should see this
This means it’s just alligned based on the size of the data type
When we continue the execution it will hit REVERT
because of course the values aren’t equal
So here’s the value I used
0x0000000000000000000000000000000000000000000000000000000000008266
Using that the execution hits STOP
which means this is the expected value
We can then submit that :)
Flag: ACTF{0x0000000000000000000000000000000000000000000000000000000000008266}
This challenge was pretty much created due to a rant about why there were no very trivial challs 😄
– We asked for it…
– He delivered
Now let’s get to it
Going to the attached url doesn’t show anything
Viewing page source reveals nothing also
On checking /robots.txt
reveals this path
Trying to access it downloads an attachment
The file downloaded was a rust compiled executable
I ran it and got this message
At this point when solving it I actually had to CTRL+C
because during the prequal there was a rev chall which would logout the current session so I thought it was the same but luckily it wasn’t
Strings & grepping for the flag pattern reveals the flag
Flag: aCtF{robotTxt_and_strings_as_requested}
For the last two webs I wasn’t the one who solved it but my teammate, incase you are wondering what the solution is here’s the payload used:
— Mystique
//note I renamed most of the obfuscated variables for better understanding.
onSubmit, the login() function is called which sets the following varaibles.
- publicKey stores a JSEncrypt key object gotten from the setPublicKey() function.
- The setPublicKey() function stores a PEM key in the key variable.
- creates a new JSEncrypt object and stores it in the jsEncryptedKey variable.
- it finally uses the .setPublicKey() method on the JSEncrypt object to the set the public key to the PEM key above.
- randomText stores a randomText generated by the generateRandomText() function
- I don't this is really important
- encryptedData stores the encrypted data returned from the encryptData() function which takes publicKey and randomText as arguments
- The encryptData function stores the result of concatinating 'user' + randomText in a variable data.
- encrypts the data using the publicKey.encrypt() method
- sendEncryptedData makes a POST request to the /Flag endpoint with the encryptedData as argument.
- before the request is sent, the encryptedData is url-encoded
[+] Steps to recreate
In your console, run the following . .
- var key = '-----BEGIN PUBLIC KEY-----\n MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ziDyee9fICsEJ5ebGyv\n N1toEnOGBwYQrehsuOfkNXm4BKoBgiSXJGAeU/+4JeXrkaX7pejDF1loZvKXFIfA\n RaaNIqDbsZfIYPB0nMpaYrXreO6R+7jyWN6a0uPTOyaYYlCdhLRjciV8w7PBcO/e\n iVzCajZSp+uNqlVz3s83o+LOl0B/RLNNUPrUjwvj7s4dattJhtKLts1mC1V7aHcL\n JquS5E2OqAzps2DzVJ1sezHmvJGw9/8+58AMwqFTwixP37+FhuAbNGUN5DHRUjSK\n zscmDAgE+HN+GPwOx6ynpVmrubqWsZ0CL14mxtfVYNUBopI/BACZYdn2B/Eze1ay\n uQIDAQAB\n -----END PUBLIC KEY-----\n'
- var jsEncryptedKey = new JSEncrypt()
- jsEncryptedKey.setPublicKey(key)
- sendEncryptedData(jsEncryptedKey.encrypt("admin"+generateRandomText())
— Daredevil
sendEncryptedData(encryptData(key,"admin:' or password like *"))
checking the source code, we need to send the user and password seperated by ':' encrypted.
sendEncryptedData(encryptData(setPublicKey(), "admin:admin"))
when you send the above in your browser console, you should get "Invalid Username or password" as the response.
but when you send this instead,
sendEncryptedData(encryptData(key,"admin' and password like '%'--:admin"))
we get the following response "Login for admin' and password like '%'-- not allowed", so we can guess the password like that.
This was the first reverse engineering challenge, and we were given an apk file
First thing I tried was to unzip the file, grep for the flag, convert the dex files to jar using dex2jar
, decompile the converted dex file using jd-gui
Doing that I didn’t really get anything and jd-gui
decompilation was a bit off
So I tried using an online decompiler
Next i downloaded the zip file
Unzipping it and opening in vscode should give this
Looking through the classes i saw Payload.java
which looked interesting
Viewing it shows this
We can right away tell we should decode that
I just copied and paste that array to python interpreter then converted them to chr
That gives error and that’s because it isn’t in the printable range for example -5
isn’t a printable value
To fix this we need to AND
it with 255 == 0xff
which is equivalent to % 256
and that would make each value there in the printable range
With that we get the flag
Flag: ACTF{Dynamic_Analysis_h0s7_R3v3al5}
The second and last reverse engineering challenge
We are given an executable file
The description states that it’s a malware yet silly me still ran it 💀
When I ran it, the program asks for input
After we give it input then it would log out from the current session
That’s why i am running it in gdb so that it won’t logout yet
One important thing is this:
Input the flag. I'll let you know if it's correct
The program claims it will let us know if our provided input is right? That means it’s going to be giving us an oracle which would basically allow us know if each character of the given input is right or wrong therefore giving us the primitive to brute force the flag
But before we think of brute forcing I needed a way to prevent it from logging out
I threw the binary into Ghidra to do some reversing or so I thought?
Unfortunately the binary is a rust compiled binary and I am not familiar with rust so I had some issue with figuring out what it does
But my goal was to figure out the instruction which would call program to logout our session and thereby patching it
Starting from the entry function I got this
The main function is the first parameter passed to __libc_start_main
which is FUN_0012bc90
Clicking on it shows this
void FUN_0012bc90(int param_1,undefined8 param_2)
{
code *local_8;
local_8 = FUN_0012b890;
FUN_002eb380(&local_8,&PTR_FUN_00398048,(long)param_1,param_2,0);
return;
}
Variable local_8
is a function pointer to FUN_0012b890
Clicking on that shows this, which seems to be the function that handles the input validation and presumably the logout function?
Since it’s going to logout after we give it input i decided to start clicking on functions which is at the end
LAB_0012baba:
local_90 = &PTR_s_Wrong!_Input_the_flag._I'll_let_y_00398098;
local_88 = 1;
local_80 =
"Wrong!\nInput the flag. I\'ll let you know if it\'s correct\nFailed to read inputmain.rsYou\'re partially right\n"
;
local_78 = ZEXT816(0);
FUN_002eee30(&local_90);
local_90 = (undefined **)thunk_FUN_00167990();
if (local_90 != (undefined **)0x0) {
FUN_0012b750(&local_90);
}
goto LAB_0012bb04;
}
goto LAB_0012ba50;
}
On clicking funtion thunk_FUN_00167990
, i saw that it’s the function that handles the logout
The function names are stripped which makes assumption hard since I don’t know rust but we can tell because of this:
cVar6 = FUN_001671e0("org.gnome.SessionManager/org/gnome/SessionManagerorg.kde.ksmserver/KSMServer org.kde.KSMServerInterfacelogoutorg.xfce.SessionManager/org/xfce/SessionManagerorg.freedesktop.log in1/org/freedesktop/login1org.freedesktop.login1.ManagerLogout"
The logout string there should just make you guess you are at the right track (don’t quote me) 👀
Now that we figured that we need to patch the opcode
0012baea ff 15 58 CALL qword ptr [->thunk_FUN_00167990] undefined thunk_FUN_00167990()
d3 27 00 = 0012de50
So we will change 0xff1558d32700
to 0x909090909090
With that instead of the program calling that function it will just do nothing (nop -> no operation)
Here’s the script I wrote to patch it
with open("sore", "rb") as f:
binary = f.read()
f.close()
binary = binary.replace(b"\xff\x15\x58\xd3\x27\x00", b'\x90'*6)
with open("patched", "wb") as f:
f.write(binary)
Running that it should patch the binary and now we can easily run it
To confirm if the program would do what it says I tried this
Luckily it wasn’t a bluff and now we can brute force
Here’s the script I wrote to achieve that
import string
import subprocess
flag = ""
charset = string.ascii_letters + '_{}'
while True:
for char in charset:
command = f"echo {flag+char} | ./patched"
execute = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
output, err = execute.communicate()
print(f"Trying {flag+char}")
if 'Wrong' not in output:
flag += char
break
if flag[-1] == "}":
break
print(f"FLAG: {flag}")
Running it works and I got the flag
Flag: ACTF{xor_xor_diff}
We are given a pcap file and when I opened it in Wireshark I got this
First thing I tried was to get an idea of the protocol hierarchy
There’s HTTP protocol but after checking it I didn’t see anything of relevance there
I actually spent lot of time working on this but it was an easy challenge once you just figure it out
I saw that there were various DNS protocol and it looked weird
The domain queried first was 89504e470d0a1a0a0000000d49484452000002c10000019008060000004f.shadowheadquarters.com
At this point I knew it was using DNS exfiltration because that’s the header of a png file
Incase you don’t know what DNS exfiltration is, it’s basically a means by which attackers/red-teamers exfiltrates data using the dns protocol
So what we need to do now is to extract all the values from the dns queried then convert it from hex
Actually during the ctf I extracted it manually by using strings
and some match & replace magic
but my teammate used a one linear which made life easier
tshark -r ctf.pcapng | grep shadowheadquarters.com | grep -v response | cut -d "A" -f 2 | cut -d "." -f 1 | xxd -r -p > lol.png
Running that command would decode the image file
When we open it we get this
Just a blue pane image
I went over to aperisolve and got the flag in the view->superimposed
pane
UPDATE: Another way you could have extracted it is using Scapy Python Module
from scapy.all import *
packets = rdpcap("ctf.pcapng")
dns_packets = []
for packet in packets:
if packet.haslayer(DNSRR):
if isinstance(packet.an, DNSRR):
dns_packets.append(packet.an.rrname)
hex_bytes = b""
for dns_name in dns_packets:
if dns_name[-24:] == b".shadowheadquarters.com.":
hex_bytes += bytes.fromhex(dns_name[:-24].decode())
with open('dumped', "wb") as f:
f.write(hex_bytes)
Flag: ACTF{our_secrets_are_in_plain_sight!!}
Just submit that :)
Flag: ACTF{Happy_Birthday!_Lytes}
Well that’s all xD
At the end of the ctf prequalification my team placed 1st while during the final we placed 2nd
The writeups are mostly the prequalification, since I couldn’t attend the final due to exam :(
Byeee!