第二届帕鲁杯 CTF 部分WP

文档说明

本文作者:SwBack
创作时间:‎2025年‎5‎月20日,09:42:51
知乎:https://www.zhihu.com/people/back-88-87
优快云:https://blog.youkuaiyun.com/qq_30817059
百度搜索: SwBack

签到题

无需多说

web1-CatBank

palu{a25a28964181450cab538d428a3a0c7e}

注册两个账户,一个admin用户一个test用户,使用test用户给admin用户转账,发现网站没有限制金额任意转即可,在cookie中发现用户的id值,admin最先注册的是9那么test就是10,根据转账id值只要转给admin用户1000000然后admin用户把多余的钱转给其他人,就获得了flag值

762e64ebce341b5ec2fbd27a2636146.png

web2-猫猫的秘密

/login没什么用,审计前端代码发现新接口/get_secret

image.png

结合/login接口和token信息,构造JWT字段验证

image.png

验证返回public,但是前端有三个权限

image.png

猜测存在权限验证字段,bp爆破一下(省略),最终得到role,直接构造admin跑一下

image.png

image.png

c081033173eb9aa688ac7a94d012f22.png

web3-CatNet

palu{ef42848e4eeb4ca4a4af93bff1e2a57c}

主页为截图功能,没啥用,打了半天ssrf…

image.png

爆破目录发现有admin页面,截图功能正常访问,直接访问被拒,构造xxf绕过得到flag链接

image.png

image.png

根据内容访问需要auth字段,构造默认的提示code错误,写个脚本对验证字段000进行FUZZ

image.png

(环境重开的,链接懒得改了)

image.png

WEB4-ezblog

比赛结束容器打不开。。。

题目描述:/app

下载附件app.jar,小茶杯反编译看源码,在app.class中发现路径别名映射

image.png

在dashboard.class中查看映射的接口,发现backdoor,但是需要key

image.png

继续跟进文件组件审计,FileStaticRepository组件中的find方法没有对路径进行过滤,该方法可以正常解析…/造成目录穿越和遍历的漏洞下载任意文件

image.pngbp构造数据包爆破一下上级发现app.jar包

payload:/assets/…/app.jar

分析下载的app.jar,继续用小茶杯打开查看,发现backdoor的key,构造POST请求得到flag

curl http://ip/backdoor --data ‘key=NlRVANA@007’

image.png

Crypto-循环锁链

palu{iC7uDoJJMAWnIhkkCNiIoCZZVmiPrk9}

先把那串十六进制字符串变成字节序列,然后根据已知明文的第一个字符是 ‘p’(也就是 p[0] = ord(‘p’)),用公式 p[i+1] = p[i] ⊕ c[i] 一次推算出后面的每一个明文字节,把所有字节拼到一起再解码,

得到答案:palu{iC7uDoJJMAWnIhkkCNiIoCZZVmiPrk9}

脚本:

hex_str = "110d190e122a7442312b2500070c163927210300280d2720262c19000c3b0439221952440d"
c = bytes.fromhex(hex_str)
n = len(c)
p = bytearray(n)
p[0] = ord('p')                # 已知起点

# 利用 p[i+1] = p[i] ⊕ c[i] 递推恢复
for i in range(n-1):
    p[i+1] = p[i] ^ c[i]

flag = p.decode()
print(flag)

image.png

Crypto-轮回密码

palu{reincarnation_cipher}

在提供的encode.py脚本中,观察到整个加密流程共分为四个关键步骤:

位移加密

Base85编码

再次位移

异或加密

image.png

从密文入手,对其进行与密钥的异或运算,撤销最后一步加密操作;

然后将结果按相同位移步长向左恢复原状,撤销第二次的位移操作;

接着使用Base85解码得到第一次位移前的中间数据;最后再次进行左轮转操作,还原出原始明文。

最终解出flag:palu{reincarnation_cipher}。

参考脚本:

import base64

def rotate_right(byte, step):
    return ((byte >> step) | ((byte << (8 - step)) & 0xFF)) & 0xFF

def rotate_left(byte, step):
    return ((byte << step) | (byte >> (8 - step))) & 0xFF

def samsara_decrypt(cipher, key_word):
    cycle_step = len(key_word) % 6 + 1
    phase3 = bytes([cipher[i] ^ key_word[i % len(key_word)] for i in range(len(cipher))])
    phase2 = bytes([rotate_left(c, cycle_step) for c in phase3])
    phase1 = base64.b85decode(phase2)
    plain = bytes([rotate_left(c, cycle_step) for c in phase1])
    return plain

# 用bytes字节写密文
cipher_bytes = b"y\xa6\x81_\x9b6\x19>X\xacy\x96!,!n\xa1mS\x1fa\xdc\xf1\xfc\xeb\x15\x18\x979\xbc\x116\x99\r"
key = b"Bore"

flag = samsara_decrypt(cipher_bytes, key)
print(flag.decode())

image.png

MISC-时间循环的信使

palu{Time_1s_cycl1c@l_0x}

时间戳排序-》保留|右侧字符重复数据-》取右侧1位组成hex解码

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
import re
import binascii

def extract_hex_from_logs(input_file='log.txt'):
    # 读取日志文件
    with open(input_file, 'r') as f:
        lines = f.readlines()
    
    # 过滤和排序
    pattern = re.compile(r'^([0-9a-fA-F])\1{7}$')
    parsed_lines = []
    
    for line in lines:
        line = line.strip()
        if not line:
            continue
        
        parts = line.split('|', 1)
        if len(parts) == 2:
            try:
                timestamp = int(parts[0])
                data = parts[1]
                if len(data) == 8 and pattern.match(data):
                    parsed_lines.append((timestamp, data))
            except ValueError:
                continue
    
    # 按时间戳排序
    sorted_lines = sorted(parsed_lines, key=lambda x: x[0])
    
    # 提取每行右侧最后一个字符并拼接
    hex_result = ''.join(data[-1] for _, data in sorted_lines)
    
    # 输出hex结果
    print(f"提取的hex: {hex_result}")
    
    # 解码hex为ASCII/字符串
    try:
        # 确保hex字符串长度是偶数
        if len(hex_result) % 2 != 0:
            hex_result += "0"  # 补0使长度为偶数
        
        decoded = binascii.unhexlify(hex_result).decode('utf-8', errors='replace')
        print(f"解码结果: {decoded}")
        
        # 保存解码结果到文件
        with open("flag.txt", 'w') as f:
            f.write(decoded)
    except Exception as e:
        print(f"解码失败: {e}")
        # 保存原始hex到文件
        with open("flag.txt", 'w') as f:
            f.write(hex_result)

if __name__ == "__main__":
    input_file = sys.argv[1] if len(sys.argv) > 1 else 'log.txt'
    extract_hex_from_logs(input_file) 

MISC-几何闪烁的秘密

palu{master_of_geometry}

先找个gif分割的网站,把gif图片分割成100张图片,

image.png

然后从第一张开始分别记录四列数据值

圆:cGFsXttcFsdXtcGFdXtt

方形:YXN0XJfYN0ZXfYXNZXJf

三角:b2Zf2VvbZfZ2vb2ZZ2Vv

五边:bWV0nI9bV0cn9bWVcnI9

cGFsXttcFsdXtcGFdXtt

YXN0XJfYN0ZXfYXNZXJf

b2Zf2VvbZfZ2vb2ZZ2Vv

bWV0nI9bV0cn9bWVcnI9

四列数据都只由典型的 Base64 字符(正斜杠、加号、大小写字母、数字和等号)组成,直觉上可以尝试将它们按顺序直接拼接成一个完整的 Base64 串;拼接后得到一个长度为20×4=80字符的字符串,再对其进行 Base64 解码时会出现部分二进制数据或不可见字符,但仔细留意可见 ASCII 区域可以提取到诸如 “pal” 、 “u{” 、 “mast” 、 “er\” 、 “of\” 、 “geometry” 和 “r}” 等连续可读片段;也就是说,拼接后完整的 Base64 串解码后呈现的文本大致是 “pal<u{mast er\of\ geomet rr=}” 这种格式,

image.png

我们去掉中间那些二进制、占位符或不可见字符并将可见字符连续起来,注意到“master_of_ geometrr” 显然不是一个合理的英文短语,但它看上去很接近 “master_of_geometry” 也就是“几何大师”。,把 “geo” + “metr” 再加上一个“y”可以得到“geometry”。经过把噪声字节忽略、挑出一连串可读的小写字母之后,剩下的 “geometrr” 其实只差一个 “y” 才能成为常见的英文单词“geometry”。字面意义上的 “y” 对应位置其实被噪声字节占掉了,但是因为“geometrr”本身并不是有效单词,合情合理地把它修正成“geometry”。最终就能拼出一句完整的 palu{master_of_geometry}

MISC-时间折叠(TimeFold Paradox)

palu{This_is_A_Sample_Flag_Change_Me!!}

整个解密流程可以拆成三步,按顺序:先提取十六进制低字节用正则表达式 在日志字符串里搜索,每次匹配都把包含在括号里的两位十六进制字符提取出来,结果生成一个列表 hex_lows。然后做异或运算把列表里每个十六进制字符串转换成整数,与固定的异或密钥 0x8E(十进制 142)做异或运算(^0x8E),运算结果就是一个新的整数值,对应一个 ASCII 码。最后把解密后的字符拼成字符串,对每个异或运算得到的整数,用 chr 转成字符,然后用 ‘’.join 把这些字符按顺序连接,得到最终的解密结果 palu{This_is_A_Sample_Flag_Change_Me!!}

脚本:

import re

# Sample log data
log_data = """
[1970-01-01 08:00:00] System boot sequence initiated
[1970-01-01 08:00:00] SYSTEM ALERT: Time anomaly detected at 00000000fe ns
[1970-01-01 08:00:00] SYSTEM ALERT: Time anomaly detected at 10000000ef ns
[1970-01-01 08:00:00] SYSTEM ALERT: Time anomaly detected at 20000000e2 ns
[1970-01-01 08:00:00] SYSTEM ALERT: Time anomaly detected at 30000000fb ns
[1970-01-01 08:00:00] SYSTEM ALERT: Time anomaly detected at 40000000f5 ns
[1970-01-01 08:00:00] SYSTEM ALERT: Time anomaly detected at 50000000da ns
[1970-01-01 08:00:00] SYSTEM ALERT: Time anomaly detected at 60000000e6 ns
[1970-01-01 08:00:00] SYSTEM ALERT: Time anomaly detected at 70000000e7 ns
[1970-01-01 08:00:00] SYSTEM ALERT: Time anomaly detected at 80000000fd ns
[1970-01-01 08:00:00] SYSTEM ALERT: Time anomaly detected at 90000000d1 ns
[1970-01-01 08:00:00] SYSTEM ALERT: Time anomaly detected at 100000000e7 ns
[1970-01-01 08:00:00] SYSTEM ALERT: Time anomaly detected at 110000000fd ns
[1970-01-01 08:00:00] SYSTEM ALERT: Time anomaly detected at 120000000d1 ns
[1970-01-01 08:00:00] SYSTEM ALERT: Time anomaly detected at 130000000cf ns
[1970-01-01 08:00:00] SYSTEM ALERT: Time anomaly detected at 140000000d1 ns
[1970-01-01 08:00:00] SYSTEM ALERT: Time anomaly detected at 150000000dd ns
[1970-01-01 08:00:00] SYSTEM ALERT: Time anomaly detected at 160000000ef ns
[1970-01-01 08:00:00] SYSTEM ALERT: Time anomaly detected at 170000000e3 ns
[1970-01-01 08:00:00] SYSTEM ALERT: Time anomaly detected at 180000000fe ns
[1970-01-01 08:00:00] SYSTEM ALERT: Time anomaly detected at 190000000e2 ns
[1970-01-01 08:00:00] SYSTEM ALERT: Time anomaly detected at 200000000eb ns
[1970-01-01 08:00:00] SYSTEM ALERT: Time anomaly detected at 210000000d1 ns
[1970-01-01 08:00:00] SYSTEM ALERT: Time anomaly detected at 220000000c8 ns
[1970-01-01 08:00:00] SYSTEM ALERT: Time anomaly detected at 230000000e2 ns
[1970-01-01 08:00:00] SYSTEM ALERT: Time anomaly detected at 240000000ef ns
[1970-01-01 08:00:00] SYSTEM ALERT: Time anomaly detected at 250000000e9 ns
[1970-01-01 08:00:00] SYSTEM ALERT: Time anomaly detected at 260000000d1 ns
[1970-01-01 08:00:00] SYSTEM ALERT: Time anomaly detected at 270000000cd ns
[1970-01-01 08:00:00] SYSTEM ALERT: Time anomaly detected at 280000000e6 ns
[1970-01-01 08:00:00] SYSTEM ALERT: Time anomaly detected at 290000000ef ns
[1970-01-01 08:00:00] SYSTEM ALERT: Time anomaly detected at 300000000e0 ns
[1970-01-01 08:00:00] SYSTEM ALERT: Time anomaly detected at 310000000e9 ns
[1970-01-01 08:00:00] SYSTEM ALERT: Time anomaly detected at 320000000eb ns
[1970-01-01 08:00:00] SYSTEM ALERT: Time anomaly detected at 330000000d1 ns
[1970-01-01 08:00:00] SYSTEM ALERT: Time anomaly detected at 340000000c3 ns
[1970-01-01 08:00:00] SYSTEM ALERT: Time anomaly detected at 350000000eb ns
[1970-01-01 08:00:00] SYSTEM ALERT: Time anomaly detected at 360000000af ns
[1970-01-01 08:00:00] SYSTEM ALERT: Time anomaly detected at 370000000af ns
[1970-01-01 08:00:00] SYSTEM ALERT: Time anomaly detected at 380000000f3 ns
[1970-01-01 08:00:00] System entering chronostasis mode
"""

# 正则提取每行最后两位十六进制数
hex_lows = re.findall(r'([0-9A-Fa-f]{2}) ns', log_data)

# 将每个字节与异或密钥 0x8E 解密
decoded_chars = [chr(int(h, 16) ^ 0x8E) for h in hex_lows]

# 拼接得到 flag
flag = ''.join(decoded_chars)
print(flag)

a44d5603d3255b6c63589b145960511.png

时空交织的密语

我们在暗网服务器中发现了一个神秘文件,据说是某个黑客组织的「时空密钥」,文件内容似乎由大量时间戳构成。情报显示,只有将时间维度与二进制低语结合才能解开秘密。线索隐藏在时空的起点与终点之间。

16进制打开.找到70616c75 palu的16进制 规律.从而拿到flag… 纯手敲

image.png

MISC-screenshot

palu{why_you_spy_me}

苹果手机打开照片用黑白滤镜拉到最右侧微调,然后调高手机亮度仔细看palu{why_you_spy_me}

d48c150ba02abc9c28fcf62845f0ecf.jpeg

Crypto-欧几里得

palu{48b635a7a2474ef743e333478b67a2f5}

思路:

已知一个超级大的数 c 等于两个秘密数 m1 和 m2 相加,找到那个我们想要的 m1(也就是 flag)。

先猜出 m2 是什么样的:猜它是由一对字节 b0、b1 反复拼成 70 字节,枚举所有 256×256 种可能;

对每一种可能的字节模式,把它当成一个大整数 m2,再用 c–m2 算出 m1;

然后把这个 m1 转成字节串,看它去掉前导零后是不是以 “palu{” 开头、以 “}” 结尾。

如果符合,就把它打印出来当做 flag,然后结束整个循环。

通过这种暴力枚举加格式校验的方法,就能从 c 中最后获得值:palu{48b635a7a2474ef743e333478b67a2f5}。

脚本:

import itertools

# 已知的解密结果 c(m1 + m2)
c = 1426774899479339414711783875769670405758108494041927642533743607154735397076811133205075799614352194241060726689487117802867974494099614371033282640015883625484033889861

BYTE_LEN_C = (c.bit_length() + 7) // 8

for b0, b1 in itertools.product(range(256), repeat=2):
    # 构造 70 字节的 m2
    pattern = bytes([b0, b1]) * 35
    m2 = int.from_bytes(pattern, 'big')
    
    if c <= m2:
        continue
    m1 = c - m2
    try:
        m1_bytes = m1.to_bytes(BYTE_LEN_C, 'big').lstrip(b'\x00')
        if m1_bytes.startswith(b'palu{') and m1_bytes.endswith(b'}'):
            print("flag:", m1_bytes.decode())
            break
    except OverflowError:
        continue

2cb8fa4a648b226431ab6fa786271cb.png

RE-PositionalXOR

palu{48b635a7a2474ef743e333478b67a2f5}

思路:

先把加密后的内容 `encrypted = b"qcoqVh{ebccocH^@Lgt{gt|g"` 存到一个字节串里,

然后用 enumerate 遍历每个字节和它的下标 `i`,对每个字节 `b` 做异或运算 `b ^ (i+1)`(下标从 0 开始,所以加 1),这样就把对应位置的加密给去掉了,得到一个新的字节列表,

再把它们拼成一个字节串 bytes([…]),最后 .decode()成普通字符串并打印出来,

这个打印结果就是解密后的 palu{48b635a7a2474ef743e333478b67a2f5}

脚本:

encrypted = b"qcoq~Vh{e~bccocH^@Lgt{gt|g"
flag = bytes([b ^ (i+1) for i, b in enumerate(encrypted)]).decode()
print(flag)

image.png

Crypto-RSA_Quartic_Quandary

palu{This_is_a_fake_flag_change_it_for_real_use}

image.png

Crypto-易如反掌

连分数攻击

取 E0​,N0​→ 计算 E0​/(N02​) 的连分数展开 → 检查其各项逼近分数 k/d→ 找到分母 d 是800位素数 → MD5(d) 得到flag。

import gmpy2
import hashlib

N_list = [
    23796646026878116589547283793150995927866567938335548416869023482791889761195291718895745055959853934513618760888513821480917766191633897946306199721200583177442944168533218236080466338723721813833112934172813408785753690869328477108925253250272864647989241887047368829689684698870160049332949549671046125158024445929082758264311584669347802324514633164611600348485747482925940752960745308927584754759033237553398957651216385369140164712159020014009858771182426893515016507774993840721603911101735647966838456333878426803669855790758035721418868768618171692143354466457771363078719423863861881209003100274869680348729,
    19552522218179875003847447592795537408210008360038264050591506858077823059915495579150792312404199675077331435544143983146080988327453540449160493126531689234464110427289951139790715136775261122038034076109559997394039408007831367922647325571759843192843854522333120187643778356206039403073606561618190519937691323868253954852564110558105862497499849080112804340364976236598384571278659796189204447521325485338769935361453819608921520780103184296098278610439625935404967972315908808657494638735904210709873823527111315139018387713381604550946445856087746716671838144925662314348628830687634437271225081272705532826343,
    20588310030910623387356293638800302031856407530120841616298227518984893505166480372963166394317326422544430837759332223527939420321960057410073228508230111170414845403161052128790464277007579491219950440477721075788978767309211469555824310913593208232853272958011299985202799390532181335087622499894389777412111445377637396650710486263652440053717323053536700098339137819966260269752816515681602936416736576044630343136577023173210517247609888936337876211461528203642347119434700140264859102502126842250671976238033270367185358966766106988830596616311824691409766437473419074865115209866730272194297815209976737570183,
    18468380817178794606027384089796802449939260582378979728469492439450780893746976934315768186829245395964644992296264093276556001477514083927556578752836255491334765496791841945178275793885002188397918857222419803612711637177559554489679414049308077300718317502586411333302434329130562745942681716547306138457088216901181646333860559988117376012816579422902808478175975263110581667936249474308868051767856694498210084853797453949193117835061402537058150493808371384063278793041752943930928932275052745657700368980150842377283198946138726219378646040515809994704174471793592322237777371900834531014326150160506449286179
]
E_list = [
    229904181453273080302209653709086531153804577507365859149808244958841045687064628362978517491609413507875726243121473678430010600891588643092042173698830147997497783886459583186019270582236955524620567373560535686287255124958954671737097645556109314142383275516997850786599322033792080045303427363366927030304214333894247469120513426641296678531965795930756543043851154646310114366477311633838078242963665452936523438928643273392454483600446242320078010627755587492056369779661382734170244060951095344418599686788550312205964136120979823565225768814898285224838691541122088693411388097496320157113230752327025862802020421665288007529320920942060329299409362236414929126050037144149017275031336018100081931062647888329912802477032857776085190828105602067426203163344931483638271679183910241511044338001446584634203146294743522375846913845041274967653508735863706778364499099286484552570083394223973734909997825522191349543295855925973354640349809770822075226834555111927586299176453943116511915434890643239957459427390624136283086434711471863737451011157026905191204496081860277138227247744470804087252965368757930797560277881668806206419629425126031049566579233056222579590529869798537893505779097868221221068867624660759084762471141,
    374749619911728044650812367560174497001343067563440477135516664935394734686391543012901514676044211541958613458868769659861216149364768233000844624035620893309356372294598009760824255187442531508754966566917198975934706398309982525100772311586501118200858124845012643495006029930202324305874402291277845166060497038915773767003006049720519011634861166208163030159519901867416488082395270295488885724507937683469910251316231210838654273986152493722244271430422693265608430755620420680629979226285393465423870727975987787149515374769359243334743541460110042872587610309611770320600248289328406805995688596910226273861759369388105641549933915686192055533242723330981192183310876306968103333706140401422550917946410378174896274789619184565321544130428008804628699594759946577979319393247067750024729672029363433673084437510430506410293512293930056667971242862448029841846596288648691077795207341975907335202945548990662460491169957175452745622341245617265849042542964819126377775749222973138584978725470886059043251544634105653274564085280013340679259157119014619894553239015777411757887293044706448625760604242512494466386343040583010961386979963779928616733980046763291988848903515836247301007113187121999960487508948748354549628160741,
    111738429639840672983162926852338651562094139707285850255632987705635459657893186493838711733560515475806567653354737245246745810892238414756414117557971683747269900627524702653772058841085258035513296218047505149691384287812041721130367506731427022265277885965948486359682023555050085264531256406043361391744086539522028829421284667293339869140564699750714145488199268791908205712660933607330454849730499840287271163350865799682565216636393526339218836244889719975150503253630419647851422620890082315396457329065508602521784001607236788620811397449483104884860551374031790663030220424841642241965983726516537123807061999084476076850833658360594525986997125319941689903869138176347916707622148840226672408554102717625456819726220575710494929111642866840516339713870850732638906870325693572445316904688582043485093120585767903009745325497085286577015692005747499504730575062998090846463157669448943725039951120963375521054164657547731579771203443617489609201617736584055562887243883898406182052632245189418568410854530995044542628531851356363297989653392057214167031332353949367816700838296651167799441279086074308299608106786918676697564002641234952760724731325383088682051108589283162705846714876543662335188222683115878319143239781,
    185935167438248768027713217055147583431480103445262049361952417166499278728434926508937684304985810617277398880507451351333771783039360671467147075085417403764439214700549777320094501151755362122677245586884124615115132430034242191429064710012407308619977881929109092467325180864745257810774684549914888829203014922855369708286801194645263982661023515570231007900615244109762444081806466412714045462184361892356485713147687194230341085490571821445962465385514845915484336766973332384198790601633964078447446832581798146300515184339036127604597014458389481920870330726947546808739829589808006774479656385317205167932706748974482578749055876192429032258189528408353619365693624106394913101463023497175917598944803733849984703912670992613579847331081015979121834040110652608301633876167262248103040352053621027994984419469689886224948280910784030347396491408399653891297071583411037119697061333228629642728635603657687121010776933023901744994067564045429384172315640135483480089769992730928266885675143187679290648773060781987273082229827156531141515679114580622348238382074084270808291251400949744720804368426414308355267344210055608246286737478682527960260877955900464059404976906697164610891962198768354924180929300959036213841843941
]

def get_convergents_pq(x, y):
    P_m2, P_m1 = 0, 1
    Q_m2, Q_m1 = 1, 0
    temp_x, temp_y = x, y
    while temp_y != 0:
        a = temp_x // temp_y
        P = a * P_m1 + P_m2
        Q = a * Q_m1 + Q_m2
        yield (P, Q)
        P_m2, P_m1 = P_m1, P
        Q_m2, Q_m1 = Q_m1, Q
        temp_x, temp_y = temp_y, temp_x % temp_y

N0 = N_list[0]
E0 = E_list[0]
M0 = N0 * N0
found_d = None

print(f"Calculating convergents for E0 / (N0^2)")
for k_candidate, d_candidate in get_convergents_pq(E0, M0):
    if d_candidate == 0:
        continue
    if d_candidate.bit_length() > 800:
        break
    if d_candidate.bit_length() == 800:
        if gmpy2.is_prime(d_candidate):
            print(f"Prime candidate d found: {d_candidate}")
            found_d = d_candidate
            break

if found_d:
    print(f"\nSuccessfully found d: {found_d}")
    d_str = str(found_d)
    md5_hash = hashlib.md5(d_str.encode('utf-8')).hexdigest()
    flag = "palu{" + md5_hash + "}"
    print(f"The flag is: {flag}")
else:
    print("\nCould not find d matching the criteria.")

RE-PaluFlat

拖IDA分析,查看main函数整体流程

image.png

跟进550函数看处理过程,编写脚本逆推

image.png

已知目标输出字节 (v5[i]) -> 按位取反 -> 结果加上85 -> 结果左旋4位 -> 结果XOR对应位置密钥 -> 得到原始输入字节。

循环对v5数组的19个字节执行此逆向操作,解题脚本如下

import ctypes

target_bytes = [
    84, -124, 84, 68, -92, -78, -124, 84, 98, 50, -113, 84, 98, -78, 84, 3, 20, 0x80, 67
]

target_unsigned_bytes = [b & 0xFF for b in target_bytes]

key_flat = b"flat"
key_palu = b"palu"

decrypted_input = []

def rotate_left_4(byte):
    byte &= 0xFF
    return ((byte << 4) | (byte >> 4)) & 0xFF

def reverse_byte_ops(target_byte_unsigned, key_byte_unsigned):
    intermediate3 = (~target_byte_unsigned) & 0xFF
    intermediate2 = (intermediate3 + 85) & 0xFF
    intermediate1 = rotate_left_4(intermediate2)
    original_byte = (intermediate1 ^ key_byte_unsigned) & 0xFF
    return original_byte

for i in range(len(target_unsigned_bytes)):
    target_byte = target_unsigned_bytes[i]
    current_key = key_flat if (i & 1) != 0 else key_palu
    key_byte = current_key[i % 4]
    original_byte = reverse_byte_ops(target_byte, key_byte)
    decrypted_input.append(chr(original_byte))

flag = "".join(decrypted_input)
print("Calculated input (flag):")
print(flag)

MISC-帕鲁迷宫

运行game把迷宫全貌跑出来,拖到本地,ai辅助写脚本根据提示跑出最优路径,路径太多了。。。一个一个试

import numpy as np
from queue import Queue
import hashlib
from itertools import product

# 迷宫数据
maze_raw = """
############################## #
#Y#                   #       X
# # ############# ### # ### ## #
# #   #         # #   #   #   ##
# ##### ####### ### # ### ### ##
#   #   #     # #   #   # #   ##
### # ### ##### # ####### # ####
# #   # #     # #     #   #   ##
# ##### # ### # # ### # ##### ##
#         #   # #   #       # ##
# ######### # # ############# ##
#   # #     # #   #           ##
### # # ######### # ######### ##
# # # #           # #       # ##
# # # ############# # ##### # ##
#   #       #       # #   #   ##
 X ## ### ### # ##### # ########
#   #   #     #   # # #   #   ##
### ### ######### # # # # # # ##
# # #   #   #   # # # # # # # ##
# # ### # # # # # # # ### # # ##
# #   # # #   #   #   #   # # ##
# ### ### ########### # # # # ##
#   #   #           # # # # # ##
# ##### # ######### # # ### # ##
#   #   #   #       # # #   # ##
### # ####### ####### # # ### ##
#   #   #   # #     # #   #   ##
# ##### # # # # ##### ##### # ##
#         #   #             #  #
#X ############ X ########### X#
################################
"""

# 处理迷宫数据
maze_lines = maze_raw.strip().split('\n')
maze_width = max(len(line) for line in maze_lines)
maze = np.array([list(line.ljust(maze_width, '#')) for line in maze_lines])

# 寻找起点(Y)和所有终点(X)
def find_special_points(maze):
    start_point = None
    exit_points = []
    
    for i in range(maze.shape[0]):
        for j in range(maze.shape[1]):
            if maze[i, j] == 'Y':
                start_point = (i, j)
            elif maze[i, j] == 'X':
                exit_points.append((i, j))
    
    return start_point, exit_points

# BFS查找两点间的所有最短路径
def find_all_shortest_paths(maze, start, end):
    rows, cols = maze.shape
    directions = [
        (-1, 0, 'w'),  # 上
        (1, 0, 's'),   # 下
        (0, -1, 'a'),  # 左
        (0, 1, 'd')    # 右
    ]
    
    # 初始化
    distances = {}
    distances[start] = 0
    parents = {}
    parents[start] = []
    
    queue = Queue()
    queue.put((start, 0, ""))  # 位置, 距离, 路径
    
    # BFS寻找最短路径
    while not queue.empty():
        (x, y), dist, path = queue.get()
        
        # 如果找到终点,不需要继续处理
        if (x, y) == end and path != "":
            continue
        
        # 探索四个方向
        for dx, dy, move in directions:
            nx, ny = x + dx, y + dy
            new_pos = (nx, ny)
            
            if (0 <= nx < rows and 0 <= ny < cols and 
                maze[nx, ny] != '#'):
                
                new_dist = dist + 1
                
                # 如果是第一次访问或找到相同距离的路径
                if new_pos not in distances or new_dist == distances[new_pos]:
                    if new_pos not in distances:
                        distances[new_pos] = new_dist
                        parents[new_pos] = []
                        queue.put((new_pos, new_dist, path + move))
                    
                    # 添加父节点和移动方向
                    parents[new_pos].append(((x, y), move))
    
    # 回溯所有可能的最短路径
    if end not in distances:
        return [], float('inf')
    
    def backtrack(node):
        if node == start:
            return [""]
        
        paths = []
        for parent, move in parents[node]:
            for p in backtrack(parent):
                paths.append(p + move)
        
        return paths
    
    all_paths = backtrack(end)
    return all_paths, distances[end]

# 使用动态规划解决TSP问题
def solve_tsp(start, exits, maze):
    n = len(exits) + 1
    points = [start] + exits
    
    # 计算所有点之间的距离矩阵
    dist_matrix = np.zeros((n, n), dtype=int)
    paths_matrix = {}
    
    for i in range(n):
        for j in range(n):
            if i != j:
                paths, dist = find_all_shortest_paths(maze, points[i], points[j])
                dist_matrix[i, j] = dist
                paths_matrix[(i, j)] = paths
    
    # 使用动态规划解决TSP
    memo = {}
    
    def dp(current, mask):
        if mask == (1 << (n - 1)) - 1:  # 已访问所有城市
            return 0, [[]]
        
        if (current, mask) in memo:
            return memo[(current, mask)]
        
        min_dist = float('inf')
        best_paths = []
        
        for next_city in range(1, n):
            if mask & (1 << (next_city - 1)):  # 如果已经访问过
                continue
            
            next_mask = mask | (1 << (next_city - 1))
            sub_dist, sub_paths = dp(next_city, next_mask)
            
            candidate_dist = dist_matrix[current, next_city] + sub_dist
            
            if candidate_dist < min_dist:
                min_dist = candidate_dist
                best_paths = []
                for path in sub_paths:
                    best_paths.append([next_city] + path)
            elif candidate_dist == min_dist:
                for path in sub_paths:
                    best_paths.append([next_city] + path)
        
        memo[(current, mask)] = (min_dist, best_paths)
        return min_dist, best_paths
    
    total_dist, optimal_routes = dp(0, 0)
    
    # 为每条路径生成所有可能的移动序列
    all_movement_sequences = []
    
    for route in optimal_routes:
        movement_sequences = [[]]
        
        # 从起点开始,添加每一段路径的移动
        current = 0  # 起点索引
        
        for next_point in route:
            paths = paths_matrix[(current, next_point)]
            
            # 更新当前移动序列,添加这段路径的所有可能移动
            new_sequences = []
            for sequence in movement_sequences:
                for path in paths:
                    new_sequences.append(sequence + [path])
            
            movement_sequences = new_sequences
            current = next_point
        
        # 将每条路径展平为单个字符串
        for sequence in movement_sequences:
            full_path = ''.join(sequence)
            all_movement_sequences.append(full_path)
    
    return total_dist, all_movement_sequences

def main():
    # 解析迷宫
    start_point, exit_points = find_special_points(maze)
    
    # 解决旅行商问题
    total_steps, all_paths = solve_tsp(start_point, exit_points, maze)
    print(f"最短总步数: {total_steps}")
    
    # 筛选出最后四步为'ssds'的路径
    valid_paths = [path for path in all_paths if path.endswith('ssds')]

    for i in range(len(valid_paths)):
        print(valid_paths[i])
        flag = hashlib.md5(valid_paths[i].encode('utf-8')).hexdigest()
        print(f"Flag: palu{{{flag}}}")
if __name__ == "__main__":
    main() 

image.png

image.png

RE-CatchPalu

ida加载发现大量无意义字节序列,花指令特征

image.png

把0xE9替换成0x90,nop打包去壳分析

image.png

程序获取用户输入和定义的密码进行匹配输出,

仔细发现原本输出的MessageBoxA 已被劫持,实际上会执行 sub_401360 函数,所以即便是输入正确密码也看不到flag

image.png

image.png

跟进sub_401360函数分析,401100是RC4自定义算法,密钥为forpalu

image.png

sub_401270为生成流密码的伪随机生成算法 (PRGA) 部分,实现了一个基于 RC4 变种的流密码

image.png

通过ai辅助分析写出解密脚本

def sub_401100_ksa_modified(S, key):
    """
    Custom KSA based on sub_401100.
    Initializes and permutes the S-box.
    """
    key_len = len(key)
    T = [key[i % key_len] for i in range(256)]

    j_ksa = 0
    # Outer loop runs 3 times
    for _ in range(3):
        # Inner loop
        for k in range(256):
            # Note: (unsigned __int8) cast is important for calculations
            # j = (T[k] + j + S[k]) % 233
            j_ksa = (T[k] + j_ksa + S[k]) % 233 # *** Modulo 233 is the key difference ***

            # Swap S[k] and S[j]
            S[k], S[j_ksa] = S[j_ksa], S[k]

def sub_401270_prga_decrypt(S, encrypted_data):
    """
    RC4 PRGA based on sub_401270.
    Generates keystream and decrypts data.
    """
    i = 0
    j = 0
    decrypted_data = []

    for byte in encrypted_data:
        i = (i + 1) % 256
        j = (j + S[i]) % 256

        # Swap S[i] and S[j]
        S[i], S[j] = S[j], S[i]

        # Keystream byte generation: K = S[(S[i] + S[j]) % 256]
        keystream_byte = S[(S[i] + S[j]) % 256]

        # Decrypt: PlaintextByte = EncryptedByte XOR KeystreamByte
        decrypted_byte = byte ^ keystream_byte
        decrypted_data.append(decrypted_byte)

    return bytes(decrypted_data) # Return as bytes

# --- Main decryption process ---

# The key (bytes are important for XOR operations)
key = b"forpalu"

# The encrypted data from v7 (convert signed values to unsigned bytes)
encrypted_bytes_signed = [
    13, -80, -65, 10, -115, 47, 2, 56, 111, 25, -82, -103, 25, -57, 110, -9, 79, -53, -112, 78, 85, -114, -47, 16, -64
]
encrypted_data = bytes([(b + 256) % 256 for b in encrypted_bytes_signed]) # Convert signed to unsigned bytes

# Initialize S-box
S = list(range(256))

# Perform the modified KSA
sub_401100_ksa_modified(S, key)

# Perform the PRGA/Decryption
decrypted_result = sub_401270_prga_decrypt(S, encrypted_data)

# Print the decrypted result (try decoding as ASCII)
print("Decrypted Data:", decrypted_result)
try:
    print("Decrypted String:", decrypted_result.decode('ascii'))
except UnicodeDecodeError:
    print("Could not decode as ASCII.")

MISC-dorodoro

四个输入框,根据提示名字为哦润吉,测试数量时发现第一个输入框四个连续的哦润吉显示正确

image.png

Doro中的字母D在字母表中排第四个,对应数字4,根据第一个判断猜测和字母位置有关,测试字母o(15个)

image.png

没问题,继续得到最终flag

image.png

image.png

MISC-TopSecret

palu{You_re_a_real_50w}

824f2b54f1ad89dcc2abd17e28701ef.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SwBack

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值