Fixes in 2019/diels-grabsch2
Make the author's statement that it compiles cleanly true (just like with diels-grabsch1) :-) Typo fixes in README.md. Format fixes in README.md.
This commit is contained in:
1 parent
9eb27e6dbf
commit
0411cc0c25
2 files changed
+50
-45
No files matched your search
@@ -1,7 +1,7 @@
|
||||
# Most self-aware
|
||||
|
||||
Volker Diels-Grabsch
|
||||
<https://njh.eu>
|
||||
Volker Diels-Grabsch
|
||||
<https://njh.eu>
|
||||
|
||||
## To build:
|
||||
|
||||
@@ -9,6 +9,12 @@
|
||||
make
|
||||
```
|
||||
|
||||
[Cody Boone Ferguson](/winners.html#Cody_Boone_Ferguson) made the author's
|
||||
statement that the entry compiles cleanly true by fixing `warning: a function
|
||||
declaration without a prototype is deprecated in all versions of C` (in
|
||||
main()). Not strictly necessary but if he's making fixes he might as well. Thank
|
||||
you Cody!
|
||||
|
||||
## To run:
|
||||
|
||||
```sh
|
||||
@@ -30,65 +36,65 @@ that you are dreaming? Or is it, wake up from the simulation?
|
||||
|
||||
## Author's comments:
|
||||
|
||||
### Almost a hash collision
|
||||
### Almost a hash collision:
|
||||
|
||||
This program prints its own SHA-512 hash. To verify, run:
|
||||
|
||||
```sh
|
||||
sha512sum prog.c; ./prog
|
||||
$ sha512sum prog.c; ./prog
|
||||
43bd0c4381a5078d2850fca9ae5e0647596bcc03cd67d8d973ad02ff35c316c728e7b347ca70abe6c74e745e63646cc7643cb0cffcd3d9a969cbf31a7ce5bf68 prog.c
|
||||
43bd0c4381a5078d2850fca9ae5e0647596bcc03cd67d8d973ad02ff35c316c728e7b347ca70abe6c74e745e63646cc7643cb0cffcd3d9a969cbf31a7ce5bf68
|
||||
```
|
||||
|
||||
Output:
|
||||
It started as a complicated "hello world" program that prints some random hex
|
||||
digits instead of "hello world". Then, the digits were adjusted until the
|
||||
resulting program matched its own SHA-512 hash, via brute force through all
|
||||
2^512 possibilities. Just kidding.
|
||||
|
||||
43bd0c4381a5078d2850fca9ae5e0647596bcc03cd67d8d973ad02ff35c316c728e7b347ca70abe6c74e745e63646cc7643cb0cffcd3d9a969cbf31a7ce5bf68 prog.c
|
||||
43bd0c4381a5078d2850fca9ae5e0647596bcc03cd67d8d973ad02ff35c316c728e7b347ca70abe6c74e745e63646cc7643cb0cffcd3d9a969cbf31a7ce5bf68
|
||||
|
||||
It started as a complicated "hello world" program that prints some
|
||||
random hex digits instead of "hello world". Then, the digits were
|
||||
adjusted until the resulting program matched its own SHA-512 hash,
|
||||
via brute force through all 2^512 possibilities. Just kidding.
|
||||
|
||||
This wasn't brute force, but involved quite some math, using a variant
|
||||
of Jane Doe's algorithm for generating SHA-512 collisions efficiently.
|
||||
If you still don't believe me, you are right.
|
||||
This wasn't brute force, but involved quite some math, using a variant of Jane
|
||||
Doe's algorithm for generating SHA-512 collisions efficiently. If you still
|
||||
don't believe me, you are right.
|
||||
|
||||
No crypto was broken in the making of this program!
|
||||
|
||||
The trick is that under the hood, this program is a quine! That is,
|
||||
it is aware of its own source code in string representation. But
|
||||
instead of printing that string, its SHA-512 hash is computed and
|
||||
printed. So two major steps were needed to obfuscate this program:
|
||||
The trick is that under the hood, this program is a quine! That is, it is aware
|
||||
of its own source code in string representation. But instead of printing that
|
||||
string, its SHA-512 hash is computed and printed. So two major steps were
|
||||
needed to obfuscate this program:
|
||||
|
||||
- Step 1: Hide the quine
|
||||
- Step 2: Hide the SHA-512 computation
|
||||
1. Hide the quine
|
||||
2. Hide the SHA-512 computation
|
||||
|
||||
Note that both steps were achieved mostly algorithmically. The code
|
||||
is otherwise as short and as clean as possible. The implementation is
|
||||
portable C99 code for 32-bit and 64-bit systems that compiles without
|
||||
any warnings on current GCC and Clang versions even with -Wextra and
|
||||
-Weverything. The program is composed of mostly pure functions, and
|
||||
the "#define"s are only used to shorten redundant parts of the code,
|
||||
any warnings on current GCC and Clang versions even with `-Wextra
|
||||
-Weverything`. The program is composed of mostly pure functions, and
|
||||
the `#define`s are only used to shorten redundant parts of the code,
|
||||
they are not meant to obfuscate much. Having that out of the way, how
|
||||
are step 1 and step 2 achieved then?
|
||||
|
||||
Regarding step 1, note that the large string literal "`x{bh}ndbq..."
|
||||
Regarding step 1, note that the large string literal
|
||||
|
||||
"`x{bh}ndbq..."
|
||||
|
||||
in the last line doesn't look like C code. Moreover, it is too short
|
||||
to contain even a compressed representation of all preceding and
|
||||
subsequent code. Instead, the preceding code including the '"'
|
||||
subsequent code. Instead, the preceding code including the `"`
|
||||
character has already been run through the SHA-512 computation by an
|
||||
external script. The internal state of the SHA-512 computation at
|
||||
external script. The internal state of the SHA-512 computation at
|
||||
that point has been extracted and encoded into the string. This works
|
||||
because the preceding code is exactly 1280 bytes, which is a multiple
|
||||
of 1024 bits, the block size of SHA-512. The program itself then just
|
||||
continues the SHA-512 computation from that point, adding the last
|
||||
1024-bit block, finalizing the hash and printing it. The last block
|
||||
is taken directly from the string literal at runtime, plus the
|
||||
subsequent code which consists of just '"', ';' and '\n'. Those 3
|
||||
subsequent code which consists of just `"`, `;` and `\n`. Those 3
|
||||
characters are short enough to be hidden in ... well, let's leave this
|
||||
as an exercise for the reader.
|
||||
|
||||
To summarize, the main trick here is to move the string constant to
|
||||
the end, so the preceding code can be precalculated and the subsequent
|
||||
the end, so the preceding code can be pre-calculated and the subsequent
|
||||
code is short enough to be hidden anywhere.
|
||||
|
||||
Regarding step 2, almost all macros, functions and variables have
|
||||
@@ -96,12 +102,11 @@ meaningless single-letter names. Moreover, the SHA-512 computation is
|
||||
reduced to the special case of adding just one block and immediately
|
||||
finalizing it, where the total size is known in advance.
|
||||
|
||||
But this is not enough! For SHA-512 we need 8 initial hash values and
|
||||
80 round constants, which are very characteristic. If any of them
|
||||
appeared in the source, a quick web search (e.g. for 0x428a2f98d728ae22)
|
||||
would tell the reader immediately what's going on. Because of the
|
||||
precomputation, the 8 initial hash values are not needed, but the
|
||||
80 round constants are.
|
||||
But this is not enough! For SHA-512 we need 8 initial hash values and 80 round
|
||||
constants, which are very characteristic. If any of them appeared in the
|
||||
source, a quick web search (e.g. for `0x428a2f98d728ae22`) would tell the reader
|
||||
immediately what's going on. Because of the pre-computation, the 8 initial hash
|
||||
values are not needed, but the 80 round constants are.
|
||||
|
||||
It is almost impossible to hide those 640 bytes in the source.
|
||||
Luckily, the round constants where not chosen by fair dice rolls.
|
||||
@@ -117,12 +122,12 @@ would have been 32-bit values. However, SHA-512 needs the cube roots
|
||||
at 64-bit precision, while double has only 53-bit mantissa precision.
|
||||
|
||||
Using higher-precision floating point is no option here, because the
|
||||
program is meant to be portable C99. And implementing
|
||||
arbitrary-precision arithmetic would have been overkill and would have
|
||||
increased the program size dramatically.
|
||||
program is meant to be portable C99. And implementing arbitrary-precision
|
||||
arithmetic would have been overkill and would have increased the program size
|
||||
dramatically.
|
||||
|
||||
Instead, the program just multiplies. That is, it computes cubes
|
||||
instead of cube roots. It steps through cube root candidates via
|
||||
instead of cube roots. It steps through cube root candidates via
|
||||
bisection, and calculates their cubes until those match the given
|
||||
prime. Thus, we don't need floating point and can use 64-bit integers
|
||||
instead. But that still doesn't solve the problem as it poses the
|
||||
@@ -144,18 +149,18 @@ around 7, so we need to compute with 3 bits for the integral part and
|
||||
64 bits for the fractional part.
|
||||
|
||||
The reason for challenge 3 is that the cube of a 67-bit number is
|
||||
3*67=201 bits, but fortunately we only need to check if it is close
|
||||
enough to the actual prime. It won't be exactly e.g. "409.0000..."
|
||||
`3*67=201` bits, but fortunately we only need to check if it is close
|
||||
enough to the actual prime. It won't be exactly e.g. `409.0000...`
|
||||
anyway, but must be accurate enough to distinguish the correct cube
|
||||
root from its neighbours, e.g. the same number with the 64th
|
||||
fractional digit increased.
|
||||
|
||||
The solution here is to split the 67-bit input into 3 integers of
|
||||
24-bit each. Those are cubed using the multiplication formulas
|
||||
numbers with 3 "digits" of base 2^24 each. During that calculation,
|
||||
numbers with 3 "digits" of base `2^24` each. During that calculation,
|
||||
at most two values are multiplied at once, and the accuracy of
|
||||
intermediate values is reduced using right-shifts as far as allowable
|
||||
for the computation. Finding woking right-shift values was mostly
|
||||
for the computation. Finding working right-shift values was mostly
|
||||
trial-and-error, producing correct results for the first 80 primes,
|
||||
but not much more. Moreover, for some terms it was possible to leave
|
||||
them out entirely, especially higher-order terms of the last digits.
|
||||
|
||||
@@ -10,6 +10,6 @@ b*c>>32))*a>>21)+(3*a*a*b>>6)+((b>>4)*(b>>4)*b>>46))>>18)+a*a*a)h(m,t((b<<16)|(c
|
||||
^(~a&c))i(r,b(a,1)^b(a,8)^(a>>7))g(o,0,0,c<8;c++,a*256+w[b*8+c])g(d,0,0,c<13;c++,a*31+w[b*13+c]-96)g(p,0,4,c;c/=2,a|c*m(b,a|c,a)
|
||||
)g(q,0,1ull<<63,c;c/=2,a|c*m(b,p(b),a|c))g(v,b>1,2,c<b;c++,a&&b%c)g(l,b?l(b-1)+1:2,a,!v(c);c++,c+1)j(n,z d=a[7]+u(a[4])+s(a[4],a
|
||||
[5],a[6])+q(l(*b))+c[*b%16];f(8,a[7-e]=e-3?e-7?a[6-e]:d+_a(a[0])+x(a[1],a[2],a[3]):d+a[3])f(16*(*b%16>14),c[e]+=c[(e+9)%16]+r(c[
|
||||
(e+1)%16])+_(c[(e+14)%16])))j(k,f(8,b[e]=a[e])f(80,n(a,&e,c))f(8,a[e]+=b[e]))int main(){z a[8],b[8],c[16];f(8,a[e]=d(e))f(16,c[e
|
||||
(e+1)%16])+_(c[(e+14)%16])))j(k,f(8,b[e]=a[e])f(80,n(a,&e,c))f(8,a[e]+=b[e]))int main(void){z a[8],b[8],c[16];f(8,a[e]=d(e))f(16,c[e
|
||||
]=e-15?o(e):d(8))k(a,b,c);f(16,c[e]=e?e-15?0:11264:1ull<<63)k(a,b,c);f(8,printf("%016llx%s",a[e],e-7?"":"\n"))return!w;}y*w=(y*)
|
||||
"crsmyiajqhwy{unwa|hjoi`hlxhpxrzb~edko~rtr~ileqyjk`znqgsuitvgqnfdfa||wedvnmhozkpokootqzcexeld~oibqzpcsuw{ib{x`m`hsa`jmn}wcfzpb";
|
||||
Reference in new issue
Block a user