[TOC]

2023 TUCTF & NBCTF

TUCTF-Crypto

网址:https://ctfd.tuctf.com/

Custom ECB Cipher | SOLVED

题面:

I have designed a simple algorithm which I believe it’s secure. I am confident you cannot compromise my message with the x value.

题目:

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
from Crypto.Util import number

flag = b"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

def convert(msg):
msg = msg ^ msg >> x
msg = msg ^ msg << 13 & 275128763
msg = msg ^ msg << 20 & 2186268085
msg = msg ^ msg >> 14
return msg

def transform(message):
assert len(message) % 4 == 0
new_message = b''
for i in range(int(len(message) / 4)):
block = message[i * 4 : i * 4 +4]
block = number.bytes_to_long(block)
block = convert(block)
block = number.long_to_bytes(block, 4)
new_message += block
return new_message

c = transform(flag[6:-1]).hex()
print('c =', c)
'''
c = e34a707c5c1970cc6375181577612a4ed07a2c3e3f441d6af808a8acd4310b89bd7e2bb9
'''

考点:ECB 异或移位算法的逆向

解题:

该题的加密逻辑比较清晰,主要通过transform函数进行块加密,每4个字节的数据进行一次加密,进入到convert函数,我们观察convert函数,可以发现,这里的异或和移位操作全部都是可逆的,所以写出一个逆向解密函数

其中bits代表比特位数,4个字节为一组,一个字节占8bit,所以bits设置为32

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
from Crypto.Util import number

# flag = b"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

# right shift inverse
def inverse_right(res, shift, bits=32):
tmp = res
for i in range(bits // shift):
tmp = res ^ tmp >> shift
return tmp

# right shift with mask inverse
def inverse_right_mask(res, shift, mask, bits=32):
tmp = res
for i in range(bits // shift):
tmp = res ^ tmp >> shift & mask
return tmp

# left shift inverse
def inverse_left(res, shift, bits=32):
tmp = res
for i in range(bits // shift):
print("test:",i)
tmp = res ^ tmp << shift
return tmp

# left shift with mask inverse
def inverse_left_mask(res, shift, mask, bits=32):
tmp = res
for i in range(bits // shift):
# print("test:",i)
tmp = res ^ tmp << shift & mask
return tmp
def convert(msg,x):
msg = msg ^ msg >> x
msg = msg ^ msg << 13 & 275128763
msg = msg ^ msg << 20 & 2186268085
msg = msg ^ msg >> 14
return msg

def inv_convert(msg,x):
msg = inverse_right(msg,14)
msg = inverse_left_mask(msg,20,2186268085)
msg = inverse_left_mask(msg,13,275128763)
msg = inverse_right(msg,x)
return msg

def inv_transform(message,x):
old_message = b''
for i in range(int(len(message) / 4)):
# print(i)
block = message[i * 4 : i * 4 +4] #先取出4个字节
block = number.bytes_to_long(block)
# print(block.bit_length())
block = inv_convert(block,x)
block = number.long_to_bytes(block, 4)
old_message += block
return old_message

c = 'e34a707c5c1970cc6375181577612a4ed07a2c3e3f441d6af808a8acd4310b89bd7e2bb9'

a = bytes.fromhex(c)
print(len(a))
for x in range(1,20):
print(inv_transform(a,x))

image-20231205150011387

flag:TUCTF{sRta^s90s-#VgsnzPsta-TjLx7s8Txgs-*Ko}

Keyboard Cipher | SOVLED

题面:

I designed an algorithm to encrypt a message using my keyboard. No one can decrypt it without any information about my algorithm. Note: Wrap the flag in TUCTF{}.

题目:

虽然提供了一个enc后缀的文件 但是直接改成txt后缀即可成功打开

1
0x636a56355279424b615464354946686b566942794e586c4849455279523359674d47394d49486845643045675a316b315569426163304d675a316c715469426163314567616b6c7354534268563252594947745063434178643045675332395149466c6e536d343d

考点:基础编码转换 键盘密码

解题:

两步转换

image-20231205155406745

得到:

r5yG Ji7y XdV r5yG DrGv 0oL xDwA gY5R ZsC gYjN ZsQ jIlM aWdX kOp 1wA KoP YgJn

类似围棋 四个键围在中间的就是flag的值

Table Encryption | SOLVED

题面:

You can’t crack my file! I am the exclusive owner of the encryption key!

题目:

题目给出了一个文件,其文件名为:table_encryption.xml.enc

考点: 文件解密

解题:

其后缀为enc 表明文件进行了加密

首先分析一下这个enc的加密模式,他是采用了某种加密方式对于文件里面的内容使用一个密钥进行加密,所以我们推测是采用了异或的加密模式,现在可以直接使用python中对于文件进行二进制流进行读取,相当于读取的内容是密文,然后因为文件后缀是xml文件 所以该文件开头部分我们是已知的,相当于明文已知,所以可以对密钥进行恢复

用010打开文件 获得前段部分的密文

image-20231205204044679

数量可以先多一点 然后得到密钥的值后进行微调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
f = open('table_encryption.xml.enc','rb').read()  #以ascii码读取

#因为是xml后缀 也就证明 我们是知道这个加密文件中的部分明文 根据部分已知明文恢复密钥
pp = '<?xml version="1.0" encoding="UTF-8"?>'
print(len(pp))#38
pm = '79 52 17 07 05 00 3B 0A 00 1A 07 08 4E 6E 56 50'.split(' ')
print(len(pm))#32 前面的位数不是必须要确定的 而是因为我们推测这个加密的体系 是将文件的内容与给出的密码进行异或

key = ""
for i in range(len(pm)):
key += chr(ord(pp[i])^int(pm[i],16))
print(key)
#到此我们拿到key = Emoji Moring Sta
#所以将目前加密的文件全部逐个十六进制与Emoji Moring Sta 进行异或 即可实现解密

x = open('flag.xml','w')

for i in range(len(f)):
x.write(chr(f[i]^ord(key[i%len(key)])))

拿到解密后的文件:

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
<?xml version="1.0" encoding="UTF-8"?>

<root>

<table name="users">

<column name="id" type="int" />

<column name="name" type="varchar" />

<column name="email" type="varchar" />

<column name="password" type="varchar" />

</table>

<table name="posts">

<column name="id" type="int" />

<column name="user_id" type="int" />

<column name="title" type="varchar" />

<column name="content" type="varchar" />

</table>

<table name="comments">

<column name="id" type="int" />

<column name="post_id" type="int" />

<column name="user_id" type="int" />

<column name="content" type="varchar" />

</table>

<users>

<user>

<id>1</id>

<name>John Doe</name>

<email>john@tuctf.com</email>

<password>TUCTF{x0r_t4bl3s_R_fun!!!11!}</password>

</user>

</users>

</root>

Bludgeon the Booty | SOLVED

题面:

You have found me treasure chest, but can you crack its code?

1
2
3
4
5
  ___________
/ \
/__|0|0|0|0|__\
| @ |
|_____________|

“This here lock be cursed by the shaman of the swamp to change keys for each attempt” nc chal.tuctf.com 30002

考点:连接服务器暴力破解

解题:

3位密码锁 从000到999全部尝试一遍暴力破解

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
import socket  
import time

def main():
# 创建socket对象
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 连接到服务器 想当于使用了nc指令
server_address = ('chal.tuctf.com', 30002) # 服务器IP地址和端口号
client_socket.connect(server_address)
for i in range(10):
# 发送选择旋转锁的命令
client_socket.sendall(b'1\n')
# time.sleep(2) # 等待服务器响应
response = client_socket.recv(1024) # 接收的最大字节数
print("服务器响应: ", response.decode())

# 发送选择旋转轮子的命令
client_socket.sendall(b'1\n')
# time.sleep(2) # 等待服务器响应
response = client_socket.recv(1024) # 接收的最大字节数
if "The chest is still locked!" not in response.decode():
print("服务器响应: ", response.decode())


# 发送选择旋转方向的命令
client_socket.sendall(b'+\n') # 假设选择顺时针方向
# time.sleep(2) # 等待服务器响应
if "The chest is still locked!" not in response.decode():
print("服务器响应: ", response.decode())
for i in range(10):
# 发送选择旋转锁的命令
client_socket.sendall(b'1\n')
# time.sleep(2) # 等待服务器响应
response = client_socket.recv(1024) # 接收的最大字节数
print("服务器响应: ", response.decode())

# 发送选择旋转轮子的命令
client_socket.sendall(b'2\n')
# time.sleep(2) # 等待服务器响应
response = client_socket.recv(1024) # 接收的最大字节数
print("服务器响应: ", response.decode())
# 发送选择旋转方向的命令
client_socket.sendall(b'+\n') # 假设选择顺时针方向
# time.sleep(2) # 等待服务器响应
response = client_socket.recv(1024) # 接收的最大字节数
print("服务器响应: ", response.decode())
for i in range(10):
# 发送选择旋转锁的命令
client_socket.sendall(b'1\n')
# time.sleep(2) # 等待服务器响应
response = client_socket.recv(1024) # 接收的最大字节数
print("服务器响应: ", response.decode())

# 发送选择旋转轮子的命令
client_socket.sendall(b'3\n')
# time.sleep(2) # 等待服务器响应
response = client_socket.recv(1024) # 接收的最大字节数
print("服务器响应: ", response.decode())
# 发送选择旋转方向的命令
client_socket.sendall(b'+\n') # 假设选择顺时针方向
# time.sleep(2) # 等待服务器响应
if "The chest is still locked!" not in response.decode():
print("服务器响应: ", response.decode())
# 关闭连接
client_socket.close()

if __name__ == '__main__':
main()

该脚本从000进行遍历,可能耗时会长一点,但是只要抛出结果,脚本就会自动结束

NBCTF

网址:https://nbctf.com/challs

crypto/Rivest Shamir forgot Adleman | SOLVED

题面:

Exponents took too long. I decided to use an alternative. It won’t about the same right? https://en.wikipedia.org/wiki/RSA*(cryptosystem)*

考点:异或

题目:

chall.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from Crypto.Util.number import *

p = getPrime(1024)
q = getPrime(1024)

n = p*q
e = 123589168751396275896312856328164328381265978316578963271231567137825613822284638216416
m = bytes_to_long(b"nbctf{[REDACTED]}")

ct = (m^e) % n

print("n = ", n)
print("e = ", e)
print("ct = ", ct)

out.txt:

1
2
3
n =  13431294979312769345517878088407659222785929563176888493659632690735510803353339825485161776891929296355466082338185199541755546384591261208929371208286410559187299345800125302598147388467283782373829399059130130575707550536466670447022349923395822077916588516101640779751198703879200863153859677174339078186779847910915309774616338231020176817201080756103027200290903849975398368943087868140010448011002364291104062990443568049879169811274854879262048473842331319786127894828031613201122015559660817797429013884663990368453887433480357502012963127000535358820517096295714967262963843868885674823702064175405493435873
e = 123589168751396275896312856328164328381265978316578963271231567137825613822284638216416
ct = 159269674251793083518243077048685663852794473778188330996147339166703385101217832722333

解题:

在模n域中进行异或

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from Crypto.Util.number import *

# p = getPrime(1024)
# q = getPrime(1024)

# n = p*q
e = 123589168751396275896312856328164328381265978316578963271231567137825613822284638216416
# m = bytes_to_long(b"nbctf{[REDACTED]}")

# ct = (m^e) % n
n = 13431294979312769345517878088407659222785929563176888493659632690735510803353339825485161776891929296355466082338185199541755546384591261208929371208286410559187299345800125302598147388467283782373829399059130130575707550536466670447022349923395822077916588516101640779751198703879200863153859677174339078186779847910915309774616338231020176817201080756103027200290903849975398368943087868140010448011002364291104062990443568049879169811274854879262048473842331319786127894828031613201122015559660817797429013884663990368453887433480357502012963127000535358820517096295714967262963843868885674823702064175405493435873
e = 123589168751396275896312856328164328381265978316578963271231567137825613822284638216416
ct = 159269674251793083518243077048685663852794473778188330996147339166703385101217832722333
re = (ct^e) % n
print(long_to_bytes(re))

crypto/32+32=64 | SOLVED

题面:

64 is too much, but 32 isn’t. 32+32=64?

考点:base64

题目:

32_1.txt

32_2.txt

两个文件都达到了一万多行 就不贴了

解题:

这么多数据 就是因为不断使用了base64进行循环加密,所以不断进行base64解密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import base64

flag = open("32_1.txt").read()

while 1:
flag = base64.b64decode(flag)
print(flag)
#b'nbctf{h0pE_'

flag = open("32_2.txt").read()

while 1:
flag = base64.b64decode(flag)
print(flag)

#b'y0U_h4d_fUn}'

crypto/SBG-ABW’s Insanity | SOLVED

题面:

“Skill Issue” - AnonymousBlueWhale

题目:

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
from Crypto.Util.number import getPrime, bytes_to_long, isPrime, long_to_bytes
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
import hashlib

m = bytes_to_long(b'we give you this as a gift!')

p = getPrime(1096)
q1 = getPrime(1096)
q2 = getPrime(1096)
n1 = p*q1
n2 = p*q2

e = 11

ct1 = pow(m,e,n1)
ct2 = pow(m,e,n2)

key = hashlib.sha256(long_to_bytes(q1)).digest()
cipher = AES.new(key, AES.MODE_ECB)
enc_flag = cipher.encrypt(pad(b"nbctf{[REDACTED]}", 16))

print("ct1 =", ct1)
print("ct2 =", ct2)
print("enc_flag =", enc_flag.hex())

考点:gcd 整数分解 AES ECB

解题:

对题目进行分析,因为AES.ECB是直接根据密钥进行逆向解密的

所以我们的目的就是获得q1

所以观察q1的生成方式,看看在哪里出现了q1,准确定位到对应代码

1
2
3
4
5
6
7
n1 = p*q1
n2 = p*q2

e = 11

ct1 = pow(m,e,n1)
ct2 = pow(m,e,n2)

其中ct1和ct2、m、e均已知,下面进行数学推导

1
2
3
4
5
6
7
8
9
ct1 = m^e + k1 * n1 = m^e + k1 * p * q1
ct2 = m^e + k2 * n2 = m^e + k2 * p * q2
=>
k1 * p * q1 = ct1 - m^e
k2 * p * q2 = ct2 - m^e
=> 然后我们发现对上面式子中求出来的两个数求gcd公约数即可获得p
=> 然后有p了之后对1式两边同时除p
k1 * q1 = (ct1 - m^e) / p
=>最后一步是要获得q1 显然是有k1的干扰两个数相乘,所以借助一点工具 对这个数进行分解

法1:本地跑yafu

法2:在线网站分解https://www.alpertron.com.ar/ECM.HTM

exp:

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
from Crypto.Util.number import getPrime, bytes_to_long, isPrime, long_to_bytes
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
import hashlib
import gmpy2

m = bytes_to_long(b'we give you this as a gift!')

# p = getPrime(1096)
# q1 = getPrime(1096)
# q2 = getPrime(1096)
# n1 = p*q1
# n2 = p*q2

e = 11

# ct1 = pow(m,e,n1)
# ct2 = pow(m,e,n2)
#根据ct1和ct2 得到n1和n2

ct1 = 196150896308015382573408099004515975466540094705348761587854272630906023749083911008835478259767648401709605726136589590310666858430120235218762651641330953170392784645631801449432061363776229651965539255255795373230255852992805188205639228954217034390731460194284731845705855212209524525682241998203303747513174581513168217999505436596818091279091144718119512522929858750349220346765422769476003604849600128605208123474607256344535541843454810706150705449483256361736428064150792476736751093915251743882647862500622465233906844054109281842278362125589335774364236155483783338907105809549449368926475631824722919958889450225026843225780470131268709445293157749
ct2 = 83507921327913521142443934492559420944369023782917085618978768157512494136296269338031471193927727958060037960270530278173852027186606624474398269053920321522448607751539858355179998108075848593814112217098612017462222420001262248144471923306139580601814218471659969728514600258330312623506466116333593434744460773476488134792248490905628242447603788884700795677619881924772996081377617066055448888668800826281711315468059146518373888252421991592124071284411947405472003802863596010724784730366575751160333120162778945930063499020829492960600318519615351417595308518636794008603089224459556289944808655338805251676963828327517925911000528943113536807796285824

tmp = pow(m,e)
flag1 = tmp - ct1 #= k1 * n1 = k1 * p * q1
flag2 = tmp - ct2 #= k2 * n2 = k2 * p * q2
p = gmpy2.gcd(flag1,flag2)
print(gmpy2.gcd(p,flag2))
print(p)
print(flag1 // p)
k1q1 = flag1 // p
#得到k1和q1分解
q1 = 603701201822386830907144477326706640694145605732107023753674808182665696931502012989218558077472289899849882120737934821898165435847435044518846871242860227586749788240998624721376490806164324545522115137075097300642534248374378375756928831273442124872283671893345317220496457140852434166575343690062190540448032738970711476061243
key = hashlib.sha256(long_to_bytes(q1)).digest()
cipher = AES.new(key, AES.MODE_ECB)
# enc_flag = cipher.encrypt(pad(b"nbctf{[REDACTED]}", 16))

enc_flag = 'ac2289b707b174c541cf0952bf3b2057561b0872451444a5bbecf18c007ea20fa2b7c8a1707a74a1657e5adb5c1a417f'
enc_flag = bytes.fromhex(enc_flag)
# print(cipher.decrypt(enc_flag))
# print(enc_flag)
if b"nbctf" in cipher.decrypt(enc_flag):
print(cipher.decrypt(enc_flag))

#b'nbctf{c0ngr4ts_0n_F1nish1n9_Th3_3_P4rt3r!!!!}\x03\x03\x03'

crypto/Too Little Information | SOLVED

题面:

My computer crashed generating my keys. :( Can you recover them for me?

题目:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from Crypto.Util.number import *

p = getPrime(512)
q = getPrime(512)

n = p*q
e = 65537

m = bytes_to_long(b"nbctf{[REDACTED]}")

ct = pow(m,e,n)

print(f"{ct = }")
print(f"{e = }")
print(f"{n = }")

hint = (p+q) >> 200 # I can't be giving you that much!
print(f"{hint = }")

ct = 20030315247290021934293927354887580426070566017560641204155610658927917290198737029903594702064351773446005018155094643288125396810753014936800515440652855824038470725838848349666236623899089094953181436465435270989651491997801177943499187812270081592263331832916362349716591828106306150603120693022149233534
e = 65537
n = 90166344558664675592644684556355545187373291859609367810958775310181360193141550862577281658089332577942193823477148064165061303827534169112815736618901965700400798345371758370344207077280925015891945591352156370597957742921722432314582261224366498475465730899163137511778647694175484386010210005826793007961
hint = 12227137598952006551839416663729660224872609953685427677011433223002140448682395830146750981200

考点:移位 CopperSmith

解题:

前置知识点:

image-20231206153106063

对移位这次一定要弄明白了:

1
hint >> 200   #右移相当于缩小 

对于十进制来说:

做除运算 等价于hint // 2**200

对于二进制来说:

移位前0b10111011100100110101010000111011001111110110110110000001101011110000000101100100000100111001110101000011001001110 11000100101100011111011100110110000110011011100100011111110111110000111110000010100001001111001100001100110000111011011011001011111111101000010111101000011010011110001011111100111000111010010001010000

移位后0b10111011100100110101010000111011001111110110110110000001101011110000000101100100000100111001110101000011001001110

相当于直接截取掉低200位二进制数据

1
hint << 200  #左移相当于扩大

对于十进制来说:

做乘运算 等价于hint * 2**200

对于二进制来说:

移位前

0b1011101110010011010101000011101100111111011011011000000110101111000000010110010000010011100111010100001100100111011000100101100011111011100110110000110011011100100011111110111110000111110000010100001001111001100001100110000111011011011001011111111101000010111101000011010011110001011111100111000111010010001010000

移位后

0b101110111001001101010100001110110011111101101101100000011010111100000001011001000001001110011101010000110010011101100010010110001111101110011011000011001101110010001111111011111000011111000001010000100111100110000110011000011101101101100101111111110100001011110100001101001111000101111110011100011101001000101000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 在低200位填充0 相当于把原来位置的权值抬高了

目前题目中给出的是

p+q >> 200

相当于p+q的低200位丢失了 需要恢复

在低位确实的运算中有这样一个特点:

n = p * q

n = p * (p + q - p)

得到关于p的一个方程

p*((p+q)-p) - n = 0

因为p+q是缺失低200位的

所以该方程解出的结果也是p也是部分数据 虽然结果也是512bit位的 但是存在部分数据丢失,具有相同的MBS(最高有效位)

1
2
3
4
5
p_ = var('p_')
#当前hint的二进制位数为313 缺少200位
approx_p_plus_q = hint << 200 #反向移位补全数据 512
approx_p = int((p_*(approx_p_plus_q - p_) - n).roots()[0][0])
print(approx_p) #因为hint进行了填充 所以此时的p也是512位 具有相同最高有效位313 只不过低200位的数据不准确

那么恢复丢失的数据 => CopperSmith

假设缺失的数据为x bit设置为200位

1
2
3
4
5
PR.<x> = PolynomialRing(Zmod(n))
f = x + p3 #p3因为低位截断了 所以一定是比真实值小的 补充一个x
x0 = f.small_roots(X=2^200, beta=0.4)[0] #求小根 其中第一个参数是2的kbits次方可以保证最后解得的小根是kbits位 就是缺失的位数
print(x0)
p = p3+x0

到此完美结束

调试exp如下:

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
#sage

#p3已知高位 这里的n就是N3
hint = 12227137598952006551839416663729660224872609953685427677011433223002140448682395830146750981200
n = 90166344558664675592644684556355545187373291859609367810958775310181360193141550862577281658089332577942193823477148064165061303827534169112815736618901965700400798345371758370344207077280925015891945591352156370597957742921722432314582261224366498475465730899163137511778647694175484386010210005826793007961

#本事
p_ = var('p_')
print(bin(hint))
print(len('''1011101110010011010101000011101100111111011011011000000110101111000000010110010000010011100111010100001100100111011000100101100011111011100110110000110011011100100011111110111110000111110000010100001001111001100001100110000111011011011001011111111101000010111101000011010011110001011111100111000111010010001010000'''))
approx_p_plus_q = hint << 200
approx_p = int((p_*(approx_p_plus_q - p_) - n).roots()[0][0])
print(approx_p)
#解出p的低位
p3=7304778595800693962623587470016492934656897172638927993210828812588740492194432727308053205092654458616385476570357533214681567145360876311971890757777044
#sage

#p3已知高位 这里的n就是N3
# p3 = 7117286695925472918001071846973900342640107770214858928188419765628151478620236042882657992902
# n = 113432930155033263769270712825121761080813952100666693606866355917116416984149165507231925180593860836255402950358327422447359200689537217528547623691586008952619063846801829802637448874451228957635707553980210685985215887107300416969549087293746310593988908287181025770739538992559714587375763131132963783147

#全位数
# pbits = 512

#缺省位数
# kbits = pbits - p3.nbits()#nbits()位数
# print (p3.nbits())
# p3 = p3 << kbits #反方向移动缺省的位数
print(bin(p3))
print(len('''10001011011110010000001110010110100000110110010101111000001110011010100001001001011001110100100111010000101100111000111000100111110011100011111110110111011000010110101111110101110111011100000001011010010111111010100010111001100101011110000110000100111100101100110010111101000001100010111001100010011001010101101011010010110100000100001010100100110000111011001101100111100111110110101011010111000010111111100000011010001000111001111010101101011100001010000001001010111100101001100001000100101101100011011010010100'''))
PR.<x> = PolynomialRing(Zmod(n))
f = x + p3 #p3因为低位截断了 所以一定是比真实值小的 补充一个x
x0 = f.small_roots(X=2^200, beta=0.4)[0] #求小根 其中第一个参数是2的kbits次方可以保证最后解得的小根是kbits位 就是缺失的位数
print(x0)
p = p3+x0
print ("p: ", hex(int(p)))
print(p)
#assert是断言语句 如果是true 则正常执行 如果是false 则程序运行中断
# assert n % p == 0
q = n // int(p)
print(q)
ct = 20030315247290021934293927354887580426070566017560641204155610658927917290198737029903594702064351773446005018155094643288125396810753014936800515440652855824038470725838848349666236623899089094953181436465435270989651491997801177943499187812270081592263331832916362349716591828106306150603120693022149233534
from Crypto.Util.number import *
import gmpy2
phi = (p-1)*(q-1)
e = 65537
d = gmpy2.invert(e,int(phi))
print(long_to_bytes(int(pow(ct,d,n))))

print("############test#####################")
print(bin(approx_p))
print(bin(p))

flag:b'nbctf{cr34t1v3_fl4gs_4r3_s0_h4rd_t0_m4k3...}'

web/Inspector Gadget

题面:

While snooping around this website, inspector gadet lost parts of his flag. Can you help him find it?

题目:

inspector-gadget.chal.nbctf.com

考点:信息收集

F12

image-20231206180159384

直接在控制台跑一下getflag()函数

image-20231206180248579

图片遮盖

image-20231206180335228


下面有很多点击功能点

挨个试一试

image-20231206180714726

进去之后

image-20231206180743641

title中发现flag

image-20231206180822341

信息泄露 robots.txt

image-20231206180907075

flag:nbctf{G00d_J06_D3tect1v3_G4dg3t352}

web/walter’s crystal shop

题面:

My buddy Walter is selling some crystals, check out his shop!

题目:

walters-crystal-shop.chal.nbctf.com

给出源码 直接打开app.js

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
const express = require("express");
const sqlite3 = require("sqlite3");
const fs = require("fs");

const app = express();
const db = new sqlite3.Database(":memory:");

const flag = fs.readFileSync("./flag.txt", { encoding: "utf8" }).trim();
const crystals = require("./crystals");

db.serialize(() => {
db.run("CREATE TABLE crystals (name TEXT, price REAL, quantity INTEGER)");

const stmt = db.prepare("INSERT INTO crystals (name, price, quantity) VALUES (?, ?, ?)");

for (const crystal of crystals) {
stmt.run(crystal["name"], crystal["price"], crystal["quantity"]);
}
stmt.finalize();

db.run("CREATE TABLE IF NOT EXISTS flag (flag TEXT)");
db.run(`INSERT INTO flag (flag) VALUES ('${flag}')`);
});

app.get("/crystals", (req, res) => {
const { name } = req.query;

if (!name) {
return res.status(400).send({ err: "Missing required fields" });
}

db.all(`SELECT * FROM crystals WHERE name LIKE '%${name}%'`, (err, rows) => {
if (err) {
console.error(err.message);
return res.status(500).send('Internal server error');
}
return res.send(rows);
});
});

app.get("/", (req, res) => {
res.sendfile(__dirname + "/index.html");
});

app.listen(3000, () => {
console.log("Server listening on port 3000");
});

考点:代码审计 SQL注入

解题:

通过代码审计 得到路由和参数

image-20231206182417672

路由:crystals

参数:name

image-20231206182456391

明确参数数量为3

image-20231206182519692

明确flag在数据库中的位置

image-20231206182542740

明确闭合方式 单引号闭合

image-20231206182623878

flag:nbctf{h0p3fuLLy_7h3_D3A_d035n7_kn0w_ab0ut_th3_0th3r_cRyst4l5}

web/secret tunnel

题面:

Can you find the flag on the other end of my secret tunnel?

题目:

secret-tunnel.chal.nbctf.com

main.py

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
#!/usr/local/bin/python

from flask import Flask, render_template, request, Response
import requests

app = Flask(__name__,
static_url_path='',
static_folder="static")

@app.route("/fetchdata", methods=["POST"])
def fetchdata():
url = request.form["url"]

if "127" in url:
return Response("No loopback for you!", mimetype="text/plain")
if url.count('.') > 2:
return Response("Only 2 dots allowed!", mimetype="text/plain")
if "x" in url:
return Response("I don't like twitter >:(" , mimetype="text/plain")
if "flag" in url:
return Response("It's not gonna be that easy :)", mimetype="text/plain")

try:
res = requests.get(url)
except Exception as e:
return Response(str(e), mimetype="text/plain")

return Response(res.text[:32], mimetype="text/plain")

@app.route("/", methods=["GET"])
def index():
return render_template("index.html")

if __name__ == "__main__":
app.run()

flag.py

1
2
3
4
5
6
7
8
9
10
11
12
from flask import Flask, Response

app = Flask(__name__)

flag = open("flag.txt", "r").read()

@app.route("/flag", methods=["GET"])
def index():
return Response(flag, mimetype="text/plain")

if __name__ == "__main__":
app.run(port=1337)

考点:SSRF

解题:

image-20231206215124712

明确4个限制

大体就知道是想让我们去访问内网的flag路由

那么在linux系统中

代替127.0.0.1的有很多

比如

0 localhost

然后根据flag.py知道对于端口为1337

同时flag需要转换 采用URL编码

image-20231206215509152

换一个f就行了

payload:http://0:1337/%66lag

image-20231206215555967

法二:

用自己的服务器利用重定向跳转

输入框 => 自己的服务器 => 重定向访问到内网的flag路由

写在服务器的内容

1
2
3
<?php
header("Location:http://127.0.0.1:1337/flag")
?>

web/Galleria

题面:

Put up some fun images for everyone in this amazing image gallery!

题目:

app.py

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
from flask import Flask, render_template, request, redirect, url_for, send_file
import os
from pathlib import Path
from werkzeug.utils import secure_filename

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = 'uploads'


@app.route('/')
def index():
return render_template('index.html')


def allowed_file(filename):
allowed_extensions = {'jpg', 'jpeg', 'png', 'gif'}
return '.' in filename and filename.rsplit('.', 1)[1].lower() in allowed_extensions


@app.route('/upload', methods=['POST'])
def upload():
file = request.files['image']
if file and allowed_file(file.filename):
file.seek(0, os.SEEK_END)
if file.tell() > 1024 * 1024 * 2:
return "File is too large", 413

file.seek(0)
filename = secure_filename(os.path.basename(file.filename))
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))

return redirect(url_for('gallery'))


def check_file_path(path):
_path = Path(path)

parts = [*Path.cwd().parts][1:]
for part in _path.parts:
if part == '.':
continue
if part == '..':
parts.pop()
else:
parts.append(part)

if len(parts) == 0:
return False

_path = os.path.join(os.getcwd(), path)
_path = Path(_path)
return _path.exists() and _path.is_file()


@app.route('/gallery')
def gallery():
if request.args.get('file'):
filename = os.path.join('uploads', request.args.get('file'))
if not check_file_path(filename):
return redirect(url_for('gallery'))

return send_file(filename)

image_files = [f for f in os.listdir(
app.config['UPLOAD_FOLDER'])]
return render_template('gallery.html', images=image_files)


if __name__ == '__main__':
app.run(debug=False, port=5000, host='0.0.0.0')

dockerfile:

1
2
3
4
5
6
7
8
9
10
11
12
13
FROM python:3.11-slim AS app

WORKDIR /var/www/html

RUN pip3 install --no-cache-dir flask

RUN mkdir uploads
COPY app.py .
COPY templates ./templates

COPY flag.txt /tmp/flag.txt

CMD ["python3", "app.py"]

考点:代码审查

解题:

我们先没有看源码的情况下 上传一张图片 然后发现看到其他人上传的照片全部显示出来了

这引起一丝警觉

然后对代码进行审查

image-20231206221703254

找到这个路由

参数是file

对file指定的文件进行读取

image-20231206221803040

然后我们又在dockerfile中看到flag文件的位置

读!

image-20231206221906877