/* * steghide 0.5.1 - a steganography program * Copyright (C) 1999-2003 Stefan Hetzl * * 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 #include #include "AUtils.h" #include "BFSAPHeuristic.h" #include "ColorPalette.h" #include "CvrStgFile.h" #include "DFSAPHeuristic.h" #include "DMDConstructionHeuristic.h" #include "BmpFile.h" #include "BmpPaletteSampleValue.h" #include "BmpRGBSampleValue.h" #include "SampleValueAdjacencyList.h" #include "SMDConstructionHeuristic.h" #include "WKSConstructionHeuristic.h" #include "common.h" #include "error.h" BmpFile::BmpFile (BinaryIO *io) : CvrStgFile() { read (io) ; } BmpFile::~BmpFile () { delete Palette ; } BmpFile::SUBFORMAT BmpFile::getSubformat () const { return subformat ; } void BmpFile::read (BinaryIO *io) { CvrStgFile::read (io) ; Palette = NULL ; readheaders() ; readdata() ; } void BmpFile::write () { CvrStgFile::write() ; writeheaders() ; writedata() ; } std::list BmpFile::getProperties () const { std::list retval ; // format std::string formatstring ; switch (getSubformat()) { case WIN: formatstring = _("Windows 3.x bitmap") ; break ; case OS2: formatstring = _("OS/2 1.x bitmap") ; break ; } retval.push_back (CvrStgFile::Property (_("format"), formatstring)) ; return retval ; } std::vector BmpFile::getMatchingAlgorithms (Graph* g, Matching* m) const { std::vector retval ; if (getBitCount() == 24) { retval.push_back (new SMDConstructionHeuristic (g, m)) ; } else { retval.push_back (new SMDConstructionHeuristic (g, m)) ; } return retval ; } unsigned long BmpFile::getNumSamples() const { unsigned long retval = 0 ; switch (getSubformat()) { case WIN: { retval = bmih.biWidth * bmih.biHeight ; break ; } case OS2: { retval = bmch.bcWidth * bmch.bcHeight ; break ; } } return retval ; } void BmpFile::replaceSample (const SamplePos pos, const SampleValue* s) { unsigned long index = 0 ; unsigned short firstbit = 0 ; calcIndex (pos, &index, &firstbit) ; unsigned short bitcount = getBitCount() ; switch (bitcount) { case 1: case 4: case 8: { const BmpPaletteSampleValue* sample = dynamic_cast (s) ; myassert (sample) ; for (unsigned short i = 0 ; i < bitcount ; i++) { BitmapData[index] = BitmapData[index] & (~(1 << (firstbit + i))) ; BitmapData[index] = BitmapData[index] | ((sample->getIndex() & (1 << i)) << firstbit) ; } break ; } case 24: { const BmpRGBSampleValue* sample = dynamic_cast (s) ; myassert (sample) ; BitmapData[index] = sample->getBlue() ; BitmapData[index + 1] = sample->getGreen() ; BitmapData[index + 2] = sample->getRed() ; break ; } } } SampleValue *BmpFile::getSampleValue (SamplePos pos) const { unsigned long index = 0 ; unsigned short firstbit = 0 ; calcIndex (pos, &index, &firstbit) ; unsigned short bitcount = getBitCount() ; SampleValue *retval = NULL ; switch (bitcount) { case 1: case 4: case 8: { BYTE idx_pal = 0 ; for (unsigned short i = 0 ; i < bitcount ; i++) { idx_pal |= ((BitmapData[index] & (1 << (firstbit + i))) >> firstbit) ; } retval = (SampleValue*) new BmpPaletteSampleValue (idx_pal) ; break ; } case 24: { retval = (SampleValue*) new BmpRGBSampleValue (BitmapData[index + 2], BitmapData[index + 1], BitmapData[index]) ; break ; } } return retval ; } std::vector BmpFile::calcSVAdjacencyLists (const std::vector& svs) const { if (getBitCount() == 24) { EmbValue m = getEmbValueModulus() ; UWORD32 r = getRadius() ; // create svalists std::vector lists (m) ; for (EmbValue i = 0 ; i < m ; i++) { lists[i] = new SampleValueAdjacencyList (svs.size()) ; } // redmap contains all sample values in a 3-dimensional RGB-tree typedef std::map BlueMap ; typedef std::map GreenMap ; typedef std::map RedMap ; RedMap redmap ; for (std::vector::const_iterator svit = svs.begin() ; svit != svs.end() ; svit++) { BmpRGBSampleValue* rgbsv = (BmpRGBSampleValue*) (*svit) ; redmap[rgbsv->getRed()][rgbsv->getGreen()][rgbsv->getBlue()] = rgbsv ; } // create hemisphere matrix (will contain a discrete hemisphere with radius getRadius()) unsigned short r_eucl = (unsigned short) sqrt (r) ; // the euclidean radius (not squared) - rounded to next lower natural number short hemisphere[2 * r_eucl + 1][2 * r_eucl + 1] ; for (short dr = -r_eucl ; dr <= r_eucl ; dr++) { for (short dg = -r_eucl ; dg <= r_eucl ; dg++) { short db_sq = r_eucl*r_eucl - dr*dr - dg*dg ; if (db_sq >= 0) { // round hemishpere points to next lower natural numbers hemisphere[dr + r_eucl][dg + r_eucl] = (short) sqrt (db_sq) ; } else { hemisphere[dr + r_eucl][dg + r_eucl] = -1 ; } } } // // fill adjacency lists // // create reservoir - for every i reservoir[i] contains the sample values that are neighbourss of // the sample value with label i and have a lower label (and have already been found) // This is necessary to use collapsing trees together with bucket sort (without huge increase in memory usage) std::vector reservoir[svs.size()] ; // neighbours sorted by distance (for the current source sample value) std::vector neighbours_byd[r + 1] ; for (std::vector::const_iterator srcsvit = svs.begin() ; srcsvit != svs.end() ; srcsvit++) { BmpRGBSampleValue* srcsv = (BmpRGBSampleValue*) (*srcsvit) ; // sort reservoir into neighbours_byd for (std::vector::const_iterator it = reservoir[srcsv->getLabel()].begin() ; it != reservoir[srcsv->getLabel()].end() ; it++) { neighbours_byd[srcsv->calcDistance(*it)].push_back (*it) ; } unsigned short r_abs_start = AUtils::bminus (srcsv->getRed(), r_eucl) ; unsigned short r_abs_end = AUtils::bplus (srcsv->getRed(), r_eucl) ; unsigned short g_abs_start = AUtils::bminus (srcsv->getGreen(), r_eucl) ; unsigned short g_abs_end = AUtils::bplus (srcsv->getGreen(), r_eucl) ; RedMap::iterator rit = redmap.lower_bound (r_abs_start) ; while ((rit != redmap.end()) && (rit->first <= r_abs_end)) { GreenMap& greenmap = rit->second ; if (greenmap.empty()) { RedMap::iterator delme = rit ; rit++ ; redmap.erase (delme) ; } else { GreenMap::iterator git = greenmap.lower_bound (g_abs_start) ; while ((git != greenmap.end()) && (git->first <= g_abs_end)) { unsigned short red_index = (rit->first - srcsv->getRed()) + r_eucl ; unsigned short green_index = (git->first - srcsv->getGreen()) + r_eucl ; short delta_b = hemisphere[red_index][green_index] ; if (delta_b < 0) { // this blue map has no sample values in sphere git++ ; } else { BlueMap& bluemap = git->second ; if (bluemap.empty()) { GreenMap::iterator delme = git ; git++ ; greenmap.erase (delme) ; } else { unsigned short b_abs_start = AUtils::bminus (srcsv->getBlue(), delta_b) ; unsigned short b_abs_end = AUtils::bplus (srcsv->getBlue(), delta_b) ; BlueMap::iterator bit = bluemap.lower_bound (b_abs_start) ; while ((bit != bluemap.end()) && (bit->first <= b_abs_end)) { if (srcsv->getLabel() < bit->second->getLabel()) { neighbours_byd[srcsv->calcDistance(bit->second)].push_back (bit->second) ; reservoir[bit->second->getLabel()].push_back (srcsv) ; bit++ ; } else { BlueMap::iterator delme = bit ; bit++ ; bluemap.erase (delme) ; } } git++ ; } } } // end of git loop rit++ ; } } // end of rit loop for (unsigned short d = 0 ; d <= r ; d++) { if (!neighbours_byd[d].empty()) { for (std::vector::const_iterator it = neighbours_byd[d].begin() ; it != neighbours_byd[d].end() ; it++) { (*(lists[(*it)->getEmbeddedValue()]))[srcsv].push_back (*it) ; } neighbours_byd[d].clear() ; } } } // end of svs for loop return lists ; } else { return CvrStgFile::calcSVAdjacencyLists(svs) ; } } void BmpFile::calcIndex (SamplePos pos, unsigned long* index, unsigned short* firstbit) const { unsigned long width = getWidth(), bitcount = getBitCount() ; unsigned long bytesperline_nonpadded = 0 ; if (width * bitcount % 8 == 0) { bytesperline_nonpadded = (width * bitcount) / 8 ; } else { bytesperline_nonpadded = (width * bitcount) / 8 + 1 ; } unsigned long row = pos / width ; pos = pos % width ; unsigned long column = 0 ; switch (bitcount) { case 1: case 4: case 8: { unsigned short samplesperbyte = 8 / bitcount ; column = pos / samplesperbyte ; // to account for the order pixels are stored in one byte: *firstbit = (samplesperbyte - (pos % samplesperbyte) - 1) * bitcount ; myassert (*firstbit < 8) ; } break ; case 24: column = pos * 3 ; *firstbit = 0 ; break ; default: myassert(false) ; break ; } *index = bytesperline_nonpadded * row + column ; } unsigned short BmpFile::getBitCount() const { unsigned short retval = 0 ; switch (getSubformat()) { case WIN: { retval = bmih.biBitCount ; break ; } case OS2: { retval = bmch.bcBitCount ; break ; } } myassert (retval == 1 || retval == 4 || retval == 8 || retval == 24) ; return retval ; } unsigned long BmpFile::getWidth() const { unsigned long retval = 0 ; switch (getSubformat()) { case WIN: retval = bmih.biWidth ; break ; case OS2: retval = bmch.bcWidth ; break ; } return retval ; } unsigned long BmpFile::getHeight() const { unsigned long retval = 0 ; switch (getSubformat()) { case WIN: { retval = bmih.biHeight ; break ; } case OS2: { retval = bmch.bcHeight ; break ; } } return retval ; } ColorPalette *BmpFile::getPalette() const { myassert (getBitCount() != 24) ; myassert (Palette) ; return Palette ; } /* reads the headers of a bmp file from disk */ void BmpFile::readheaders () { try { bmfh.bfType = IdBm ; bmfh.bfSize = getBinIO()->read32_le() ; bmfh.bfReserved1 = getBinIO()->read16_le() ; bmfh.bfReserved2 = getBinIO()->read16_le() ; bmfh.bfOffBits = getBinIO()->read32_le() ; unsigned long tmpSize = getBinIO()->read32_le() ; switch (tmpSize) { case SizeBMINFOHEADER: { // this file is in the Windows bmp format subformat = WIN ; bmpwin_readheaders () ; break ; } case SizeBMCOREHEADER: { // this file is in the OS/2 bmp format subformat = OS2 ; bmpos2_readheaders () ; break ; } default: { if (getBinIO()->is_std()) { throw NotImplementedError (_("the bmp data from standard input has a format that is not supported (biSize: %lu)."), tmpSize) ; } else { throw NotImplementedError (_("the bmp file \"%s\" has a format that is not supported (biSize: %lu)."), getBinIO()->getName().c_str(), tmpSize) ; } break ; } } } catch (BinaryInputError e) { switch (e.getType()) { case BinaryInputError::FILE_ERR: { throw SteghideError (_("an error occured while reading the bmp headers from the file \"%s\"."), getBinIO()->getName().c_str()) ; break ; } case BinaryInputError::FILE_EOF: { throw SteghideError (_("premature end of file \"%s\" while reading bmp headers."), getBinIO()->getName().c_str()) ; break ; } case BinaryInputError::STDIN_ERR: { throw SteghideError (_("an error occured while reading the bmp headers from standard input.")) ; break ; } case BinaryInputError::STDIN_EOF: { throw SteghideError (_("premature end of data from standard input while reading bmp headers.")) ; break ; } } } } void BmpFile::bmpwin_readheaders () { bmih.biSize = SizeBMINFOHEADER ; bmih.biWidth = getBinIO()->read32_le () ; bmih.biHeight = getBinIO()->read32_le () ; bmih.biPlanes = getBinIO()->read16_le () ; myassert (bmih.biPlanes == 1) ; bmih.biBitCount = getBinIO()->read16_le () ; switch (bmih.biBitCount) { case 1: setSamplesPerVertex (SamplesPerVertex_SmallPalette) ; setEmbValueModulus (EmbValueModulus_SmallPalette) ; setRadius (Radius_Palette) ; break ; case 4: setSamplesPerVertex (SamplesPerVertex_SmallPalette) ; setEmbValueModulus (EmbValueModulus_SmallPalette) ; setRadius (Radius_Palette) ; break ; case 8: setSamplesPerVertex (SamplesPerVertex_LargePalette) ; setEmbValueModulus (EmbValueModulus_LargePalette) ; setRadius (Radius_Palette) ; break ; case 24: setSamplesPerVertex (SamplesPerVertex_RGB) ; setEmbValueModulus (EmbValueModulus_RGB) ; setRadius (Radius_RGB) ; break ; default: if (getBinIO()->is_std()) { throw NotImplementedError (_("the bmp data from standard input has a format that is not supported (biBitCount: %d)."), bmih.biBitCount) ; } else { throw NotImplementedError (_("the bmp file \"%s\" has a format that is not supported (biBitCount: %d)."), getBinIO()->getName().c_str(), bmih.biBitCount) ; } break ; } bmih.biCompression = getBinIO()->read32_le () ; if (bmih.biCompression != COMPRESSION_BI_RGB) { if (getBinIO()->is_std()) { throw NotImplementedError (_("the bitmap data from standard input is compressed which is not supported.")) ; } else { throw NotImplementedError (_("the bitmap data in \"%s\" is compressed which is not supported."), getBinIO()->getName().c_str()) ; } } bmih.biSizeImage = getBinIO()->read32_le () ; bmih.biXPelsPerMeter = getBinIO()->read32_le () ; bmih.biYPelsPerMeter = getBinIO()->read32_le () ; bmih.biClrUsed = getBinIO()->read32_le () ; bmih.biClrImportant = getBinIO()->read32_le () ; if (bmih.biBitCount != 24) { /* a color table exists */ Palette = new ColorPalette() ; unsigned int ncolors = 0 ; switch (bmih.biBitCount) { case 1: { if (Args.Command.getValue() == EMBED) { Warning w (_("using a black/white bitmap as cover is very insecure!")) ; w.printMessage() ; } ncolors = 2 ; break ; } case 4: { if (Args.Command.getValue() == EMBED) { Warning w (_("using a 16-color bitmap as cover is very insecure!")) ; w.printMessage() ; } ncolors = 16 ; break ; } case 8: { ncolors = 256 ; break ; } default: { myassert (0) ; break ; } } if (bmih.biClrUsed != 0) { ncolors = bmih.biClrUsed ; } for (unsigned int i = 0 ; i < ncolors ; i++) { unsigned char b = getBinIO()->read8() ; unsigned char g = getBinIO()->read8() ; unsigned char r = getBinIO()->read8() ; if (getBinIO()->read8() != 0) { Warning w (_("maybe corrupted windows bmp data (Reserved in RGBQUAD is non-zero).")) ; w.printMessage() ; } Palette->addEntry(r, g, b) ; } } } void BmpFile::bmpos2_readheaders () { bmch.bcSize = SizeBMCOREHEADER ; bmch.bcWidth = getBinIO()->read16_le () ; bmch.bcHeight = getBinIO()->read16_le () ; bmch.bcPlanes = getBinIO()->read16_le () ; myassert (bmch.bcPlanes == 1) ; bmch.bcBitCount = getBinIO()->read16_le () ; switch (bmch.bcBitCount) { case 1: setSamplesPerVertex (SamplesPerVertex_SmallPalette) ; setEmbValueModulus (EmbValueModulus_SmallPalette) ; setRadius (Radius_Palette) ; break ; case 4: setSamplesPerVertex (SamplesPerVertex_SmallPalette) ; setEmbValueModulus (EmbValueModulus_SmallPalette) ; setRadius (Radius_Palette) ; break ; case 8: setSamplesPerVertex (SamplesPerVertex_LargePalette) ; setEmbValueModulus (EmbValueModulus_LargePalette) ; setRadius (Radius_Palette) ; break ; case 24: setSamplesPerVertex (SamplesPerVertex_RGB) ; setEmbValueModulus (EmbValueModulus_RGB) ; setRadius (Radius_RGB) ; break ; default: if (getBinIO()->is_std()) { throw NotImplementedError (_("the bmp data from standard input has a format that is not supported (bcBitCount: %d)."), bmch.bcBitCount) ; } else { throw NotImplementedError (_("the bmp file \"%s\" has a format that is not supported (bcBitCount: %d)."), getBinIO()->getName().c_str(), bmch.bcBitCount) ; } break ; } if (bmch.bcBitCount != 24) { /* a color table exists */ unsigned int ncolors = 0 ; Palette = new ColorPalette() ; switch (bmch.bcBitCount) { case 1: { if (Args.Command.getValue() == EMBED) { Warning w (_("using a black/white bitmap as cover is very insecure!")) ; w.printMessage() ; } ncolors = 2 ; break ; } case 4: { if (Args.Command.getValue() == EMBED) { Warning w (_("using a 16-color bitmap as cover is very insecure!")) ; w.printMessage() ; } ncolors = 16 ; break ; } case 8: { ncolors = 256 ; break ; } default: { myassert (0) ; break ; } } for (unsigned int i = 0 ; i < ncolors ; i++) { unsigned char b = getBinIO()->read8() ; unsigned char g = getBinIO()->read8() ; unsigned char r = getBinIO()->read8() ; Palette->addEntry (r, g, b) ; } } } /* writes the headers of a bmp file to disk */ void BmpFile::writeheaders () { try { getBinIO()->write16_le (bmfh.bfType) ; getBinIO()->write32_le (bmfh.bfSize) ; getBinIO()->write16_le (bmfh.bfReserved1) ; getBinIO()->write16_le (bmfh.bfReserved2) ; getBinIO()->write32_le (bmfh.bfOffBits) ; switch (getSubformat()) { case WIN: { bmpwin_writeheaders() ; break ; } case OS2: { bmpos2_writeheaders() ; break ; } default: { myassert (0) ; break ; } } } catch (BinaryOutputError e) { switch (e.getType()) { case BinaryOutputError::FILE_ERR: { throw SteghideError (_("an error occured while writing the bmp headers to the file \"%s\"."), getBinIO()->getName().c_str()) ; break ; } case BinaryOutputError::STDOUT_ERR: { throw SteghideError (_("an error occured while writing the bmp headers to standard output.")) ; break ; } } } } void BmpFile::bmpwin_writeheaders () { getBinIO()->write32_le (bmih.biSize) ; getBinIO()->write32_le (bmih.biWidth) ; getBinIO()->write32_le (bmih.biHeight) ; getBinIO()->write16_le (bmih.biPlanes) ; getBinIO()->write16_le (bmih.biBitCount) ; getBinIO()->write32_le (bmih.biCompression) ; getBinIO()->write32_le (bmih.biSizeImage) ; getBinIO()->write32_le (bmih.biXPelsPerMeter) ; getBinIO()->write32_le (bmih.biYPelsPerMeter) ; getBinIO()->write32_le (bmih.biClrUsed) ; getBinIO()->write32_le (bmih.biClrImportant) ; if (Palette != NULL) { for (unsigned int i = 0 ; i < Palette->getSize() ; i++) { getBinIO()->write8 ((*Palette)[i].Blue) ; getBinIO()->write8 ((*Palette)[i].Green) ; getBinIO()->write8 ((*Palette)[i].Red) ; getBinIO()->write8 (0) ; } } } void BmpFile::bmpos2_writeheaders () { getBinIO()->write32_le (bmch.bcSize) ; getBinIO()->write16_le (bmch.bcWidth) ; getBinIO()->write16_le (bmch.bcHeight) ; getBinIO()->write16_le (bmch.bcPlanes) ; getBinIO()->write16_le (bmch.bcBitCount) ; if (Palette != NULL) { for (unsigned int i = 0 ; i < Palette->getSize() ; i++) { getBinIO()->write8 ((*Palette)[i].Blue) ; getBinIO()->write8 ((*Palette)[i].Green) ; getBinIO()->write8 ((*Palette)[i].Red) ; } } } /* returns the number of bytes used to store the pixel data of one scan line */ unsigned long BmpFile::calcLinelength () { unsigned long retval = 0 ; switch (getSubformat()) { case WIN: { if (bmih.biBitCount * bmih.biWidth % 8 == 0) { retval = bmih.biBitCount * bmih.biWidth / 8 ; } else { retval = (bmih.biBitCount * bmih.biWidth / 8) + 1; } break ; } case OS2: { if (bmch.bcBitCount * bmch.bcWidth % 8 == 0) { retval = bmch.bcBitCount * bmch.bcWidth / 8 ; } else { retval = (bmch.bcBitCount * bmch.bcWidth / 8) + 1; } break ; } default: { myassert (0) ; break ; } } return retval ; } /* reads a bmp file from disk into a CVRSTGFILE structure */ void BmpFile::readdata () { try { unsigned long linelength = calcLinelength () ; unsigned long height = getHeight () ; int paddinglength = 0 ; if (linelength % 4 == 0) { paddinglength = 0 ; } else { paddinglength = 4 - (linelength % 4) ; } BitmapData.resize (height * linelength) ; for (unsigned long line = 0 ; line < height ; line++) { for (unsigned long posinline = 0 ; posinline < linelength ; posinline++) { BitmapData[line * linelength + posinline] = getBinIO()->read8() ; } for (int i = 0 ; i < paddinglength ; i++) { if (getBinIO()->read8() != 0) { Warning w (_("maybe corrupted bmp data (padding byte at 0x%lx set to non-zero)."), getBinIO()->getPos() - 1) ; w.printMessage() ; } } } atend.clear() ; while (!getBinIO()->eof()) { atend.push_back (getBinIO()->read8()) ; } } catch (BinaryInputError e) { switch (e.getType()) { case BinaryInputError::FILE_ERR: { throw SteghideError (_("an error occured while reading the bmp data from the file \"%s\"."), getBinIO()->getName().c_str()) ; break ; } case BinaryInputError::FILE_EOF: { throw SteghideError (_("premature end of file \"%s\" while reading bmp data."), getBinIO()->getName().c_str()) ; break ; } case BinaryInputError::STDIN_ERR: { throw SteghideError (_("an error occured while reading the bmp data from standard input.")) ; break ; } case BinaryInputError::STDIN_EOF: { throw SteghideError (_("premature end of bmp data from standard input.")) ; break ; } } } } void BmpFile::writedata () { try { unsigned long linelength = calcLinelength () ; unsigned long height = getHeight () ; unsigned int paddinglength = 0 ; if (linelength % 4 == 0) { paddinglength = 0 ; } else { paddinglength = 4 - (linelength % 4) ; } for (unsigned long line = 0 ; line < height ; line++) { for (unsigned long posinline = 0 ; posinline < linelength ; posinline++) { getBinIO()->write8 (BitmapData[line * linelength + posinline]) ; } // write padding bytes for (unsigned int i = 0 ; i < paddinglength ; i++) { getBinIO()->write8 (0) ; } } for (std::vector::iterator i = atend.begin() ; i != atend.end() ; i++) { getBinIO()->write8 (*i) ; } } catch (BinaryOutputError e) { switch (e.getType()) { case BinaryOutputError::FILE_ERR: { throw SteghideError (_("an error occured while writing the bitmap data to the file \"%s\"."), getBinIO()->getName().c_str()) ; break ; } case BinaryOutputError::STDOUT_ERR: { throw SteghideError (_("an error occured while writing the bitmap data to standard output.")) ; break ; } } } }