Happy New Year 2018 – Challenge Solution
Credit to Author: SSD / Noam Rathaus| Date: Mon, 08 Jan 2018 06:15:57 +0000
Want to get paid for a vulnerability similar to this one?
Contact us at: sxsxdx@xbxexyxoxnxdxsxexcxuxrxixtxy.xcom
See our full scope at: https://blogs.securiteam.com/index.php/product_scope
In our post found here: https://blogs.securiteam.com/index.php/archives/3616, we hid a challenge.
The challenge was split into two parts:
1. Finding it
2. Solving it
Finding it wasn’t very hard, the challenge was hidden inside the image, it wasn’t anything fancy, just inside the image you had a zip file appended to the end of the file:
1 2 3 4 5 6 7 8 9 10 11 | $ xxd 2018_2.jpg | tail 000148a0: 0000 e817 0000 0900 1800 0000 0000 0000 ................ 000148b0: 0000 fd81 0000 0000 6368 616c 6c65 6e67 ........challeng 000148c0: 6555 5405 0003 b50b 495a 7578 0b00 0104 eUT.....IZux.... 000148d0: e803 0000 04e8 0300 0050 4b01 021e 0314 .........PK..... 000148e0: 0000 0008 009b 9021 4c14 3bc1 9d86 0000 .......!L.;..... 000148f0: 009c 0000 0006 0018 0000 0000 0001 0000 ................ 00014900: 00b4 817b 0900 0052 4541 444d 4555 5405 ...{...READMEUT. 00014910: 0003 265c 4a5a 7578 0b00 0104 e803 0000 ..&JZux........ 00014920: 04e8 0300 0050 4b05 0600 0000 0002 0002 .....PK......... 00014930: 009b 0000 0041 0a00 0000 00 .....A..... |
If you binwalk inspect the file you will see:
1 2 3 4 5 6 7 8 | $ binwalk 2018_2.jpg DECIMAL HEXADECIMAL DESCRIPTION ———————————————————————————————————————— 0 0x0 JPEG image data, JFIF standard 1.01 81481 0x13E49 Zip archive data, at least v2.0 to extract, compressed size: 2360, uncompressed size: 6120, name: challenge 83908 0x147C4 Zip archive data, at least v2.0 to extract, compressed size: 134, uncompressed size: 156, name: README 84261 0x14925 End of Zip archive |
This looks really promising now, a ZIP file has been appended to the image, and binwalk tells us it’s located at offset 81481. We can use dd to get the archive.
1 2 3 4 | $ dd if=2018_2.jpg of=challenge.zip bs=1 skip=81481 2802+0 records in 2802+0 records out 2802 bytes (2.8 kB, 2.7 KiB) copied, 0.00661634 s, 423 kB/s |
Binwalk also tells us, there are two files inside the archive (challenge and README). Use unzip to get them.
1 2 3 4 | $ unzip challenge.zip Archive: challenge.zip inflating: challenge inflating: README |
(NOTE: If you downloaded the file to a Linux machine (though other machines may have also worked), and just unziped it you got two files:
1. README
2. challenge
There was no need to use dd)
The readme was pretty simple, just instructed you to make the challenge ELF binary file spit out text:
1 2 3 4 5 | Make ‘challenge’ output the following text (without a new line): Happy New Year! From Beyond Security SSD :) First correct submission will get 1,000$ USD! |
From this point the solution varied, our first solver reversed engineered the file and discovered what it does, which basically breaks down to:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | int main(int argc, char **argv, char **envp) { int ret; char filename[9]; char key[13]; strcpy(filename, “eapfxlya”); strcpy(key, “xFFx6Bx28x66xD6x35xDAx01x4Dx64x47xA3”); ret = challenge(filename, key); return ret; } int keyhash(const char *key) { int ret; unsigned int i; ret = 0; for ( i = 0; i < strlen(key); ++i ) ret = _rotl(key[i] ^ ret, 7); return ret; } int decode(unsigned int *key, char *out, unsigned int size) { int result; int i; for ( i = 0; ; ++i ) { result = i; if ( i >= size ) break; *key *= 0x8088405; out[i] ^= ++*key >> 24; } return result; } int challenge(const char *filename, char *key) { int result; int seed; unsigned int n; FILE *fp; char *ptr; fp = fopen(filename, “rb”); if ( fp ) { n = 1; seed = keyhash(key); while ( n ) { ptr = (char *)malloc(0x200uLL); n = fread(ptr, 1uLL, 0x200uLL, fp); decode(&seed, ptr, n); write(1, ptr, n); } fclose(fp); putchar(‘n’); result = 1; } else { puts(“file does not exist!”); result = 0; } return result; } |
The program executes the following actions:
- Open an encrypted file named “eapfxlya” (this can be confirmed with strace)
- Generate a 32-bit key based on “xFFx6Bx28x66xD6x35xDAx01x4Dx64x47xA3” (see function keyhash)
- Read the contents of the opened file
- Decode it with XOR/ADD/MUL/SHR tricks (see function decode)
The keyhash function is pretty straight-forward so let’s have a closer look at the decode function. It’s purpose is to generate a sequence of 32-bit numbers based on a linear congruential generator (aka *predictive* pseudo number generator) which takes a precomputed hash for seed. Each number of this sequence is then shifted right and used as a 8-bit xor-mask on every byte in the file stream. In conclusion, this program can be used to decode and encode any file in a symmetric way. So let’s use the happy new year string “Happy New Year! From Beyond Security SSD :)” and feed it into the reversed program.
1 2 3 4 5 | $ echo –ne “Happy New Year! From Beyond Security SSD :)” > eapfxlya $ ./challenge > tmp $ dd if=tmp of=eapfxlya bs=43 count=1 # don’t forget, it’s without a new line $ ./challenge Happy New Year! From Beyond Security SSD :) |
Congratulations to: Alexandre for solving the challenge first (within 2 hours of posting it online).
A few other solutions we received included a brute forcing code (a cool one from Tukan):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | root@ubuntu–512mb–ams2–01:~# cat solver.py import sys def reversit(inp, checksum=0xf5f6103f): out = ” for c in inp: checksum *= 0x08088405 checksum &= 2**32–1 checksum += 1 outc = ord(c) ^ ((checksum) >> 24) out += chr(outc) return out winner = reversit(‘Happy New Year! From Beyond Security SSD :)’ + ‘x1b’ + ‘P’) sys.stdout.write(winner) root@ubuntu–512mb–ams2–01:~# python solver.py > eapfxlya root@ubuntu–512mb–ams2–01:~# ./challenge |