URL: http://asis-ctf.ir/challenges/
Type: block of bits encoding
Solution: ASIS_c0d9dce6d68b484f7a37d5c80d576913
Description
Source code of encryptor and encrypted file is given
file
Archive contains 2 files:
$ tar xvfJ crypto_250_bf16a61aa8117be9c994f171023d37ff x f753c38c5baadab5a53052ad06e7d7a0 $ unzip f753c38c5baadab5a53052ad06e7d7a0 inflating: encrypted inflating: plough.cpp
plough.cpp
is the C++ source code of the algorithm used to encrypt the file:
#include<iostream> #include<stdio.h> #include<stdlib.h> #include<string.h> using namespace std; //---------------------------------- class class1 { private: FILE*file; unsigned char k; char chw; public: class1(char[20]); ~class1(); void w(char); void wn(int, char); }; //--------- class1::class1(char FileName[20]) { if(!(file = fopen(FileName, "wb"))) { cout << "\nError: cant crate file!"; exit(1); } k = 128; chw = 0; } //--------- class1::~class1() { if(k != 128) putc(chw, file); fclose(file); } //--------- void class1::w(char b) { if(b > 1) b = 1; chw += b * k; if(!(k /= 2)) { putc(chw, file); k = 128; chw = 0; } } //--------- void class1::wn(int n, char ch) { int k = (n > 0); char temp; for(; n > 1; n--) k *= 2; for(; k; k /= 2) { temp = ch & k; w(temp); } } //---------------------------------------------------- class class2 { private: FILE*file; unsigned char k; char chR; public: class2(char[20]); ~class2(); int r(); char rn(int); int end(); }; //--------- class2::class2(char FileName[20]) { if(!(file = fopen(FileName, "rb"))) { cout << "\nError: cant read file!"; exit(1); } chR = getc(file); k = 128; } //--------- class2::~class2() { fclose(file); } //--------- int class2::r() { int ret = k & chR; if(!(k /= 2)) { chR = getc(file); k = 128; } if(ret) return 1; else return 0; } //--------- char class2::rn(int n) { int ret = 0; int k = (n > 0); for(; n > 1; n--) k *= 2; for(; k; k /= 2) ret += r() * k; return (char) ret; } //--------- int class2::end() { if(feof(file)) return 1; else return 0; } //---------------------------------- char* zip(char *inFileName, char *outFileName); //---------------------------------- int main(int argc,char*argv[]) { if(argc != 3) cout << "usage:\tplough inputFilePath outputFilePath"; else cout << zip(argv[1], argv[2]); return 0; } //---------------------------------- char* zip(char *inFileName, char *outFileName) { class2 IBFile(inFileName); class1 OBFile(outFileName); char shift = 0, counter = 0; char r; int ended = 0; do { r = IBFile.r(); ended = IBFile.end(); if(ended) { if(!shift) { if(!r) OBFile.wn(3, ++counter); else { OBFile.wn(3, counter); OBFile.wn(2, 1); } } else { if(r) OBFile.wn(2, ++counter); else { OBFile.wn(2, counter); OBFile.wn(3, 1); } } } else { if(!shift) { if(!r) { counter++; if(counter == 7) { OBFile.wn(3, 7); counter = 0; shift = 1; } } else { OBFile.wn(3, counter); shift = 1; counter = 1; } } else { if(r) { counter++; if(counter == 3) { OBFile.wn(2, 3); counter = 0; shift = 0; } } else { OBFile.wn(2, counter); shift = 0; counter = 1; } } } }while(!ended); return (char*)"End\n"; }
Algorithm analysis
We have two classes:
class1
is for dealing with output bits flowconstructor
&destructor
: handle opening & closing output filew(char b)
: set (b=1
) / unset (b=0
) the current bit of current output bytewn(int n, char ch)
: encodech
value usingn
bits
class2
is for dealing with input bits flowconstructor
&destructor
: handle opening & closing input filer()
: return value (0
or1
) of current bit of current input bytern(int n)
: return compound value of then
bits starting from current bit of current input byte (not used here)end()
: return end of input file status
And one encoding function zip(char *inFileName, char *outFileName)
:
r
is the value of current bit of current input byteshift
is used to toggle writing of 3 bits and 2 bitscounter
is the « distance » between a bit value and the next one
Reverse encoding scheme
As seen above there is 2 main writing processes:
- 3 bits (
shift=0
): the 3 bits value specifies the « position » of the next bit'1'
(or'0'
if the 3 bits value is0b111
) - 2 bits (
shift=1
): it depends of the 2 bits value and thecounter
value. For an easier implementation, we can define all the decoding combinations through this matrix:
counter = 0
counter = 1
2 bits = 00
0
2 bits = 01
10
0
2 bits = 10
110
10
2 bits = 11
111
11
If the result value ends with a
'1'
thencounter = 0
elsecounter = 1
Python decoder
Python is not optimized for decoding such big files, but is easier to see how it works. In order to simplify it, we do not decode the end of file (not neccessary).
f = open('encrypted', 'rb') iSTR = f.read() iBSTR = ''.join(['{:08b}'.format(ord(c)) for c in iSTR]) f.close() oBSTR = '' shift, counter = 0, 0 while (iBSTR != ''): if (shift == 0): r, iBSTR = int(iBSTR[:3],2), iBSTR[3:] oBSTR += '0' * (r - counter) if (r < 7): oBSTR += '1' counter = 1 else: counter = 0 shift = 1 else: r, iBSTR = int(iBSTR[:2],2), iBSTR[2:] oBSTR += [['0', '10', '110', '111'], ['', '0', '10', '11']][counter][r] if (oBSTR[-1] == '1'): counter = 0 else: counter = 1 shift = 0 oSTR = ''.join([chr(int(oBSTR[i:i+8],2)) for i in range(0, len(oBSTR), 8)]) f = open('plain', 'wb') f.write(oSTR) f.close()
Running on encrypted
, we obtain the following decoded PNG image flag file: