376 lines
9.2 KiB
C++
376 lines
9.2 KiB
C++
|
/*
|
||
|
* steghide 0.5.1 - a steganography program
|
||
|
* Copyright (C) 1999-2003 Stefan Hetzl <shetzl@chello.at>
|
||
|
*
|
||
|
* This program is free software; you can redistribute it and/or
|
||
|
* modify it under the terms of the GNU General Public License
|
||
|
* as published by the Free Software Foundation; either version 2
|
||
|
* of the License, or (at your option) any later version.
|
||
|
*
|
||
|
* This program is distributed in the hope that it will be useful,
|
||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
* GNU General Public License for more details.
|
||
|
*
|
||
|
* You should have received a copy of the GNU General Public License
|
||
|
* along with this program; if not, write to the Free Software
|
||
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
#include "AUtils.h"
|
||
|
#include "BinaryIO.h"
|
||
|
#include "BitString.h"
|
||
|
#include "EmbData.h"
|
||
|
#include "error.h"
|
||
|
#include "MCryptPP.h"
|
||
|
#include "MHashPP.h"
|
||
|
#include "common.h"
|
||
|
|
||
|
EmbData::EmbData (MODE m, std::string pp, std::string fn)
|
||
|
: Mode(m), Passphrase(pp), FileName(fn)
|
||
|
{
|
||
|
if (m == EXTRACT) {
|
||
|
NumBitsNeeded = NumBitsRequested = NBitsMagic ;
|
||
|
Version = CodeVersion ;
|
||
|
State = READ_MAGIC ;
|
||
|
Reservoir = BitString() ;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool EmbData::finished ()
|
||
|
{
|
||
|
myassert (Mode == EXTRACT) ;
|
||
|
return (State == END) ;
|
||
|
}
|
||
|
|
||
|
unsigned long EmbData::getNumBitsRequested ()
|
||
|
{
|
||
|
myassert (Mode == EXTRACT) ;
|
||
|
return NumBitsRequested ;
|
||
|
}
|
||
|
|
||
|
void EmbData::addBits (BitString addbits)
|
||
|
{
|
||
|
myassert (Mode == EXTRACT) ;
|
||
|
|
||
|
#ifdef DEBUG
|
||
|
printDebug (1, "\nEmbData::addBits called with") ;
|
||
|
printDebug (1, " addbits:") ;
|
||
|
addbits.printDebug (1, 2) ;
|
||
|
printDebug (1, " Reservoir:") ;
|
||
|
Reservoir.printDebug (1, 2) ;
|
||
|
#endif
|
||
|
|
||
|
Reservoir.append (addbits) ;
|
||
|
BitString bits ;
|
||
|
if (Reservoir.getLength() >= NumBitsNeeded) {
|
||
|
bits = Reservoir.cutBits (0, NumBitsNeeded) ; // take exactly the first NumBitsNeeded bits from Reservoir | addbits
|
||
|
}
|
||
|
else { // Reservoir.getLength() < NumBitsNeeded
|
||
|
myassert(false) ;
|
||
|
}
|
||
|
|
||
|
#ifdef DEBUG
|
||
|
printDebug (1, "bits is now:") ;
|
||
|
bits.printDebug (1, 2) ;
|
||
|
#endif
|
||
|
|
||
|
switch (State) {
|
||
|
case READ_MAGIC:
|
||
|
{
|
||
|
#ifdef DEBUG
|
||
|
printDebug (1, "in the READ_MAGIC state") ;
|
||
|
#endif
|
||
|
if (bits.getValue(0, NBitsMagic) == Magic) {
|
||
|
NumBitsNeeded = 1 ;
|
||
|
NumBitsRequested = AUtils::bminus<unsigned long> (NumBitsNeeded, Reservoir.getLength()) ;
|
||
|
State = READ_VERSION ;
|
||
|
}
|
||
|
else {
|
||
|
throw SteghideError (_("could not extract any data with that passphrase!")) ;
|
||
|
}
|
||
|
break ;
|
||
|
}
|
||
|
|
||
|
case READ_VERSION:
|
||
|
{
|
||
|
#ifdef DEBUG
|
||
|
printDebug (1, "in the READ_VERSION state") ;
|
||
|
#endif
|
||
|
if (bits[0] == true) {
|
||
|
Version++ ;
|
||
|
NumBitsNeeded = AUtils::bminus ((UWORD32) 1, Reservoir.getLength()) ;
|
||
|
}
|
||
|
else {
|
||
|
if (Version > CodeVersion) {
|
||
|
throw CorruptDataError (_("attempting to read an embedding of version %d but steghide %s only supports embeddings of version %d."), Version, VERSION, CodeVersion) ;
|
||
|
}
|
||
|
NumBitsNeeded = EncryptionAlgorithm::IRep_size + EncryptionMode::IRep_size ;
|
||
|
NumBitsRequested = AUtils::bminus<unsigned long> (NumBitsNeeded, Reservoir.getLength()) ;
|
||
|
State = READ_ENCINFO ;
|
||
|
}
|
||
|
break ;
|
||
|
}
|
||
|
|
||
|
case READ_ENCINFO: {
|
||
|
#ifdef DEBUG
|
||
|
printDebug (1, "in the READ_ENCINFO state") ;
|
||
|
#endif
|
||
|
|
||
|
unsigned int algo = (unsigned int) bits.getValue (0, EncryptionAlgorithm::IRep_size) ;
|
||
|
if (EncryptionAlgorithm::isValidIntegerRep (algo)) {
|
||
|
EncAlgo.setValue ((EncryptionAlgorithm::IRep) algo) ;
|
||
|
}
|
||
|
unsigned int mode = (unsigned int) bits.getValue (EncryptionAlgorithm::IRep_size, EncryptionMode::IRep_size) ;
|
||
|
if (EncryptionMode::isValidIntegerRep (mode)) {
|
||
|
EncMode.setValue ((EncryptionMode::IRep) mode) ;
|
||
|
}
|
||
|
|
||
|
NumBitsNeeded = NBitsNPlainBits ;
|
||
|
NumBitsRequested = AUtils::bminus<unsigned long> (NumBitsNeeded, Reservoir.getLength()) ;
|
||
|
State = READ_NPLAINBITS ;
|
||
|
|
||
|
#ifndef USE_LIBMCRYPT
|
||
|
if (EncAlgo.getIntegerRep() != EncryptionAlgorithm::NONE) {
|
||
|
throw SteghideError (_(
|
||
|
"The embedded data is encrypted but steghide has been compiled without encryption\n"
|
||
|
"support. To be able to read the embedded data, you have to install libmcrypt\n"
|
||
|
"(http://mcrypt.sourceforge.net/) and recompile steghide.")) ;
|
||
|
}
|
||
|
#endif
|
||
|
break ; }
|
||
|
|
||
|
case READ_NPLAINBITS: {
|
||
|
#ifdef DEBUG
|
||
|
printDebug (1, "in the READ_NPLAINBITS state") ;
|
||
|
#endif
|
||
|
|
||
|
NPlainBits = bits.getValue (0, NBitsNPlainBits) ;
|
||
|
|
||
|
#ifdef USE_LIBMCRYPT
|
||
|
NumBitsNeeded = (UWORD32) MCryptPP::getEncryptedSize (EncAlgo, EncMode, NPlainBits) ;
|
||
|
#else
|
||
|
NumBitsNeeded = NPlainBits ;
|
||
|
#endif
|
||
|
NumBitsRequested = AUtils::bminus<unsigned long> (NumBitsNeeded, Reservoir.getLength()) ;
|
||
|
|
||
|
State = READ_ENCRYPTED ;
|
||
|
break ; }
|
||
|
|
||
|
case READ_ENCRYPTED: {
|
||
|
#ifdef DEBUG
|
||
|
printDebug (1, "in the READ_ENCRYPTED state") ;
|
||
|
#endif
|
||
|
|
||
|
BitString plain ;
|
||
|
#ifdef USE_LIBMCRYPT
|
||
|
if (EncAlgo.getIntegerRep() == EncryptionAlgorithm::NONE) {
|
||
|
plain = bits ;
|
||
|
}
|
||
|
else {
|
||
|
MCryptPP crypto (EncAlgo, EncMode) ;
|
||
|
plain = crypto.decrypt (bits, Passphrase) ;
|
||
|
}
|
||
|
#else
|
||
|
plain = bits ;
|
||
|
#endif
|
||
|
|
||
|
plain.truncate (0, NPlainBits) ; // cut off random padding used to achieve full number of encryption blocks
|
||
|
|
||
|
unsigned long pos = 0 ;
|
||
|
|
||
|
// read Compression (and uncompress)
|
||
|
Compression = ((plain[pos++]) ? 9 : 0) ; // to make compression contain a value that makes sense
|
||
|
#ifdef DEBUG
|
||
|
printDebug (2, " compression: %d\n", plain[pos - 1]) ;
|
||
|
#endif
|
||
|
if (Compression > 0) {
|
||
|
UWORD32 NUncompressedBits = plain.getValue (pos, NBitsNUncompressedBits) ;
|
||
|
#ifdef DEBUG
|
||
|
printDebug (2, " nuncobits: %lu\n", NUncompressedBits) ;
|
||
|
#endif
|
||
|
pos += NBitsNUncompressedBits ;
|
||
|
|
||
|
plain.truncate (pos, plain.getLength()) ;
|
||
|
pos = 0 ;
|
||
|
plain.uncompress (NUncompressedBits) ;
|
||
|
}
|
||
|
|
||
|
// read Checksum
|
||
|
Checksum = plain[pos++] ;
|
||
|
#ifdef DEBUG
|
||
|
printDebug (2, " checksum: %d\n", plain[pos - 1]) ;
|
||
|
#endif
|
||
|
if (Checksum) {
|
||
|
CRC32 = plain.getValue (pos, NBitsCrc32) ;
|
||
|
#ifdef DEBUG
|
||
|
printDebug (2, " crc32: 0x%x\n", CRC32) ;
|
||
|
#endif
|
||
|
pos += NBitsCrc32 ;
|
||
|
}
|
||
|
|
||
|
// read filename
|
||
|
char curchar = '\0' ;
|
||
|
FileName = "" ;
|
||
|
do {
|
||
|
curchar = (char) plain.getValue (pos, 8) ;
|
||
|
if (curchar != '\0') {
|
||
|
FileName += curchar ;
|
||
|
}
|
||
|
pos += 8 ;
|
||
|
} while (curchar != '\0') ;
|
||
|
|
||
|
// extract data
|
||
|
if ((plain.getLength() - pos) % 8 != 0) {
|
||
|
throw CorruptDataError (_("the embedded data has an invalid length.")) ;
|
||
|
}
|
||
|
const unsigned long extdatalen = (plain.getLength() - pos) / 8 ;
|
||
|
Data.resize (extdatalen) ;
|
||
|
for (unsigned int i = 0 ; i < extdatalen ; i++) {
|
||
|
Data[i] = ((BYTE) plain.getValue (pos, 8)) ;
|
||
|
pos += 8 ;
|
||
|
}
|
||
|
|
||
|
NumBitsNeeded = 0 ;
|
||
|
NumBitsRequested = 0 ;
|
||
|
State = END ;
|
||
|
break ; }
|
||
|
|
||
|
case END:
|
||
|
default: {
|
||
|
myassert (0) ;
|
||
|
break ; }
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool EmbData::checksumOK (void) const
|
||
|
{
|
||
|
// test if checksum is ok
|
||
|
bool ok = true ;
|
||
|
if (Checksum) {
|
||
|
MHashPP hash (MHASH_CRC32) ;
|
||
|
for (std::vector<BYTE>::const_iterator i = Data.begin() ; i != Data.end() ; i++) {
|
||
|
hash << *i ;
|
||
|
}
|
||
|
hash << MHashPP::endhash ;
|
||
|
unsigned long calccrc32 = hash.getHashBits().getValue(0, NBitsCrc32) ;
|
||
|
|
||
|
if (calccrc32 == CRC32) {
|
||
|
ok = true ;
|
||
|
}
|
||
|
else {
|
||
|
ok = false ;
|
||
|
}
|
||
|
}
|
||
|
return ok ;
|
||
|
}
|
||
|
|
||
|
void EmbData::setEncAlgo (EncryptionAlgorithm a)
|
||
|
{
|
||
|
EncAlgo = a ;
|
||
|
}
|
||
|
|
||
|
EncryptionAlgorithm EmbData::getEncAlgo () const
|
||
|
{
|
||
|
return EncAlgo ;
|
||
|
}
|
||
|
|
||
|
void EmbData::setEncMode (EncryptionMode m)
|
||
|
{
|
||
|
EncMode = m ;
|
||
|
}
|
||
|
|
||
|
EncryptionMode EmbData::getEncMode () const
|
||
|
{
|
||
|
return EncMode ;
|
||
|
}
|
||
|
|
||
|
void EmbData::setCompression (int c)
|
||
|
{
|
||
|
Compression = c ;
|
||
|
}
|
||
|
|
||
|
int EmbData::getCompression (void) const
|
||
|
{
|
||
|
return Compression ;
|
||
|
}
|
||
|
|
||
|
void EmbData::setChecksum (bool c)
|
||
|
{
|
||
|
Checksum = c ;
|
||
|
}
|
||
|
|
||
|
bool EmbData::getChecksum (void) const
|
||
|
{
|
||
|
return Checksum ;
|
||
|
}
|
||
|
|
||
|
BitString EmbData::getBitString ()
|
||
|
{
|
||
|
myassert (Mode == EMBED) ;
|
||
|
|
||
|
// assembling data that can be compressed
|
||
|
BitString compr ;
|
||
|
|
||
|
compr.append (Checksum) ;
|
||
|
if (Checksum) {
|
||
|
MHashPP hash (MHASH_CRC32) ;
|
||
|
for (std::vector<BYTE>::iterator i = Data.begin() ; i != Data.end() ; i++) {
|
||
|
hash << *i ;
|
||
|
}
|
||
|
hash << MHashPP::endhash ;
|
||
|
compr.append (hash.getHashBits()) ;
|
||
|
}
|
||
|
|
||
|
compr.append (stripDir (FileName)) ;
|
||
|
compr.append ((BYTE) 0, 8) ; // end of fileame
|
||
|
|
||
|
compr.append (Data) ;
|
||
|
|
||
|
// assembling data that can be encrypted
|
||
|
BitString plain ;
|
||
|
plain.append ((Compression > 0) ? true : false) ;
|
||
|
if (Compression > 0) {
|
||
|
plain.append (compr.getLength(), NBitsNUncompressedBits) ;
|
||
|
compr.compress (Compression) ;
|
||
|
}
|
||
|
plain.append (compr) ;
|
||
|
|
||
|
// put it all together
|
||
|
BitString main ;
|
||
|
main.append(Magic, NBitsMagic) ;
|
||
|
for (unsigned short i = 0 ; i < CodeVersion ; i++) {
|
||
|
main.append(true) ;
|
||
|
}
|
||
|
main.append(false) ; // end of version
|
||
|
main.append((UWORD16) EncAlgo.getIntegerRep(), EncryptionAlgorithm::IRep_size) ;
|
||
|
main.append((UWORD16) EncMode.getIntegerRep(), EncryptionMode::IRep_size) ;
|
||
|
main.append(plain.getLength(), NBitsNPlainBits) ;
|
||
|
|
||
|
#ifdef USE_LIBMCRYPT
|
||
|
if (EncAlgo.getIntegerRep() != EncryptionAlgorithm::NONE) {
|
||
|
MCryptPP crypto (EncAlgo, EncMode) ;
|
||
|
plain = crypto.encrypt (plain, Passphrase) ;
|
||
|
}
|
||
|
#else
|
||
|
myassert (EncAlgo.getIntegerRep() == EncryptionAlgorithm::NONE) ;
|
||
|
#endif
|
||
|
|
||
|
main.append (plain) ;
|
||
|
|
||
|
return main ;
|
||
|
}
|
||
|
|
||
|
std::string EmbData::stripDir (std::string s)
|
||
|
{
|
||
|
unsigned int start = 0 ;
|
||
|
if ((start = s.find_last_of ("/\\")) == std::string::npos) {
|
||
|
start = 0 ;
|
||
|
}
|
||
|
else {
|
||
|
start += 1 ;
|
||
|
}
|
||
|
return s.substr (start, std::string::npos) ;
|
||
|
}
|