diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ae64cba --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*~ +\#*\# +.\#* +result +build diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e212642 --- /dev/null +++ b/LICENSE @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3c2230d --- /dev/null +++ b/Makefile @@ -0,0 +1,47 @@ +CC ?= arm-remarkable-linux-gnueabi-gcc +LD ?= arm-remarkable-linux-gnueabi-ld +AR ?= arm-remarkable-linux-gnueabi-ar +OBJCOPY ?= arm-remarkable-linux-gnueabi-objcopy + +.PHONY: all clean + +all: build/libqsgepaper_extract_info +all: build/payload.bin +all: build/libqsgepaper-snoop.so build/libqsgepaper-snoop-standalone.a +clean: + rm -rf build + +build: + mkdir -p build +build/%.o: %.c | build + $(CC) $(CFLAGS) -c $< -o $@ $(LDFLAGS) +build/%.o: %.s | build + $(CC) $(ASFLAGS) -c $< -o $@ $(LDFLAGS) +build/%.a: | build + $(AR) cr $@ $^ +build/%.so: | build + $(CC) -shared -z defs -o $@ $^ $(LDFLAGS) +build/%.xz: build/% | build + xz -f -k $< + +build/extract_info.o: cached_info.h +build/libqsgepaper_extract_info: build/extract_info.o | build + $(CC) $< -o $@ -Wl,--start-group -lunicorn -larm-softmmu -Wl,--end-group -lpthread +build/libqsgepaper_extract_info.bin: build/libqsgepaper_extract_info.xz + $(OBJCOPY) -I binary -O elf32-littlearm -B arm $< $@ + +build/payload-a.o: private ASFLAGS=-ffreestanding +build/payload-c.o: private CFLAGS=-ffreestanding -fno-stack-protector -fPIE -fno-plt -mpic-data-is-text-relative +build/payload.o: build/payload-a.o build/payload-c.o payload.ld | build + $(LD) -Tpayload.ld -s build/payload-c.o build/payload-a.o -o $@ +build/payload.bin: build/payload.o + $(OBJCOPY) -O binary -j .binary $< $@ + +build/inject.o: cached_info.h +build/inject-standalone.o: build/inject.o inject-standalone.ld build/libqsgepaper_extract_info.bin build/payload.o | build + $(LD) -Tinject-standalone.ld -i -o $@ build/inject.o + +build/libqsgepaper-snoop-standalone.a: build/inject-standalone.o + +build/libqsgepaper-snoop.so: build/inject.o +build/libqsgepaper-snoop.so: private override LDFLAGS += -lcrypto -llzma diff --git a/README.md b/README.md new file mode 100644 index 0000000..3f02e07 --- /dev/null +++ b/README.md @@ -0,0 +1,75 @@ +# Introduction + +This library provides damage tracking information version 2 of the +[reMarkable tablet](https://remarkable.com). Unlike the reMarkable 1, +the reMarkable 2 does not use the integrated e-paper display +controller on the SoC used for the system, but instead drives the +display directly from software. This makes it impossible to extract +damage information from the (nonexistent) hardware driver, which was +the approach taken on the rM1. + +It is therefore necessary to inject code into the process that _is_ +controlling the framebuffer in order to provide notification of damage +update. In all known production software, the display is controlled by +executables using the proprietary static library `libqsgepaper.a`. The +purpose of this project, therefore, is to inject code into a +`libqsgepaper.a`-based binary that exports the software framebuffer to +which content is drawn, as well as damage notifications whenever an +update is sent to the display. + +In order to work on as wide a range of driving binaries as possible, +this library avoids using hardcoded addresses or similar techniques. +Instead, it uses data taken from Qt metaobjects that are part of the +interface of `libqsgepaper.a` in order to locate relevant functions +and data. + +It is relatively easy to find the Qt metaobject in the data section of +the binary@riving the display. Unfortunately, this does not give +direct access to the virtual framebuffer, or to the function used to +notify the display that an update is present. In order to find these +things, the [Unicorn Engine](https://www.unicorn-engine.org/) emulator +is used to emulate the Qt static meta-call function (when given +appropriate parameters). This uses the fact that `libqsgepaper.a` +passes the framebuffer address to `QImage::fill` in the (inlined) +`clearScreen` function, the fact that `sendUpdate` is not inlined into +`QObject::metacall`, and little else. + +Once the appropriate addresses have been found, a writable and +executable page is `mmap`d via ptrace()ing the target process, and a +payload built from [payload-c.c](./payload-c.c) and +[payload-a.s](./payload-a.s) is injected. The preamble of the +`sendUpdate` function in the original process is overwritten to +redirect to a portion of the payload which saves the information +damaged, runs the original `sendUpdate`,and then exports the damage +information over a Unix domain socket. The payload also includes +initialization code that replaces the private anonymous mapping of the +framebuffer with a shared mapping backed by an in-memory file from +`memfd_create` and connects to a Unix domain socket opened by the +process requesting the information. + +The necessary addresses appear to in practice remain stable over +multiple executions of the same binary, as the `EPFramebuffer` class +which is being hooked into is a singleton and address-space layout +randomization is not used on the reMarkable. Therefore, in order to +improve startup time and minimize resource usage, the Unicorn-based +emulation analysis is not run every time, but rather stored in a +cache. + +# Building + +The supported way to build this is via the +[Nix](https://nixos.org/nix) package manager, through the +[nix-remarkable](https://github.com/peter-sa/nix-remarkable) +expressions. To build just this project via `nix build` from this +repo, download it into the `pkgs/` directory of `nix-remarkable`. + +For other systems, the [Makefile](./Makefile) provides the necessary +commands. A suitable build of the Unicorn engine (static for the +standalone static library) is required. + +Prebuilt binaries are available in the [Releases +tab](https://github.com/pl-semiotics/libqsgepaper-snoop/releases). + +# Usage + +See [libqsgepaper-snoop.h](./libqsgepaper-snoop.h). diff --git a/cached_info.h b/cached_info.h new file mode 100644 index 0000000..0cc485d --- /dev/null +++ b/cached_info.h @@ -0,0 +1,31 @@ +#ifndef CACHED_INFO_H_ +#define CACHED_INFO_H_ + +#include + +#include "private.h" + +#define HEADER_MAGIC "libqsgepaper-snoop cached info v1\n" +#define SIZE_BYTE sizeof(HEADER_MAGIC) +#define STATE_BEGIN sizeof(HEADER_MAGIC) + sizeof(uint); + +struct check_bit { + uint addr; + uint eval; +}; +struct cached_state { + uint qimage_bits_addr_addr; + uint mmap_addr_addr; + uint fb_addr; + uint sendUpdate_addr; + /* the user code could use this directly, but really it's used for + * checking that nothing's changed. however, since we might have + * injected already, it doesn't go in cbits below. + */ + uint su_preamble[N_PREAMBLE_INSTRS]; + /* for verification that this is the correct executable only */ + uint ncbits; + struct check_bit cbits[]; +}; + +#endif /* CACHED_INFO_H_ */ diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..321234a --- /dev/null +++ b/default.nix @@ -0,0 +1 @@ +(import ../.. {}).rmPkgs.libqsgepaper-snoop diff --git a/derivation.nix b/derivation.nix new file mode 100644 index 0000000..3eb9896 --- /dev/null +++ b/derivation.nix @@ -0,0 +1,19 @@ +{ stdenv, lib, unicorn }: + +stdenv.mkDerivation { + pname = "libqsgepaper-snoop"; + version = "0.0.1"; + src = lib.cleanSource ./.; + buildInputs = [ unicorn ]; + installPhase = '' + mkdir -p $out/lib + cp build/libqsgepaper-snoop.so $out/lib + cp build/libqsgepaper-snoop-standalone.a $out/lib + mkdir -p $out/share/libqsgepaper-snoop + cp build/payload.bin $out/share/libqsgepaper-snoop + mkdir -p $out/libexec/libqsgepaper-snoop + cp build/libqsgepaper_extract_info $out/libexec/libqsgepaper-snoop + mkdir -p $out/include + cp libqsgepaper-snoop.h $out/include + ''; +} diff --git a/extract_info.c b/extract_info.c new file mode 100644 index 0000000..fa445b3 --- /dev/null +++ b/extract_info.c @@ -0,0 +1,480 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "cached_info.h" + +#define MO_METACALL_OFF 4*3 + +/***************************** + * UTILITIES/DATA STRUCTURES * + *****************************/ +static void diep(char *words) { + perror(words); + exit(1); +} + +static void die(const char* format, ...) { + va_list args; + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); + exit(1); +} + +/* a metaobject candidate, in a linked list of such */ +struct mo_cand { + uint mo_addr; + int clearScreen_num; + int sendUpdate_num; + struct mo_cand *next; +}; +/* the information we can get from the exe */ +struct exe_info { + struct mo_cand *mo; + uint qimage_fill_addr_addr; + uint qimage_bits_addr_addr; + uint mmap_addr_addr; +}; +/* that plus the stuff we pull out of a process */ +struct dyn_info { + pid_t pid; + const struct exe_info *static_info; + uc_engine *uc; + uint metacall_addr; + uint qimage_fill_addr; + uint fb_addr; + uint sendUpdate_addr; + uint su_preamble[N_PREAMBLE_INSTRS]; +}; + +/******************* + * STATIC ANALYSIS * + *******************/ +#define SHDR(n) ((Elf32_Shdr *)(x+hdr->e_shoff+n*hdr->e_shentsize)) +#define NSHDR (hdr->e_shnum ? hdr->e_shnum : SHDR(0)->sh_size) +#define SKIP_RESERVED_SECTIONS(i) if (i == 0 || (i >= SHN_LORESERVE && i <= SHN_HIRESERVE)) { continue; } +#define ELF_MAGIC {0x7f,'E','L','F',ELFCLASS32,ELFDATA2LSB,EV_CURRENT,ELFOSABI_LINUX} +#define QSMOA_MANGLED "_ZN7QObject16staticMetaObjectE" +#define QIF_MANGLED "_ZN6QImage4fillEN2Qt11GlobalColorE" +#define QIB_MANGLED "_ZN6QImage4bitsEv" + +#define NOT_FOUND ((uint)-1) +static Elf32_Shdr *find_section(char *x, Elf32_Ehdr *hdr, uint addr) { + for (int k = 0; k < NSHDR; k++) { + SKIP_RESERVED_SECTIONS(k) + Elf32_Shdr *s = SHDR(k); + if (s->sh_type == SHT_NOBITS) { continue; } + if (s->sh_addr <= addr && s->sh_addr + s->sh_size > addr) { + return s; + } + } + return NULL; +} +static int addr_to_off_s(char *x, Elf32_Shdr *s, uint addr) { + if (!s) { return NOT_FOUND; } + if (s->sh_addr <= addr && s->sh_addr + s->sh_size > addr) { + return addr-s->sh_addr+s->sh_offset; + } + return NOT_FOUND; +} +static int addr_to_off(char *x, Elf32_Ehdr *hdr, uint addr) { + return addr_to_off_s(x, find_section(x, hdr, addr), addr); +} +static int invalid_off(char *x, Elf32_Shdr *s, uint off) { + return !(s && s->sh_offset <= off && s->sh_offset + s->sh_size > off); +} +static uint off_to_addr(char *x, Elf32_Ehdr *hdr, uint off) { + for (int k = 0; k < NSHDR; k++) { + SKIP_RESERVED_SECTIONS(k) + Elf32_Shdr *s = SHDR(k); + if (s->sh_type == SHT_NOBITS) { continue; } + if (s->sh_offset <= off && s->sh_offset + s->sh_size > off) { + return off-s->sh_offset+s->sh_addr; + } + } + return NOT_FOUND; +} + +static struct mo_cand *search_for_epfb(char *x, Elf32_Ehdr *hdr, + size_t size, int qtsmoa, + struct mo_cand *cands) { + for (uint i = 0; i < size-16; i += 4) { + if (qtsmoa == *(uint*)(x+i)) { + Elf32_Shdr *sm = find_section(x, hdr, *(uint*)(x+i+8)); + uint m = addr_to_off_s(x, sm, *(uint*)(x+i+8)); + if (m == NOT_FOUND || invalid_off(x, sm, m+4)) { continue; } + uint cn = *(uint*)(x+m+4); + Elf32_Shdr *ss = find_section(x, hdr, *(uint*)(x+i+4)); + uint st = addr_to_off_s(x, ss, *(uint*)(x+i+4)+16*cn+12); + if (st == NOT_FOUND) { continue; } + if (invalid_off(x, ss, st-12+*(uint*)(x+st))) { continue; } + if (strcmp(x+st-12+*(uint*)(x+st), "EPFramebuffer")) { continue; } + + if (invalid_off(x, sm, m+16) || invalid_off(x, sm, m+20)) { continue; } + int nmeth = *(uint*)(x+m+16); + int methoff = *(uint*)(x+m+20); + + int getInstance = -1; + struct mo_cand *next = malloc(sizeof(struct mo_cand)); + next->mo_addr = off_to_addr(x, hdr, i); + next->clearScreen_num = -1; + next->sendUpdate_num = -1; + next->next = cands; + if (!next) { diep("memory allocation failed"); } + for (int j = 0; j < nmeth; j++) { + if (invalid_off(x, sm, m+methoff*4+j*5*4)) { continue; } + uint noff = *(uint*)(x+m+methoff*4+j*5*4); + uint na = addr_to_off_s(x, ss, *(uint*)(x+i+4)+16*noff+12); + if (invalid_off(x, ss, na)) { continue; } + if (invalid_off(x, ss, na-12+*(uint*)(x+na))) { continue; } + if (!strcmp(x+na-12+*(uint*)(x+na), "clearScreen") && + next->clearScreen_num < 0) { + next->clearScreen_num = j; + } + if (!strcmp(x+na-12+*(uint*)(x+na), "sendUpdate") && + next->sendUpdate_num < 0) { + next->sendUpdate_num = j; + } + } + if (next->clearScreen_num >= 0 && next->sendUpdate_num >= 0) { + cands = next; + } else { + free(next); + } + } + } + return cands; +} + +static struct exe_info read_exe(char *path) { + int r; + if ((r = open(path, O_RDONLY)) < 0) { diep("open"); } + struct stat stat; + if (fstat(r, &stat)) { diep("stat"); } + char *x = mmap(NULL, stat.st_size, PROT_READ, MAP_PRIVATE, r, 0); + if (x == MAP_FAILED) { diep("mmap"); } + Elf32_Ehdr *hdr = (Elf32_Ehdr*)x; + char desired_magic[] = ELF_MAGIC; + if (strncmp(&desired_magic[0],&hdr->e_ident[0],8)) { die("bad elf\n"); } + + struct exe_info result = { + .mo = NULL, + .qimage_fill_addr_addr = -1, + .qimage_bits_addr_addr = -1, + .mmap_addr_addr = -1, + }; + int found = 0; + for (int i = 0; i < NSHDR; i++) { + SKIP_RESERVED_SECTIONS(i) + Elf32_Shdr *s = SHDR(i); + if (s->sh_type == SHT_DYNSYM) { + Elf32_Shdr *sstr = SHDR(s->sh_link); + for (int j = 0; j * s->sh_entsize < s->sh_size; j++) { + Elf32_Sym *sym = (Elf32_Sym *)(x+s->sh_offset+j*s->sh_entsize); + if (!strcmp(x+sstr->sh_offset+sym->st_name, QSMOA_MANGLED)) { + result.mo = search_for_epfb(x, hdr, + stat.st_size, sym->st_value, + result.mo); + } + } + } + if (s->sh_type == SHT_REL) { + Elf32_Shdr *ssym = SHDR(s->sh_link); + Elf32_Shdr *sstr = SHDR(ssym->sh_link); + for (int j = 0; j * s->sh_entsize < s->sh_size; j++) { + Elf32_Rel *r = (Elf32_Rel *)(x+s->sh_offset+j*s->sh_entsize); + Elf32_Sym *sym = (Elf32_Sym *) + (x+ssym->sh_offset+ELF32_R_SYM(r->r_info)*ssym->sh_entsize); + if (!strcmp(x+sstr->sh_offset+sym->st_name, "mmap")) { + result.mmap_addr_addr = r->r_offset; + } + if (!strcmp(x+sstr->sh_offset+sym->st_name, QIF_MANGLED)) { + result.qimage_fill_addr_addr = r->r_offset; + } + if (!strcmp(x+sstr->sh_offset+sym->st_name, QIB_MANGLED)) { + result.qimage_bits_addr_addr = r->r_offset; + } + } + } + } + return result; +} + +/******************** + * DYNAMIC ANALYSIS * + ********************/ +static void wait_for_stop(pid_t pid, int sig) { + int status; + do { + while (pid != waitpid(pid, &status, 0)) {} + if (WIFEXITED(status) || WIFSIGNALED(status)) { + die("Framebuffer controlling process %d existed\n", pid); + } + } while (!WIFSTOPPED(status) || WSTOPSIG(status) != sig); +} + +static void get_addrs(struct dyn_info *info) { + errno = 0; + info->metacall_addr = ptrace(PTRACE_PEEKDATA, info->pid, + info->static_info->mo->mo_addr+12, 0); + if (errno) { diep("peek metacall addr"); } + errno = 0; + info->qimage_fill_addr = ptrace(PTRACE_PEEKDATA, info->pid, + info->static_info->qimage_fill_addr_addr, 0); + if (errno) { diep("peek qimage_fill addr"); } +} + +static bool uc_map_cb(uc_engine *uc, uc_mem_type type, + uint64_t address, int size, + int64_t value, pid_t *pid) { + /* todo (perhaps): coalescing optimizations */ + uint64_t page_base = address & ~0x0FFF; + size += address-page_base; + address = page_base; + do { + uc_err r = uc_mem_unmap(uc, address, 4096); + if (r != UC_ERR_OK && r != UC_ERR_NOMEM) { die("uc_mem_unmap %u\n", r); } + r = uc_mem_map(uc, address, 4096, UC_PROT_ALL); + if (r != UC_ERR_OK) { die("uc_mem_map %u\n", r); } + unsigned long bytes; + for (int p = address; p < address + 4096; p += 4) { + errno = 0; + bytes = ptrace(PTRACE_PEEKDATA, *pid, p, 0); + if (errno) { diep("peek"); } + uc_mem_write(uc, p, &bytes, 4); + } + size -= 4096; address += 4096; + } while (size > 0); + return 1; +} +static void init_uc(struct dyn_info *info) { + uc_err r; + r = uc_open(UC_ARCH_ARM, UC_MODE_LITTLE_ENDIAN|UC_MODE_ARM, &info->uc); + if (r != UC_ERR_OK) { die("uc_open %u\n", r); } + uc_hook h; + r = uc_hook_add(info->uc, &h, UC_HOOK_MEM_UNMAPPED, + uc_map_cb, &info->pid, + 0x00000000, 0xFFFFFFFF); + if (r != UC_ERR_OK) { die("uc hook mem %u\n", r); } +} + +static void close_uc(struct dyn_info *info) { + uc_close(info->uc); + info->uc = NULL; +} + +struct qtmc_arg { + int nwords; + uint *words; +}; +static void setup_metacall(struct dyn_info *info, int call_index, + int this, int argc, struct qtmc_arg *argv) { + + struct user_regs regs; + if (ptrace(PTRACE_GETREGS, info->pid, NULL, ®s)) { diep("get uc regs"); } + + int len = 1; // for some reason, the generated metacall ignores one + for (int i = 0; i < argc; ++i) { + len += argv[i].nwords+1; // +1 for the pointer + } + if (len % 2) { len += 1; } // 8-byte sp alignment on ARM + + uc_err r; + + uint sp = regs.uregs[13] - 4*len; + uc_map_cb(info->uc, UC_ERR_WRITE_UNMAPPED, sp, 4*len, 0, &info->pid); + uint nextarg = sp + 4*(argc+1); + for (int i = 0; i < argc; ++i) { + r = uc_mem_write(info->uc, sp + 4*(i+1), &nextarg, 4); + if (r != UC_ERR_OK) { die("uc mc write arg ptr %u\n", r); } + r = uc_mem_write(info->uc, nextarg, argv[i].words, 4*argv[i].nwords); + if (r != UC_ERR_OK) { die("uc mc write arg %u\n", r); } + nextarg += 4*argv[i].nwords; + } + + int zero = 0; + void *regvals[] = { &this, &zero, &call_index, &sp, &sp }; + int wregs[] = { + UC_ARM_REG_R0, UC_ARM_REG_R1, UC_ARM_REG_R2, + UC_ARM_REG_R3, UC_ARM_REG_SP + }; + r = uc_reg_write_batch(info->uc, wregs, regvals, 4); + if (r != UC_ERR_OK) { die("uc mc write regs %u\n", r); } +} + +static void find_fb_address(struct dyn_info *info) { + setup_metacall(info, info->static_info->mo->clearScreen_num, 0, 0, NULL); + + uc_err r = + uc_emu_start(info->uc, info->metacall_addr, info->qimage_fill_addr, 0, 0); + if (r != UC_ERR_OK) { die("uc emu start %u\n", r); } + + r = uc_reg_read(info->uc, UC_ARM_REG_R0, &info->fb_addr); + if (r != UC_ERR_OK) { die("uc read fb address %u\n", r); } +} + +struct fsua_cc_data { + int found_call; + uint pc; + uint magic[5]; +}; +static void find_sendUpdate_code_callback( + uc_engine *uc, uint64_t address, uint32_t size, struct fsua_cc_data *d) { + uint pc; + uc_err r = uc_reg_read(uc, UC_ARM_REG_PC, &pc); + if (r != UC_ERR_OK) { die("uc pc read magic %u\n", r); } + + if (!d->found_call) { + uint nregvals[4]; + void *nregvaladdrs[4] = + { &nregvals[0], &nregvals[1], &nregvals[2], &nregvals[3] }; + int nregs[4] = + { UC_ARM_REG_R0, UC_ARM_REG_R1, UC_ARM_REG_R2, UC_ARM_REG_R3 }; + r = uc_reg_read_batch(uc, nregs, nregvaladdrs, 4); + if (r != UC_ERR_OK) { die("uc reg read magic %u\n", r); } + + uint sp, nreg5; + r = uc_reg_read(uc, UC_ARM_REG_SP, &sp); + if (r != UC_ERR_OK) { die("uc read sp %u\n", r); } + r = uc_mem_read(uc, sp, &nreg5, 4); + if (r != UC_ERR_OK) { die("uc sp read magic %u\n", r); } + + if (!memcmp(nregvals, d->magic, 4*4) && + nreg5 == d->magic[4]) { + d->found_call = 1; + r = uc_reg_read(uc, UC_ARM_REG_PC, &pc); + if (r != UC_ERR_OK) { die("uc pc read magic %u\n", r); } + } + } else if (pc != d->pc+4) { + r = uc_emu_stop(uc); + if (r != UC_ERR_OK) { die("uc emu stop %u\n", r); } + } + + d->pc = pc; +} +static uint find_sendUpdate_address(struct dyn_info *info) { + struct fsua_cc_data cd = { + .found_call = 0, + .pc = 0, + /* randomly chosen magic numbers to identify the correct call insn*/ + .magic = { 0xA08B5335, 0xFCE554AE, 0xF7B8C80F, 0x4730C327, 0x5F370907 }, + }; + uint zero = 0; + struct qtmc_arg args[] = { + { .nwords = 4, .words = &cd.magic[1], }, + /* for compatibility with various variants, stick a bunch of extra + * (valid) pointers to 0 in here */ + { .nwords = 1, .words = &zero, }, + { .nwords = 1, .words = &zero, }, + { .nwords = 1, .words = &zero, }, + { .nwords = 1, .words = &zero, }, + }; + + setup_metacall(info, info->static_info->mo->sendUpdate_num, + cd.magic[0], 5, args); + + uc_hook h; + uc_err r = uc_hook_add(info->uc, &h, UC_HOOK_CODE, + find_sendUpdate_code_callback, &cd, + 0x00000000, 0xFFFFFFFF); + if (r != UC_ERR_OK) { die("uc hook code %u\n", r); } + + r = uc_emu_start(info->uc, info->metacall_addr, 0, 0, 0); + info->sendUpdate_addr = cd.pc; +} + +/* warning: not the same as get_sendUpdate_preamble in inject.c! */ +static void get_sendUpdate_preamble(struct dyn_info *info) { + for (int i = 0; i < N_PREAMBLE_INSTRS; ++i) { + errno = 0; + info->su_preamble[i] = + ptrace(PTRACE_PEEKTEXT, info->pid, info->sendUpdate_addr + 4*i, 0); + if (errno) { diep("peek su instr"); } + } +} + +static void extract_from_process(struct dyn_info *info) { + if (ptrace(PTRACE_ATTACH, info->pid, NULL, NULL)) { diep("attach"); } + wait_for_stop(info->pid, SIGSTOP); + + get_addrs(info); + init_uc(info); + find_fb_address(info); + find_sendUpdate_address(info); + close_uc(info); + get_sendUpdate_preamble(info); +} + +/********** + * OUTPUT * + **********/ +static void rmkdir(char *filename) { + char *n2 = malloc(strlen(filename)); + strcpy(n2, filename); + char *d = dirname(n2); + if (faccessat(AT_FDCWD, d, F_OK, AT_EACCESS) != 0) { + rmkdir(d); + if (mkdir(d, S_IRWXU) < 0) { diep("mkdir"); }; + } + free(n2); +} +static void write_all(int fd, void *buf, size_t size) { + do { + int n = write(fd, buf, size); + if (n < 0) { + if (errno == EINTR) { continue; } + else { diep("write"); } + } + buf += n; size -= n; + } while (size > 0); +} +#define ncbits_ 1 +#define state_size (sizeof(struct cached_state)+ncbits_*sizeof(struct check_bit)) +static void write_cache(const struct dyn_info *info, char *filename) { + char struct_mem[state_size] = {0}; + struct cached_state *state = (struct cached_state *)struct_mem; + state->qimage_bits_addr_addr = info->static_info->qimage_bits_addr_addr; + state->mmap_addr_addr = info->static_info->mmap_addr_addr; + state->fb_addr = info->fb_addr; + state->sendUpdate_addr = info->sendUpdate_addr; + for (int i = 0; i < N_PREAMBLE_INSTRS; i++) { + state->su_preamble[i] = info->su_preamble[i]; + } + state->ncbits = ncbits_; + state->cbits[0].addr = info->static_info->mo->mo_addr + MO_METACALL_OFF; + state->cbits[0].eval = info->metacall_addr; + rmkdir(filename); + int out = open(filename, O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); + if (out < 0) { diep("open output"); } + write_all(out, HEADER_MAGIC, sizeof(HEADER_MAGIC)); + uint size = state_size; + write_all(out, &size, 4); + write_all(out, state, state_size); +} + +int main(int argc, char *argv[]) { + pid_t pid = atoi(argv[1]); + char *exename; + if (asprintf(&exename, "/proc/%d/exe", pid) < 0) { die("asprintf failed\n"); } + if (!exename) { die("asprintf\n"); } + struct exe_info static_info = read_exe(exename); + struct dyn_info info = { + .pid = pid, + .static_info = &static_info, + }; + extract_from_process(&info); + write_cache(&info, argv[2]); + return 0; +} diff --git a/inject-standalone.ld b/inject-standalone.ld new file mode 100644 index 0000000..c955ec7 --- /dev/null +++ b/inject-standalone.ld @@ -0,0 +1,13 @@ +SECTIONS +{ + .extract_info : { + extract_info_begin = . ; + build/libqsgepaper_extract_info.bin(.data) + extract_info_end = . ; + } + .payload : { + injectable_begin = . ; + build/payload.o(.binary) + injectable_end = . ; + } +} diff --git a/inject.c b/inject.c new file mode 100644 index 0000000..b4c37a8 --- /dev/null +++ b/inject.c @@ -0,0 +1,566 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "cached_info.h" + +/* Since the display seems a bit delicate, prefer to kill xochitl if + * something goes wrong---it seems less likely to cause damage given + * prevent_frying_pan than allowing xochitl to continue in some + * undefined state. + */ +int kill_fb_proc_when_dying = -1; +/* Similarly, but for the process that we use to get a name for the + * unix socket. + */ +int kill_binder_when_dying = -1; +static void diep(char *words) { + perror(words); + if (kill_fb_proc_when_dying != -1) { kill(kill_fb_proc_when_dying, SIGKILL); } + if (kill_binder_when_dying != -1) { kill(kill_binder_when_dying, SIGKILL); } + exit(1); +} + +static void die(const char* format, ...) { + va_list args; + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); + if (kill_fb_proc_when_dying != -1) { kill(kill_fb_proc_when_dying, SIGKILL); } + if (kill_binder_when_dying != -1) { kill(kill_binder_when_dying, SIGKILL); } + exit(1); +} + +struct dyn_info { + pid_t pid; + struct cached_state *cached; + uint qimage_bits_addr; + uint mmap_addr; + uint su_preamble[N_PREAMBLE_INSTRS]; + + pid_t socket_binder_pid; + int socket_fd; + uint scratch_page; + + uint fb_bits_addr; + int fb_fd; +}; + +static pid_t find_process(void) { + struct stat sbf; + if (stat("/dev/fb0", &sbf)) { diep("stat fb0"); } + DIR *d = opendir("/proc"); + if (!d) { diep("proc"); } + errno = 0; + pid_t fbpid = 0; + struct dirent *de; + while (de = readdir(d)) { + pid_t pid = 0; + for (char* p = de->d_name; *p; p++) { + if ('0' > *p || '9' << *p) { goto bad_dir_0; } + pid *= 10; + pid += *p-'0'; + } + char *fdn; + if (asprintf(&fdn, "/proc/%s/fd/", de->d_name) < 0) { goto bad_dir_0; }; + if (!fdn) { die("asprint failed\n"); } + DIR *fdd = opendir(fdn); + if (!fdd) { goto bad_dir_1; } + errno = 0; + struct dirent *fde; + while (fde = readdir(fdd)) { + char *p; + for (p = fde->d_name; *p; p++) { + if ('0' > *p || '9' << *p) { break; } + } + if (*p) { continue; } + struct stat fdsbf; + char *fdpath = NULL; + if (asprintf(&fdpath, "/proc/%s/fd/%s", de->d_name, fde->d_name) < 0) { + errno = 0; continue; + } + if (stat(fdpath, &fdsbf)) { errno = 0; continue; } + if (sbf.st_dev == fdsbf.st_dev && sbf.st_ino == fdsbf.st_ino) { + fbpid = pid; + closedir(fdd); + free(fdpath); + free(fdn); + goto found_entry; + } + errno = 0; + } + closedir(fdd); + bad_dir_1: + free(fdn); + bad_dir_0: + errno = 0; + } + if (errno) { diep("proc entry"); } +found_entry: + if (!fbpid) { die("no process using fb0!\n"); } + closedir(d); + return fbpid; +} + +#define CACHE_DIR_ENV "LIBQSGEPAPER_SNOOP_CACHE_DIR" +#define DEFAULT_CACHE_DIR "/home/root/.cache/libqsgepaper-snoop" +char *find_cache_file(struct dyn_info *info, const char *exename) { + char *prefix = getenv(CACHE_DIR_ENV); + if (!prefix) { prefix = DEFAULT_CACHE_DIR; } + size_t file_name_size = EVP_MAX_MD_SIZE*2+strlen(prefix)+1; + char *cache_file_name = malloc(file_name_size); + if (!cache_file_name) { diep("malloc cache_file_name"); } + strcpy(cache_file_name, prefix); + + int fd = open(exename, O_RDONLY); + if (fd < 0) { diep("open"); } + struct stat stat; + if (fstat(fd, &stat)) { diep("stat"); } + char *x = mmap(NULL, stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (x == MAP_FAILED) { diep("mmap"); } + + EVP_MD_CTX *mdctx = EVP_MD_CTX_create(); + if (!mdctx) { die("md_ctx_new"); } + if (EVP_DigestInit_ex(mdctx, EVP_sha256(), NULL) != 1) { die("digestinit"); } + if (EVP_DigestUpdate(mdctx, x, stat.st_size) != 1) { die("digestupdate"); } + size_t digest_len; + char *digest_start = &cache_file_name[file_name_size-EVP_MAX_MD_SIZE]; + if (EVP_DigestFinal_ex(mdctx, digest_start, &digest_len) != 1) { die("digestfinal"); } + EVP_MD_CTX_destroy(mdctx); + + cache_file_name[strlen(prefix)] = '/'; + for (int i = 0; i < digest_len; ++i) { + sprintf(cache_file_name+strlen(prefix)+1+2*i, "%02x", digest_start[i]); + } + + munmap(x, stat.st_size); + close(fd); + + return cache_file_name; +} + +#define EXTRACT_INFO_ENV "LIBQSGEPAPER_SNOOP_EXTRACT_INFO" +char __attribute__((weak)) extract_info_begin = 0; +char __attribute__((weak)) extract_info_end = 0; +static void extract_cached_info(struct dyn_info *info, char *cache_path) { + char *external = getenv(EXTRACT_INFO_ENV); + int fd; + char *start = &extract_info_begin; + size_t size = &extract_info_end - &extract_info_begin; + char *exe; + if (external) { + exe = external; + } else { + printf("Uncompressing extraction program\n"); + /* check for first character of XZ magic */ + if (*start != 0xFD) { die("Need an extract_info binary!\n"); } + + fd = memfd_create("extract_info", 0); + lzma_stream strm = LZMA_STREAM_INIT; + lzma_ret ret = lzma_stream_decoder(&strm, UINT64_MAX, 0); + if (ret != LZMA_OK) { die("lzma_stream_decoder %u\n", ret); } + strm.next_in = start; + strm.avail_in = size; + char outbuf[BUFSIZ]; + while (1) { + strm.next_out = outbuf; + strm.avail_out = sizeof(outbuf); + ret = lzma_code(&strm, LZMA_RUN); + size_t len = sizeof(outbuf) - strm.avail_out; + char *p = outbuf; + if (!strm.avail_out || ret == LZMA_STREAM_END) { + while (len > 0) { + size_t n = write(fd, p, len); + len -= n; + p += n; + } + } + if (ret != LZMA_OK) { + if (ret == LZMA_STREAM_END) { break; } + else { die("lzma_code %u\n", ret); } + } + } + if (asprintf(&exe, "/proc/self/fd/%d", fd) < 0) { die("asprintf exe\n"); } + } + + int child = fork(); + if (child < 0) { diep("fork"); } + if (!child) { + printf("Running extraction pass %d\n", getpid()); + char *pid; + if (asprintf(&pid, "%d", info->pid) < 0) { die("asprintf pid\n"); }; + char *const argv[] = { exe, pid, cache_path, NULL }; + execve(exe, argv, NULL); + } else { + int status; + while (child != waitpid(child, &status, 0) || !WIFEXITED(status)) {}; + } + + if (!external) { + madvise(start, size, MADV_DONTNEED); + close(fd); + } +} + +static void read_all(int fd, void *buf, size_t size) { + do { + int n = read(fd, buf, size); + if (n < 0) { + if (errno == EINTR) { continue; } + else { diep("read"); } + } + buf += n; size -= n; + } while (size > 0); +} +static void load_cached_info(struct dyn_info *info) { + char *exename; + if (asprintf(&exename, "/proc/%d/exe", info->pid) < 0) { + die("asprintf failed\n"); + } + if (!exename) { die("asprint failed\n"); } + char *cache_path = find_cache_file(info, exename); + + if (faccessat(AT_FDCWD, cache_path, F_OK, AT_EACCESS) != 0) { + printf("No cached info found for %s\n", exename); + extract_cached_info(info, cache_path); + } + + int fd = open(cache_path, O_RDONLY); + if (fd < 0) { diep("open cache"); } + char magic[sizeof(HEADER_MAGIC)]; + read_all(fd, magic, sizeof(HEADER_MAGIC)); + if (strcmp(magic, HEADER_MAGIC)) { die("bad magic\n"); } + uint size; + read_all(fd, &size, 4); + info->cached = malloc(size); + if (!info->cached) { diep("malloc"); } + read_all(fd, info->cached, size); +} + +static void wait_for_stop(pid_t pid, int sig) { + int status; + do { + while (pid != waitpid(pid, &status, 0)) {} + if (WIFEXITED(status) || WIFSIGNALED(status)) { + die("Framebuffer controlling process %d existed\n", pid); + } + } while (!WIFSTOPPED(status) || WSTOPSIG(status) != sig); +} +static void attach(struct dyn_info *info) { + if (ptrace(PTRACE_ATTACH, info->pid, NULL, NULL)) { diep("attach"); } + wait_for_stop(info->pid, SIGSTOP); +} + +static void get_sendUpdate_preamble(struct dyn_info *info) { + for (int i = 0; i < N_PREAMBLE_INSTRS; ++i) { + errno = 0; + info->su_preamble[i] = + ptrace(PTRACE_PEEKTEXT, info->pid, + info->cached->sendUpdate_addr + 4*i, 0); + if (errno) { diep("peek su instr"); } + } + if ((info->su_preamble[0] & 0xFFF0F000) == 0xE300C000 && + (info->su_preamble[1] & 0xFFF0F000) == 0xE340C000 && + (info->su_preamble[2] == 0xE1A0F00C)) { + uint addr = + (info->su_preamble[0] & 0xFFF) | + ((info->su_preamble[0] >> 4) & 0xF000) | + ((info->su_preamble[1] & 0xFFF) << 16) | + ((info->su_preamble[1] & 0xF0000) << 12); + uint magic[4]; + for (int i = 0; i < 4; ++i) { + errno = 0; + magic[i] = + ptrace(PTRACE_PEEKTEXT, info->pid, addr - 4*INJ_SUPRHK_OFF + 4*i, 0); + if (errno) { diep("peek su magic"); } + } + if (magic[0] == INJ_MAGIC_0 && + magic[1] == INJ_MAGIC_1 && + magic[2] == INJ_MAGIC_2 && + magic[3] == INJ_MAGIC_3) { + for (int i = 0; i < N_PREAMBLE_INSTRS; ++i) { + errno = 0; + info->su_preamble[i] = + ptrace(PTRACE_PEEKTEXT, info->pid, + addr - 4*INJ_SUPRHK_OFF + 4*(INJ_SUPR_OFF + i), 0); + if (errno) { diep("peek su orig instr"); } + } + } + } +} +static void get_dyn_info(struct dyn_info *info) { + errno = 0; + info->qimage_bits_addr = ptrace(PTRACE_PEEKDATA, info->pid, + info->cached->qimage_bits_addr_addr, 0); + if (errno) { diep("peek qimage_bits addr"); } + errno = 0; + info->mmap_addr = ptrace(PTRACE_PEEKDATA, info->pid, + info->cached->mmap_addr_addr, 0); + if (errno) { diep("peek mmap addr"); } + + get_sendUpdate_preamble(info); +} + +void check_cached_info(struct dyn_info *info) { + for (int i = 0; i < info->cached->ncbits; ++i) { + errno = 0; + uint rval = ptrace(PTRACE_PEEKDATA, info->pid, + info->cached->cbits[i].addr, 0); + if (rval != info->cached->cbits[i].eval) { die("Bad cache cbit %d\n", i); } + } + for (int i = 0; i < N_PREAMBLE_INSTRS; ++i) { + if (info->su_preamble[i] != info->cached->su_preamble[i]) { + die("Bad cache preamble %d\n", i); + } + } +} + +void bind_socket(struct dyn_info *info) { + info->socket_fd = socket(AF_UNIX, SOCK_DGRAM, 0); + if (info->socket_fd < 0) { diep("socket"); } + struct sockaddr_un addr = {0}; + addr.sun_family = AF_UNIX; + strcpy(addr.sun_path, "socket"); + + info->socket_binder_pid = fork(); + if (info->socket_binder_pid < 0) { diep("fork"); } + if (!info->socket_binder_pid) { + if (unshare(CLONE_NEWNS)) { diep("unshare"); } + if (mount("/", "/", "", MS_PRIVATE|MS_REC, 0)) { diep("mount-p"); } + if (mount("tmpfs", "..", "tmpfs", 0, 0)) { diep("mount-t"); } + if (chdir("..")) { diep("chdir"); } + if (bind(info->socket_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + diep("bind"); + } + while (1) { pause(); } + } + kill_binder_when_dying = info->socket_binder_pid; +} + +void make_scratch_page(struct dyn_info *info) { + pid_t pid = info->pid; + struct user_regs regs; + if (ptrace(PTRACE_GETREGS, pid, NULL, ®s)) { diep("get orig regs"); } + struct user_regs inj_regs = regs; + inj_regs.uregs[15] = info->mmap_addr; + inj_regs.uregs[0] = 0; + inj_regs.uregs[1] = 4096; // PAGESIZ + inj_regs.uregs[2] = PROT_READ|PROT_WRITE|PROT_EXEC; + inj_regs.uregs[3] = MAP_PRIVATE|MAP_ANONYMOUS; + inj_regs.uregs[13] -= 8; + if (ptrace(PTRACE_POKEDATA, pid, regs.uregs[13]-8, -1)) { diep("mmap fd"); } + if (ptrace(PTRACE_POKEDATA, pid, regs.uregs[13]-4, 0)) { diep("mmap off"); } + if (ptrace(PTRACE_SETREGS, pid, NULL, &inj_regs)) { diep("mmap regs"); }; +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 3, 0) + int need_to_eat_syscall = 0; +#endif + kill_fb_proc_when_dying = info->pid; +wait_for_syscall_entry: + if (ptrace(PTRACE_SYSCALL, pid, NULL, 0)) { diep("syscall begin"); }; + wait_for_stop(pid, SIGTRAP); + siginfo_t sigdata; + if (ptrace(PTRACE_GETSIGINFO, pid, NULL, &sigdata)) { diep("get siginfo"); } + if (sigdata.si_code != SIGTRAP) { goto wait_for_syscall_entry; } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 3, 0) + struct ptrace_syscall_info sysinfo; + if (ptrace(PTRACE_GET_SYSCALL_INFO, pid, sizeof(struct ptrace_syscall_info), &sysinfo)) { diep("get sysinfo"); } + if (sysinfo.op != PTRACE_SYSCALL_INFO_ENTRY || + sysinfo.entry.nr != __NR_mmap2 || + sysinfo.entry.args[0] != inj_regs.uregs[0] || + sysinfo.entry.args[1] != inj_regs.uregs[1] || + sysinfo.entry.args[2] != inj_regs.uregs[2] || + sysinfo.entry.args[3] != inj_regs.uregs[3] || + sysinfo.entry.args[4] != -1 || + sysinfo.entry.args[5] != 0) { + goto wait_for_syscall_entry; + } +#else + if (need_to_eat_syscall) { + need_to_eat_syscall = 0; + goto wait_for_syscall_entry; + } + struct user_regs sys_regs; + if (ptrace(PTRACE_GETREGS, pid, NULL, &sys_regs)) { diep("get sysin regs"); } + if (sys_regs.uregs[7] != __NR_mmap2 || + sys_regs.uregs[0] != inj_regs.uregs[0] || + sys_regs.uregs[1] != inj_regs.uregs[1] || + sys_regs.uregs[2] != inj_regs.uregs[2] || + sys_regs.uregs[3] != inj_regs.uregs[3] || + sys_regs.uregs[4] != -1 || + sys_regs.uregs[5] != 0) { + need_to_eat_syscall = 1; + goto wait_for_syscall_entry; + } +#endif +wait_for_syscall_exit: + if (ptrace(PTRACE_SYSCALL, pid, NULL, 0)) { diep("syscall run"); }; + wait_for_stop(pid, SIGTRAP); + if (ptrace(PTRACE_GETSIGINFO, pid, NULL, &sigdata)) { diep("get siginfo"); } + if (sigdata.si_code != SIGTRAP) { goto wait_for_syscall_exit; } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 3, 0) + struct ptrace_syscall_info sysinfo; + if (ptrace(PTRACE_GET_SYSCALL_INFO, pid, sizeof(struct ptrace_syscall_info), &sysinfo)) { diep("get sysinfo"); } + if (sysinfo.op != PTRACE_SYSCALL_INFO_EXIT) { goto wait_for_syscall_exit; } + if (sysinfo.exit.is_error) { + if (sysinfo.exit.rval == -EINTR) { goto wait_for_syscall_entry; } + else { errno = -sysinfo.exit.rval; diep("mmap failed"); } + } + info->scratch_page = rval; +#else + if (ptrace(PTRACE_GETREGS, pid, NULL, &sys_regs)) { diep("get sysout regs"); } + if (sys_regs.uregs[0] <= 0 && sys_regs.uregs[0] >= -4095) { + if (sys_regs.uregs[0] == -EINTR) { goto wait_for_syscall_entry; } + errno = -sys_regs.uregs[0]; + diep("mmap failed"); + } + info->scratch_page = sys_regs.uregs[0]; +#endif + if (ptrace(PTRACE_SETREGS, pid, NULL, ®s)) { diep("restore regs"); } + kill_fb_proc_when_dying = -1; +} + +#define INJECTABLE_ENV "LIBQSGEPAPER_SNOOP_PAYLOAD" +uint __attribute__((weak)) injectable_begin = 0; +uint __attribute__((weak)) injectable_end = 0; +void load_injectable(struct dyn_info *info) { + uint *start = &injectable_begin; + size_t size = &injectable_end-&injectable_begin; + + char *external = getenv(INJECTABLE_ENV); + int fd; + if (external) { + fd = open(external, O_RDONLY); + if (fd < 0) { diep("open"); } + struct stat stat; + if (fstat(fd, &stat)) { diep("stat"); } + start = mmap(NULL, stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (start == MAP_FAILED) { diep("mmap"); } + size = stat.st_size/4; + } + + if (start[0] != INJ_MAGIC_0 || + start[1] != INJ_MAGIC_1 || + start[2] != INJ_MAGIC_2 || + start[3] != INJ_MAGIC_3) { + die("Need an injectable payload!\n"); + } + + uint p = info->scratch_page; + for (size_t i = 0; i < size; ++i) { + uint buf = start[i]; + if (i == INJ_FBADDR_OFF) { buf = info->cached->fb_addr; } + if (i == INJ_SKPID_OFF) { buf = info->socket_binder_pid; } + if (i == INJ_QIB_OFF) { buf = info->qimage_bits_addr; } + if (i == INJ_SUADDR_OFF) { + buf = info->cached->sendUpdate_addr + 4*N_PREAMBLE_INSTRS; + } + if (i >= INJ_SUPR_OFF && i < INJ_SUPR_OFF+N_PREAMBLE_INSTRS) { + buf = info->su_preamble[i-INJ_SUPR_OFF]; + } + + if (ptrace(PTRACE_POKEDATA, info->pid, p, buf)) { diep("inject"); } + p += 4; + } + + if (external) { + munmap(start, size); + close(fd); + } +} + +void steal_frame_buffer(struct dyn_info *info) { + struct user_regs regs; + if (ptrace(PTRACE_GETREGS, info->pid, NULL, ®s)) { diep("inj orig regs"); } + struct user_regs inj_regs = regs; + inj_regs.uregs[15] = info->scratch_page + 4*INJ_SFB_OFF; + + kill_fb_proc_when_dying = info->pid; + if (ptrace(PTRACE_SETREGS, info->pid, NULL, &inj_regs)) { diep("inj regs"); } + if (ptrace(PTRACE_CONT, info->pid, NULL, 0)) { diep("inj cont"); } + wait_for_stop(info->pid, SIGTRAP); + + uint suprhk = info->scratch_page + 4*INJ_SUPRHK_OFF; + uint movw = 0xE300C000 | (suprhk & 0xFFF) | (((suprhk >> 12) & 0xF) << 16); + uint movt = 0xE340C000 | ((suprhk >> 16) & 0xFFF) | ((suprhk >> 28) << 16); + uint movpc = 0xE1A0F00C; + uint bxlr = 0xE12FFF1E; + if (ptrace(PTRACE_POKEDATA, info->pid, info->cached->sendUpdate_addr, bxlr)) { + diep("bxlr"); + } + if (ptrace(PTRACE_POKEDATA, info->pid, info->cached->sendUpdate_addr+8, movpc)) { + diep("movpc"); + } + if (ptrace(PTRACE_POKEDATA, info->pid, info->cached->sendUpdate_addr+4, movt)) { + diep("movt"); + } + if (ptrace(PTRACE_POKEDATA, info->pid, info->cached->sendUpdate_addr, movw)) { + diep("movw"); + } + + if (ptrace(PTRACE_SETREGS, info->pid, NULL, ®s)) { diep("inj rest regs"); } + kill_fb_proc_when_dying = -1; + if (ptrace(PTRACE_DETACH, info->pid, NULL, 0)) { diep("ptrace go away"); } + + kill(info->socket_binder_pid, SIGKILL); /* don't need this anymore! */ + kill_binder_when_dying = -1; + + struct iovec v = { + .iov_base = &info->fb_bits_addr, + .iov_len = sizeof(uint), + }; + struct msghdr msg = { 0 }; + msg.msg_iov = &v; + msg.msg_iovlen = 1; + union { + char buf[CMSG_SPACE(sizeof(int))]; + struct cmsghdr align; + } u; + msg.msg_control = u.buf; + msg.msg_controllen = sizeof(u.buf); + if (recvmsg(info->socket_fd, &msg, 0) < 0) { diep("recvmsg"); }; + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + info->fb_fd = *(int*)CMSG_DATA(cmsg); +} + +struct libqsgepaper_snoop_fb libqsgepaper_snoop(void) { + struct dyn_info info = { + .pid = find_process(), + }; + load_cached_info(&info); + attach(&info); + get_dyn_info(&info); + check_cached_info(&info); + bind_socket(&info); + make_scratch_page(&info); + load_injectable(&info); + steal_frame_buffer(&info); + struct libqsgepaper_snoop_fb ret = { + .fb_fd = info.fb_fd, + .offset = info.fb_bits_addr & 0xFFF, + .socket_fd = info.socket_fd, + }; + return ret; +} diff --git a/libqsgepaper-snoop.h b/libqsgepaper-snoop.h new file mode 100644 index 0000000..17be48f --- /dev/null +++ b/libqsgepaper-snoop.h @@ -0,0 +1,12 @@ +#ifndef LIBQSGEPAPER_SNOOP_H_ +#define LIBQSGEPAPER_SNOOP_H_ + +struct libqsgepaper_snoop_fb { + int fb_fd; + size_t offset; + int socket_fd; +}; + +struct libqsgepaper_snoop_fb libqsgepaper_snoop(void); + +#endif /* LIBQSGEPAPER_SNOOP_H_ */ diff --git a/payload-a.s b/payload-a.s new file mode 100644 index 0000000..8ee9ae1 --- /dev/null +++ b/payload-a.s @@ -0,0 +1,53 @@ + .section .asheader + + .section .asentry +sfb: b steal_frame_buffer + @ sendupdate preamble---the bytes we overwrote + @ in order to to put in the jump to the hook + @ (hopefully they don't do anything pc-relative) +suaddr: .word 0 +supr: .word 0 + .word 0 + .word 0 + ldr pc, suaddr + @ sendupdate hook---save the parameter values and finish the call +suprhk: adr ip, suarg1 + stmia ip, {r1-r3,lr} + ldr lr, [sp, #0x0] + str lr, suarg4 + adr lr, supo + b supr + @ storage for sendupdate params +suarg1: .word 0 +suarg2: .word 0 +suarg3: .word 0 +sulr: .word 0 +suarg4: .word 0 + @ sendupdate postamble---give the stored params to C and return +supo: push {r0-r4} + adr ip, suarg1 + ldmia ip, {r0-r2} + ldr r3, suarg4 + bl sendupdate_hook + pop {r0-r4} + ldr lr, sulr + bx lr + + @ primitives for C, since we don't have a standard library + .Section .text + .global _syscall +_syscall: + push {r4-r7} + cpy r7, r0 + cpy r0, r1 + cpy r1, r2 + cpy r2, r3 + ldr r3, [sp, #16] + ldr r4, [sp, #20] + ldr r5, [sp, #24] + ldr r6, [sp, #28] + swi 0 + pop {r4-r7} + bx lr + .global trap +trap: .word 0xE7F001F0 diff --git a/payload-c.c b/payload-c.c new file mode 100644 index 0000000..7adaf73 --- /dev/null +++ b/payload-c.c @@ -0,0 +1,166 @@ +#include +#include +#include +#include +#include +#include + +#include "private.h" + +typedef unsigned int uint; +extern uint _syscall(uint, uint, uint, uint, uint, uint, uint, uint); +extern void trap(void); + +#define syscalll(nr, arg1, arg2, arg3, arg4, arg5, arg6, arg7, args...) \ + _syscall(nr, (uint)arg1, (uint)arg2, (uint)arg3, (uint)arg4, (uint)arg5, (uint)arg6, (uint)arg7) +#define syscall(nr, ...) syscalll(nr, ## __VA_ARGS__, 0, 0, 0, 0, 0, 0, 0, 0) + +#define MAX_PID_CHARS 7 /* at least right now */ +struct sockaddr_un addr = { + .sun_family = AF_UNIX, + .sun_path = "/proc/XXXXXXX/cwd/socket", +}; + +#define FBLEN (1404*1872*2) + +__attribute__((section(".magic"))) +uint magic[] = { + INJ_MAGIC_0, + INJ_MAGIC_1, + INJ_MAGIC_2, + INJ_MAGIC_3, +}; + +__attribute__((section(".cheader0"))) +volatile uint fbimgaddr = 0; +__attribute__((section(".cheader1"))) +volatile uint skpid = 0; +__attribute__((section(".cheader2"))) +volatile uint (*qimage_bits)(uint qimage) = NULL; + +uint fbaddr = 0; +uint socketfd = -1; + +static void set_socket_path(void) { + char *p = &addr.sun_path[5+MAX_PID_CHARS]; + while (skpid != 0) { + *p = '0' + (skpid % 10); + --p; + skpid /= 10; + } + char *o = &addr.sun_path[5]; + do { + *++o = *++p; + } while (*p); +} + +#define strlen our_strlen +static size_t strlen(char *str) { + size_t len = 0; + while (*str++) { len += 1; } + return len; +} + +static void diep(char *msg, int ret) { + syscall(__NR_write, 2, msg, strlen(msg)); + ret = -ret; /* positive-ify */ + while (ret != 0) { /* it'll print out backwards */ + char x = '0' + (ret % 10); + syscall(__NR_write, 2, &x, 1); + ret /= 10; + } + syscall(__NR_write, 2, "\n", 1); + trap(); +} +static void die(char *msg) { + syscall(__NR_write, 2, msg, strlen(msg)); + syscall(__NR_write, 2, "\n", 1); + trap(); +} + +int fbfd = -1; +static void fb_thread(void) { + uint fbpage = fbaddr & ~0x0FFF; + uint real_len = FBLEN + (fbaddr - fbpage); + if (fbfd < 0) { + int fd = syscall(__NR_memfd_create, "fb", 0); + if (fd < 0) { diep("memfd_create", fd); } + int written = 0; + while (written < real_len) { + int ret = syscall(__NR_write, fd, fbpage+written, real_len - written); + if (ret < 0) { diep("write", ret); } + written += ret; + } + if (!__sync_bool_compare_and_swap(&fbfd, -1, fd)) { + syscall(__NR_close, fd); + } + } + if (fbfd < 0) { die("fbfd"); } + int ret; + ret = syscall(__NR_mmap2, fbpage, real_len, + PROT_READ|PROT_WRITE, MAP_FIXED|MAP_SHARED, fbfd, 0); + if (ret != fbpage) { diep("mmap", ret); } +} + +static void handle_sigsegv(int sig, siginfo_t *si, void *unused) { + if ((char*)si->si_addr >= (char*)fbaddr && + (char*)si->si_addr < (char*)fbaddr + FBLEN) { + fb_thread(); + } +} + +void steal_frame_buffer(void) { + fbaddr = qimage_bits(fbimgaddr); + + struct sigaction sa, oldsa; + sa.sa_flags = SA_SIGINFO; + for (int i = 0; i < sizeof(sigset_t); ++i) { /* sigsetempty, hopefully */ + *((char*)(&sa.sa_mask)+i) = 0; + } + int ret; + if ((ret = syscall(__NR_sigaction, SIGSEGV, &sa, &oldsa)) < 0) { + diep("signal", ret); + } + if ((ret = syscall(__NR_mprotect, fbaddr & ~0x0FFF, FBLEN + (fbaddr & 0xFFF), + PROT_READ)) < 0) { diep("mprotect", ret); } + fb_thread(); + if ((ret = syscall(__NR_sigaction, SIGSEGV, &oldsa, NULL)) < 0) { + diep("signal restore", ret); + } + + socketfd = syscall(__NR_socket, AF_UNIX, SOCK_DGRAM, 0); + if (socketfd < 0) { diep("socket", socketfd); } + set_socket_path(); + ret = syscall(__NR_connect, socketfd, (struct sockaddr *)&addr, sizeof(addr)); + if (ret < 0) { diep("connect", ret); } + + /* Avoid R_ARM_ABS32. Why does one show up even with -fpic? */ + uint addr_for_io = fbaddr; + struct iovec v = { + .iov_base = &addr_for_io, + .iov_len = sizeof(uint), + }; + struct msghdr msg = { 0 }; + msg.msg_iov = &v; + msg.msg_iovlen = 1; + union { + char buf[CMSG_SPACE(sizeof(int))]; + struct cmsghdr align; + } u; + msg.msg_control = u.buf; + msg.msg_controllen = sizeof(u.buf); + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + *(int*)CMSG_DATA(cmsg) = fbfd; + ret = syscall(__NR_sendmsg, socketfd, &msg, 0); + if (ret < 0) { diep("sendmsg", ret); } + + trap(); +} + +void sendupdate_hook(uint p0, uint p1, uint p2, uint p3) { + uint params[] = { p0, p1, p2, p3 }; + syscall(__NR_write, socketfd, params, 16); +} diff --git a/payload.ld b/payload.ld new file mode 100644 index 0000000..3788fbe --- /dev/null +++ b/payload.ld @@ -0,0 +1,17 @@ +ENTRY(steal_frame_buffer) +SECTIONS +{ + .binary : AT(0) { + *(.magic) + *(.asheader) + *(.cheader0) + *(.cheader1) + *(.cheader2) + *(.asentry) + *(.text) + *(.data) + *(.rodata) + *(.rodata*) + *(.bss) + } +} diff --git a/private.h b/private.h new file mode 100644 index 0000000..b5ba810 --- /dev/null +++ b/private.h @@ -0,0 +1,15 @@ +#include "libqsgepaper-snoop.h" + +#define INJ_FBADDR_OFF 4 +#define INJ_SKPID_OFF 5 +#define INJ_QIB_OFF 6 +#define INJ_SFB_OFF 7 +#define INJ_SUADDR_OFF 8 +#define INJ_SUPR_OFF 9 +#define INJ_SUPRHK_OFF 13 +#define N_PREAMBLE_INSTRS 3 + +#define INJ_MAGIC_0 0x562bf2d9 +#define INJ_MAGIC_1 0x4e47f834 +#define INJ_MAGIC_2 0xe47d65fa +#define INJ_MAGIC_3 0x218c1698