├── LICENSE ├── Makefile ├── README.md ├── pagebuster.c ├── userland └── c │ ├── sigsegv.c │ └── simple.c └── userpagebuster ├── Makefile ├── README.md ├── example.c └── userpagebuster.c /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ccflags-y := \ 2 | -std=gnu99 \ 3 | -Werror \ 4 | -Wno-declaration-after-statement \ 5 | $(CCFLAGS) 6 | 7 | obj-m+=pagebuster.o 8 | 9 | KBUILD_DIR=/lib/modules/$(shell uname -r)/build 10 | 11 | # Kernel module build dependency 12 | all: 13 | make -C $(KBUILD_DIR) M=$(PWD) modules 14 | # 15 | # Kernel module clean dependency 16 | clean: 17 | make -C $(KBUILD_DIR) M=$(PWD) clean 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PageBuster 2 | 3 | >_Ever wanted to dump all the executable pages of a process? Do you crave something capable of dealing with **packed** processes?_ 4 | 5 | We've got you covered! May I introduce **PageBuster**, our tool to gather dumps of all executable pages of packed processes. 6 | 7 | [![asciicast](https://asciinema.org/a/cJH2O5N8w8Dd0GUuHw9kj8CZM.svg)](https://asciinema.org/a/cJH2O5N8w8Dd0GUuHw9kj8CZM) 8 | 9 | Introduction 10 | ------------ 11 | 12 | There are plenty of scenarios in which the ability to dump executable pages is highly desirable. Of course, there are many methods, some of which standard _de facto_, but it is not always as easy as it seems. 13 | 14 | For example, think about the case of packed malware samples. Run-time packers are often used by malware-writers to obfuscate their code and hinder static analysis. Packers can be of growing complexity, and, in many cases, a precise moment in time when the entire original code is completely unpacked in memory doesn't even exist. 15 | 16 | Therefore, the goals of **PageBuster** are: 17 | 18 | 1. To dump all the executable pages, without assuming there is a moment in time where the program is fully unpacked; 19 | 2. To do this in a stealthy way (no VM, no ptrace). 20 | 21 | In particular, given the widespread use of packers and their variety, our objective is to have a single all-encompassing solution, as opposed to packer-specific ones. 22 | 23 | Ultimately, PageBuster fits in the context of the rev.ng decompiler. Specifically, it is related to what we call [MetaAddress](https://github.com/revng/revng/blob/9869f05/include/revng/Support/MetaAddress.h#L382). Among other things, a MetaAddress enables you to represent an absolute value of an address together with a timestamp (_epoch_), so that it can be used to track how a memory location changes during the execution of a program. Frequently, you can have different code at different moments at the same address during program execution. PageBuster was designed around this simple yet effective data structure. 24 | 25 | For more information, please refer to our [blogpost](https://rev.ng/blog/pagebuster/post.html). 26 | 27 | There are two PageBuster implementations: a prototype user-space-only and the full-fledged one, employing with a kernel module. 28 | The former is described in `userpagebuster/`. 29 | The rest of this document describes the latter. 30 | 31 | Build 32 | ----- 33 | 34 | Make sure you have installed GCC and Linux kernel headers for your kernel. For Debian-based systems: 35 | 36 | ```sh 37 | sudo apt install build-essential linux-headers-$(uname -r) 38 | ``` 39 | 40 | Then, build the kernel module: 41 | 42 | ```sh 43 | cd pagebuster 44 | make 45 | ``` 46 | 47 | This will produce `pagebuster.ko`, the module for the kernel you are currently running. 48 | Please make sure the kernel version is lower than v5.9.2 since **PageBuster** has not been tested for newer versions. 49 | 50 | **Note**: Please consider using a **virtual machine** (VirtualBox, VMWare, QEMU, etc.) for testing. The module could be harmful. Avoid killing your machine or production environment by accident. 51 | 52 | Usage 53 | ----- 54 | 55 | To test **PageBuster**, you can insert the LKM and try it with whatever binary you want. We provided you with [`sigsegv.c`](https://github.com/zTehRyaN/pagebuster/blob/main/sigsegv.c), a `.c` program that simply maps and executes a shellcode. Inside the `/userland/c/` directory you will also find `simple.c`, the one shown in the demo. 56 | 57 | So, just `insmod` the module and pass the name of the process as argument. Then, execute it. 58 | 59 | ```sh 60 | insmod pagebuster.ko path=sigsegv.out 61 | ./sigsegv.out 62 | ``` 63 | 64 | Inside the `/tmp` directory, you will find all the timestamped dumps. 65 | 66 | ```sh 67 | ls /tmp 68 | ``` 69 | 70 | You should get an output similar to the following: 71 | 72 | ``` 73 | 100000000_494 7ffff7d4b000_291 7ffff7dc7000_415 7ffff7eb9000_30 74 | 100001000_495 7ffff7d4c000_292 7ffff7dc8000_416 7ffff7eba000_31 75 | 7ffff7cd1000_169 7ffff7d4d000_293 7ffff7dc9000_417 7ffff7ebb000_32 76 | 7ffff7cd2000_170 7ffff7d4e000_294 7ffff7dca000_418 7ffff7ebc000_33 77 | 7ffff7cd3000_171 7ffff7d4f000_295 7ffff7dcb000_419 7ffff7ebd000_34 78 | 7ffff7cd4000_172 7ffff7d50000_296 7ffff7dcc000_420 7ffff7ebe000_35 79 | 7ffff7cd5000_173 7ffff7d51000_297 7ffff7dcd000_421 7ffff7ebf000_36 80 | 7ffff7cd6000_174 7ffff7d52000_298 7ffff7dce000_422 7ffff7ec0000_37 81 | ... 82 | ``` 83 | 84 | To remove the LKM, run: 85 | 86 | ```sh 87 | rmmod pagebuster.ko 88 | ``` 89 | 90 | Quickly test in QEMU 91 | -------------------- 92 | 93 | If you want to test it on a safe environment, you can use [Ciro Santilli's](https://github.com/cirosantilli/linux-kernel-module-cheat) emulation setup. 94 | 95 | This setup has been mostly tested on Ubuntu. 96 | Reserve 12 GB of disk and run: 97 | 98 | ```sh 99 | git clone https://github.com/cirosantilli/linux-kernel-module-cheat 100 | cd linux-kernel-module-cheat 101 | git reset --hard 5ec6595e1f3afb6213ba7c14ab5e4e3893a4089f 102 | ``` 103 | 104 | Unlike Ubuntu 20.04 LTS, here `kprobes` is not enabled by default. So, you must enable it on linux kernel configs. 105 | 106 | ```sh 107 | cd linux_config 108 | cat <> default 109 | 110 | # Kprobes 111 | CONFIG_KPROBES=y 112 | EOT 113 | cd .. 114 | ``` 115 | 116 | Now, you can start the build: 117 | 118 | ```sh 119 | # For Debian derivatives 120 | ./build --download-dependencies qemu-buildroot 121 | # If you use another distro, you'll have to install the deps manually 122 | ./build --no-apt --download-dependencies qemu-buildroot 123 | ``` 124 | 125 | The initial build will take a while (30 minutes to 2 hours) to clone and build. 126 | 127 | Finally, what you need to do is to insert inside the environment the kernel module as well as all the `c` programs you want to test it on. 128 | If you want to do it manually, you can build the module as shown before, the target programs and then just put them inside QEMU: 129 | 130 | ```sh 131 | cd linux-kernel-module-cheat 132 | cp /path/to/files $PWD/out/buildroot/build/default/x86_64/target/lkmc/ 133 | ./build-buildroot 134 | ``` 135 | 136 | In this way, you will find them inside the directory where you spawn. 137 | 138 | You can now run QEMU: 139 | 140 | ```sh 141 | ./run 142 | ``` 143 | 144 | Use with `Ctrl-A X` to quit QEMU or type `poweroff`. 145 | 146 | If you use `linux-kernel-module-cheat` to build the module and the programs for you, you can put `pagebuster.c` inside `/kernel_modules`, and the `c` files inside `/userland/c`. Then run: 147 | 148 | ```sh 149 | # Rebuild and run 150 | ./build-userland 151 | ./build-modules 152 | ./run 153 | 154 | # Load kernel module 155 | cd /mnt/9p/out_rootfs_overlay/lkmc 156 | insmod pagebuster.ko path=sigsegv.out 157 | 158 | # Run the program 159 | ./c/sigsegv.out 160 | 161 | # List dumped pages 162 | ls /tmp 163 | ``` 164 | 165 | If you want to test with other binaries, you may put the source `.c` file inside the [`/userland/c`](https://github.com/cirosantilli/linux-kernel-module-cheat/tree/master/userland/c) folder and let the simulator compile it for you by running `./build-userland`. Now, after running the system, you will find it compiled inside `/mnt/9p/out_rootfs_overlay/lkmc/c/`. 166 | 167 | UPX testing 168 | ----------- 169 | 170 | If you want to try how **PageBuster** behaves with UPX-packed binaries, you should prepare them outside the QEMU guest environment, and then inject into it. 171 | First of all, install [upx](https://upx.github.io/). On Ubuntu 20.04 LTS, run: 172 | 173 | ```sh 174 | sudo apt-get update -y 175 | sudo apt-get install -y upx-ucl 176 | ``` 177 | 178 | Then, for instance, grab a `.c` program and compile it. Make sure it reaches the minimum size required by upx to pack it: UPX cannot handle binaries under 40Kb. The best way to work-around this problem is to compile your binary in static mode, in order to get a bigger executable file. 179 | So, just try: 180 | 181 | ```sh 182 | gcc -static -o mytest mytest.c 183 | upx -o mytest_packed mytest 184 | ``` 185 | 186 | The easiest way to put it inside QEMU is the following. 187 | 188 | ```c 189 | cd linux-kernel-module-cheat 190 | cp /path/to/mytest_packed $PWD/out/buildroot/build/default/x86_64/target/lkmc/ 191 | ./build-buildroot 192 | ``` 193 | 194 | Now you can test it, in the usual way: 195 | 196 | ```sh 197 | ./run 198 | insmod /mnt/9p/out_rootfs_overlay/lkmc/pagebuster.ko path=mytest_packed 199 | ./mytest_packed 200 | ls /tmp 201 | ``` 202 | 203 | Output will be something like that: 204 | 205 | ``` 206 | 401000_2 427000_40 44d000_78 473000_116 207 | 402000_3 428000_41 44e000_79 474000_117 208 | 403000_4 429000_42 44f000_80 475000_118 209 | 404000_5 42a000_43 450000_81 476000_119 210 | 405000_6 42b000_44 451000_82 477000_120 211 | 406000_7 42c000_45 452000_83 478000_121 212 | 407000_8 42d000_46 453000_84 479000_122 213 | 408000_9 42e000_47 454000_85 47a000_123 214 | 409000_10 42f000_48 455000_86 47b000_124 215 | 40a000_11 430000_49 456000_87 47c000_125 216 | ``` 217 | 218 | Licensing 219 | --------- 220 | 221 | The content of this repository is licensed under the [GPLv2](https://github.com/zTehRyaN/pagebuster/blob/main/LICENSE). 222 | Many thanks to Alexei Lozovsky which inspired the ftrace hooking part of the project. 223 | -------------------------------------------------------------------------------- /pagebuster.c: -------------------------------------------------------------------------------- 1 | /* 2 | * PageBuster - dump all executable pages of packed processes. 3 | * 4 | * Copyright (C) 2021 Matteo Giordano 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License along 17 | * with this program; if not, write to the Free Software Foundation, Inc., 18 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 19 | */ 20 | 21 | #define pr_fmt(fmt) "pagebuster: " fmt 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | 43 | #include 44 | 45 | /* External parameters */ 46 | static char *path; 47 | module_param(path, charp, 0000); 48 | MODULE_PARM_DESC(path, "Path/Name of the target process"); 49 | 50 | /* 51 | * Data structure for memory areas tracked. 52 | * 53 | * Each entry is a memory page, index by its address 54 | * and with some protection flags. 55 | */ 56 | static LIST_HEAD(marea_list); 57 | 58 | /* Data structure for all the memory areas to be considered */ 59 | extern struct list_head marea_list; 60 | 61 | /* 62 | * Structure for a memory area to be tracked. 63 | * 64 | * Entry of the doubly-linked list. 65 | * 66 | */ 67 | struct marea { 68 | 69 | /* intrusive list core */ 70 | struct list_head list; 71 | 72 | /* mprotect/mmap address */ 73 | unsigned long addr; 74 | 75 | /* mprotect/mmap access flag */ 76 | unsigned long prot; 77 | }; 78 | 79 | static unsigned long epoch_counter = 0; 80 | 81 | /* 82 | * Searches inside the list @marea_list if it exists 83 | * an entry for the given @addr_given 84 | * 85 | */ 86 | static struct marea *search_page(unsigned long addr_given, struct list_head marea_list) 87 | { 88 | struct marea *result = NULL; 89 | 90 | list_for_each_entry(result, &marea_list, list) { 91 | unsigned long start_addr = (unsigned long) result->addr; 92 | unsigned long end_addr = start_addr + PAGE_SIZE -1; 93 | 94 | if(addr_given >= start_addr && addr_given <= end_addr){ 95 | break; 96 | } 97 | } 98 | 99 | return result; 100 | } 101 | 102 | /* Allocates space for a new struct marea */ 103 | struct marea *new_marea(unsigned long addr, int prot) 104 | { 105 | struct marea *new_m = (struct marea *) kmalloc(sizeof(struct marea), GFP_KERNEL); 106 | if (new_m) { 107 | new_m->addr = addr; 108 | new_m->prot = prot; 109 | INIT_LIST_HEAD(&new_m->list); 110 | } 111 | return new_m; 112 | } 113 | 114 | static void dump_to_file(unsigned long buf, size_t size, loff_t *offset) 115 | { 116 | struct file *dest_file = NULL; 117 | char file_path[100]; 118 | 119 | // Permanent path 120 | //sprintf(file_path, "/lkmc/dump/%lx_%lu", buf, epoch_counter); 121 | 122 | // Temporary path 123 | sprintf(file_path, "/tmp/%lx_%lu", buf, epoch_counter); 124 | 125 | epoch_counter++; 126 | 127 | size_t res; 128 | 129 | dest_file = filp_open(file_path, O_CREAT | O_RDWR | O_APPEND | O_LARGEFILE, 0666); 130 | if (IS_ERR(dest_file)) { 131 | printk("Error in opening: <%ld>", (long) dest_file); 132 | } 133 | if (dest_file == NULL) { 134 | printk("Error in opening: null"); 135 | } 136 | 137 | res = kernel_write(dest_file, (void *) buf, size, offset); 138 | filp_close(dest_file, NULL); 139 | } 140 | 141 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(5,7,0) 142 | static unsigned long lookup_name(const char *name) 143 | { 144 | struct kprobe kp = { 145 | .symbol_name = name 146 | }; 147 | unsigned long retval; 148 | 149 | if (register_kprobe(&kp) < 0) return 0; 150 | retval = (unsigned long) kp.addr; 151 | unregister_kprobe(&kp); 152 | return retval; 153 | } 154 | #else 155 | static unsigned long lookup_name(const char *name) 156 | { 157 | return kallsyms_lookup_name(name); 158 | } 159 | #endif 160 | #if LINUX_VERSION_CODE < KERNEL_VERSION(5,11,0) 161 | #define FTRACE_OPS_FL_RECURSION FTRACE_OPS_FL_RECURSION_SAFE 162 | #endif 163 | 164 | #if LINUX_VERSION_CODE < KERNEL_VERSION(5,11,0) 165 | #define FTRACE_OPS_FL_RECURSION FTRACE_OPS_FL_RECURSION_SAFE 166 | #endif 167 | 168 | #if LINUX_VERSION_CODE < KERNEL_VERSION(5,11,0) 169 | #define ftrace_regs pt_regs 170 | 171 | static __always_inline struct pt_regs *ftrace_get_regs(struct ftrace_regs *fregs) 172 | { 173 | return fregs; 174 | } 175 | #endif 176 | 177 | /* 178 | * There are two ways of preventing vicious recursive loops when hooking: 179 | * - detect recusion using function return address (USE_FENTRY_OFFSET = 0) 180 | * - avoid recusion by jumping over the ftrace call (USE_FENTRY_OFFSET = 1) 181 | */ 182 | #define USE_FENTRY_OFFSET 0 183 | 184 | /** 185 | * struct ftrace_hook - describes a single hook to install 186 | * 187 | * @name: name of the function to hook 188 | * 189 | * @function: pointer to the function to execute instead 190 | * 191 | * @original: pointer to the location where to save a pointer 192 | * to the original function 193 | * 194 | * @address: kernel address of the function entry 195 | * 196 | * @ops: ftrace_ops state for this function hook 197 | * 198 | * The user should fill in only &name, &hook, &orig fields. 199 | * Other fields are considered implementation details. 200 | */ 201 | struct ftrace_hook { 202 | const char *name; 203 | void *function; 204 | void *original; 205 | 206 | unsigned long address; 207 | struct ftrace_ops ops; 208 | }; 209 | 210 | static int fh_resolve_hook_address(struct ftrace_hook *hook) 211 | { 212 | hook->address = lookup_name(hook->name); 213 | 214 | if (!hook->address) { 215 | pr_debug("Unresolved symbol: %s\n", hook->name); 216 | return -ENOENT; 217 | } 218 | 219 | #if USE_FENTRY_OFFSET 220 | *((unsigned long*) hook->original) = hook->address + MCOUNT_INSN_SIZE; 221 | #else 222 | *((unsigned long*) hook->original) = hook->address; 223 | #endif 224 | 225 | return 0; 226 | } 227 | 228 | static void notrace fh_ftrace_thunk(unsigned long ip, unsigned long parent_ip, 229 | struct ftrace_ops *ops, struct ftrace_regs *fregs) 230 | { 231 | struct pt_regs *regs = ftrace_get_regs(fregs); 232 | struct ftrace_hook *hook = container_of(ops, struct ftrace_hook, ops); 233 | 234 | #if USE_FENTRY_OFFSET 235 | regs->ip = (unsigned long) hook->function; 236 | #else 237 | if (!within_module(parent_ip, THIS_MODULE)) 238 | regs->ip = (unsigned long) hook->function; 239 | #endif 240 | } 241 | 242 | /** 243 | * fh_install_hooks() - register and enable a single hook 244 | * @hook: a hook to install 245 | * 246 | * Returns: zero on success, negative error code otherwise. 247 | */ 248 | int fh_install_hook(struct ftrace_hook *hook) 249 | { 250 | int err; 251 | 252 | err = fh_resolve_hook_address(hook); 253 | if (err) 254 | return err; 255 | 256 | /* 257 | * We're going to modify %rip register so we'll need IPMODIFY flag 258 | * and SAVE_REGS as its prerequisite. ftrace's anti-recursion guard 259 | * is useless if we change %rip so disable it with RECURSION. 260 | * We'll perform our own checks for trace function reentry. 261 | */ 262 | hook->ops.func = fh_ftrace_thunk; 263 | hook->ops.flags = FTRACE_OPS_FL_SAVE_REGS 264 | | FTRACE_OPS_FL_RECURSION 265 | | FTRACE_OPS_FL_IPMODIFY; 266 | 267 | err = ftrace_set_filter_ip(&hook->ops, hook->address, 0, 0); 268 | if (err) { 269 | pr_debug("ftrace_set_filter_ip() failed: %d\n", err); 270 | return err; 271 | } 272 | 273 | err = register_ftrace_function(&hook->ops); 274 | if (err) { 275 | pr_debug("register_ftrace_function() failed: %d\n", err); 276 | ftrace_set_filter_ip(&hook->ops, hook->address, 1, 0); 277 | return err; 278 | } 279 | 280 | return 0; 281 | } 282 | 283 | /** 284 | * fh_remove_hooks() - disable and unregister a single hook 285 | * @hook: a hook to remove 286 | */ 287 | void fh_remove_hook(struct ftrace_hook *hook) 288 | { 289 | int err; 290 | 291 | err = unregister_ftrace_function(&hook->ops); 292 | if (err) { 293 | pr_debug("unregister_ftrace_function() failed: %d\n", err); 294 | } 295 | 296 | err = ftrace_set_filter_ip(&hook->ops, hook->address, 1, 0); 297 | if (err) { 298 | pr_debug("ftrace_set_filter_ip() failed: %d\n", err); 299 | } 300 | } 301 | 302 | /** 303 | * fh_install_hooks() - register and enable multiple hooks 304 | * @hooks: array of hooks to install 305 | * @count: number of hooks to install 306 | * 307 | * If some hooks fail to install then all hooks will be removed. 308 | * 309 | * Returns: zero on success, negative error code otherwise. 310 | */ 311 | int fh_install_hooks(struct ftrace_hook *hooks, size_t count) 312 | { 313 | int err; 314 | size_t i; 315 | 316 | for (i = 0; i < count; i++) { 317 | err = fh_install_hook(&hooks[i]); 318 | if (err) 319 | goto error; 320 | } 321 | 322 | return 0; 323 | 324 | error: 325 | while (i != 0) { 326 | fh_remove_hook(&hooks[--i]); 327 | } 328 | 329 | return err; 330 | } 331 | 332 | /** 333 | * fh_remove_hooks() - disable and unregister multiple hooks 334 | * @hooks: array of hooks to remove 335 | * @count: number of hooks to remove 336 | */ 337 | void fh_remove_hooks(struct ftrace_hook *hooks, size_t count) 338 | { 339 | size_t i; 340 | 341 | for (i = 0; i < count; i++) 342 | fh_remove_hook(&hooks[i]); 343 | } 344 | 345 | #ifndef CONFIG_X86_64 346 | #error Currently only x86_64 architecture is supported 347 | #endif 348 | 349 | #if defined(CONFIG_X86_64) && (LINUX_VERSION_CODE >= KERNEL_VERSION(4,17,0)) 350 | #define PTREGS_SYSCALL_STUBS 1 351 | #endif 352 | 353 | /* 354 | * Tail call optimization can interfere with recursion detection based on 355 | * return address on the stack. Disable it to avoid machine hangups. 356 | */ 357 | #if !USE_FENTRY_OFFSET 358 | #pragma GCC optimize("-fno-optimize-sibling-calls") 359 | #endif 360 | 361 | /* mprotect() hook'd function */ 362 | static asmlinkage long (*real_sys_mprotect)(struct pt_regs *regs); 363 | 364 | /* 365 | * %rdi regs->di unsigned long addr 366 | * %rsi regs->si size_t len 367 | * %rdx regs->dx unsigned long prot 368 | */ 369 | static asmlinkage long fh_sys_mprotect(struct pt_regs *regs) 370 | { 371 | int n_pages; 372 | size_t quotient, remainder; 373 | 374 | struct marea *entry, *tmp; 375 | 376 | if (strstr(current->comm, path) != NULL) { 377 | 378 | quotient = regs->si / PAGE_SIZE; 379 | remainder = regs->si % PAGE_SIZE; 380 | 381 | if (remainder == 0) 382 | n_pages = quotient; 383 | else 384 | n_pages = quotient + 1; 385 | 386 | /* Avoid cohexistence of W^X */ 387 | if (regs->dx >= (PROT_EXEC | PROT_WRITE)) { 388 | 389 | /* Create an entry for each page covered by mprotect */ 390 | for (int i = 0; i < n_pages; i++) { 391 | entry = NULL; 392 | int replaced = 0; 393 | 394 | list_for_each_entry(entry, &marea_list, list) { 395 | if (entry->addr == (regs->di + (i * PAGE_SIZE))) { 396 | struct marea *tmp = new_marea(regs->di + (i * PAGE_SIZE), regs->dx); 397 | list_replace(&entry->list, &tmp->list); 398 | replaced = 1; 399 | break; 400 | } 401 | } 402 | 403 | if(!replaced) { 404 | struct marea *tmp = new_marea(regs->di + (i * PAGE_SIZE), regs->dx); 405 | list_add(&tmp->list, &marea_list); 406 | } 407 | } 408 | 409 | regs->dx &= ~PROT_WRITE; 410 | 411 | } else if ((regs->dx == PROT_EXEC) || (regs->dx == (PROT_EXEC | PROT_READ))) { 412 | 413 | /* EFLAGS.AC <- 1 */ 414 | stac(); 415 | for (int i = 0; i < n_pages; i++) { 416 | loff_t offset = 0; 417 | loff_t *off_p = &offset; 418 | dump_to_file(regs->di + (i * PAGE_SIZE), PAGE_SIZE, off_p); 419 | } 420 | 421 | /* EFLAGS.AC <- 0 */ 422 | clac(); 423 | 424 | /* User issued mprotect(0|1|2|3) */ 425 | } else { 426 | for (int i = 0; i < n_pages; i++) { 427 | entry = NULL; 428 | tmp = NULL; 429 | list_for_each_entry_safe(entry, tmp, &marea_list, list) { 430 | if (entry->addr == (regs->di + (i * PAGE_SIZE))) { 431 | list_del(&entry->list); 432 | kfree(entry); 433 | } 434 | } 435 | } 436 | } 437 | 438 | return real_sys_mprotect(regs); 439 | 440 | } else { 441 | return real_sys_mprotect(regs); 442 | } 443 | } 444 | 445 | /* mmap() hook'd function */ 446 | static asmlinkage long (*real_sys_mmap)(struct pt_regs *regs); 447 | 448 | /* 449 | * %rdi regs->di unsigned long addr 450 | * %rsi regs->si unsigned long len 451 | * %rdx regs->dx unsigned long prot 452 | * %r10 regs->r10 unsigned long flags 453 | * %r8 regs->r8 unsigned long fd 454 | * %r9 regs->r9 unsigned long off 455 | */ 456 | static asmlinkage long fh_sys_mmap(struct pt_regs *regs) 457 | { 458 | long ret; 459 | int n_pages; 460 | 461 | size_t quotient, remainder; 462 | 463 | long intended_permissions; 464 | 465 | struct marea *entry, *tmp; 466 | 467 | if (strstr(current->comm, path) != NULL) { 468 | 469 | regs->r10 |= MAP_POPULATE; 470 | 471 | quotient = regs->si / PAGE_SIZE; 472 | remainder = regs->si % PAGE_SIZE; 473 | 474 | if (remainder == 0) 475 | n_pages = quotient; 476 | else 477 | n_pages = quotient + 1; 478 | 479 | /* Avoid cohexistence of W^X */ 480 | if (regs->dx >= (PROT_EXEC | PROT_WRITE)) { 481 | 482 | intended_permissions = regs->dx; 483 | regs->dx &= ~PROT_WRITE; 484 | 485 | ret = real_sys_mmap(regs); 486 | 487 | /* Create an entry for each page allocated by the mmap */ 488 | for (int i = 0; i < n_pages; i++) { 489 | entry = NULL; 490 | int replaced = 0; 491 | 492 | list_for_each_entry(entry, &marea_list, list) { 493 | if (entry->addr == (ret + (i * PAGE_SIZE))) { 494 | struct marea *tmp = new_marea(ret + (i * PAGE_SIZE), intended_permissions); 495 | list_replace(&entry->list, &tmp->list); 496 | replaced = 1; 497 | break; 498 | } 499 | } 500 | 501 | if (!replaced) { 502 | struct marea *tmp = new_marea(ret + (i * PAGE_SIZE), intended_permissions); 503 | list_add(&tmp->list, &marea_list); 504 | } 505 | } 506 | 507 | return ret; 508 | 509 | } else if ((regs->dx == PROT_EXEC) || (regs->dx == (PROT_EXEC | PROT_READ))) { 510 | ret = real_sys_mmap(regs); 511 | 512 | /* EFLAGS.AC <- 1 */ 513 | stac(); 514 | 515 | for (int i = 0; i < n_pages; i++) { 516 | loff_t offset = 0; 517 | loff_t *off_p = &offset; 518 | dump_to_file(ret + (i * PAGE_SIZE), PAGE_SIZE, off_p); 519 | } 520 | 521 | /* EFLAGS.AC <- 0 */ 522 | clac(); 523 | 524 | return ret; 525 | 526 | /* User issued mmap(0|1|2|3) */ 527 | } else { 528 | ret = real_sys_mmap(regs); 529 | for (int i = 0; i < n_pages; i++) { 530 | entry = NULL; 531 | tmp = NULL; 532 | list_for_each_entry_safe(entry, tmp, &marea_list, list) { 533 | if (entry->addr == (ret + (i * PAGE_SIZE))) { 534 | list_del(&entry->list); 535 | kfree(entry); 536 | } 537 | } 538 | } 539 | return ret; 540 | } 541 | 542 | } else { 543 | return real_sys_mmap(regs); 544 | } 545 | } 546 | 547 | static asmlinkage long (*real_force_sig_fault)(int sig, int code, void __user *addr); 548 | 549 | static asmlinkage int fh_force_sig_fault(int sig, int code, void __user *addr) 550 | { 551 | struct marea *page_inducted = NULL; 552 | unsigned long address = (unsigned long) addr; 553 | 554 | /* If found, then this segfault has been inducted */ 555 | if (!list_empty(&marea_list)) { 556 | page_inducted = search_page(address, marea_list); 557 | } 558 | 559 | 560 | /* Standard SIGSEGV handling */ 561 | if (page_inducted == NULL) { 562 | return real_force_sig_fault(sig, code, addr); 563 | 564 | /* Custom SIGSEGV handling */ 565 | } else { 566 | struct pt_regs *regs = kmalloc(sizeof(struct pt_regs), GFP_KERNEL); 567 | // Address 568 | regs->di = page_inducted->addr; 569 | // Length 570 | regs->si = PAGE_SIZE; 571 | 572 | unsigned long new_permissions = page_inducted->prot; 573 | 574 | /* Invalid write attempt */ 575 | if (current->thread.error_code & X86_PF_WRITE) { 576 | 577 | new_permissions &= ~PROT_EXEC; 578 | 579 | // Protections 580 | regs->dx = new_permissions; 581 | 582 | real_sys_mprotect(regs); 583 | 584 | /* Bad jump */ 585 | } else if (current->thread.error_code & X86_PF_INSTR) { 586 | 587 | new_permissions &= ~PROT_WRITE; 588 | 589 | stac(); 590 | loff_t offset = 0; 591 | loff_t *off_p = &offset; 592 | dump_to_file(page_inducted->addr, PAGE_SIZE, off_p); 593 | clac(); 594 | 595 | // Protections 596 | regs->dx = new_permissions; 597 | 598 | real_sys_mprotect(regs); 599 | } 600 | kfree(regs); 601 | 602 | return 0; 603 | } 604 | } 605 | 606 | /* execve() hook'd function */ 607 | static asmlinkage long (*real_sys_execve)(struct pt_regs *regs); 608 | 609 | static asmlinkage long fh_sys_execve(struct pt_regs *regs) 610 | { 611 | struct marea *entry = NULL, *tmp = NULL; 612 | 613 | char *kernel_filename = kmalloc(4096, GFP_KERNEL); 614 | 615 | if (!kernel_filename) { 616 | return -ENOMEM; 617 | } 618 | 619 | if (strncpy_from_user(kernel_filename, (const char __user *) regs->di, 4096) < 0) { 620 | kfree(kernel_filename); 621 | return -EINVAL; 622 | } 623 | 624 | /* 625 | * If the path matches the given parameter, then empty the list. 626 | */ 627 | if (strstr(kernel_filename, path) != NULL) { 628 | list_for_each_entry_safe(entry, tmp, &marea_list, list) { 629 | list_del(&entry->list); 630 | } 631 | } 632 | 633 | kfree(kernel_filename); 634 | 635 | return real_sys_execve(regs); 636 | } 637 | 638 | /* 639 | * x86_64 kernels have a special naming convention for syscall entry points in newer kernels. 640 | * That's what you end up with if an architecture has 3 (three) ABIs for system calls. 641 | */ 642 | #ifdef PTREGS_SYSCALL_STUBS 643 | #define SYSCALL_NAME(name) ("__x64_" name) 644 | #else 645 | #define SYSCALL_NAME(name) (name) 646 | #endif 647 | 648 | #define HOOK(_name, _function, _original) \ 649 | { \ 650 | .name = SYSCALL_NAME(_name), \ 651 | .function = (_function), \ 652 | .original = (_original), \ 653 | } 654 | 655 | #define HOOK_NOSYS(_name, _function, _original) \ 656 | { \ 657 | .name = _name, \ 658 | .function = (_function), \ 659 | .original = (_original), \ 660 | } 661 | 662 | static struct ftrace_hook demo_hooks[] = { 663 | HOOK("sys_mprotect", fh_sys_mprotect, &real_sys_mprotect), 664 | HOOK("sys_mmap", fh_sys_mmap, &real_sys_mmap), 665 | HOOK("sys_execve", fh_sys_execve, &real_sys_execve), 666 | HOOK_NOSYS("force_sig_fault", fh_force_sig_fault, &real_force_sig_fault), 667 | }; 668 | 669 | static int fh_init(void) 670 | { 671 | int err; 672 | 673 | err = fh_install_hooks(demo_hooks, ARRAY_SIZE(demo_hooks)); 674 | if (err) 675 | return err; 676 | pr_info("PageBuster Module loaded\n"); 677 | 678 | return 0; 679 | } 680 | module_init(fh_init); 681 | 682 | static void fh_exit(void) 683 | { 684 | fh_remove_hooks(demo_hooks, ARRAY_SIZE(demo_hooks)); 685 | pr_info("PageBuster Module unloaded\n"); 686 | } 687 | module_exit(fh_exit); 688 | 689 | MODULE_DESCRIPTION("PageBuster - dump all executable pages of packed processes."); 690 | MODULE_AUTHOR("Matteo Giordano "); 691 | MODULE_LICENSE("GPL"); 692 | -------------------------------------------------------------------------------- /userland/c/sigsegv.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define page 4096 8 | 9 | typedef int func(void); 10 | 11 | int main() 12 | { 13 | char shellcode[] = "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"; 14 | char nop_sled_1_page[4095]; 15 | size_t alloc_size = page; 16 | 17 | printf("Start of sigsegv.out"); 18 | 19 | char *region = mmap( 20 | (void*) (alloc_size * (1 << 20)), // Map from the start of the 2^20th page 21 | 2 * alloc_size, // for one page length 22 | PROT_READ | PROT_EXEC | PROT_WRITE, 23 | 34, // to a private block of hardware memory 24 | 0, 25 | 0 26 | ); 27 | 28 | for (int i = 0; i < 4095; i++) { 29 | strcat(nop_sled_1_page, "\x90"); 30 | } 31 | strcat(nop_sled_1_page, shellcode); 32 | strcpy(region, nop_sled_1_page); 33 | 34 | func* f = (func*)0x100000000; 35 | f(); 36 | 37 | return 0; 38 | } 39 | 40 | -------------------------------------------------------------------------------- /userland/c/simple.c: -------------------------------------------------------------------------------- 1 | #include 2 | int main() { 3 | printf("Hello, World!"); 4 | return 0; 5 | } 6 | -------------------------------------------------------------------------------- /userpagebuster/Makefile: -------------------------------------------------------------------------------- 1 | # Usage: 2 | # make # build library 3 | # make clean # remove ALL binaries and objects 4 | 5 | .PHONY: all clean 6 | 7 | CC := gcc 8 | 9 | GLIB = $(shell pkg-config --cflags --libs glib-2.0) 10 | 11 | all: userpagebuster.so example 12 | 13 | example: example.c 14 | @ ${CC} $< -g -o $@ 15 | 16 | userpagebuster.so: userpagebuster.c 17 | @ ${CC} -shared -fPIC ${GLIB} $< -ldl -o $@ 18 | 19 | clean: 20 | @ echo "Cleaning up ..." 21 | @ rm *.so 22 | -------------------------------------------------------------------------------- /userpagebuster/README.md: -------------------------------------------------------------------------------- 1 | # PageBuster - A user-space prototype 2 | 3 | This is a user-space-only prototype implementation of PageBuster. 4 | 5 | Rather than immediately getting our hands dirty with the kernel-side implementation, we started with a user-space prototype, and then we moved the logic to the kernel level solution. 6 | In order to hook/hijack `mmap/mrotect`, we leveraged `LD_PRELOAD` environment variable. It's a simple way to hook library calls in a program. If you are not familiar with it, check out [ 7 | Rafał Cieślak's blog post on this topic](https://rafalcieslak.wordpress.com/2013/04/02/dynamic-linker-tricks-using-ld_preload-to-cheat-inject-features-and-investigate-programs/). 8 | The interesting thing is that the libraries inside that variable have the highest priority. If you set `LD_PRELOAD` to the path of a shared object, that file will be loaded **before** any other library (including the C runtime, `libc.so`). 9 | 10 | For more information, please refer to our [blogpost](https://rev.ng/blog/pagebuster/post.html). 11 | 12 | **Note**: This is a proof-of-concept of the real PageBuster. Its utility was only to warm up with the hook/dump logic. Main limitation: it will catch only library calls performed by the target process itself, and _not_ the ones by the kernel nor the loader. 13 | 14 | Usage 15 | ----- 16 | 17 | To build the library and the test binary just run: 18 | 19 | ```sh 20 | sudo apt-get install gcc build-essential libglib2.0-dev 21 | make 22 | ``` 23 | 24 | Then you'll have to load the library inside the `LD_PRELOAD` variable. 25 | 26 | If you want to set the `LD_PRELOAD` variable only for a single execution of the target process, use this: 27 | 28 | ```sh 29 | rm -f 0x*_* 30 | LD_PRELOAD=$PWD/userpagebuster.so ./example 31 | ``` 32 | 33 | At this point, you can execute the target binary to obtain the executable pages dumped out of it. 34 | 35 | ``` 36 | objdump -b binary -m i386:x86-64 -D 0x*_* 37 | ``` 38 | 39 | You should see three `nop` and `ret`. 40 | -------------------------------------------------------------------------------- /userpagebuster/example.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | typedef void (*function_pointer)(void); 5 | 6 | int main() { 7 | char *page = (char *) mmap(NULL, 8 | 4096, 9 | PROT_READ | PROT_WRITE, 10 | MAP_ANONYMOUS | MAP_PRIVATE, 11 | 0, 12 | 0); 13 | char *cursor = page; 14 | *(cursor++) = '\x90'; 15 | *(cursor++) = '\x90'; 16 | *(cursor++) = '\x90'; 17 | *(cursor++) = '\xc3'; 18 | mprotect(page, 4096, PROT_READ | PROT_EXEC); 19 | function_pointer call_me = (function_pointer) page; 20 | call_me(); 21 | return 0; 22 | } 23 | -------------------------------------------------------------------------------- /userpagebuster/userpagebuster.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Userspace PoC of PageBuster 3 | * 4 | * Copyright (C) 2021 Matteo Giordano 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License along 17 | * with this program; if not, write to the Free Software Foundation, Inc., 18 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 19 | */ 20 | 21 | #define _POSIX_C_SOURCE 200809L 22 | #define _GNU_SOURCE 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #define pagesize getpagesize() 36 | 37 | #if !defined(__linux__) || !defined(__x86_64__) 38 | #error This example only works in Linux on x86-64. 39 | #endif 40 | 41 | void init(void) __attribute__((constructor)); 42 | 43 | /* 44 | * Structure for a memory area to be tracked. 45 | * 46 | * Entry of the doubly-linked list. 47 | * 48 | */ 49 | struct marea { 50 | 51 | /* mprotect/mmap address */ 52 | void *addr; 53 | 54 | /* mprotect/mmap access flag */ 55 | int prot; 56 | }; 57 | 58 | /* Utils */ 59 | struct marea *new_marea(void *addr, int prot); 60 | void free_marea(gpointer marea); 61 | int compare(gconstpointer a, gconstpointer b); 62 | int search(gconstpointer a, gconstpointer b); 63 | 64 | void dump(void* addr, size_t __len); 65 | void *(*real_mmap)(void *, size_t, int, int, int, off_t); 66 | int (*real_mprotect)(void *, size_t, int); 67 | 68 | /* Global variables */ 69 | char *counter_filename = "/tmp/counter"; 70 | int current_epoch; 71 | GSList *hareas; 72 | struct sigaction def_sigsegv; 73 | 74 | 75 | /* Custom handler for intercept SIGSEGV in the target process */ 76 | void handler(int signal, siginfo_t *siginfo, void *contextptr) 77 | { 78 | ucontext_t *const ctx = (ucontext_t *const) contextptr; 79 | 80 | if (real_mprotect == NULL) { 81 | real_mprotect = (int (*)(void *, size_t, int))dlsym(RTLD_NEXT, "mprotect"); 82 | } 83 | 84 | /* Check if INDUCED sigsegv */ 85 | GSList *node = g_slist_find_custom(hareas, siginfo->si_addr, search); 86 | 87 | /* Standard SIGSEGV handling */ 88 | if (node == NULL) { 89 | 90 | if (def_sigsegv.sa_flags & SA_SIGINFO) 91 | (*def_sigsegv.sa_sigaction)(signal, siginfo, contextptr); 92 | else 93 | (*def_sigsegv.sa_handler)(signal); 94 | 95 | /* Custom SIGSEGV handling */ 96 | } else { 97 | 98 | struct marea *found = (struct marea*) node->data; 99 | 100 | /* Invalid write attempt */ 101 | if (ctx->uc_mcontext.gregs[REG_ERR] & 2){ 102 | 103 | int new_permissions = found->prot; 104 | new_permissions &= ~PROT_EXEC; 105 | 106 | real_mprotect(found->addr, pagesize, new_permissions); 107 | 108 | /* Bad jump */ 109 | } else if (ctx->uc_mcontext.gregs[REG_ERR] & 16) { 110 | 111 | int new_permissions = found->prot; 112 | new_permissions &= ~PROT_WRITE; 113 | 114 | dump(found->addr, pagesize); 115 | 116 | hareas = g_slist_remove(hareas, found); 117 | 118 | real_mprotect(found->addr, pagesize, new_permissions); 119 | free_marea(found); 120 | } 121 | } 122 | } 123 | 124 | 125 | void init(void) 126 | { 127 | struct sigaction act; 128 | memset(&act, 0, sizeof(struct sigaction)); 129 | sigemptyset(&act.sa_mask); 130 | act.sa_sigaction = handler; 131 | act.sa_flags = SA_SIGINFO | SA_ONSTACK; 132 | 133 | sigaction(SIGSEGV, &act, &def_sigsegv); 134 | 135 | hareas = NULL; 136 | } 137 | 138 | /* mmap hijack */ 139 | void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset) 140 | { 141 | int n_pages; 142 | 143 | size_t quot = length / pagesize; 144 | size_t rem = length % pagesize; 145 | 146 | /* Compute the number of pages */ 147 | if(rem == 0) 148 | n_pages = quot; 149 | else 150 | n_pages = quot + 1; 151 | 152 | if (real_mmap == NULL){ 153 | real_mmap = (void *(*)(void *, size_t, int, int, int, off_t)) dlsym(RTLD_NEXT, "mmap"); 154 | } 155 | 156 | if(prot >= (PROT_EXEC | PROT_WRITE)) { 157 | 158 | int intended_permissions = prot; 159 | prot &= ~PROT_WRITE; 160 | 161 | void *real_address = real_mmap(addr, length, prot, flags, fd, offset); 162 | 163 | /* Create an entry for each page allocated by the mmap */ 164 | for (int i = 0; i < n_pages; i++){ 165 | hareas = g_slist_insert_sorted (hareas, (gpointer) new_marea(real_address + (i * pagesize), intended_permissions), compare); 166 | } 167 | 168 | return real_address; 169 | 170 | } else { 171 | 172 | return real_mmap(addr, length, prot, flags, fd, offset); 173 | } 174 | } 175 | 176 | /* mprotect hijack */ 177 | int mprotect(void *__addr, size_t __len, int __prot) 178 | { 179 | int n_pages; 180 | 181 | size_t quot = __len / pagesize; 182 | size_t rem = __len % pagesize; 183 | 184 | /* Compute the number of pages */ 185 | if(rem == 0) 186 | n_pages = quot; 187 | else 188 | n_pages = quot + 1; 189 | 190 | if(__prot >= (PROT_EXEC | PROT_WRITE)){ 191 | 192 | /* Create an entry for each page covered by the mprotect */ 193 | for (int i = 0; i < n_pages; i++){ 194 | hareas = g_slist_insert_sorted (hareas, (gpointer) new_marea(__addr + (i * pagesize), __prot), compare); 195 | } 196 | 197 | /* Adjust the protection flags */ 198 | __prot &= ~PROT_WRITE; 199 | 200 | /* W and X permissions are put in different time instants */ 201 | } else if ((__prot == PROT_EXEC) || (__prot == (PROT_EXEC | PROT_READ))) { 202 | dump(__addr, __len); 203 | } 204 | 205 | if (real_mprotect == NULL){ 206 | real_mprotect = (int (*)(void *, size_t, int)) dlsym(RTLD_NEXT, "mprotect"); 207 | } 208 | return real_mprotect(__addr, __len, __prot); 209 | } 210 | 211 | void dump(void *addr, size_t __len) 212 | { 213 | /* Global counter for epochs */ 214 | FILE *counter; 215 | if(access(counter_filename, F_OK) != -1) { 216 | 217 | counter = fopen(counter_filename, "r+"); 218 | } else { 219 | 220 | counter = fopen(counter_filename, "w+"); 221 | fprintf(counter, "%d", 0); 222 | } 223 | 224 | if(counter == NULL){ 225 | printf("Unable to create/open file.\n"); 226 | exit(EXIT_FAILURE); 227 | } 228 | 229 | fscanf(counter, "%d", ¤t_epoch); 230 | 231 | FILE *fPtr; 232 | char title[100]; 233 | sprintf(title, "%p_%d", addr, current_epoch); 234 | 235 | fPtr = fopen(title, "wb"); 236 | if(fPtr == NULL){ 237 | printf("Unable to create/open file.\n"); 238 | exit(EXIT_FAILURE); 239 | } 240 | 241 | /* Dump */ 242 | fwrite(addr, __len, 1, fPtr); 243 | 244 | /* Global counter update */ 245 | fclose(counter); 246 | counter = fopen(counter_filename, "w"); 247 | fprintf(counter, "%d", ++current_epoch); 248 | 249 | fclose(fPtr); 250 | fclose(counter); 251 | } 252 | 253 | /* Allocates space for a new struct marea */ 254 | struct marea *new_marea(void *addr, int prot) 255 | { 256 | struct marea *new_m = (struct marea *) malloc(sizeof(struct marea)); 257 | if (new_m) { 258 | new_m->addr = addr; 259 | new_m->prot = prot; 260 | } 261 | 262 | return new_m; 263 | } 264 | 265 | /* Frees the allocated space for struct marea */ 266 | void free_marea(gpointer marea) 267 | { 268 | free((struct marea *) marea); 269 | } 270 | 271 | /* Order criterion for the list --> growing addresses */ 272 | int compare(gconstpointer a, gconstpointer b) 273 | { 274 | GSList *elem_a = (GSList *)a; 275 | GSList *elem_b = (GSList *)b; 276 | 277 | return ((struct marea *) elem_a)->addr > ((struct marea *) elem_b)->addr; 278 | } 279 | 280 | int search(gconstpointer a, gconstpointer b) 281 | { 282 | 283 | GSList *target_elem = (GSList *)a; 284 | void *addr_given = (void *)b; 285 | 286 | void *start_addr_iter = ((struct marea *) target_elem)->addr; 287 | void *end_addr_iter = start_addr_iter + pagesize -1; 288 | 289 | return !(addr_given >= start_addr_iter && addr_given <= end_addr_iter); 290 | } 291 | --------------------------------------------------------------------------------