/*
* affdiskprint.cpp:
*
* Creates a diskprint AFF structure
*/
/*
* PUBLIC DOMAIN
* By Simson L. Garfinkel
*
* The software provided here is released by the Naval Postgraduate
* School (NPS), an agency of the U.S. Department of Navy.The software
* bears no warranty, either expressed or implied. NPS does not assume
* legal liability nor responsibility for a User's use of the software
* or the results of such use.
*
* Please note that within the United States, copyright protection,
* under Section 105 of the United States Code, Title 17, is not
* available for any work of the United States Government and/or for
* any works created by United States Government employees. User
* acknowledges that this software contains work which was created by
* NPS employees and is therefore in the public domain and not
* subject to copyright.
*/
#include "affconfig.h"
#include "afflib.h"
#include "afflib_i.h"
#include "base64.h"
#include "hashextent.h"
#ifdef HAVE_LIBEXPAT
#include <openssl/evp.h>
#ifdef WIN32
#include "unix4win32.h"
#endif
#include <vector>
#include <string>
#include <iostream>
#include <sstream>
#include <map>
#include <set>
#ifdef HAVE_CSTRING
#include <cstring>
#endif
const char *hashes[] = {"SHA256","SHA1",0}; // what should we hash?
using namespace std;
#if HAVE_CTYPE_H
#include <ctype.h>
#endif
#if !defined(HAVE_ISALPHANUM) && defined(HAVE_ISALNUM)
#define isalphanum(c) isalnum(c)
#endif
#if !defined(HAVE_ISALPHANUM) && !defined(HAVE_ISALNUM)
#define isalphanum(c) (isalpha(c)||isdigit(c))
#endif
#if !defined(O_BINARY)
#define O_BINARY 0
#endif
const char *progname = "affdiskprint";
const char *xml_special_chars = "<>\r\n&'\"";
void usage()
{
printf("%s version %s\n",progname,PACKAGE_VERSION);
printf("usage: %s [options] infile \n",progname);
printf(" -x XML = Verify the diskprint\n");
printf(" -V = Just print the version number and exit.\n");
printf(" -h = Print this help.\n");
exit(0);
}
/****************************************************************
** Support routines...
*/
/**
* Return a random 64-bit number
*/
uint64_t random64()
{
return (((uint64_t)random())<<32) | random();
}
uint64_t atoi64(const char *buf)
{
uint64_t ret=0;
sscanf(buf,"%" PRIu64,&ret);
return ret;
}
static int *hexcharvals = 0;
static void nsrl_bloom_init()
{
if(hexcharvals==0){
/* Need to initialize this */
int i;
hexcharvals = (int *)calloc(sizeof(int),256);
for(i=0;i<10;i++){
hexcharvals['0'+i] = i;
}
for(i=10;i<16;i++){
hexcharvals['A'+i-10] = i;
hexcharvals['a'+i-10] = i;
}
}
}
/**
* Convert a hex representation to binary, and return
* the number of bits converted.
* @param binbuf output buffer
* @param binbuf_size size of output buffer in bytes.
* @param hex input buffer (in hex)
*/
int nsrl_hex2bin(unsigned char *binbuf,size_t hexbuf_size,const char *hex)
{
int bits = 0;
if(hexcharvals==0) nsrl_bloom_init();
while(hex[0] && hex[1] && hexbuf_size>0){
*binbuf++ = ((hexcharvals[(unsigned char)hex[0]]<<4) |
hexcharvals[(unsigned char)hex[1]]);
hex += 2;
bits += 8;
hexbuf_size -= 1;
}
if(hexbuf_size>0) binbuf[0] = 0; // might as well null-terminate if there is room
return bits;
}
/**
* Strip an XML string as necessary for a tag name.
*/
void out_xmlstr(ostream &ostr,int indent,const char *tag,const char *value)
{
for(int i=0;i<indent;i++) ostr << " ";
ostr << "<" << tag << ">";
for(const char *ch=value;*ch;ch++){
if(isprint(*ch) && !strchr(xml_special_chars,*ch)){
ostr << *ch;
}
}
ostr << "</" << tag << ">";
}
void out_xmlhex(ostream &ostr,int indent,const char *tag,const char **attribs,
unsigned char *md,int len)
{
for(int i=0;i<indent;i++) ostr << ' ';
ostr << "<" << tag << " coding='base16'";
for(int i=0;attribs && attribs[i];i++){
ostr << " ";
ostr << attribs[i];
}
ostr << ">" << hashextent::bin2hex(md,len) << "</" << tag << ">\n";
}
/**
* Calculate the disk fingerprint for a spcific file and output it to stdout.
* Includes other named segments.
*
* @param infile the file to process.
*/
int diskprint(const char *infile)
{
/** segments to include in output.
*/
const char *segments[] = {AF_MD5,AF_SHA1,AF_SHA256,AF_CREATOR,AF_CASE_NUM,AF_IMAGE_GID,
AF_ACQUISITION_ISO_COUNTRY,
AF_ACQUISITION_COMMAND_LINE,AF_ACQUISITION_DATE,
AF_ACQUISITION_NOTES,AF_ACQUISITION_TECHNICIAN,
AF_BATCH_NAME,AF_BATCH_ITEM_NAME,0};
AFFILE *af = af_open(infile,O_RDONLY,0);
if(!af){
warn("%s",infile);
return -1;
}
cout << "<!-- XML generated by " << progname << " version " << PACKAGE_VERSION << " -->\n";
cout << "<diskprint image_filename='" << infile << "'>\n";
/* First handle the imagesize */
int64_t imagesize = af_get_imagesize(af);
if(imagesize>0){
char buf[32];
snprintf(buf,sizeof(buf),"%" PRIu64,imagesize);
out_xmlstr(cout,2,AF_IMAGESIZE,buf);
cout << "\n";
}
/* Get sector size and number of sectors */
uint32_t sectorsize=512; // default sectorsize
af_get_seg(af,AF_SECTORSIZE,§orsize,0,0);
if(sectorsize==0) sectorsize=512; // default sectorsize
int64_t sectors = imagesize/sectorsize;
if(sectors>0){
char buf[32];
snprintf(buf,sizeof(buf),"%" PRIu32,sectorsize);
out_xmlstr(cout,2,AF_SECTORSIZE,buf);
cout << "\n";
}
/* Output specific named segments */
for(int i=0;segments[i];i++){
char buf[65536];
size_t buflen = sizeof(buf);
if(af_get_seg(af,segments[i],0,(u_char *)buf,&buflen)==0){
buf[buflen] = 0; // null terminate it
if(af_display_as_hex(segments[i])){
out_xmlhex(cout,2,segments[i],0,(u_char *)buf,buflen);
} else {
out_xmlstr(cout,2,segments[i],buf);
}
}
}
/**
* The list of segments to hash is defined by:
* 1. The first 128K sectors.
* 2. The last 128K sectors.
* 3. A random set of 64K sectors.
*/
hashvector hashextents;
for(int i=0;i<8;i++){
hashextents.push_back(hashextent(131072*i,131072));
}
for(int i=0;i<8;i++){
hashextents.push_back(hashextent(imagesize-131072*(8-i),131072));
}
/* Pick some random hashextents as well */
for(int i=0;i<100;i++){
uint64_t sector = random64() % (sectors-128);
hashextents.push_back(hashextent(sector*sectorsize,65536));
}
/** Sort the segments for maximal seek efficiency.
*/
sort(hashextents.begin(),hashextents.end(),hashextent::compare);
/** Send the hashes to stdout using print_hash.
*/
cout << " <hashes>\n";
for(hashvector::iterator it=hashextents.begin(); it!=hashextents.end(); it++){
for(int i=0;hashes[i];i++){
if((*it).compute_digest(af,hashes[i])==0){
cout << " " << (*it).toXML() << "\n";
}
}
}
cout << " </hashes>\n";
cout << "</diskprint>\n";
af_close(af);
return 0;
}
/**
* Code for reading the hashvector XML structure.
*/
#include <expat.h>
class diskprintReader {
public:
/* General EXPAT stuff */
XML_Parser parser;
bool get_cdata; // this is cdata worth getting
string cdata; // the cdata that has been gotten
diskprintReader():get_cdata(false),hash(0){
parser = XML_ParserCreate(NULL);
XML_SetUserData(parser,this);
XML_SetElementHandler(parser,startElement,endElement);
XML_SetCharacterDataHandler(parser,cHandler);
}
int parse(const char *buf,int len) { return XML_Parse(parser, buf, len, 1);}
void clear(){
cdata = "";
get_cdata = false;
}
/* Specific stuff for XML diskprint */
hashextent *hash; // current hash
hashvector hashextents; // all discovered hashes
/* Turn the static functions into method calls */
static void startElement(void *userData,const char *name,const char **attrs){
((diskprintReader *)userData)->startElement(name,attrs);
}
static void endElement(void *userData,const char *name){
((diskprintReader *)userData)->endElement(name);
}
static void cHandler(void *userData,const XML_Char *s,int len){
diskprintReader *dh = (diskprintReader *)userData;
if(dh->get_cdata) dh->cdata.append(s,len);
}
void startElement(string name,const char **attrs){
clear();
/* If this is an element that we want, indicate such */
if(name=="hash"){
hash = new hashextent();
for(int i=0;attrs[i];i+=2){
if(strcmp(attrs[i],"coding")==0){ hash->coding = attrs[i+1]; continue;}
if(strcmp(attrs[i],"start")==0){ hash->start = atoi64(attrs[i+1]); continue;}
if(strcmp(attrs[i],"bytes")==0){ hash->bytes = atoi64(attrs[i+1]); continue;}
if(strcmp(attrs[i],"alg")==0){ hash->digest_name = attrs[i+1]; continue;}
}
get_cdata = true;
}
}
void endElement(const char *name){
if(get_cdata==false) return; // don't care about it.
if(!strcmp(name,"hash")){
if(hash->coding=="base16"){
hash->hexdigest = cdata;
}
hashextents.push_back(*hash);
}
if(!strcmp(name,"diskprint")){
XML_StopParser(parser,0); // stop the parser
return;
}
get_cdata = false;
}
};
void diskprint_verify(const char *filename,const char *xmlfile)
{
AFFILE *af = af_open(filename,O_RDONLY,0);
if(!af) err(1,"af_open(%s): ",filename);
/* Let's read the XML file */
int fd = open(xmlfile,O_RDONLY|O_BINARY);
if(!fd) err(1,"open: %s",xmlfile);
struct stat st;
if(fstat(fd,&st)) err(1,"stat: %s",xmlfile);
char *buf = (char *)malloc(st.st_size+1);
if(!buf) err(1,"malloc");
if(read(fd,buf,st.st_size)!=st.st_size) err(1,"cannot read XML file");
buf[st.st_size]=0; // terminate the buffer (not strictly needed)
diskprintReader dp;
dp.parse(buf,st.st_size);
cout << "Number of digests read: "<< dp.hashextents.size() << "\n";
const EVP_MD *strongest = dp.hashextents.strongest_available();
cout << "Strongest hash available: " << EVP_MD_name(strongest) << "\n";
/* Now verify each hash */
int matched=0;
int notmatched=0;
for(hashvector::iterator it = dp.hashextents.begin(); it!=dp.hashextents.end() && notmatched==0;it++){
if(EVP_MD_name(strongest) == (*it).digest_name){
hashextent &hp = (*it); // hash print
hashextent hs(af,hp.digest_name,hp.start,hp.bytes);
//cout << "hp: " << hp << "\n";
//cout << "hs: " << hs << "\n";
if(hp==hs){
matched++;
} else {
notmatched++;
}
}
}
if(notmatched){
cout << "Diskprint does not match.\n";
}
if(notmatched==0 && matched){
cout << "Diskprint matches.\n";
}
if(notmatched==0 && matched==0){
cout << "Cannot verify Diskprint; no available hash functions.\n";
}
exit(0);
}
int main(int argc,char **argv)
{
int ch;
const char *opt_x=0;
/* Initialize */
#ifdef HAVE_SRANDOMEDEV
srandomdev();
#endif
OpenSSL_add_all_digests();/* Dynamically loads the digests */
/* Parse arguments */
while ((ch = getopt(argc, argv, "x:h?V")) != -1) {
switch (ch) {
case 'h':
case '?':
default:
usage();
break;
case 'x': opt_x = optarg; break;
case 'V':
printf("%s version %s\n",progname,PACKAGE_VERSION);
exit(0);
}
}
argc -= optind;
argv += optind;
if(argc!=1){ // currently only generates for one file
usage();
}
if(opt_x){
diskprint_verify(argv[0],opt_x);
return(0);
}
/* Loop through all of the files */
printf("<?xml version='1.0' encoding='UTF-8'?>\n");
printf("<diskprints>\n");
while(*argv){
if(opt_x){
diskprint_verify(*argv,opt_x);
}
else {
diskprint(*argv);
}
argv++;
argc--;
}
printf("</diskprints>\n");
exit(0);
}
#else
int main(int argc,char **argv)
{
fprintf(stderr,"affdiskprint requires EXPAT. Cannot continue.\n");
exit(-1);
}
#endif