Can you obtain the more secure secret? Even with all those filters? I don't think so :)

We also have the configuration files from the previous challenge Less Secure Secrets.

When we visit the given link, we see that the page prints out the source code of the current page:

<img width="300px" src="/img.jpg" style="display:block;">

function no_errors_baby($ab){
    die("I don't like errors and warnings");
function no_race($item, $key){}
array_walk_recursive($_SERVER, 'no_race');
array_walk_recursive($_GET, 'no_race');
array_walk_recursive($_POST, 'no_race');
array_walk_recursive($_REQUEST, 'no_race');
set_error_handler ( "no_errors_baby" , E_ALL );

} else {
    sleep(2); //Anti-race
    echo "<!--";
    echo "-->";
    if(preg_match('/\$|\?|`|\'|"|%|!|[0-9]|@|\(|\)|\^|&|\*|-|\+|=|<|>|\\|{|}|\/|\||true|false|null|secret/i', $_GET["yummy"]) 
        || strlen($_GET["yummy"]) > 5000)
        die("Don't try harder");
Continue Reading - applestore

Posted on August 27, 2020* in ctf-writeups


tomcr00se rooted the galaxy S5, but we need you to jailbreak the iPhone8!

nc 10104

We're also given a binary and libc shared executable.


When reversing binaries, I usually run the binary and compare its execution alongside the disassemby+pseudocode. When we run the given binary, we're greeted by a menu with 6 options:

Continue Reading


When on website: +1 spam resistance +10 user annoyance

Gotta be fast! 500 in 10 minutes!

We're given a link to a website, which contains a picture of a Minecraft enchanting window. When we try to type into the input field, our input is displayed in the Standard Galactic Alphabet. The challenge description tells us that we need to solve 500 of these captchas in 10 minutes. Even if I were fluent in this language, it would be difficult to solve 50 of these in a minute.

Continue Reading

redpwnCTF - aall

Posted on June 25, 2020* in ctf-writeups


how many layers of vm are you on

like,, maybe 5, or 6 right now my dude

you are like a baby... watch this

nc 31755

We're also given a python file and a Dockerfile.


Looking at the python file shows that it writes out a file named breakout.aallo and calls exec on a string after base64-decoding and lzma-uncompressing it. We can modify the file to save the executed file to disk instead. It's a python script, but all of the variables are random unicode characters. Although the python interpreter is happy to run the code, it's nearly impossible to understand.

Continue Reading

redpwnCTF - tux fanpage

Posted on June 25, 2020* in ctf-writeups


My friend made a fanpage for Tux; can you steal the source code for me?


We're also given the source code:

Continue Reading

fd - 1pt


Mommy! what is a file descriptor in Linux?

ssh -p2222 (pw:guest)

Source Code

After ssh-ing into the server with the given details, we can view the source code of the challenge by running cat fd.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char buf[32];
int main(int argc, char* argv[], char* envp[]){
                printf("pass argv[1] a number\n");
                return 0;
        int fd = atoi( argv[1] ) - 0x1234;
        int len = 0;
        len = read(fd, buf, 32);
        if(!strcmp("LETMEWIN\n", buf)){
                printf("good job :)\n");
                system("/bin/cat flag");
        printf("learn about Linux file IO\n");
        return 0;
Continue Reading


Can you see anything?

Get a shell for me.

nc 10200




Files are internally represented using the _IO_FILE_plus struct in glibc:

struct _IO_FILE_plus
  _IO_FILE file;
  const struct _IO_jump_t *vtable;
Continue Reading


I developed a new video streaming service just for hackers. Learn all about viruses, IP addresses, and more on LeetTube! Here's the source code and the Dockerfile.

Note: the server is also running behind NGINX.

The application is simple enough--It serves videos and has both public and unlisted videos. The Dockerfile uses FROM kmh11/python3.1, which is weird because Python 3.1 was released nearly 11 years ago. To ensure the name wasn't misleading, I ended up verifying the Python binary in the docker image, but I didn't find any related vulnerabilities. However, there is a noticeable vulnerability in how paths are handled.

Continue Reading


Make tcache great again !

nc 10207



Per-thread cache (tcache) is an optimization enabled in versions of libc after 2.26. To increase heap performance, security checks are limited within the tcache implementation. Tcache is implemented using two important internal structures:

Continue Reading


Please kill the werewolf with silver bullet!

nc 10103

We are also provided a binary and the libc used on the server.


When running the binary, we can see that we have four options:

The provided binary was not stripped, so reversing was easy with Ghidra.

void create_bullet(bullet *bullet)
  size_t size;
  if (bullet->description[0] == '\0') {
    printf("Give me your description of bullet :",0);
    read_input((char *)bullet,0x30);
    size = strlen((char *)bullet);
    printf("Your power is : %u\n",size);
    bullet->power = size;
    puts("Good luck !!");
  else {
    puts("You have been created the Bullet !");
Continue Reading


A good Hacker should always take good notes!

nc 10102

We are also provided a binary and the libc used on the server.


When running the binary, we can see four options:

Understanding the binary

When reversing the binary, we can use the shown options to help identify functions used. I reversed the binary in Ghidra, and the following are the cleaned up decompilation output from the binary.

Continue Reading


We are given a binary to exploit. By running checksec on it, we see that the binary has the following protections: [show-line-numbers]: false

Canary                        : No
NX                            : Yes
PIE                           : Yes
Fortify                       : No
RelRO                         : Partial

Because the binary has NX, we cannot place executable shell code in a buffer and then jump to it. PIE allows the code section of the binary to be located anywhere in memory. This means that we don't know the address of functions within the binary; however, we still know relative offsets.

Reverse Engineering

Dump of assembler code for function main:
   0x0000000000001145 <+0>:     push   rbp                                  # Setup Stack
   0x0000000000001146 <+1>:     mov    rbp,rsp                              
   0x0000000000001149 <+4>:     sub    rsp,0x110                            # Allocate 0x110 bytes on stack
   0x0000000000001150 <+11>:    mov    DWORD PTR [rbp-0x104],edi            # Copies edi into stack offset 0x104
   0x0000000000001156 <+17>:    mov    QWORD PTR [rbp-0x110],rsi            # Copies rsi into stack offset 0x110
   0x000000000000115d <+24>:    mov    rax,QWORD PTR [rbp-0x110]            # Copies value of pointer into rax
   0x0000000000001164 <+31>:    add    rax,0x8                              # Adds size_t
   0x0000000000001168 <+35>:    mov    rdx,QWORD PTR [rax]                  # Copies the value of the pointer [rax + 8] into rdx
   0x000000000000116b <+38>:    lea    rax,[rbp-0x100]                      # Loads the address of [rbp - 0x100] into rax
   0x0000000000001172 <+45>:    mov    rsi,rdx                              # rsi = rdx
   0x0000000000001175 <+48>:    mov    rdi,rax                              # rdi = rax
   0x0000000000001178 <+51>:    call   0x1030 <strcpy@plt>                  # Calls strcpy(rsi, rdi) ; strcpy ([rbp - 0x100], argv[1])
   0x000000000000117d <+56>:    lea    rax,[rbp-0x100]                      # Loads address of [rbp-0x100] into rax
   0x0000000000001184 <+63>:    mov    rdi,rax                              # rdi = rax
   0x0000000000001187 <+66>:    call   0x1040 <puts@plt>                    # puts(rdi)
   0x000000000000118c <+71>:    mov    eax,0x0                              # return 0
   0x0000000000001191 <+76>:    leave                                       
   0x0000000000001192 <+77>:    ret                                         
End of assembler dump.
Continue Reading - start

Posted on November 22, 2019* in ctf-writeups

We are given a linux binary. To start off, lets run checksec on it:

Arch:     i386-32-little
Stack:    No canary found
NX:       NX disabled
PIE:      No PIE (0x8048000)

It looks like NX is disabled, so if needed, we can place and execute shell code from the stack. To understand how this binary works, I opened it with Ghidra. There are only two functions, _entry and _exit. Let's look at _entry first. Although the decompilation is mostly useless, the disassembly is more than enough.

We can see two int 0x80s that are syscalls. Looking at the value of eax and by referencing a 32-bit syscall table, we can identify them. The reversed syscalls are shown in comments in the above screenshot. It's also clear that we control EIP due to the large read syscall that will overflow into the save return address on the stack.

As we saw earlier, the NX bit is disabled, so we can jump to our shellcode. To do so, we need to leak a stack address to identify where to jump to. Using pwndbg, it's easy to watch the stack during execution.

Continue Reading

picoCTF 2019 - Java Script Kiddie 2

Posted on October 12, 2019* in ctf-writeups

We are given a website, that is nearly identical to Java Script Kiddie 1. The assemble_png function takes in a key of length 32, and manipulates the bytes to decode the src attribute of an image.

function assemble_png(u_in){
    var LEN = 16;
    var key = "00000000000000000000000000000000";
    var shifter;
    if(u_in.length == key.length){
        key = u_in;
    var result = [];
    for(var i = 0; i < LEN; i++){
        shifter = Number(key.slice((i*2),(i*2)+1));
        for(var j = 0; j < (bytes.length / LEN); j ++){
            result[(j * LEN) + i] = bytes[(((j + shifter) * LEN) % bytes.length) + i]
    while(result[result.length-1] == 0){
        result = result.slice(0,result.length-1);
    document.getElementById("Area").src = "data:image/png;base64," + btoa(String.fromCharCode.apply(null, new Uint8Array(result)));
    return false;
Continue Reading

picoCTF 2019 - shark on wire 2

Posted on October 9, 2019* in ctf-writeups

We are given a packet capture with little description of what to look for. While looking through the UDP streams, I came across two packets with the text start and end within in the data section:

Packet 1104:

Packet 1303:

The only bytes changing between the start packet (#1104) and the next UDP packet (#1106) were the data field, checksums, and the source port. I noticed that the difference between the source ports of these two packets (5112 - 5000 = 112) was the ASCII code for the letter p. I repeated this for all the UDP packets (excluding MDNS queries) and found the following numbers:

Continue Reading


This program is quite short, but has got printf and gets in it! This shouldn't be too hard, right?

Connect at nc 31283

We are given the libc used, the binary, and the source code.

#include <stdio.h>

void vuln()
	char buf[64];
	fputs("Type something>", stdout);
	fputs("You typed: ", stdout);

int main()
	/* Disable buffering on stdout */
	setvbuf(stdout, NULL, _IONBF, 0);


	return 0;
Continue Reading


This is it. The last group test of the year. Dr. J patched his prng again so numbers won't repeat, so I guess Leaf won't get to know the group test pairs ahead of time... oh WEYL. Who knew middle square could make such a good prng?

nc 31382

We're also given the source of the running application:

#include <stdio.h>
#include <stdint.h>
#include <sodium.h>
#include <stdbool.h>
#include <string.h>
#define NUM_CORRECT 10

uint64_t x = 0, w = 0, s = 0xb5ad4eceda1ce2a9;

uint32_t nextRand() {
  w += s;
  x = x*x + w;
  x = (x>>32) | (x<<32);
  return x % (1UL<<32);

void init_seed() {
  uint64_t r1 = (uint64_t) randombytes_random();
  uint64_t r2 = (uint64_t) randombytes_random();
  x = (r1 << 32) + r2;
  r1 = (uint64_t) randombytes_random();
  r2 = (uint64_t) randombytes_random();
  w = (r1 << 32) + r2;

void print_flag() {
  FILE *f = fopen("flag.txt", "r");
  char flag[100];
  fgets(flag, sizeof(flag), f);
  printf("%s\n", flag);
  fflush( stdout );

const char *messages[NUM_CORRECT] =
{ "\nHmm... lucky guess...\n",
  "\nWow, that was coincidental!\n",
  "\nWhat? How did you guess that?\n",
  "\nThat's right, but you won't be able to guess right again!\n> ",
  "\nStrangely, that's correct...\n"

int main() {
  setvbuf(stdout, NULL, _IONBF, 0);
  printf("\nWelcome to Dr. J's Random Number Generator v3! We have received reports of "
  "a vulnerability involving repetition of output. This vulnerability has since been patched, "
  "and Dr. J's RNG is now 100%% secure. \n"
  "[r] Print a new random number \n"
  "[g] Guess the next ten random numbers and receive the flag! \n"
  "[q] Quit \n\n");
  char line[100];
  while (true) {
    printf("> ");
    fgets (line, sizeof(line), stdin);
    line[strcspn(line, "\n")] = 0;

    if (!strcmp("r", line)) {
      uint64_t r = nextRand();
      printf("%lu\n", r);
    if (!strcmp("g", line)) {
      printf("\nGuess the next ten random numbers for a flag! "
      "The chance of guessing all ten numbers correctly is 1/(2*10^96). I hope you're lucky! "
      "\nEnter Guess 1:\n> ");

      for (int i = 0; i < NUM_CORRECT; i++) {
        uint64_t guess = 0;
        fgets (line, sizeof(line), stdin);
        sscanf(line, "%lu", &guess);
        if (guess == nextRand()) {
          int m = randombytes_uniform(5);
          printf("%s", messages[m]);
          if (i < NUM_CORRECT-1) {
            printf("Enter Guess %d:\n> ", i+2);
          else {
            printf("What sorcery is this? That's impossible! I guess you deserve this flag:\n");
        else {
          printf("That's incorrect. Get out of here!\n");
    if (!strcmp("q", line)) {
  return 0;
Continue Reading

overthewire - bandit

Posted on June 20, 2019* in ctf-writeups

Level 0

The password for the next level is stored in a file called readme located in the home directory. Use this password to log into bandit1 using SSH. Whenever you find a password for a level, use SSH (on port 2220) to log into that level and continue the game.

To begin, we need to ssh into the server.

ssh -p 2220

The password was given to be bandit0. After connecting to the server, we can list the files in the directory using ls.

Continue Reading


Read the flag from /home/orw/flag.

Only open read write syscall are allowed to use.

nc 10001


The binary simply reads in 200 bytes and then jumps to its address, after using prctl to prevent calling execve:

int main(void) {
  printf("Give my your shellcode:");
  (*(code *)shellcode)();
  return 0;

By using strace, we see that orw_seccomp calls prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, {len = 12, filter = 0x400000020}).
Based on this and the challenge description, it is clear that we cannot use a execve shell code like in the previous challenge.

To assemble shellcode, I used an online x86 assembler rather than setting up nasm. We know that the flag is located in /home/orw/flag. Our shellcode needs to accomplish the following:

Continue Reading

picoCTF 2018 - Lambdash

Posted on October 18, 2018* in ctf-writeups


C? Who uses that anymore. If we really want to be secure, we should all start learning lambda calculus.


An extremely large payload to the interpreter results in an node.js error message

PayloadTooLargeError: request entity too large
    at readStream (/problems/lambdash-3_0_867a993b23b277b2e144cc3e2d73f6e4/node_modules/raw-body/index.js:155:17)
    at getRawBody (/problems/lambdash-3_0_867a993b23b277b2e144cc3e2d73f6e4/node_modules/raw-body/index.js:108:12)
    at read (/problems/lambdash-3_0_867a993b23b277b2e144cc3e2d73f6e4/node_modules/body-parser/lib/read.js:77:3)
    at urlencodedParser (/problems/lambdash-3_0_867a993b23b277b2e144cc3e2d73f6e4/node_modules/body-parser/lib/types/urlencoded.js:116:5)
    at Layer.handle [as handle_request] (/problems/lambdash-3_0_867a993b23b277b2e144cc3e2d73f6e4/node_modules/express/lib/router/layer.js:95:5)
    at trim_prefix (/problems/lambdash-3_0_867a993b23b277b2e144cc3e2d73f6e4/node_modules/express/lib/router/index.js:317:13)
    at /problems/lambdash-3_0_867a993b23b277b2e144cc3e2d73f6e4/node_modules/express/lib/router/index.js:284:7
    at Function.process_params (/problems/lambdash-3_0_867a993b23b277b2e144cc3e2d73f6e4/node_modules/express/lib/router/index.js:335:12)
    at next (/problems/lambdash-3_0_867a993b23b277b2e144cc3e2d73f6e4/node_modules/express/lib/router/index.js:275:10)
    at expressInit (/problems/lambdash-3_0_867a993b23b277b2e144cc3e2d73f6e4/node_modules/express/lib/middleware/init.js:40:5)
Continue Reading

picoCTF 2018 - circuit123

Posted on October 17, 2018* in ctf-writeups


Can you crack the key to decrypt map2 for us? The key to map1 is 11443513758266689915.




Given the problem and the hint, it is clear that we can use z3 to solve this problem. We can create a z3 BitVec and pass it into the verify function to avoid writing a custom decrypter. Because we don't know the length of the bit vector, I used a conservative estimate of 128.

Continue Reading


As the song draws closer to the end, another executable be-quick-or-be-dead-3 suddenly pops up. This one requires even faster machines. Can you run it fast enough too?


After decompiling the program with Snowman, we can see pseudocode for the calc function:

uint32_t calc(uint32_t edi) {
    uint32_t eax2;
    uint32_t eax3;
    uint32_t eax4;
    uint32_t eax5;
    uint32_t eax6;
    uint32_t v7;

    if (edi > 4) {
        eax2 = calc(edi - 1);
        eax3 = calc(edi - 2);
        eax4 = calc(edi - 3);
        eax5 = calc(edi - 4);
        eax6 = calc(edi - 5);
        v7 = eax6 * 0x1234 + (eax2 - eax3 + (eax4 - eax5));
    } else {
        v7 = edi * edi + 0x2345;
    return v7;
Continue Reading


Dressing up dogs are kinda the new thing, see if you can get this lovely girl ready for her costume party. Dog Or Frog


When we visit the page, we are greeted with a submission form. The instructions tell us that this is a machine learning problem and to not approach it as a web-exploitation problem. The goal is to submit an image that is similar (on a byte-by-byte basis) that is similar to the dog, but is recognized as a tree frog.

Continue Reading

picoCTF 2018 - eleCTRic

Posted on October 13, 2018* in ctf-writeups


"You came across a custom server that Dr Xernon's company eleCTRic Ltd uses. It seems to be storing some encrypted files. Connect with nc 15037. Can you get us the flag?" Source


The title makes a clear reference to AES-CTR. We can see that this mode of AES requires a unique nonce for each encrypted value to remain cryptographically secure. If we look at the problem's source code, we can see that the counter remains constant for all values. This breaks the encryption used and we are able to reveal the keystream and encrypt arbritary data.

class AESCipher(object):
    def __init__(self): = 32
        random =
        self.key =
        self.ctr =

    def encrypt(self, raw):
        cipher =, AES.MODE_CTR, counter=lambda: self.ctr)
        return cipher.encrypt(raw).encode('base64').replace('\n', '')
Continue Reading

picoCTF 2018 - Flaskcards and Freedom

Posted on October 12, 2018* in ctf-writeups


"There seem to be a few more files stored on the flash card server but we can't login. Can you?"


The site appears to be the same as the previous flaskcard challenges Flaskcards Skeleton Key and Flaskcards.

When we create an account, we are presented with the following screen:

We can try server side injection. If we type in {{1+1}}, the webpage displays 2. Any values in double brackets is being executed on the server. After trying to find hidden variables, such as config, flag, etc; I decided to look for a remote code execution vulnerability.

Continue Reading