BUUCTF NewStarCTF新知识记录

7 分钟

前言

作为一名网络安全的小白,CTF的菜鸡,BUUCTF最近举办了NewStarCTF活动,注释说比较适合入门,自己也做了下,同时也是尝试做MISC的题目,记录下接触到的新的知识。

一、eazyxor

知识:os.urandom(size)

size: 它是字符串随机字节的大小

返回值: 此方法返回一个字符串,该字符串表示适合加密用途的随机字节。

如:



import os
key=os.urandom(1)
print(key)
print(key[0])


输出:
b'\xdd'
221


from os import urandom
from secret import flag
key = urandom(1)

def xor(plaintext, key):
    ret = []
    for i in range(len(plaintext)):
        ret.append(plaintext[i] ^ key[0])
    return bytes(ret)

ciphertext = xor(flag, key)

print(ciphertext.hex())

output:9b919c9a8685cd8fa294c8a28c88cc89cea2ce9c878480

这是一道异或加密题,一个数进行两次异或,结果等于本神,所以只需要对异或得到的结果再异或。因为这里加密的key[0]是随机的,所以需要对key[0]进行爆破,找出flag值。

解密如下:



from os import urandom
import binascii

key = urandom(1)


def xor(plaintext, key):
    ret = []
    for i in range(len(plaintext)):
        ret.append(plaintext[i] ^ key)
    return bytes(ret)


global i
try:
    for i in range(150, 255):
        s = binascii.a2b_hex('9b919c9a8685cd8fa294c8a28c88cc89cea2ce9c878480')
        ciphertext = xor(s, i)
        print(ciphertext.decode(), i)
except UnicodeDecodeError:
    print('错误', i)

二、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}')
    # phi和n有公因数p
    # demod(phi)=1
    # hint=d^emodn
    # n=p*p*q
    # (de)^emod(n)=(kphi+1)^emodn
    # hint*e^emodn=(kphi+1)^emodn
    # p是n和\(hint * e ^ e - 1\)的最大公约数
    # hint*e^e-1=kphi       phi=p*(p-1)*(q-1)

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])

这里将一个flag分成了5块,分别使用了RSA进行加密,所以要分五块进行分别解密

level1:



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}')


c = 22160015525054597533062795679117215923801827397299805735087138192137742945881204146337349060934854888054628153923021387981306839951210090523829296521835965212118849043671673133979884712755090374758002677916820953359774554825569218497687506468472278309097929775388010403607769802840990547048001743970754496905
p = 6962443023774446497102092246794613339314677593117417573764609329949026862782472380488956732038459060928443992561763464365758383525259954798321350043810351
q = 9631855759661411029901156175243744760977799976661519182223576693685069000499866459636568713055906075171480855575061732016121299027658733834671035383233163
质数p和q都给了,直接解密即可

level2:



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}')

c = 17250922799297131008803303235771955129
n = 134097988095851988085603926250918812377

p和q比较小,只有64位,可以尝试下factor质数分解直接爆破n。

[factordb.com![icon-
default.png?t=M85B](https://csdnimg.cn/release/blog_editor_html/release2.2.0/ckeditor/plugins/CsdnLink/icons/icon-
default.png?t=M85B)http://factordb.com/index.php?](http://factordb.com/index.php?
"factordb.com")

爆出p为10094271714305059493,q为13284562957208247589,可以直接解密了

level3:



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}')
e只有3,明显可以使用低指数加密攻击,因为e很小的话,明文m的e次方=密文c+kn,可以直接通过对c+kn开e次方根,返回能开e次方根的结果。

脚本如下:



import gmpy2
import libnum

c = 2776571135646565181849912433877522437622755332262910824866791711
n = 85793694792655420934945863688968944466300304898903354212780512650924132933351787673979641944071634528676901506049360194331553838080226562532784448832916022442020751986591703547743056267118831445759258041047213294368605599719242059474324548598203039032847591828382166845797857139844445858881218318006747115157


def de(c, e, n):
    k = 0
    while True:
        mm = c + n * k
        result, flag = gmpy2.iroot(mm, e)
        if flag:
            return result
        k += 1


e = 3

m = de(c, e, n)
print(m)
print(libnum.n2s(int(m)).decode())

level4:



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}')


output:
c = 68588738085497640698861260094482876262596289469248772328560280530093163764972313090939471997156632421517452790632223565521726590730640805290182026911025142051864898712501214753986865172996090706657535814234291235489829621372021092488300236623525366939477695283380634188510950335639019458758643273802572617191
e = 51999725233581619348238930320668315462087635295211755849675812266270026439521805156908952855288255992098479180003264827305694330542325533165867427898010879823017054891520626992724274019277478717788189662456052796449734904215067032681345261878977193341769514961038309763898052908572726913209883965288047452751
n = 68816697240190744603903822351423855593899797203703723038363240057913366227564780805815565183450516726498872118491739132110437976570592602837245705802946829337567674506561850972973663435358068441037127926802688722648016352967768929007662772115485020718202683004813042834036078650571763978066558718285783045969
看e的结果,e一般为65535这些,这里的e很大,可以使用渐进式分数的方法把d爆出来。

脚本:



import gmpy2


def transform(x, y):  # 使用辗转相处将分数 x/y 转为连分数的形式
    res = []
    while y:
        res.append(x // y)
        x, y = y, x % y
    return res


def continued_fraction(sub_res):
    numerator, denominator = 1, 0
    for i in sub_res[::-1]:  # 从sublist的后面往前循环
        denominator, numerator = numerator, i * numerator + denominator
    return denominator, numerator  # 得到渐进分数的分母和分子,并返回


# 求解每个渐进分数
def sub_fraction(x, y):
    res = transform(x, y)
    res = list(map(continued_fraction, (res[0:i] for i in range(1, len(res)))))  # 将连分数的结果逐一截取以求渐进分数
    return res


def get_pq(a, b, c):  # 由p+q和pq的值通过维达定理来求解p和q
    par = gmpy2.isqrt(b * b - 4 * a * c)  # 由上述可得,开根号一定是整数,因为有解
    x1, x2 = (-b + par) // (2 * a), (-b - par) // (2 * a)
    return x1, x2


def wienerAttack(e, n):
    for (d, k) in sub_fraction(e, n):  # 用一个for循环来注意试探e/n的连续函数的渐进分数,直到找到一个满足条件的渐进分数
        if k == 0:  # 可能会出现连分数的第一个为0的情况,排除
            continue
        if (e * d - 1) % k != 0:  # ed=1 (mod φ(n)) 因此如果找到了d的话,(ed-1)会整除φ(n),也就是存在k使得(e*d-1)//k=φ(n)
            continue

        phi = (e * d - 1) // k  # 这个结果就是 φ(n)
        px, qy = get_pq(1, n - phi + 1, n)
        if px * qy == n:
            p, q = abs(int(px)), abs(int(qy))  # 可能会得到两个负数,负负得正未尝不会出现
            d = gmpy2.invert(e, (p - 1) * (q - 1))  # 求ed=1 (mod  φ(n))的结果,也就是e关于 φ(n)的乘法逆元d
            return d
    print("该方法不适用")


e = 51999725233581619348238930320668315462087635295211755849675812266270026439521805156908952855288255992098479180003264827305694330542325533165867427898010879823017054891520626992724274019277478717788189662456052796449734904215067032681345261878977193341769514961038309763898052908572726913209883965288047452751
n = 68816697240190744603903822351423855593899797203703723038363240057913366227564780805815565183450516726498872118491739132110437976570592602837245705802946829337567674506561850972973663435358068441037127926802688722648016352967768929007662772115485020718202683004813042834036078650571763978066558718285783045969
d = wienerAttack(e, n)
print("d=", d)

得到d= 12966126097163765179,便可以直接解密出明文

level5:



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}')
    # phi和n有公因数p
    # demod(phi)=1
    # hint=d^emodn
    # n=p*p*q
    # hint*e^e-1=kphi       phi=p*(p-1)*(q-1)
    # (de)^emod(n)=(kphi+1)^emodn
    # hint*e^emodn=(kphi+1)^emodn
    # p是n和(hint * e ^ e - 1)的最大公约数
   
把n做了变式,这里的n=ppq,然后给了一个hint,肯定是要利用hint将p或q求出来,通过简单的式子推导,猜测p是n和(hint*e^e-1)的最大公约数,为了印证自己的请求,自己造一个明文然后进行相同的加密看看是不是如此。


from Crypto.Util.number import getPrime, inverse, bytes_to_long, long_to_bytes
from gmpy2 import gmpy2

message=b'flag{RSA_is_Good_to_Study}'
m = bytes_to_long(message)
p = getPrime(512)
q = getPrime(512)
n = p * p * q
e = 65537
d = inverse(e, p * (p - 1) * (q - 1))
c = pow(m, e, n)
hint = pow(d, e, n)
k = gmpy2.gcd(hint*pow(e,e)-1, n)
print(k==p)



output:True
果真如此

解密:



from Crypto.Util.number import inverse, long_to_bytes

c = 17250922799297131008803303235771955129
n = 134097988095851988085603926250918812377
p = 10094271714305059493
q = 13284562957208247589
e = 65537
d = inverse(e, (p - 1) * (q - 1))
m = long_to_bytes(pow(c, d, n))
print(m)

三、Yesec no drumsticks

知识: LSB隐写(最低有效位隐写)

LSB隐写一般发生在PNG图片中,因为PNG图片是无损压缩,在压缩过程中修改的信息不会被损坏。一般PNG图像像数由RGB红绿蓝三色组成,每个颜色占用8位,取值是0x00-0xFF,一共有256的三次方种颜色,人类的眼睛有好多种颜色是无法区分的。而LSB隐写就是将RGB中的最低二进制位修改,人的眼睛却无法发现这种颜色变化,因此可以修改携带信息。

直接解密脚本:



try:
    from PIL import Image
    from PIL import ImageFile
except:
    import os
    os.system('pip install Pillow')
    from PIL import Image
    from PIL import ImageFile

ImageFile.LOAD_TRUNCATED_IMAGES=True

def full_eight(str):
    return str.zfill(8)
def get_text_bin(strr):
    string=""
    s_text=strr.encode()
    for i in range(len(s_text)):
        string=string+full_eight(bin(s_text[i]).replace('0b',''))
    return string
def mod(x,y):
    return x%y
def tell_you_bad(str1,str2,str3):
    im=Image.open(str1)
    width=im.size[0]
    height=im.size[1]
    count=0
    key=get_text_bin(str2)
    keylen=len(key)
    for h in range(0,height):
        for w in range(0,width):
            pixel=im.getpixel((w,h))
            a=pixel[0]
            b=pixel[1]
            c=pixel[2]
            if count==keylen:
                break
            a=a-mod(a,2)+int(key[count])
            count+=1
            if count==keylen:
                im.putpixel((w,h),(a,b,c))
                break
            b=b-mod(b,2)+int(key[count])
            count+=1
            if count==keylen:
                im.putpixel((w,h),(a,b,c))
                break
            c=c-mod(c,2)+int(key[count])
            count+=1
            if count==keylen:
                im.putpixel((w,h),(a,b,c))
                break
            if count%3==0:
                im.putpixel((w,h),(a,b,c))
    im.save(str3)
def tell_you_fun(le,str1):
    a=""
    b=""
    im = Image.open(str1)
    lenth = le*8
    width = im.size[0]
    height = im.size[1]
    count = 0
    for h in range(0, height):
        for w in range(0, width):
            pixel = im.getpixel((w, h))
            if count%3==0:
                count+=1
                b=b+str((mod(int(pixel[0]),2)))
                if count ==lenth:
                    break
            if count%3==1:
                count+=1
                b=b+str((mod(int(pixel[1]),2)))
                if count ==lenth:
                    break
            if count%3==2:
                count+=1
                b=b+str((mod(int(pixel[2]),2)))
                if count ==lenth:
                    break
        if count == lenth:
            break
    st=""
    for i in range(0,len(b),8):
        stra = int(b[i:i+8],2)
        st+=chr(stra)
    return st
def main():

    print("加密(1) OR 解密(2):",end=' ')
    choice=int(input())
    if choice==1:
        try:
            print("[+]加密图片:",end=' ')
            old=input()
            print("[+]加密文字(以#号结束):",end=' ')
            kkk=input()
            print("[+]加密图片保存重命名:",end=' ')
            new=input()
            tell_you_bad(old,kkk,new)
            print("[+]Fun Picture done!")
        except:
            print("[-]未找到此图片,请检查图片名和路径")

    if choice==2:
        le = 30
        try:
            print("[+]解密图片:",end=' ')
            new = input()
            word=tell_you_fun(le,new).split('#')
            print('[+]Picture Tell You: ',word[0])
        except:
            print("[-]未找到此图片,请检查图片名和路径")
if __name__=="__main__":
    main()

四、EzSnake

前面下载附件,发现是个jar贪吃蛇小游戏,丢入winhex看看。

50 4B 03 04的文件头格式,是zip压缩包,改成压缩包就看到了源代码,因为要分数大于114才能有flag,所以直接看分数大于114的那一段代码。


if (this.score >= 114) {
                    this.isStart = false;
                    final File tf = new File("./" + Data.o0o0o0o00 + Data.oo000o0o0o + Data.o0o0o0o00o0o + Data.o0o0o00oo0o0);
                    InputStream is = null;
                    FileOutputStream os = null;

                    try {
                        is = GamePanel.class.getResourceAsStream("/statics/1919810/114514");
                        os = new FileOutputStream(tf);
                        if (!tf.exists()) {
                            tf.createNewFile();
                        }

                        byte[] b = new byte[1024];
                        byte[] tmp = new byte[1024];

                        for(int data = is.read(b); data != -1; data = is.read(b)) {
                            for(int i = 0; i < data; ++i) {
                                tmp[i] = (byte)(b[i] ^ 88);
                            }

                            os.write(tmp, 0, data);
                        }

                        JFrame jf = new JFrame("114514");
                        jf.setSize(600, 600);
                        jf.setResizable(false);
                        jf.setLocationRelativeTo((Component)null);
                        JPanel jp = new JPanel() {
                            protected void paintComponent(Graphics g) {
                                try {
                                    Image bg = ImageIO.read(tf);
                                    g.drawImage(bg, 0, 0, this.getWidth(), this.getHeight(), (ImageObserver)null);
                                } catch (IOException var6) {
                                    var6.printStackTrace();
                                } finally {
                                    tf.delete();
                                }

                            }
                        };
                        jp.setVisible(true);
                        jf.add(jp);
                        jf.setVisible(true);
                    } catch (Exception var22) {
                        var22.printStackTrace();
                    } finally {
                        if (is != null) {
                            try {
                                is.close();
                            } catch (IOException var21) {
                                var21.printStackTrace();
                            }
                        }

                        if (os != null) {
                            try {
                                os.close();
                            } catch (IOException var20) {
                                var20.printStackTrace();
                            }
                        }

                    }
                }

从源代码可以看出,当分数大于114时,创建了一个新的File文件,并且从static里面的静态文件中读取了文件流,解题的关键就在于读取文件流时,对字节进行了异或88加密,可以尝试对静态文件进行异或88解密,得到二维码图片,增加定位码,即可得到flag。

[CyberChefThe Cyber Swiss Army Knife - a web app for encryption, encoding,
compression and data
analysishttps://gchq.github.io/CyberChef/](https://gchq.github.io/CyberChef/
"CyberChef")



for(int data = is.read(b); data != -1; data = is.read(b)) {
                            for(int i = 0; i < data; ++i) {
                                tmp[i] = (byte)(b[i] ^ 88);
                            }

五、Pyre

之前在广东攻防大赛也有过这样的题,这次又忘了,记录下。

下载py脚本:

[Download PyInstaller Extractor from SourceForge.netExtract contents of a
Windows executable file created by
pyinstallerhttps://sourceforge.net/projects/pyinstallerextractor/files/dist/pyinstxtractor.py/download?use_mirror=udomain](https://sourceforge.net/projects/pyinstallerextractor/files/dist/pyinstxtractor.py/download?use_mirror=udomain
"Download PyInstaller Extractor from
SourceForge.net")1、首先使用下载的脚本会生成一共extracted文件夹,文件夹中有pyre和struct文件,因为pyinstaller将py文件生成exe时,会将文件头去掉,因此需要复原文件头。

从E3开始,抽取struct文件第一行到E3的十六进制数值,插入到pyre的E3前面,结果如下

随后将得到的pyre添加后缀pyc,使用python的uncompyle6将pyc文件反编译,输出py文件。


flag = ''
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]

def enc(input):
    tmp = ''
    for i in range(len(input)):
        tmp += input[table[i]]

    return tmp


if __name__ == '__main__':
    print('Please input your flag:')
    flag = input()
    if len(flag) != 23:
        print('Length Wrong!!')
    else:
        final = enc(flag)
        if final == encode:
            print('Wow,you get the right flag!!')
        else:
            print('Sorry,Your input is Wrong')

对以上进行解密即可,只是将字母调换了顺序



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]

# encode = enc(flag)
d = {}

result=''
def enc(input):
    tmp = ''
    for i in range(len(input)):
        d[table[i]] = encode[i]

enc(encode)
for index in sorted(d):
    result+=d[index]
print(result)



总结

这是第一次尝试接触图片隐写等MISC,发现还挺有趣的,以后可以多尝试学习下。

~  ~  The   End  ~  ~


 赏 
承蒙厚爱,倍感珍贵,我会继续努力哒!
logo图像
tips
文章二维码 分类标签:CTFCTF
文章标题:BUUCTF NewStarCTF新知识记录
文章链接:https://aiwin.fun/index.php/archives/700/
最后编辑:2024 年 1 月 5 日 21:09 By Aiwin
许可协议: 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)
(*) 8 + 7 =
快来做第一个评论的人吧~