diff --git a/2019/diels-grabsch2/README.md b/2019/diels-grabsch2/README.md index f6337e5a4..80dbf9d4a 100644 --- a/2019/diels-grabsch2/README.md +++ b/2019/diels-grabsch2/README.md @@ -1,7 +1,7 @@ # Most self-aware - Volker Diels-Grabsch - +Volker Diels-Grabsch + ## 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. diff --git a/2019/diels-grabsch2/prog.c b/2019/diels-grabsch2/prog.c index 85bf2b349..ff04c9f4c 100644 --- a/2019/diels-grabsch2/prog.c +++ b/2019/diels-grabsch2/prog.c @@ -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,c14),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";