{"id":25904442,"url":"https://github.com/toasterbirb/subst","last_synced_at":"2025-07-03T19:33:15.278Z","repository":{"id":244264821,"uuid":"814740311","full_name":"Toasterbirb/subst","owner":"Toasterbirb","description":"[MIRROR] Find and substitute bytes in programs ","archived":false,"fork":false,"pushed_at":"2025-04-11T14:17:55.000Z","size":107,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-11T15:32:13.642Z","etag":null,"topics":["binary","ctf","reverse-engineering"],"latest_commit_sha":null,"homepage":"http://birbgitfh224rep6tmdofmr6qlo6wx43umqzt3hjubnncr55sdlfmtad.onion/ctf/subst","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Toasterbirb.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-06-13T15:52:39.000Z","updated_at":"2025-04-11T14:17:58.000Z","dependencies_parsed_at":"2024-06-13T18:49:06.162Z","dependency_job_id":"f0d81d5a-f5d7-45b1-a7eb-c524bb7a81b7","html_url":"https://github.com/Toasterbirb/subst","commit_stats":null,"previous_names":["toasterbirb/subst"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/Toasterbirb/subst","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Toasterbirb%2Fsubst","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Toasterbirb%2Fsubst/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Toasterbirb%2Fsubst/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Toasterbirb%2Fsubst/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Toasterbirb","download_url":"https://codeload.github.com/Toasterbirb/subst/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Toasterbirb%2Fsubst/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":263388704,"owners_count":23459254,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["binary","ctf","reverse-engineering"],"created_at":"2025-03-03T04:26:23.838Z","updated_at":"2025-07-03T19:33:15.252Z","avatar_url":"https://github.com/Toasterbirb.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# subst\n\nFind hex strings in a binary and replace bytes with an interpreted language. For usage information run the compiled binary with no arguments\n\n## subst file format\n```\nrep ; bytes ; replacement_bytes                  # Replace all instances of the byte array with the given byte array\nrepat ; location ; replacement_bytes             # Replace bytes at the given location with the given bytes\nnop ; bytes                                      # NOP all instances of the given byte array\nnop ; location ; amount_of_bytes_to_replace      # Replace a certain amount of bytes with NOP starting from the given location\nnopi ; location                                  # NOP out an instruction at the given location\nnopi ; location ; amount_of_instructions_to_nop  # NOP out a certain amount of instruction at the given location\ninv ; location                                   # Invert a conditional at the given location\njmp ; location ; destination                     # Create a jmp instruction to the given location that jumps to the destination address\n```\n\n\u003e [!Note]\n\u003e The *location* value is an offset from the beginning of the file. This value may not always be equal to the absolute addresses of instructions that different disassemblers may show. If the thing you are trying to change doesn't get changed, this is most likely the culprit.\n\n# Example\nImagine the following program:\n```c\n#include \u003cstdio.h\u003e\n#include \u003cstdlib.h\u003e\n\nint main(int argc, char** argv)\n{\n\tif (argc != 2)\n\t{\n\t\tprintf(\"%s\\n\", \"Usage: impossible [some number]\");\n\t\treturn 1;\n\t}\n\n\tint answer = 42;\n\tint user_input = atoi(argv[1]);\n\n\tif (user_input == answer)\n\t\tuser_input++;\n\n\tif (user_input == answer)\n\t\tprintf(\"%s\\n\", \"You win!\");\n\telse\n\t\tprintf(\"%s\\n\", \"You lose!\");\n\n\treturn 0;\n}\n```\nWhat argument would you have to give to the program to get the winning output? If your answer is \"I don't know\", we can fix that problem with `subst`.\n\nThe main function of the \"impossible\" program looks like this in radare:\n```\n╭ 131: int main (uint32_t argc, char **argv);\n│           ; arg uint32_t argc @ rdi\n│           ; arg char **argv @ rsi\n│           ; var uint32_t var_4h @ rbp-0x4\n│           ; var int64_t var_8h @ rbp-0x8\n│           ; var uint32_t var_14h @ rbp-0x14\n│           ; var char **str @ rbp-0x20\n│           0x00001175      55             push rbp\n│           0x00001176      4889e5         mov rbp, rsp\n│           0x00001179      4883ec20       sub rsp, 0x20\n│           0x0000117d      897dec         mov dword [var_14h], edi    ; argc\n│           0x00001180      488975e0       mov qword [str], rsi        ; argv\n│           0x00001184      837dec02       cmp dword [var_14h], 2\n│       ╭─\u003c 0x00001188      7416           je 0x11a0\n│       │   0x0000118a      488d05770e..   lea rax, str.Usage:_impossible__some_number_ ; 0x2008 ; \"Usage: impossible [some number]\"\n│       │   0x00001191      4889c7         mov rdi, rax                ; const char *s\n│       │   0x00001194      e897feffff     call sym.imp.puts           ; int puts(const char *s)\n│       │   0x00001199      b801000000     mov eax, 1\n│      ╭──\u003c 0x0000119e      eb56           jmp 0x11f6\n│      ││   ; CODE XREF from main @ 0x1188(x)\n│      │╰─\u003e 0x000011a0      c745fc2a00..   mov dword [var_4h], 0x2a    ; '*'\n│      │    0x000011a7      488b45e0       mov rax, qword [str]\n│      │    0x000011ab      4883c008       add rax, 8\n│      │    0x000011af      488b00         mov rax, qword [rax]\n│      │    0x000011b2      4889c7         mov rdi, rax                ; const char *str\n│      │    0x000011b5      e886feffff     call sym.imp.atoi           ; int atoi(const char *str)\n│      │    0x000011ba      8945f8         mov dword [var_8h], eax\n│      │    0x000011bd      8b45f8         mov eax, dword [var_8h]\n│      │    0x000011c0      3b45fc         cmp eax, dword [var_4h]\n│      │╭─\u003c 0x000011c3      7504           jne 0x11c9\n│      ││   0x000011c5      8345f801       add dword [var_8h], 1\n│      ││   ; CODE XREF from main @ 0x11c3(x)\n│      │╰─\u003e 0x000011c9      8b45f8         mov eax, dword [var_8h]\n│      │    0x000011cc      3b45fc         cmp eax, dword [var_4h]\n│      │╭─\u003c 0x000011cf      7511           jne 0x11e2\n│      ││   0x000011d1      488d05500e..   lea rax, str.You_win_       ; 0x2028 ; \"You win!\"\n│      ││   0x000011d8      4889c7         mov rdi, rax                ; const char *s\n│      ││   0x000011db      e850feffff     call sym.imp.puts           ; int puts(const char *s)\n│     ╭───\u003c 0x000011e0      eb0f           jmp 0x11f1\n│     │││   ; CODE XREF from main @ 0x11cf(x)\n│     ││╰─\u003e 0x000011e2      488d05480e..   lea rax, str.You_lose_      ; 0x2031 ; \"You lose!\"\n│     ││    0x000011e9      4889c7         mov rdi, rax                ; const char *s\n│     ││    0x000011ec      e83ffeffff     call sym.imp.puts           ; int puts(const char *s)\n│     ││    ; CODE XREF from main @ 0x11e0(x)\n│     ╰───\u003e 0x000011f1      b800000000     mov eax, 0\n│      │    ; CODE XREF from main @ 0x119e(x)\n│      ╰──\u003e 0x000011f6      c9             leave\n╰           0x000011f7      c3             ret\n```\nThere are multiple ways we could approach this situation. One way would be to skip the portion of the code that increments the user input making the challenge (maybe) impossible. This can be achieved with the following subst code:\n```\nnopi ; 0x000011c5\n```\nThe code above would patch out the instruction at 0x000011c5, which in this case would be `add dword [var_8h], 1`. The entire patching process would look like this (assuming the name of the binary is \"impossible\"):\n```sh\ntoasterbirb@tux /tmp/subst $ ls\nimpossible  impossible.c  impossible.sbst\ntoasterbirb@tux /tmp/subst $ cat impossible.sbst\nnopi ; 0x000011c5\ntoasterbirb@tux /tmp/subst $ subst patch ./impossible\npatching out a add instruction at 0x11c5\ntoasterbirb@tux /tmp/subst $ ls\nimpossible  impossible.c  impossible.patched  impossible.sbst\ntoasterbirb@tux /tmp/subst $ chmod +x impossible.patched\ntoasterbirb@tux /tmp/subst $ ./impossible.patched 42\nYou win!\ntoasterbirb@tux /tmp/subst $\n```\nNote the name of the subst file `impossible.sbst`. The file is named after the binary we are going to patch. However a custom `sbst` file name can be used with the `-s` argument.\n\nHere's another way to solve the puzzle above:\n```\njmp ; 0x0000117d ; 0x000011d1\n```\nThis time we are creating a jump to the beginning of the program that skips straight to the winning message without even checking the user input. The patched program would work like this:\n```\ntoasterbirb@tux /tmp/subst $ subst patch ./impossible\ncreating a short jump 0x117d -\u003e 0x11d1 (84 bytes)\ntoasterbirb@tux /tmp/subst $ chmod +x impossible.patched\ntoasterbirb@tux /tmp/subst $ ./impossible.patched\nYou win!\ntoasterbirb@tux /tmp/subst $\n```\nThe patched program looks like this in radare:\n```\n╭ 34: int main (int argc, char **argv, char **envp);\n│           0x00001175      55             push rbp\n│           0x00001176      4889e5         mov rbp, rsp\n│           0x00001179      4883ec20       sub rsp, 0x20\n│       ╭─\u003c 0x0000117d      eb52           jmp 0x11d1\n..\n      │││   ; CODE XREF from main @ +0x13(x)\n      │││   ; CODE XREF from main @ +0x4e(x)\n│     │││   ; CODE XREF from main @ 0x117d(x)\n│     ││╰─\u003e 0x000011d1      488d05500e..   lea rax, str.You_win_       ; 0x2028 ; \"You win!\"\n│     ││    0x000011d8      4889c7         mov rdi, rax                ; const char *s\n│     ││    0x000011db      e850feffff     call sym.imp.puts           ; int puts(const char *s)\n│     ││╭─\u003c 0x000011e0      eb0f           jmp 0x11f1\n      │││   ; CODE XREF from main @ +0x5a(x)\n..\n│     │ │   ; CODE XREF from main @ 0x11e0(x)\n│     │ ╰─\u003e 0x000011f1      b800000000     mov eax, 0\n│     │     ; CODE XREF from main @ +0x29(x)\n│     ╰───\u003e 0x000011f6      c9             leave\n╰           0x000011f7      c3             ret\n```\n\nIn more complicated cases the subst file might be longer. You can have as many commands as you'd like and they are interpreted line by line sequentially. The file format also supports comments with the `#` character. Also all whitespace and tabs get stripped out, so use them freely\n\n## Building\n\n### External dependencies\n- capstone\n- doctest\n\nBuild the project with cmake by running the following commands\n```sh\nmkdir build\ncd build\ncmake ..\nmake -j$(nproc)\n```\n\n## Installation\nTo install subst to /usr/local/bin, run the following command\n```sh\nmake install\n```\nYou can customize the installation *PREFIX* and *DESTDIR* variables normally with cmake and make.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftoasterbirb%2Fsubst","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftoasterbirb%2Fsubst","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftoasterbirb%2Fsubst/lists"}