{"id":16048290,"url":"https://github.com/jorianwoltjer/bashrandomcracker","last_synced_at":"2025-03-18T04:30:57.531Z","repository":{"id":173952046,"uuid":"651559220","full_name":"JorianWoltjer/BashRandomCracker","owner":"JorianWoltjer","description":"Crack Bash's $RANDOM variable to get the internal seed and predict future values, after only 2-3 samples","archived":false,"fork":false,"pushed_at":"2024-03-30T07:33:06.000Z","size":42,"stargazers_count":8,"open_issues_count":0,"forks_count":3,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-12T17:50:48.729Z","etag":null,"topics":["algorithm","bash","brute-force","cli","crypto","random","rng"],"latest_commit_sha":null,"homepage":"https://crates.io/crates/bashrand","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/JorianWoltjer.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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}},"created_at":"2023-06-09T14:05:09.000Z","updated_at":"2025-01-29T03:56:10.000Z","dependencies_parsed_at":null,"dependency_job_id":"b0fd0dd4-97a4-4d3f-b63a-43a21a426bf9","html_url":"https://github.com/JorianWoltjer/BashRandomCracker","commit_stats":null,"previous_names":["jorianwoltjer/bashrandomcracker"],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JorianWoltjer%2FBashRandomCracker","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JorianWoltjer%2FBashRandomCracker/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JorianWoltjer%2FBashRandomCracker/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JorianWoltjer%2FBashRandomCracker/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/JorianWoltjer","download_url":"https://codeload.github.com/JorianWoltjer/BashRandomCracker/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244156527,"owners_count":20407525,"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":["algorithm","bash","brute-force","cli","crypto","random","rng"],"created_at":"2024-10-09T00:05:56.972Z","updated_at":"2025-03-18T04:30:57.526Z","avatar_url":"https://github.com/JorianWoltjer.png","language":"Rust","readme":"# Bash `$RANDOM` Cracker\n\n**Crack Bash's $RANDOM variable to get the internal seed and predict future values, after only 2-3 samples**\n\nThis tool brute-forces the internal seed of Bash's `$RANDOM` variable after only 2-3 samples, in seconds. After doing so, you are able to predict all future values to break the randomness of this generator. This can be used to break password/token generation, or other systems that rely on this randomness for security. \n\nFor context, the `bash` shell has a dynamic variable called `$RANDOM` you can access at any time to receive a random 15-bit number:\n\n```Shell\n$ echo $RANDOM $RANDOM $RANDOM\n3916 29151 6095\n```\n\nTo seed this random number generator, you can set the variable directly to get the same values every time:\n\n```Shell\n$ RANDOM=1337; echo $RANDOM $RANDOM $RANDOM\n24879 21848 15683\n$ RANDOM=1337; echo $RANDOM $RANDOM $RANDOM\n24879 21848 15683\n```\n\nThere are **2 different calculations** depending on your **bash version**, which may make one seed give two different outputs.  \nAll versions *\u003e= 5.1* will add a small extra step, and to this tool, are considered the \"new\" versions, while any lower versions are considered \"old\". This can be set explicitly using the `--version` (`-v`) argument in this tool, or otherwise, it will simply try both. \n\n## Installation\n\n```Bash\ncargo install bashrand\n```\n\nOr **download** and **extract** a pre-compiled binary from the [Releases](https://github.com/JorianWoltjer/BashRandomCracker/releases) page. \n\n## Example\n\n```Shell\n$ bashrand crack -n 3 $RANDOM $RANDOM $RANDOM\nSeed: 2137070299 +3 (old)\n  Next 3 values: [22404, 16453, 2365]\n$ echo $RANDOM $RANDOM $RANDOM\n22404 16453 2365\n```\n\n[![Bash $RANDOM Cracker - Showcase](https://asciinema.org/a/sa9iP4ZGtIMQdq2dl4Qv5Ga01.svg)](https://asciinema.org/a/sa9iP4ZGtIMQdq2dl4Qv5Ga01?autoplay=1)\n\n\u003c!-- \nbash --version\necho $RANDOM $RANDOM $RANDOM\nbashrand crack 12077 14368\nbashrand crack 12077 14368 25452\necho $RANDOM $RANDOM $RANDOM\nbashrand get 1687126207\nbashrand get 1687126207 -v old\nbashrand get 1687126207 -v old -s 6\nbashrand get 1687126207 -v old -s 6 -n 3\necho $RANDOM $RANDOM $RANDOM\nexit\n --\u003e\n\n## Usage\n\nUse `bashrand crack` and provide 2-3 `$RANDOM` variables for it to brute-force the seed. Afterward, you can use `bashrand get` to get an arbitrary part of the sequence in advance, providing the seed found in the first step. See example above as well. \n\n```Shell\n$ bashrand --help\nBash $RANDOM Cracker\n\nUsage: bashrand [OPTIONS] \u003cCOMMAND\u003e\n\nCommands:\n  crack    Provide random numbers to brute-force the seed\n  get      Get random numbers from a seed\n  seeds    Get next N seeds from a seed\n  collide  Find a seed where both old and new versions are the same\n  help     Print this message or the help of the given subcommand(s)\n\nOptions:\n  -v, --version \u003cVERSION\u003e\n          Which bash version to use for generation (check with `bash --version`)\n\n          [default: both]\n\n          Possible values:\n          - old:  Bash versions 5.0 and older\n          - new:  Bash versions 5.1 and newer\n          - both: Try both old and new versions if unsure\n\n  -n, --number \u003cNUMBER\u003e\n          Number of values to generate\n\n          [default: 10]\n```\n\n```Shell\n$ bashrand crack --help\nProvide random numbers to brute-force the seed\n\nUsage: bashrand crack [OPTIONS] \u003cNUMBERS\u003e \u003cNUMBERS\u003e...\n\nArguments:\n  \u003cNUMBERS\u003e \u003cNUMBERS\u003e...\n          2-3 $RANDOM numbers as input for brute-forcing the seed\n\n          2 =\u003e multiple possible seeds, 3 =\u003e single seed\n```\n\n```Shell\n$ bashrand get --help\nGet random numbers from a seed\n\nUsage: bashrand get [OPTIONS] \u003cSEED\u003e\n\nArguments:\n  \u003cSEED\u003e\n          Seed to use for generating random numbers\n```\n\nThe subcommands `seeds` and `collide` are for more advanced use, check them out if you want to.\n\n## Reverse Engineering (How?!)\n\nTo implement the `$RANDOM` algorithm, the first requirement is understanding the algorithm. Luckily Bash is open-source meaning all the clear and documented code is available. I used [this repository](https://github.com/bminor/bash) to look for anything related to the generation of this variable, and found the definition [here](https://github.com/bminor/bash/blob/ec8113b9861375e4e17b3307372569d429dec814/variables.c#L1914):\n\n```C\nINIT_DYNAMIC_VAR (\"RANDOM\", (char *)NULL, get_random, assign_random);\n```\n\nIt assigns two functions to the variable: [`get_random()`](https://github.com/bminor/bash/blob/ec8113b9861375e4e17b3307372569d429dec814/variables.c#L1443-L1450) and [`assign_random`](https://github.com/bminor/bash/blob/ec8113b9861375e4e17b3307372569d429dec814/variables.c#L1401-L1420). The first is when you access the variable like `echo $RANDOM`, and the second is for when you assign a value yourself to the variable, like `RANDOM=1337`. \n\n`get_random()` is the most interesting as we want to predict its output. It calls the [`get_random_number()`](https://github.com/bminor/bash/blob/ec8113b9861375e4e17b3307372569d429dec814/variables.c#L1422C1-L1440) function which itself calls [`brand()`](https://github.com/bminor/bash/blob/ec8113b9861375e4e17b3307372569d429dec814/lib/sh/random.c#L98C1-L112) inside the `/lib/sh/random.c` file. Here it starts to get interesting:\n\n```C\n#define BASH_RAND_MAX 32767   /* 0x7fff - 16 bits */\n\n/* Returns a pseudo-random number between 0 and 32767. */\nint brand () {\n  unsigned int ret;\n\n  rseed = intrand32 (rseed);\n  if (shell_compatibility_level \u003e 50)\n    ret = (rseed \u003e\u003e 16) ^ (rseed \u0026 65535);\n  else\n    ret = rseed;\n  return (ret \u0026 BASH_RAND_MAX);\n}\n```\n\nFirst, notice the `BASH_RAND_MAX` variable that is a 15-bit mask over the output. Also the `shell_compatibility_level` is the bash version, meaning if it is greater than version 50 (5.0) it will use a slightly different calculation. In both cases however it first gets a random number from [`intrand32()`](https://github.com/bminor/bash/blob/ec8113b9861375e4e17b3307372569d429dec814/lib/sh/random.c#L55-L84), and that already contains the core of the algorithm!\n\n```C\nbits32_t h, l, t;\nu_bits32_t ret;\n\n/* Can't seed with 0. */\nret = (last == 0) ? 123459876 : last;\nh = ret / 127773;\nl = ret - (127773 * h);\nt = 16807 * l - 2836 * h;\nret = (t \u003c 0) ? t + 0x7fffffff : t;\n\nreturn (ret);\n```\n\nThese are some simple calculations that we can recreate in any programming language. Importantly, it uses a `last` variable as its only argument in the calculation, which is given by `rseed = intrand32(rseed)` in the calling function. This means there is an internal seed that is iterated every time this function is called. If we can sync up with this seed, we will be able to predict any future values by copying the algorithm. \n\nThe initial seed value is [complicated](https://github.com/bminor/bash/blob/ec8113b9861375e4e17b3307372569d429dec814/lib/sh/random.c#L87-L96), and is calculated with a lot of unpredictable data. If you remember it was also possible to *set* the seed, using [`assign_random()`](https://github.com/bminor/bash/blob/ec8113b9861375e4e17b3307372569d429dec814/variables.c#L1401-L1420). Looking at this function, it takes the value we set it to, and passes it to [`sbrand()`](https://github.com/bminor/bash/blob/ec8113b9861375e4e17b3307372569d429dec814/lib/sh/random.c#L115-L121), a very simple function that simply sets the seed directly to the provided value:\n\n```C\n/* Set the random number generator seed to SEED. */\nvoid sbrand (seed) unsigned long seed; {\n  rseed = seed;\n  last_random_value = 0;\n}\n```\n\nSo in theory, if the seed was set manually, we could now simply try many seeds until we find one that matches the output. But what about seeds that aren't set manually? This case happens a lot more often. Luckily, the internal seed is an integer of only **32 bits**, easily brute-forcible with such a fast algorithm. After some testing, we can find the search space is actually only 30 bits for the newer bash versions and 31 bits for old bash versions. \n\nThis program implements this brute-force method to search through the whole space in a few seconds, and shows the found seeds together with future values it predicts. \n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjorianwoltjer%2Fbashrandomcracker","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjorianwoltjer%2Fbashrandomcracker","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjorianwoltjer%2Fbashrandomcracker/lists"}