├── DES.cpp ├── Hook.cpp ├── Hook.hpp ├── dllmain.cpp └── readme.MD /DES.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | using std::hex; 15 | std::ofstream TraceFile; 16 | using std::max; 17 | 18 | typedef unsigned long long u64; 19 | typedef unsigned char u4; 20 | typedef unsigned char u8; 21 | typedef unsigned long u32; 22 | 23 | enum LogMessages_t 24 | { 25 | LogFeistel = 0x01, 26 | LogRounds = 0x02, 27 | LogPermutations = 0x04, 28 | }; 29 | 30 | class DES 31 | { 32 | private: 33 | LogMessages_t m_ToLog; 34 | u64 m_SubKeys[16]; 35 | 36 | public: 37 | static u8 IPTable[64]; 38 | static u8 FPTable[64]; 39 | static u8 PermutationTable[32]; 40 | static u8 InversePermutationTable[32]; 41 | static u4 SBOX[8][64]; 42 | static u8 ExpansionTable[48]; 43 | static u8 PermutationChoice1Table[56]; 44 | static u8 PermutationChoice2Table[48]; 45 | static u8 LeftRotations[16]; 46 | 47 | void Log(LogMessages_t LogType, const char *Msg) 48 | { 49 | if(m_ToLog & LogType) 50 | TraceFile << Msg << "\n"; 51 | } 52 | 53 | void Log(LogMessages_t LogType, const char *Msg, u32 Value) 54 | { 55 | if(m_ToLog & LogType) 56 | TraceFile << Msg << ": " << std::setfill('0') << std::setw(8) << hex << Value << "\n"; 57 | } 58 | 59 | void Log(LogMessages_t LogType, const char *Msg, u64 Value) 60 | { 61 | if(m_ToLog & LogType) 62 | TraceFile << Msg << ": " << std::setfill('0') << std::setw(16) << hex << Value << "\n"; 63 | } 64 | 65 | void LogU48(LogMessages_t LogType, const char *Msg, u64 Value) 66 | { 67 | if(m_ToLog & LogType) 68 | TraceFile << Msg << ": " << std::setfill('0') << std::setw(12) << hex << Value << "\n"; 69 | } 70 | 71 | void Log(LogMessages_t LogType, const char *Msg, u8 Value) 72 | { 73 | if(m_ToLog & LogType) 74 | TraceFile << Msg << ": " << std::setfill('0') << std::setw(2) << hex << (u32)Value << "\n"; 75 | } 76 | 77 | void LogU4(LogMessages_t LogType, const char *Msg, u8 Value) 78 | { 79 | if(m_ToLog & LogType) 80 | TraceFile << Msg << ": " << std::setfill('0') << std::setw(1) << hex << (u32)Value << "\n"; 81 | } 82 | 83 | public: 84 | DES(LogMessages_t lm) : m_ToLog(lm) {}; 85 | 86 | #define GETBIT(x,n) (((x)>>(n))&1) 87 | #define SETBIT(o,m,b) o |= ((b)<<(m)); 88 | u64 InitialPermutation(u64 Input) 89 | { 90 | u64 Output = 0ULL; 91 | for(int i = 0; i < sizeof(IPTable); ++i) 92 | { 93 | SETBIT(Output,i,GETBIT(Input,IPTable[i])); 94 | } 95 | return Output; 96 | } 97 | u64 FinalPermutation(u64 Input) 98 | { 99 | u64 Output = 0ULL; 100 | for(int i = 0; i < sizeof(FPTable); ++i) 101 | { 102 | SETBIT(Output,i,GETBIT(Input,FPTable[i])); 103 | } 104 | return Output; 105 | } 106 | u64 Expand(u32 Input) 107 | { 108 | u64 Output = 0ULL; 109 | for(int i = 0; i < sizeof(ExpansionTable); ++i) 110 | { 111 | SETBIT(Output,i,(u64)GETBIT(Input,ExpansionTable[i])); 112 | } 113 | return Output; 114 | } 115 | u32 Permute(u32 Input) 116 | { 117 | u32 Output = 0ULL; 118 | for(int i = 0; i < sizeof(PermutationTable); ++i) 119 | { 120 | SETBIT(Output,i,GETBIT(Input,PermutationTable[i])); 121 | } 122 | return Output; 123 | } 124 | u32 PermuteInverse(u32 Input) 125 | { 126 | u32 Output = 0ULL; 127 | for(int i = 0; i < sizeof(InversePermutationTable); ++i) 128 | { 129 | SETBIT(Output,i,GETBIT(Input,InversePermutationTable[i])); 130 | } 131 | return Output; 132 | } 133 | u64 PermutationChoice1(u64 Input) 134 | { 135 | u64 Output = 0ULL; 136 | for(int i = 0; i < sizeof(PermutationChoice1Table); ++i) 137 | { 138 | SETBIT(Output,55-i,GETBIT(Input,63-PermutationChoice1Table[i])); 139 | } 140 | return Output; 141 | } 142 | u64 PermutationChoice2(u64 Input) 143 | { 144 | u64 Output = 0ULL; 145 | for(int i = 0; i < sizeof(PermutationChoice2Table); ++i) 146 | { 147 | SETBIT(Output,47-i,GETBIT(Input,55-PermutationChoice2Table[i])); 148 | } 149 | return Output; 150 | } 151 | 152 | u32 RotateLeft(u32 x, u32 n, u32 numBits) 153 | { 154 | n %= numBits; 155 | u32 Mask = (1 << numBits) - 1; 156 | return ((x << n) | (x >> (numBits - n))) & Mask; 157 | } 158 | 159 | void GenerateSubkeys(u64 Key) 160 | { 161 | u64 PC1 = PermutationChoice1(Key); 162 | u64 KeyState = PC1; 163 | u32 KSL = (KeyState >> 28) & 0x0FFFFFFF; 164 | u32 KSR = KeyState & 0x0FFFFFFF; 165 | for(int i = 0; i < sizeof(LeftRotations); ++i) 166 | { 167 | KSL = RotateLeft(KSL, LeftRotations[i], 28); 168 | KSR = RotateLeft(KSR, LeftRotations[i], 28); 169 | m_SubKeys[i] = PermutationChoice2(((u64)KSL << 28) | KSR); 170 | } 171 | } 172 | 173 | virtual void FeistelBegin(int round, u64 SubKey, u32 R, u32 LInverse) 174 | { 175 | Log(LogFeistel, "R", R); 176 | Log(LogFeistel, "LInverse", LInverse); 177 | LogU48(LogFeistel, "SubKey", SubKey); 178 | } 179 | 180 | virtual void FeistelAfterInit(int round, u64 Subkey, u32 R, u32 LInverse, u64 ExpandedR, u64 ERxorSubKey) 181 | { 182 | LogU48(LogFeistel, "ExpandedR", ExpandedR); 183 | LogU48(LogFeistel, "ExpandedR ^ SubKey", ERxorSubKey); 184 | } 185 | 186 | // Get rid of PermutedSBIdx 187 | virtual void FeistelAfterGroup(int round, int group, u8 SBIdx, u8 PermutedSBIdx, u4 SBOut) 188 | { 189 | char buf[256]; 190 | sprintf(buf, "SBIdx for bits %d-%d (group %d)", (group*6)+5,group*6, (7-group)+1); 191 | Log(LogFeistel, buf, SBIdx); 192 | Log(LogFeistel, "Permuted SBIdx", PermutedSBIdx); 193 | LogU4(LogFeistel, "SBOut", SBOut); 194 | } 195 | 196 | virtual void FeistelEnd(int round, u32 SBoxesOut, u32 OxorLInv, u32 FinalOutput) 197 | { 198 | Log(LogFeistel, "SBoxes output total", SBoxesOut); 199 | Log(LogFeistel, "Output ^ LInverse", OxorLInv); 200 | Log(LogFeistel, "Feistel output (Permute(Output^LInverse))", FinalOutput); 201 | } 202 | 203 | // So I don't forget: I would like to factor this out into "events" so that I 204 | // can derive a class off of this to capture the data that I want. 205 | u32 Feistel(int round, u64 SubKey, u32 R, u32 LInverse) 206 | { 207 | FeistelBegin(round, SubKey, R, LInverse); 208 | u64 ExpandedR = Expand(R); 209 | u64 ERxorSubKey = ExpandedR ^ SubKey; 210 | FeistelAfterInit(round, SubKey, R, LInverse, ExpandedR, ERxorSubKey); 211 | 212 | u32 Output = 0; 213 | for(int i = 7; i >= 0; --i) 214 | { 215 | u8 SBIdx = (ERxorSubKey >> (i*6)) & 0x3F; 216 | u4 SBOut = SBOX[7-i][SBIdx]; 217 | Output |= SBOut << 4*i; 218 | FeistelAfterGroup(round, i, SBIdx, SBIdx, SBOut); 219 | } 220 | u32 OxorLInv = Output ^ LInverse; 221 | u32 FinalOutput = Permute(OxorLInv); 222 | FeistelEnd(round, Output, OxorLInv, FinalOutput); 223 | return FinalOutput; 224 | } 225 | 226 | void IsolatedRound(u64 SubKey, u64 Plaintext) 227 | { 228 | u64 State = InitialPermutation(Plaintext); 229 | u32 L = State >> 32, R = State; 230 | u32 LInverse = PermuteInverse(L); 231 | u32 NewR = Feistel(1, SubKey, R, LInverse); 232 | } 233 | 234 | u64 EncryptBlock(u64 Key, u64 Block) 235 | { 236 | GenerateSubkeys(Key); 237 | 238 | Log(LogPermutations, "Original", Block); 239 | u64 AfterIP = InitialPermutation(Block); 240 | Log(LogPermutations, "Initial", AfterIP); 241 | 242 | u64 State = AfterIP; 243 | for(int i = 1; i <= 16; ++i) 244 | { 245 | u32 L = State >> 32, R = State; 246 | u32 LInverse = PermuteInverse(L); 247 | u32 NewR = Feistel(i, m_SubKeys[i-1], R, LInverse); 248 | if(i != 16) 249 | State = ((u64)R << 32) | NewR; 250 | else 251 | State = ((u64)NewR << 32) | R; 252 | } 253 | u64 AfterFP = FinalPermutation(State); 254 | Log(LogPermutations, "Final", AfterFP); 255 | return AfterFP; 256 | } 257 | }; 258 | 259 | u8 DES::IPTable[64] = 260 | { 261 | 57, 49, 41, 33, 25, 17, 9, 1, 262 | 59, 51, 43, 35, 27, 19, 11, 3, 263 | 61, 53, 45, 37, 29, 21, 13, 5, 264 | 63, 55, 47, 39, 31, 23, 15, 7, 265 | 56, 48, 40, 32, 24, 16, 8, 0, 266 | 58, 50, 42, 34, 26, 18, 10, 2, 267 | 60, 52, 44, 36, 28, 20, 12, 4, 268 | 62, 54, 46, 38, 30, 22, 14, 6 269 | }; 270 | 271 | u8 DES::FPTable[64] = 272 | { 273 | 39, 7, 47, 15, 55, 23, 63, 31, 274 | 38, 6, 46, 14, 54, 22, 62, 30, 275 | 37, 5, 45, 13, 53, 21, 61, 29, 276 | 36, 4, 44, 12, 52, 20, 60, 28, 277 | 35, 3, 43, 11, 51, 19, 59, 27, 278 | 34, 2, 42, 10, 50, 18, 58, 26, 279 | 33, 1, 41, 9, 49, 17, 57, 25, 280 | 32, 0, 40, 8, 48, 16, 56, 24 281 | }; 282 | 283 | u8 DES::PermutationTable[32] = { 284 | 7, 28, 21, 10, 26, 2, 19, 13, 285 | 23, 29, 5, 0, 18, 8, 24, 30, 286 | 22, 1, 14, 27, 6, 9, 17, 31, 287 | 15, 4, 20, 3, 11, 12, 25, 16 288 | }; 289 | 290 | u8 DES::InversePermutationTable[32] = { 291 | 11, 17, 5, 27, 25, 10, 20, 0, 292 | 13, 21, 3, 28, 29, 7, 18, 24, 293 | 31, 22, 12, 6, 26, 2, 16, 8, 294 | 14, 30, 4, 19, 1, 9, 15, 23 295 | }; 296 | 297 | u4 DES::SBOX[8][64] = 298 | { 299 | { 300 | 14, 0, 4, 15, 13, 7, 1, 4, 2, 14, 15, 2, 11, 13, 8, 1, 301 | 3, 10, 10, 6, 6, 12, 12, 11, 5, 9, 9, 5, 0, 3, 7, 8, 302 | 4, 15, 1, 12, 14, 8, 8, 2, 13, 4, 6, 9, 2, 1, 11, 7, 303 | 15, 5, 12, 11, 9, 3, 7, 14, 3, 10, 10, 0, 5, 6, 0, 13 304 | }, 305 | { 306 | 15, 3, 1, 13, 8, 4, 14, 7, 6, 15, 11, 2, 3, 8, 4, 14, 307 | 9, 12, 7, 0, 2, 1, 13, 10, 12, 6, 0, 9, 5, 11, 10, 5, 308 | 0, 13, 14, 8, 7, 10, 11, 1, 10, 3, 4, 15, 13, 4, 1, 2, 309 | 5, 11, 8, 6, 12, 7, 6, 12, 9, 0, 3, 5, 2, 14, 15, 9 310 | }, 311 | { 312 | 10, 13, 0, 7, 9, 0, 14, 9, 6, 3, 3, 4, 15, 6, 5, 10, 313 | 1, 2, 13, 8, 12, 5, 7, 14, 11, 12, 4, 11, 2, 15, 8, 1, 314 | 13, 1, 6, 10, 4, 13, 9, 0, 8, 6, 15, 9, 3, 8, 0, 7, 315 | 11, 4, 1, 15, 2, 14, 12, 3, 5, 11, 10, 5, 14, 2, 7, 12 316 | }, 317 | { 318 | 7, 13, 13, 8, 14, 11, 3, 5, 0, 6, 6, 15, 9, 0, 10, 3, 319 | 1, 4, 2, 7, 8, 2, 5, 12, 11, 1, 12, 10, 4, 14, 15, 9, 320 | 10, 3, 6, 15, 9, 0, 0, 6, 12, 10, 11, 1, 7, 13, 13, 8, 321 | 15, 9, 1, 4, 3, 5, 14, 11, 5, 12, 2, 7, 8, 2, 4, 14 322 | }, 323 | { 324 | 2, 14, 12, 11, 4, 2, 1, 12, 7, 4, 10, 7, 11, 13, 6, 1, 325 | 8, 5, 5, 0, 3, 15, 15, 10, 13, 3, 0, 9, 14, 8, 9, 6, 326 | 4, 11, 2, 8, 1, 12, 11, 7, 10, 1, 13, 14, 7, 2, 8, 13, 327 | 15, 6, 9, 15, 12, 0, 5, 9, 6, 10, 3, 4, 0, 5, 14, 3 328 | }, 329 | { 330 | 12, 10, 1, 15, 10, 4, 15, 2, 9, 7, 2, 12, 6, 9, 8, 5, 331 | 0, 6, 13, 1, 3, 13, 4, 14, 14, 0, 7, 11, 5, 3, 11, 8, 332 | 9, 4, 14, 3, 15, 2, 5, 12, 2, 9, 8, 5, 12, 15, 3, 10, 333 | 7, 11, 0, 14, 4, 1, 10, 7, 1, 6, 13, 0, 11, 8, 6, 13 334 | }, 335 | { 336 | 4, 13, 11, 0, 2, 11, 14, 7, 15, 4, 0, 9, 8, 1, 13, 10, 337 | 3, 14, 12, 3, 9, 5, 7, 12, 5, 2, 10, 15, 6, 8, 1, 6, 338 | 1, 6, 4, 11, 11, 13, 13, 8, 12, 1, 3, 4, 7, 10, 14, 7, 339 | 10, 9, 15, 5, 6, 0, 8, 15, 0, 14, 5, 2, 9, 3, 2, 12 340 | }, 341 | { 342 | 13, 1, 2, 15, 8, 13, 4, 8, 6, 10, 15, 3, 11, 7, 1, 4, 343 | 10, 12, 9, 5, 3, 6, 14, 11, 5, 0, 0, 14, 12, 9, 7, 2, 344 | 7, 2, 11, 1, 4, 14, 1, 7, 9, 4, 12, 10, 14, 8, 2, 13, 345 | 0, 15, 6, 12, 10, 9, 13, 0, 15, 3, 3, 5, 5, 6, 8, 11 346 | } 347 | }; 348 | 349 | u8 DES::ExpansionTable[48] = 350 | { 351 | 31, 0, 1, 2, 3, 4, 352 | 3, 4, 5, 6, 7, 8, 353 | 7, 8, 9, 10, 11, 12, 354 | 11, 12, 13, 14, 15, 16, 355 | 15, 16, 17, 18, 19, 20, 356 | 19, 20, 21, 22, 23, 24, 357 | 23, 24, 25, 26, 27, 28, 358 | 27, 28, 29, 30, 31, 0 359 | }; 360 | 361 | u8 DES::PermutationChoice1Table[56] = 362 | { 363 | 56, 48, 40, 32, 24, 16, 8, 364 | 0, 57, 49, 41, 33, 25, 17, 365 | 9, 1, 58, 50, 42, 34, 26, 366 | 18, 10, 2, 59, 51, 43, 35, 367 | 62, 54, 46, 38, 30, 22, 14, 368 | 6, 61, 53, 45, 37, 29, 21, 369 | 13, 5, 60, 52, 44, 36, 28, 370 | 20, 12, 4, 27, 19, 11, 3 371 | }; 372 | 373 | u8 DES::PermutationChoice2Table[48] = 374 | { 375 | 13, 16, 10, 23, 0, 4, 376 | 2, 27, 14, 5, 20, 9, 377 | 22, 18, 11, 3, 25, 7, 378 | 15, 6, 26, 19, 12, 1, 379 | 40, 51, 30, 36, 46, 54, 380 | 29, 39, 50, 44, 32, 47, 381 | 43, 48, 38, 55, 33, 52, 382 | 45, 41, 49, 35, 28, 31 383 | }; 384 | 385 | u8 DES::LeftRotations[16] = 386 | { 387 | 1, 1, 2, 2, 388 | 2, 2, 2, 2, 389 | 1, 2, 2, 2, 390 | 2, 2, 2, 1 391 | }; 392 | 393 | struct TestVector 394 | { 395 | u64 Key; 396 | u64 Input; 397 | u64 Output; 398 | }; 399 | 400 | #define WBDES_KEY 0x3032343234363236ULL 401 | 402 | TestVector Tests[1] = { 403 | { 404 | WBDES_KEY, 405 | 0x1122334455667788ULL, 406 | 0xc403d32e2bc6cfeeULL, 407 | } 408 | }; 409 | 410 | static u64 GetRand() 411 | { 412 | u64 Output = 0; 413 | for(int i = 0; i < 8; ++i) 414 | { 415 | Output <<= 8; 416 | Output |= rand() & 0xFF ; 417 | } 418 | return Output; 419 | } 420 | 421 | int main(int, char **) 422 | { 423 | //TraceFile.open("trace"); 424 | DES d((LogMessages_t)((int)LogFeistel|(int)LogRounds|(int)LogPermutations)); 425 | 426 | u64 pt = 0x5555555555555553ULL; 427 | //Tests[0].Input; 428 | u64 perm = d.InitialPermutation(pt); 429 | u64 deperm = d.FinalPermutation(perm); 430 | printf("pt = %16llx, perm = %16llx, deperm = %16llx\n", pt, perm, deperm); 431 | 432 | for(int i = 0; i < sizeof(DES::IPTable); ++i) 433 | { 434 | assert(DES::FPTable[DES::IPTable[i]] == i); 435 | assert(DES::IPTable[DES::FPTable[i]] == i); 436 | } 437 | 438 | for(int i = 0; i < 10; ++i) 439 | { 440 | u64 value = GetRand(); 441 | u64 IP = d.InitialPermutation(value); 442 | u64 FP = d.FinalPermutation(IP); 443 | assert(value == FP); 444 | FP = d.FinalPermutation(value); 445 | IP = d.InitialPermutation(FP); 446 | assert(value == IP); 447 | 448 | u32 v32 = value; 449 | u32 P = d.Permute(v32); 450 | u32 I = d.PermuteInverse(P); 451 | assert(v32 == I); 452 | I = d.PermuteInverse(value); 453 | P = d.Permute(I); 454 | assert(v32 == P); 455 | 456 | } 457 | 458 | for(int i = 0; i < sizeof(Tests)/sizeof(Tests[0]); ++i) 459 | { 460 | u64 Output = d.EncryptBlock(Tests[i].Key, Tests[i].Input); 461 | assert(Output == Tests[i].Output); 462 | } 463 | printf("All tests passed\n"); 464 | TraceFile.close(); 465 | } 466 | -------------------------------------------------------------------------------- /Hook.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include 3 | 4 | void StoreDword(unsigned char *Buffer, DWORD What) 5 | { 6 | Buffer[0] = What; 7 | Buffer[1] = What>>8; 8 | Buffer[2] = What>>16; 9 | Buffer[3] = What>>24; 10 | } 11 | 12 | bool WriteJump(unsigned long src_ea, unsigned long dest_ea, HANDLE ProcessTo) 13 | { 14 | bool retval = true; 15 | unsigned char write_buffer[5] = {'\xe9', '\0', '\0', '\0', '\0'}; 16 | unsigned long diff = dest_ea - (src_ea + 5); 17 | DWORD oldProtect, numBytesWritten; 18 | 19 | StoreDword(&write_buffer[1], diff); 20 | 21 | if(!VirtualProtect((LPVOID)src_ea, sizeof(write_buffer), PAGE_EXECUTE_READWRITE, &oldProtect)) 22 | return false; 23 | 24 | if(!WriteProcessMemory(ProcessTo, (LPVOID)src_ea, &write_buffer, sizeof(write_buffer), &numBytesWritten) || 25 | numBytesWritten != sizeof(write_buffer)) 26 | 27 | retval = false; 28 | 29 | if(!VirtualProtect((LPVOID)src_ea, sizeof(write_buffer), oldProtect, &oldProtect)) 30 | return false; 31 | 32 | return retval; 33 | } 34 | 35 | -------------------------------------------------------------------------------- /Hook.hpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #pragma once 3 | 4 | bool WriteJump(unsigned long src_ea, unsigned long dest_ea, HANDLE ProcessTo); 5 | -------------------------------------------------------------------------------- /dllmain.cpp: -------------------------------------------------------------------------------- 1 | // dllmain.cpp : Defines the entry point for the DLL application. 2 | #include "stdafx.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include "Hook.hpp" 8 | 9 | #include 10 | #include 11 | 12 | // Include the DES .cpp file to get the tables and not just the DES class 13 | #include "DES.cpp" 14 | 15 | // Uncomment to receive thread-related debug messages 16 | //#define THREAD_DEBUG 17 | 18 | // Uncomment to receive verbose debug messages 19 | #define VERBOSE_DEBUG 20 | 21 | // wb_init() [inlined] hook locations: 22 | // .text:004011C5 mov [ebp+var_4C], 0 <- hook after argument atox converstion 23 | // .text:004011CC cmp [ebp+var_4C], 0Bh <- resume at top of wb_init() 24 | unsigned long g_WBInitHookLocation = 0x004011C5; 25 | unsigned long g_WBInitResumeLocation = 0x004011CC; 26 | 27 | // wb_round() [inlined] hook locations: 28 | // .text:004015EC cmp [ebp+var_4C], 0Eh <- instruction not long enough to hook... 29 | // .text:004015F0 jg loc_401DE7 <- ... so hook this location instead 30 | // .text:004015F6 mov [ebp+var_50], 0 <- resume execution here 31 | unsigned long g_WBRoundHookLocation = 0x004015F0; 32 | unsigned long g_WBRoundResumeLocation = 0x004015F6; 33 | 34 | // This gets updated in HookWBRound when doing wb_round differential analysis 35 | // If we need to compute a round of wb_round(), set this to g_WBRoundResumeLocation 36 | // If we need to start over at wb_init(), set this to g_WBInitResumeLocation 37 | unsigned long g_WBRoundNextLocation; 38 | 39 | // 40 | // wbDES.exe memory locations of local variables in main() 41 | // 42 | // Where the atox()'ed input is stored 43 | unsigned char *gp_Input; 44 | 45 | // The 96-bit white-box state 46 | unsigned char *gp_WBState; 47 | 48 | // Integer stack variable for round number 49 | unsigned long *gp_RoundNumber; 50 | 51 | // 52 | // Handles to synchronization events, since the attack code runs in its own thread 53 | // 54 | // Signalled by wbDES main thread hooks after argument conversion or wb_round() has completed 55 | HANDLE g_ReadyToComputeEvent; 56 | 57 | // Signalled by wbDES main thread hooks after one wb_round() has completed 58 | HANDLE g_ComputationFinishedEvent; 59 | 60 | // Signalled by attack thread when we want to compute a wb_round() 61 | HANDLE g_ComputationRequestedEvent; 62 | 63 | // For debugging purposes, print an 8-byte u8 array (e.g., for the input). 64 | void PrintInput(u8 *Input) 65 | { 66 | for(int i = 0; i < 8; ++i) 67 | printf("%02Lx ", Input[i]); 68 | printf("\n"); 69 | } 70 | 71 | // For debugging purposes, print a 12-byte u8 array (e.g., for the white-box state). 72 | void PrintWBState(u8 *State) 73 | { 74 | for(int i = 0; i < 12; ++i) 75 | printf("%02Lx ", State[i]); 76 | printf("\n"); 77 | } 78 | 79 | // For debugging purposes, print a 12-byte u8 differential array 80 | void PrintDifferential(u8 nSbox, u8 nBit, u8 *Differential) 81 | { 82 | printf("Differential for bit %d (SBOX%d): ", nBit, nSbox); 83 | PrintWBState(Differential); 84 | } 85 | 86 | // This is the C portion of the ASM hook for wb_init(). It initializes the 87 | // pointers to the important variables on wbDES!main()'s stack frame, and uses 88 | // events to communicate with the attack thread. 89 | // Must be declared __stdcall. 90 | void __stdcall HookWBInit(unsigned long EBP) 91 | { 92 | gp_Input = (unsigned char *)(EBP-0x20); 93 | gp_WBState = (unsigned char *)(EBP-0x38); 94 | gp_RoundNumber = (unsigned long *)(EBP-0x4C); 95 | memset(gp_Input, 0, 8); 96 | 97 | // This is what we overwrote with our hook 98 | *gp_RoundNumber = 0; 99 | 100 | #ifdef THREAD_DEBUG 101 | printf("HookWBInit: Ready to compute; waiting...\n"); 102 | #endif 103 | 104 | // Signal to the attack thread that we are ready to compute wb_round() 105 | SetEvent(g_ReadyToComputeEvent); 106 | // Wait for the attack thread to request a computation 107 | WaitForSingleObject(g_ComputationRequestedEvent, INFINITE); 108 | 109 | #ifdef THREAD_DEBUG 110 | printf("HookWBInit: Got computation request\n"); 111 | #endif 112 | } 113 | 114 | // This is the C portion of the ASM hook for wb_round(). It manages the logic 115 | // for determining when round-1 has been computed, and uses events to 116 | // communicate with the attack thread. 117 | // Must be declared __stdcall. 118 | void __stdcall HookWBRound(int RoundNum) 119 | { 120 | // Entered after the first wb_round() has completed 121 | if(RoundNum == 1) 122 | { 123 | // Set the round number back to 0 (we overwrite this with our hook) 124 | *gp_RoundNumber = 0; 125 | 126 | // Inform the ASM hook stub that the next location is at wb_init() 127 | g_WBRoundNextLocation = g_WBInitResumeLocation; 128 | 129 | #ifdef THREAD_DEBUG 130 | printf("HookWBRound: About to send computation finished result\n"); 131 | #endif 132 | 133 | // Inform the attack thread that the computation has finished 134 | SetEvent(g_ComputationFinishedEvent); 135 | // Inform the attack thread that we are ready to compute the next wb_round() 136 | SetEvent(g_ReadyToComputeEvent); 137 | 138 | #ifdef THREAD_DEBUG 139 | printf("HookWBRound: Waiting for computation request\n"); 140 | #endif 141 | 142 | // Wait for the attack thread to request a wb_round() computation 143 | WaitForSingleObject(g_ComputationRequestedEvent, INFINITE); 144 | 145 | #ifdef THREAD_DEBUG 146 | // Print the input copied from the attack thread 147 | printf("HookWBRound: about to compute round-1 output for input: "); 148 | PrintInput(gp_Input); 149 | #endif 150 | } 151 | 152 | // Otherwise, the first iteration of wb_round() has not yet executed, so our 153 | // hook needs to resume execution at wb_round(), not wb_init() 154 | else 155 | { 156 | g_WBRoundNextLocation = g_WBRoundResumeLocation; 157 | } 158 | } 159 | 160 | // Cygwin caused a major headache when I tried to use standard DLL injection, 161 | // so I ended up having to add an IMAGE_IMPORT_DESCRIPTOR to the binary. Thus, 162 | // my DLL needed an export. This is that export. 163 | void __declspec(dllexport) Blah() {} 164 | 165 | // The hook stub for wb_init(). Passes EBP as a parameter. 166 | void __declspec(naked) HookWBInitASM() 167 | { 168 | __asm { 169 | pushad 170 | pushfd 171 | push ebp 172 | call HookWBInit 173 | popfd 174 | popad 175 | mov eax, g_WBInitResumeLocation 176 | jmp eax 177 | } 178 | } 179 | 180 | // The hook stub for wb_round(). Passes the round number as a parameter. 181 | void __declspec(naked) HookWBRoundASM() 182 | { 183 | __asm { 184 | pushad 185 | pushfd 186 | push dword ptr [ebp-0x4c] // Round number from stack variable 187 | call HookWBRound 188 | popfd 189 | popad 190 | mov eax, g_WBRoundNextLocation 191 | jmp eax 192 | } 193 | } 194 | 195 | // Inputs: 196 | // u8 *WhichBits: pointer to bytes which specify bit numbers to set within the plaintext 197 | // u8 nBits: size of the WhichBits array 198 | // u8 nBitValues: a bitmask for setting the bits specified in WhichBits. I.e. if this is 0b10, 199 | // then WhichBits[0] is not set, and WhichBits[1] is set within the plaintext 200 | // u8 *Output: 12-byte output, the wb_state array copied from wbDES.exe!main()'s stack frame 201 | void ComputeRound1Output(u8 *WhichBits, u8 nBits, u8 nBitValues, u8 *Output) 202 | { 203 | #ifdef THREAD_DEBUG 204 | printf("ComputeRound1Output: Waiting for ready to compute event...\n"); 205 | #endif 206 | 207 | // Attack thread waits for the main thread to complete any wb_round() computations 208 | WaitForSingleObject(g_ReadyToComputeEvent, INFINITE); 209 | 210 | #ifdef THREAD_DEBUG 211 | printf("ComputeRound1Output: About to send computation request...\n"); 212 | #endif 213 | 214 | // Initialize wbDES's input stack variable to zeroes 215 | memset(gp_Input, 0, 8); 216 | 217 | // Iterate through WhichBits and set the bits in the plaintext requested by the caller 218 | for(int i = 0; i < nBits; ++i) 219 | { 220 | // Was the bit requested as set? 221 | if(nBitValues & (1 << i)) 222 | { 223 | // Then convert it into a byte:bit offset and set it 224 | int nTranslated = 63-WhichBits[i]; 225 | unsigned char InitDiffByte = nTranslated / 8; 226 | unsigned char InitDiffBit = 1 << (7-(nTranslated % 8)); 227 | gp_Input[InitDiffByte] |= InitDiffBit; 228 | } 229 | } 230 | 231 | #ifdef THREAD_DEBUG 232 | printf("About to request round-1 output for: \n"); 233 | PrintInput(gp_Input); 234 | #endif 235 | 236 | // Attack thread informs wbDES main thread that we want to compute wb_round() 237 | SetEvent(g_ComputationRequestedEvent); 238 | 239 | // Attack thread waits for wbDES main thread to compute wb_round() 240 | WaitForSingleObject(g_ComputationFinishedEvent, INFINITE); 241 | 242 | // Copy the white-box state from wbDES!main() stack frame 243 | memcpy(Output, gp_WBState, 12); 244 | 245 | #ifdef THREAD_DEBUG 246 | printf("Output was: "); 247 | PrintWBState(Output); 248 | printf("ComputeRound1Output: Got computation result!\n"); 249 | #endif 250 | } 251 | 252 | // First, compute the round-1 output via ComputeRound1Output. Then, compute the 253 | // differential by XORing with the other specified round-1 output. 254 | void ComputeRound1Differential(u8 *WhichBits, u8 nBits, u8 nBitValues, u8 *Output, u8 *OtherOutput) 255 | { 256 | ComputeRound1Output(WhichBits, nBits, nBitValues, Output); 257 | for(int i = 0; i < 12; ++i) 258 | Output[i] ^= OtherOutput[i]; 259 | } 260 | 261 | // Enum used for comparing two differentials 262 | enum WBDesCompResult 263 | { 264 | WBDesComp_Identical, // Both affected the two nibbles in the same way 265 | WBDesComp_Unmodified, // The two nibbles were not affected 266 | WBDesComp_Incomparable // At least one nibble was affected in an unexpected way 267 | }; 268 | 269 | // Compare the differential output of two calls to wb_state. 270 | // Inputs: 271 | // u8 lDifferential[12]: "left-hand" differential 272 | // u8 rDifferential[12]: "right-hand" differential 273 | // u8 Mask[12]: generated from lDifferential; if a nibble was affected at position i, 274 | // Mask[i] contains 0xF for that nibble 275 | // Output: 276 | // WBDesComp_Identical: the positions in lDifferential specified by Mask matched in rDifferential 277 | // WBDesComp_Unmodified: the positions in rDifferential specified by Mask were all zero 278 | // WBDesComp_Incomparable: the expected values of the affected nibbles differed in rDifferential 279 | // vis-a-vis the ones in lDifferential 280 | WBDesCompResult CompareDifferentialsMasked(u8 *lDifferential, u8 *rDifferential, u8 *Mask) 281 | { 282 | WBDesCompResult res = WBDesComp_Incomparable; 283 | bool bFirst = true; 284 | 285 | // Iterate through all bytes of the Mask 286 | for(int i = 0; i < 12; ++i) 287 | { 288 | if(Mask[i]) 289 | { 290 | // Did the mask indicate that a change might occur in one of these two nibbles, 291 | // and in fact no change occurred? 292 | if((rDifferential[i] & Mask[i]) == 0) 293 | { 294 | // Is this our first time matching? If so, set the result indicating the nibble was not modified 295 | if(bFirst) 296 | res = WBDesComp_Unmodified; 297 | 298 | // Otherwise, did the previous mask position nibble match? Then we have conflictory results. 299 | else if(res == WBDesComp_Identical) 300 | res = WBDesComp_Incomparable; 301 | } 302 | 303 | // The mask indicated that a change might occur, and a change did in fact occur. 304 | // Were the modifications identical to those in the other differential? 305 | else 306 | if((lDifferential[i] & Mask[i]) == (rDifferential[i] & Mask[i])) 307 | { 308 | // Is this our first time matching? If so, set the result indicating the nibble was modified in an identical way 309 | if(bFirst) 310 | res = WBDesComp_Identical; 311 | 312 | // Otherwise, did the previous mask position nibble not match? Then we have conflictory results. 313 | else if(res == WBDesComp_Unmodified) 314 | res = WBDesComp_Incomparable; 315 | } 316 | 317 | // Otherwise, the results conflicted. 318 | else 319 | res = WBDesComp_Incomparable; 320 | 321 | bFirst = false; 322 | } 323 | } 324 | return res; 325 | } 326 | 327 | // Go nibble-by-nibble through the differential array. For any non-zero nibbles, 328 | // mark the output with an 0xF nibble. 329 | void ComputeNibbleBitmask(u8 *Differential, u8 *DifferentialMask) 330 | { 331 | for(int i = 0; i < 12; ++i) 332 | { 333 | if((Differential[i] & 0x0F) != 0) DifferentialMask[i] |= 0x0F; 334 | if((Differential[i] & 0xF0) != 0) DifferentialMask[i] |= 0xF0; 335 | } 336 | } 337 | 338 | // Inputs: 339 | // KeyPossibilities: a list of not-yet-filtered-out key possibilities 340 | // KeyBit0: those partial keys in 0 <= k < 1<<6 whose SBox output for the specified bit was 0 341 | // KeyBit1: those partial keys in 0 <= k < 1<<6 whose SBox output for the specified bit was 1 342 | // rComb: the XOR mask for the SBOX input (i.e. the bits from the right-hand input) 343 | // bShouldBeInDifferentSets: whether SBOX[k][bit] and SBOX[k^rComb][bit] should differ 344 | // Returns: 345 | // Modifies the KeyPossibilities list to remove those which failed the tests 346 | void FilterKeyPossibilities(std::list &KeyPossibilities, std::set &KeyBit0, std::set &KeyBit1, u8 rComb, bool bShouldBeInDifferentSets) 347 | { 348 | // Iterate through all keys that have not yet been discarded 349 | for(std::list::iterator i = KeyPossibilities.begin(); i != KeyPossibilities.end(); /*update handled in loop*/) 350 | { 351 | // Determine in which set the key lies (SBox output was 0 vs. 1) 352 | bool kInSet0 = KeyBit0.find(*i) != KeyBit0.end(); 353 | 354 | // Determine in which set key^rComb lies (SBox output was 0 vs. 1) 355 | bool rCombInSet0 = KeyBit0.find(*i ^ rComb) != KeyBit0.end(); 356 | 357 | // Were they in the same set? 358 | bool bInSameSet = (kInSet0 && rCombInSet0) || (!kInSet0 && !rCombInSet0); 359 | 360 | // If they were in the same set and should have been in different sets, or 361 | // if they were in different sets but should have been in the same set, 362 | // then differential analysis has discarded this key. 363 | if((bInSameSet && bShouldBeInDifferentSets) || (!bInSameSet && !bShouldBeInDifferentSets)) 364 | { 365 | #ifdef VERBOSE_DEBUG 366 | printf("Filtering key %d\n", *i); 367 | #endif 368 | // Key was bad, remove it 369 | KeyPossibilities.erase(i++); 370 | } 371 | else 372 | ++i; 373 | } 374 | } 375 | 376 | // Derived from the DES specification: the set of 6 input bits, and 4 output bits, 377 | // involved in a single DES SBOX computation. 378 | struct SBoxInOut 379 | { 380 | u8 nGroup; 381 | u8 InputBits[6]; 382 | u8 OutputBits[4]; 383 | }; 384 | 385 | // The real values for the structure just described. 386 | SBoxInOut SBoxBitMappings[8] = 387 | { 388 | { 0, { 7, 57, 49, 41, 33, 25, }, { 34, 52, 16, 38 } }, 389 | { 1, { 33, 25, 17, 9, 1, 59, }, { 54, 42, 28, 56 } }, 390 | { 2, { 1, 59, 51, 43, 35, 27, }, { 18, 20, 32, 30 } }, 391 | { 3, { 35, 27, 19, 11, 3, 61, }, { 22, 0, 44, 62 } }, 392 | { 4, { 3, 61, 53, 45, 37, 29, }, { 6, 12, 26, 8 } }, 393 | { 5, { 37, 29, 21, 13, 5, 63, }, { 46, 40, 60, 58 } }, 394 | { 6, { 5, 63, 55, 47, 39, 31, }, { 10, 14, 24, 36 } }, 395 | { 7, { 39, 31, 23, 15, 7, 57, }, { 48, 50, 2, 4 } }, 396 | }; 397 | 398 | // This implements the differential cryptanalysis attack described in SysK's paper. 399 | DWORD WINAPI Attack(LPVOID) 400 | { 401 | // Begin by computing the whitebox round-1 output for the plaintext of all zeroes. 402 | u8 nullaryOutput[12]; 403 | ComputeRound1Output(NULL, 0, 0, nullaryOutput); 404 | 405 | #ifdef VERBOSE_OUTPUT 406 | printf("Round-1 output for nullary: "); 407 | PrintWBState(nullaryOutput); 408 | #endif 409 | 410 | // Iterate through all 8 6-bit partial subkeys and their associated SBoxes 411 | for(int i = 0; i < 8; ++i) 412 | { 413 | // Initially, set the list of all key possibilities to all 64 partial subkeys 414 | std::list KeyPossibilities; 415 | for(int j = 0; j < 1<<6; ++j) 416 | KeyPossibilities.push_back(j); 417 | 418 | // Iterate through all 4 output bits of the SBox 419 | for(int j = 0; j < 4; ++j) 420 | { 421 | // Create two sets, for whether the SBox output bit for that key was 0 or 1 422 | std::set KeyBit1, KeyBit0; 423 | for(int k = 0; k < 1<<6; ++k) 424 | { 425 | // For each key, get the SBOX value, and mask off just the desired bit 426 | // Having erroneously written "i" instead of "7-i" cost me several hours :( 427 | if(DES::SBOX[7-i][k] & (1 << j)) 428 | KeyBit1.insert(k); 429 | else 430 | KeyBit0.insert(k); 431 | } 432 | 433 | // Compute the differential for the single left-hand side bit associated with 434 | // that SBox output bit (differential is computed against the nullary plaintext) 435 | u8 lBitDifferential[12]; 436 | ComputeRound1Differential(&SBoxBitMappings[i].OutputBits[j], 1, 1, lBitDifferential, nullaryOutput); 437 | 438 | #ifdef VERBOSE_DEBUG 439 | PrintDifferential(i, SBoxBitMappings[i].OutputBits[j], lBitDifferential); 440 | #endif 441 | 442 | // Compute the bitmask for lBitDifferential indicating the two non-zero nibbles 443 | u8 lBitDifferentialMask[12]; 444 | memset(lBitDifferentialMask, 0, sizeof(lBitDifferentialMask)); 445 | ComputeNibbleBitmask(lBitDifferential, lBitDifferentialMask); 446 | 447 | #ifdef VERBOSE_DEBUG 448 | printf("Differential bitmask: "); 449 | PrintDifferential(i, j, lBitDifferentialMask); 450 | #endif 451 | 452 | // Iterate through all 64 combinations of right-hand input bits (those XORed 453 | // against the key to produce the SBox index) 454 | for(int rComb = 1; rComb < 1<<6; ++rComb) 455 | { 456 | // Compute the differential between the round-1 output for that 457 | // combination of right-hand input bits, against the round-1 output 458 | // for the nullary plaintext. 459 | u8 rCombDifferential[12]; 460 | ComputeRound1Differential(SBoxBitMappings[i].InputBits, 6, rComb, rCombDifferential, nullaryOutput); 461 | 462 | // Compare the left-hand (odd) bit differential against the right-hand (even) bits 463 | // differential for this particular combination of input bits. 464 | WBDesCompResult comp = CompareDifferentialsMasked(lBitDifferential, rCombDifferential, lBitDifferentialMask); 465 | 466 | #ifdef VERBOSE_DEBUG 467 | printf("Comparison result = %d ", comp); 468 | PrintDifferential(i, rComb, rCombDifferential); 469 | #endif 470 | 471 | // If the modified nibbles in the right-hand differential were not either 472 | // A) identical to those in the left-hand differential, OR 473 | // B) entirely unmodified, 474 | // Then this does not give us information with which to filter the key possibilities. 475 | if(comp != WBDesComp_Incomparable) 476 | { 477 | // If CompareDifferentialsMasked() returns WBDesComp_Identical, that 478 | // means that the differential for the right-hand bits specified by 479 | // rComb induced the same modification as the differential for the 480 | // left-hand bit. Thus, the key by itself produces either 0/1, and 481 | // the differential with rComb produces the opposite bit 1/0. Hence, 482 | // we have a relation: RELEVANTBIT(SBOX[k]) != RELEVANTBIT(SBOX[k^rComb]). 483 | // In other words, k and k^rComb should be in different sets (see the 484 | // declaration and initialization of KeyBit0 and KeyBit1, above). 485 | 486 | // Otherwise, if it returns WBDesComp_Unmodified, then we have the 487 | // relation: RELEVANTBIT(SBOX[k]) == RELEVANTBIT(SBOX[k^rComb]). 488 | // In other words, k and k^rComb should be in the same set (see the 489 | // declaration and initialization of KeyBit0 and KeyBit1, above). 490 | 491 | // Use the relation just described to filter out possible keys. 492 | FilterKeyPossibilities(KeyPossibilities, KeyBit0, KeyBit1, rComb, comp == WBDesComp_Identical); 493 | } 494 | } 495 | } 496 | 497 | // Once we get here, we have filtered the subkey by all of the definitive 498 | // relations we generated above. 499 | printf("Remaining subkey possibilities for group %d:\n", i); 500 | 501 | // Print them. 502 | for(std::list::iterator j = KeyPossibilities.begin(); j != KeyPossibilities.end(); ++j) 503 | printf("\t%d\n", *j); 504 | } 505 | exit(0); 506 | return 0; 507 | } 508 | 509 | /* Output: [these subkeys are correct; see for example https://github.com/SideChannelMarvels/Deadpool/wiki/Tutorial-%231:-DCA-against-Wyseur-2007-challenge] 510 | Remaining subkey possibilities for group 0: 511 | 7 512 | Remaining subkey possibilities for group 1: 513 | 15 514 | Remaining subkey possibilities for group 2: 515 | 32 516 | Remaining subkey possibilities for group 3: 517 | 49 518 | Remaining subkey possibilities for group 4: 519 | 44 520 | Remaining subkey possibilities for group 5: 521 | 50 522 | Remaining subkey possibilities for group 6: 523 | 2 524 | Remaining subkey possibilities for group 7: 525 | 20 526 | */ 527 | 528 | // DllMain(), which hooks wbDES.exe's .text section, creates the attack thread, 529 | // and initializes the Event objects that main thread hook and attack thread 530 | // use to communicate with one another. 531 | BOOL APIENTRY DllMain(HMODULE hModule,DWORD ul_reason_for_call,LPVOID lpReserved) 532 | { 533 | // MessageBoxA(NULL, "ATTACH", "ATTACH", 0); 534 | 535 | // Only hook once 536 | static bool bInstalled = false; 537 | if(!bInstalled) 538 | { 539 | bInstalled = true; 540 | 541 | // Install the two hooks into our ASM stubs above. 542 | WriteJump((unsigned long)g_WBInitHookLocation, (unsigned long)&HookWBInitASM, GetCurrentProcess()); 543 | WriteJump((unsigned long)g_WBRoundHookLocation, (unsigned long)&HookWBRoundASM, GetCurrentProcess()); 544 | 545 | // Create the event objects used for communication. 546 | g_ReadyToComputeEvent = CreateEvent(NULL, FALSE, FALSE, NULL); 547 | g_ComputationFinishedEvent = CreateEvent(NULL, FALSE, FALSE, NULL); 548 | g_ComputationRequestedEvent = CreateEvent(NULL, FALSE, FALSE, NULL); 549 | 550 | // Create the attack thread. 551 | CreateThread(NULL, 0, Attack, NULL, 0, NULL); 552 | } 553 | return TRUE; 554 | } 555 | -------------------------------------------------------------------------------- /readme.MD: -------------------------------------------------------------------------------- 1 | This is my DLL-injection based solution to Brecht Wyseur's wbDES challenge [1], originating from his Ph.D. thesis [0], and described in SysK's Phrack article "Practical Cracking of White-Box Implementations" [2]. To make a long story short, I initially set about implementing the Differential Computation Analysis-based attack [3-4] as a shortcut to reading SysK's solution, but ran into issues with my implementation. (Although both of those authors released their source code, I decided to use their publications as a starting point for developing my own code rather than using the tools they released, for maximum educational purposes.) After spending far too long debugging it, I decided to bite the bullet and read SysK's article (which, ironically, took much less time to implement and debug) in the hopes that I could use that knowledge to fix my differential computation analysis code. That latter piece of work remains to be done. 2 | 3 | The source code herein consists of a working implementation for breaking the wbDES challenge. Rather than decompiling and re-implementing the wbDES logic (as did SysK), I decided to go with a real-world attack scenario using DLL injection. To wit, my DLL hooks into the wb_init() and wb_round() functionality in wbDES.exe, and another thread then uses the original functionality as a black box to produce the necessary vectors for differential cryptanalysis. Then, the differential attack described by SysK is implemented from-scratch according to my understanding of SysK's approach. 4 | 5 | The TL;DR version of SysK's approach: from reading the DES specification, you can determine which bits are involved in the round-0 computation of the S-BOX indices, and which input bits are XORed against each S-BOX's output. Then, you can use differential cryptanalysis to determine "the rough location" where those bits are located within the white-box state (that's a simplification), by toggling those individual bits and examining the relationship with the output from where those bits were not toggled. Since the S-BOX lookup is key-dependent, these pieces of information combined gives you a set of constraints that the subkey for that S-BOX must satisfy. E.g. if I toggled the 0th bit in the right-hand S-BOX input and the output did not change in the 2nd bit, then I must have that SBOX[k] & 0x4 == SBOX[k^1] & 0x4, which allows you to discard values of k that do not satisfy this property for the relevant S-BOX. Repeat for all 2^6 right-hand S-BOX input XOR values (where feasible -- not every XOR mask will be feasible due to how the white-box state is encoded), and at the end you will have a unique 6-bit key that satisfied all constraints. Repeat for all 8 SBOXes to recover the complete 48 round subkey one six-bit group at a time, additively, and then the full 56-bit symmetric key can be determined by a 2^8*DES()-complexity brute-force. This takes milliseconds. 6 | 7 | As for my implementation, I experienced issues injecting my DLL into wbDES.exe. After eventually tracking down the culprit functionality to the Cygwin CRT code, I decided that on an alternative approach to DLL injection -- namely, adding an IMAGE_IMPORT_DESCRIPTOR to wbDES.exe and forcing it to load my DLL that way. So if you want to tinker with my code, you'll need to do the same thing to wbDES -- a tool called IIDKing [5] will make this easy. 8 | 9 | [0] http://www.whiteboxcrypto.com/research.php 10 | 11 | [1] http://www.whiteboxcrypto.com/challenges.php 12 | 13 | [2] http://phrack.org/issues/68/8.html 14 | 15 | [3] https://eprint.iacr.org/2015/753.pdf 16 | 17 | [4] https://github.com/SideChannelMarvels/Deadpool/tree/master/wbs_des_wyseur2007 18 | 19 | [5] https://tuts4you.com/download.php?view.413 20 | 21 | [6] https://en.wikipedia.org/wiki/DES_supplementary_material#Initial_permutation_.28IP.29 22 | 23 | 24 | Rambling errata: 25 | 26 | As usual when implementing cryptography or cryptanalysis, the thing that costs me the most time is indexing issues: some publications start counting at one; some of them start counting bits MSB rather than the LSB; and so on. These issues can multiply atop one another. 27 | 28 | Beyond plaguing my implementation, those issues also played a role in SysK's write-up. Specifically, SysK describes the DES initial permutation IP as moving all of the odd bits into the upper 32-bit quantity, which is then called L (for "left-hand side") within most treatments of DES. And indeed, a cursory examination of the DES IP table would give you the same impression [6]. 29 | 30 | However, note that the table refers to bits indexed starting at *1* (i.e., 0 does not appear in that table). So if you start indexing your bits at zero -- as SysK does in his publication -- it's actually true that the *even* bits are permuted into L, rather than the odd ones. But wait! That's only true if you start indexing from the LSB. Actually, SysK indexes from 0 starting at the MSB, so all is well with the world again (in that the so-called "odd" bits are the same as the "even" bits when indexing from 0 at the LSB). Stemming from this, I experienced some confusion with an off-by-one issue (versus my own expectations of the nomenclature) in sections 6.2-6.3. Also due to indexing issues, my SBoxBitMappings table is different from his for reasons I only partially understand. Nevertheless, my implementation does produce the correct round-0 subkey, so it works. 31 | 32 | If all of that is gibberish to you, consider yourself lucky! I spent about two days confused before I fixed those issues. 33 | 34 | --------------------------------------------------------------------------------