Obfuscate 4 Posted November 16, 2023 (edited) CONQUER.INI supports a number of hidden options activated by key phrases. The key phrases themselves are not stored in the game, but only the 32-bit codes that result from hashing them with a custom hash algorithm. Plausible preimages have been found for most of the hidden options. (Because the output range of the hash algorithm is limited, many inputs hash to the same code, but there is usually one that is obviously the intended one.) Using a list of about 50 million page titles from English Wikipedia and Wiktionary, I found a key phrase I have not yet seen documented. To activate the "Combat" option (PARM_COMBAT), use the key phrase "CHARGE!" (including the exclamation point). Besides the CONQUER.INI options, the same hash-based obfuscation is used for certain command-line options ("FUNPARK" is an example). There are also some commented-out codes for PARM_SPECIAL (i.e., unused synonyms for "FUNPARK"). Using the same wiki title list, I was able to find plausible preimages for many of these as well. Here are preimages for the PARM_* codes in DEFINES.H that are not already documented at TCRF (in a list maintained by Nyerguds) or in the source code: 0xDC57C4B2 PARM_COMBAT "CHARGE!" 0xDFABC23A PARM_CHEATADAM "ADEN" / "ADUN" / "ATEN" / "ATUN" 0xB5B63531 PARM_CHEATBILL "BLUB" 0x9F38A19D PARM_CHEATERIK "SUPER" 0x00532693 PARM_CHEATMIKE "NADE" / "NATE" 0x39D01821 PARM_CHEATPHIL "WIZARD" 0x2E7FE493 PARM_CHEAT_STEVET "BOLLOCKS" 0xD18129F6 PARM_SPECIAL "JURASSIC" 0x2E84E394 PARM_SPECIAL #1 "FUNFUN" 0x85F110A5 PARM_SPECIAL #3 "WHAT_THE" 0x7F65F13C PARM_SPECIAL #4 "ITS_ABOUT_TIME" 0x431F5F61 PARM_SPECIAL #5 "LOST_IN_TIME" 0xE0F651B9 PARM_SPECIAL #7 "GONEWILD" 0xEE1CD37D PARM_SPECIAL #9 "CHOMPCHOMP" That leaves just these codes whose preimages remain unknown: 0xF7867BF0 PARM_BIB 0xBE79088C PARM_CHEATDAVID 0xABDD0362 PARM_CHEATJOE 0xC2AA509B PARM_EDITORERIK 0x63CE7584 PARM_SPECIAL #2 0x10B9683D PARM_SPECIAL #8 To reproduce these results: Download and extract the attached cnc_obfuscate.zip source code. Download the wiki titles: https://dumps.wikimedia.org/enwiki/latest/enwiki-latest-all-titles.gz https://dumps.wikimedia.org/enwiktionary/latest/enwiktionary-latest-all-titles.gz Filter and deduplicate the wiki titles: (gzip -dc enwiki-latest-all-titles.gz | ./filter-wiki-titles.py; gzip -dc enwiktionary-latest-all-titles.gz | ./filter-wiki-titles.py) | sort -u > wordlist.txt Then run the program: cargo run --release < wordlist.txt cnc_obfuscate.zip Edited November 16, 2023 by Obfuscate Avoid including column header line in filter-wiki-titles.py command. 1 Share this post Link to post
Obfuscate 4 Posted November 16, 2023 (edited) I also tried using the Z3 theorem prover to find inputs that hash to the desired codes, with limited success. It does find working preimages, but it's slow and the preimages it finds are obviously random hash collisions, not the inputs intended by the developers. The only one I saw that looked remotely plausible was "OBWAN" for 0xABDD0362 (PARM_CHEATJOE), but that's not as clearly correct as known cheat strings like "SUPER" and "WIZARD". I'm no Z3 expert, so maybe the program can be improved. The source code is attached. The program actually finds the right answer for some already solved codes like 0x59E975CE (PARM_EASY), 0x5D9F6F24 (PARM_6PLAYER), and 0x6F4BE7CA (PARM_CHEAT), as well as collisions of different lengths. "unsat" means there are no solutions of the given length. 0x59E975CE 1 unsat 2 unsat 3 unsat 4 'EASY' 5 unsat 6 'Y%KZJ`' 7 'F9H]JC-' 8 '&76FX=$"' 9 '||MQL6K:]' 10 '-K#?S132:L' 11 '9E#}3T{((__' 12 'N.HZ}FC^V~_{' 13 '`!=A["$M99|\'%' 14 '`OX_:^N~{S@<M|' 15 '9B}81"^B""V0A)8' 16 '^}^L%2JERWJ^C{|{' 0x5D9F6F24 1 '6' 2 unsat 3 unsat 4 '2E0~' 5 '>@!,\\' 6 'V9J>\\+' 7 '`7~^1#3' 8 'X2D*@+WD' 9 ">.0|1PC&'" 10 "20MO:'^'BA" 11 '8:#3;~%(?`=' 12 '-:,81?^DR=~(' 13 '(JW01VX[*EB2G' 14 '=~3OX0*~"E~)U1' 15 '^`~NY.)#$;~RTLH' 16 '3[~]<4H=3\\Q)|B7}' 0x6F4BE7CA 1 unsat 2 unsat 3 unsat 4 unsat 5 'CHEAT' 6 'Z##T4I' 7 'XTVJAV/' 8 '1!,"%CV\'' 9 'S|Q.4"@AM' 10 '}<N_6{K42.' 11 'U>;LE|FT=]*' 12 'W(V/{O}N#{MG' 13 '{LA6T%FG~"1FJ' 14 'GW0/C{~{QAFZ`8' 15 ';%Z79T0R2\\^({}~' 16 '+"$_AS-|$""}]$"<' But for the unsolved codes I tried, it just finds random collisions. "OBWAN" for 0xABDD0362 might be plausible, the rest are definitely not the intended strings. 0xABDD0362 1 unsat 2 unsat 3 unsat 4 unsat 5 'OBWAN' 6 '/*5<F^' 7 "1U_T=U'" 8 '2D[Y@".&' 9 '`N01IX?TQ' 10 ',`51%/FLOP' 11 'V:7D.Y.J88"' 12 'P=![(,&MLFQ}' 13 ',}=1|0$0MR!O}' 14 "C.Y(6~G'({O+#$" 15 'Y}^!|QU?JB_F1{{' 16 "E,^6@A18EP(0'%=!" 0xBE79088C 1 unsat 2 unsat 3 unsat 4 unsat 5 'MS"Q_' 6 ']SFY~(' 7 'M7PH7,@' 8 '%/@.:*^:' 9 "'UY9P,GE." 10 '`}7)&FE3?}' 11 '9B}~1`^&NN?' 12 '+OI?FC+;U6VN' 13 '^UF12WF;L?.S,' 14 'OW\\"+0$1#0;?D{' 15 'IB`]Q"|@R1P]`@[' 16 '8K0=&B!T-+`W$\\Q`' Restricting the input to alphabetic characters (rather than the full isgraph character set allowed by the hash algorithm), the discovered solutions are still not meaningful. 0x63CE7584 1 unsat 2 unsat 3 unsat 4 unsat 5 unsat 6 unsat 7 unsat 8 'BEJSVLBD' 9 'UPPBHLNVD' 10 'HTFXNEVSCB' 11 'YSJKBROQCWG' 12 'XCHGECOCIZJF' 13 'EEYODOQOHSLDH' 14 'WMHGPUIBMCJYFC' 15 'LHYFDQPQIWXOCYW' 16 'KBOEMFKNMFYXCLXF' 0xBE79088C 1 unsat 2 unsat 3 unsat 4 unsat 5 unsat 6 unsat 7 'CWNUNHO' 8 'UQHZIWBM' 9 'HKBIDGEDV' 10 'POHEEEXMUA' 11 'EUEJHDXZJXB' 12 'GIJCGTNWWGNM' 13 'IKHJTZQMRDKDC' 14 'CWWKBBDFKJTFDO' 15 'LMMMJUXDYWHTXXF' 16 'GXTTNVXSTLWTYYWX' 0xE0F651B9 1 unsat 2 unsat 3 unsat 4 unsat 5 'QEPOT' 6 'IYTMPD' 7 'UTAMDKK' 8 'WPZEYWLH' 9 'QFFEYFYOM' 10 'JLZHNNLSDC' 11 'LWZKHYJVDEX' 12 'BRZFFZVDEZBA' 13 'AEFLMWRBAEJJM' 14 'AEHZPMQEGENLHJ' 15 'QVEZCRMTARMXEBG' 16 'CMXGQWXUKSNTKEXX' 0xF7867BF0 1 unsat 2 unsat 3 unsat 4 unsat 5 unsat 6 unsat 7 unsat 8 'BFNEYHMI' 9 'YUOWEELIL' 10 'PVDILKVDND' 11 'EFVQLUYXBDT' 12 'MLDDWBNDWBDD' 13 'IVAJGNJCNTDGF' 14 'HKZGTFIQSVSYFB' 15 'CTZUXWQLPVVDEUV' 16 'JJFENREYWBNUADFY' cnc_obfuscate_z3.py.zip Edited November 16, 2023 by Obfuscate Remove syntax highlighting. 1 1 Share this post Link to post
Nyerguds 100 Posted November 16, 2023 Oh wow, a lot of these are unknown. I brute forced on these a lot, but I never thought of including underscores or exclamation marks, because the algorithm just substitutes them for alphanumeric characters anyway. Never made the obvious realisation that that didn't mean they didn't use them. I found JURASSIC, FUNFUN, GONEWILD, CHOMPCHOMP, and a few others you got as well; basically, purely alphanumeric dictionary stuff. The #2 one you're missing matches "RAPATTACK", but I have no clue about #8 either. #4 actually has a lot of fun dictionary matches, but none seemed very plausible. I like the "ANKYLOSAURTOWEL" one though "OBWAN" is completely confirmed. First of all, it's obviously a Star Wars reference, and secondly, It's Joe Bostic's developer password, and Joe has always used "Joe B Wan" as online nickname. In fact if you look in the Petroglyph Discord, "Joebwan" is his nickname there. PARM_EDITORERIK (0xC2AA509B) matches "SUPEREDIT", so that seems extremely plausible too. Red Alert also has a bunch of hashes like it. I got a spreadsheet on which I keep track of everything found and confirmed so far: https://docs.google.com/spreadsheets/d/1EnkKM28eDt1VbhtFKHnD1PCm-FmboygiuIyehFfSudo/edit#gid=0 2 Share this post Link to post
Obfuscate 4 Posted November 20, 2023 I'm glad you replied. I knew you had worked on reversing the hashes, but I did not know the extent of what you had already found. I also did not know about the hashes in Red Alert. After reading your explanation, I agree "OBWAN" is right for PARM_CHEATJOE. I don't have many more ideas, other than trying a dedicated password cracker, for example John the Ripper. It obviously doesn't have support for this custom hash algorithm, but you can run it in with --stdout and pipe it into another program (--stdout is desirable anyway, because it will keep finding additional inputs for the same hash). The below john.conf file implements variation rules that are basically equivalent to what I had in my custom program: [List.Rules:Wordlist] # the original word : # add various suffixes : $[.?!] # strip whitespace /?w @?w /?w @?w $[.?!] # replace whitespace with other characters /?w s?w[_\-] /?w s?w[_\-] $[.?!] # strip punctuation and symbols /?p @?p @?s /?p @?p @?s $[.?!] /?s @?p @?s /?s @?p @?s $[.?!] # add dash prefix if otherwise ok for command line /?w /?p /?s ^- [Incremental:ASCII] File = $JOHN/ascii.chr MinLen = 0 CharCount = 95 After hacking my earlier program not to apply its own variations, it can be run as follows: john --wordlist=wordlist.txt --rules --stdout | cargo run --release The incremental mode finds other preimages, not using a wordlist: john --incremental --stdout | cargo run --release I did not find any plausible-looking preimages for the remaining hashes this way, however. Any progress by this method will require a different wordlist and/or different variation rules. 1 Share this post Link to post
Nyerguds 100 Posted November 20, 2023 The tool I used was something I wrote before in C# with the original intent to brute force hashes from mix files. When we started digging into these secret password hashes, I added the obfuscate algorithm into it. http://nyerguds.arsaneus-design.com/project_stuff/2014/CnCMixNameFinder/ I'm not sure if I got the latest version of my tool uploaded (it's possible that the uploaded version does not contain the dictionary attack support), but anyway, it's a rather unoptimised C# desktop app. It doesn't have any kind of special way to distribute loads; it's plain CPU-powered, without multi-core processing or anything like that. To do parallel hacking, I generally just started it 8 times, assigned each instance to another core manually in Windows Task Manager, and gave each instance another range to go over. I mean, it was originally just meant to brute-force hashes from mix files, which means pure ASCII filenames up to 8 characters of which the extension is generally already known. So yea, for that purpose, it was good enough Note that the Obfuscate algorithm I use was partially rewritten by Tomsons26, with findings which I think were based on earlier reverse-engineering. Toms noticed that certain operations cancelled each other out, and have no effect on the end result. So the c# code I have is in fact more optimised than the version in the original code, and thus, more suitable for brute force operations. [edit] Ugh, seems the whole Obfuscate method isn't in the currently released code yet. I'll upload it asap. 1 Share this post Link to post
Obfuscate 4 Posted November 21, 2023 I modified the wiki title filtering program to extract all n-grams. This increases the size of the candidate word list to 138 million, and finds a couple more reasonable preimages: 0x10B9683D PARM_SPECIAL #8 "ISITCLEAR" 0x72A47EF6 WWChat "DOTHISMYSELF " No luck with PARM_BIB or PARM_CHEATDAVID. It seems to be hard for any input to hash to these values. Even with the whole n-gram candidate list, only one preimage is found for each, neither very plausible: 0xF7867BF0 PARM_BIB "6C50-97F-9C02-EDD1." 0xBE79088C PARM_CHEATDAVID "EYETOO..." I suspect that one reason some hash outputs are more difficult to achieve than others is a bug in the "unroll" step of Obfuscate. The code looks like it is trying to shift the lower 8 bits of temp into the upper 8 bits of code. But the code>>8 is an arithmetic (signed) right shift, not a logical right shift. If the most significant bit of code is 1, the shift operation makes the upper 8 bits become 0xff, not 0x00 as expected. After a few more iterations code becomes 0xffffffff, which makes the second half of the buffer often be filled with 0xff bytes. The lack of variation in half of the buffer increases the probability of collisions in this intermediate step of the hash computation. 17 hours ago, Nyerguds said: Toms notice that certain operations cancelled each other out, and have no effect on the end result. I noticed that too. This code looks like it is trying to compute the "CRC"s of the forward and backward buffer and XOR them together: long code = Calculate_CRC(buffer, length); long copy = code; strrev(buffer); code ^= Calculate_CRC(buffer, length); code = code ^ copy; strrev(buffer); But the XOR operations cancel out and it becomes equivalent to the "CRC" of only the backward buffer: strrev(buffer); long code = Calculate_CRC(buffer, length); strrev(buffer); This can be optimized further (eliminating the two strrevs) with a specialized reverse-CRC function that byteswaps each 4-byte chunk and iterates over the chunks in reverse order. cnc_obfuscate_20231121.zip Share this post Link to post
Nyerguds 100 Posted November 21, 2023 Interesting! Yea, the special #8 and wchat ones do seem pretty plausible. I uploaded my code now, in v1.0.7 of the tool. Though note that it's not very polished; some of the controls don't disable as they should while the operation is in progress. I'll probably release a 1.0.7.1 soon to fix these small details. The tool can accept a list of hashes, so that tends to speed things up a bit, since the result of one hashing operation can be compared to all of the values. Though in that aspect, the command line parameters and ini options still need to be treated as separate categories, of course, since the ini ones can contain spaces. Share this post Link to post
Nyerguds 100 Posted November 22, 2023 Brute-forcing with extra characters added in the dictionary entry list ( ! . - _ ? , ) gave some matches for the bibs one, but nothing plausible. F7867BF0 = "!GEARECLEANUP" F7867BF0 = ".BUSHELSJOULING" F7867BF0 = ". DENOTE HAJJ" F7867BF0 = ". KERNED YACCA" F7867BF0 = "- COMBY OVERFEE" F7867BF0 = "-DEFUNDSFARAD" [edit] Apparently, in the RA Play Codes, "ANTHRAXROCKAWAY" is a somewhat plausible match for Paul Mudra's code; he was a sound guy, and Anthrax is a thrash metal band from the 80s. It's quite long compared to all the other ones though. Share this post Link to post