From 190f2cf8358bf684aa19bf440eb1baf53e6f185c Mon Sep 17 00:00:00 2001 From: Thibault Marquand Date: Sun, 29 May 2016 23:38:11 +0200 Subject: [PATCH 01/14] aespython as external lib --- peepdf/aes.py | 2 +- peepdf/aespython/__init__.py | 0 peepdf/aespython/aes_cipher.py | 64 ------------- peepdf/aespython/aes_tables.py | 156 ------------------------------- peepdf/aespython/cbc_mode.py | 65 ------------- peepdf/aespython/cfb_mode.py | 65 ------------- peepdf/aespython/key_expander.py | 97 ------------------- peepdf/aespython/ofb_mode.py | 63 ------------- peepdf/aespython/test_keys.py | 119 ----------------------- setup.py | 3 +- 10 files changed, 3 insertions(+), 631 deletions(-) delete mode 100644 peepdf/aespython/__init__.py delete mode 100644 peepdf/aespython/aes_cipher.py delete mode 100644 peepdf/aespython/aes_tables.py delete mode 100644 peepdf/aespython/cbc_mode.py delete mode 100644 peepdf/aespython/cfb_mode.py delete mode 100644 peepdf/aespython/key_expander.py delete mode 100644 peepdf/aespython/ofb_mode.py delete mode 100644 peepdf/aespython/test_keys.py diff --git a/peepdf/aes.py b/peepdf/aes.py index d3b2970..3b34fa3 100644 --- a/peepdf/aes.py +++ b/peepdf/aes.py @@ -29,7 +29,7 @@ """ import sys -from peepdf.aespython import key_expander, aes_cipher, cbc_mode +from aespython import key_expander, aes_cipher, cbc_mode def decryptData(data, password = None, keyLength = None, mode = 'CBC'): ''' diff --git a/peepdf/aespython/__init__.py b/peepdf/aespython/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/peepdf/aespython/aes_cipher.py b/peepdf/aespython/aes_cipher.py deleted file mode 100644 index fdf4c5d..0000000 --- a/peepdf/aespython/aes_cipher.py +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env python -""" -AES Block Cipher. - -Performs single block cipher decipher operations on a 16 element list of integers. -These integers represent 8 bit bytes in a 128 bit block. -The result of cipher or decipher operations is the transformed 16 element list of integers. - -Running this file as __main__ will result in a self-test of the algorithm. - -Algorithm per NIST FIPS-197 http://csrc.nist.gov/publications/fips/fips197/fips-197.pdf - -Copyright (c) 2010, Adam Newman http://www.caller9.com/ -Licensed under the MIT license http://www.opensource.org/licenses/mit-license.php -""" -__author__ = "Adam Newman" - -#Normally use relative import. In test mode use local import. -try:from .aes_tables import sbox,i_sbox,galI,galNI -except ValueError:from aes_tables import sbox,i_sbox,galI,galNI -ups=",".join("s%x"%x for x in range(16)) -upr=ups.replace("s","r") -mix=",".join(",".join(("g{0}[s%x]^g{1}[s%x]^g{2}[s%x]^g{3}[s%x]^r%x"%(i+(i[0]+(0,3,2,1)[j],))).format(j&3,j+1&3,j+2&3,j+3&3) for j in (0,3,2,1)) for i in ((0,1,2,3),(4,5,6,7),(8,9,10,11),(12,13,14,15))).replace("g2","g").replace("g3","g") -i=mix.find("g[") -while i!=-1: - mix=mix[:i]+mix[i+2:i+4]+mix[i+5:] - i=mix.find("g[",i) -imix=",".join(",".join(("g{0}[s%x]^g{1}[s%x]^g{2}[s%x]^g{3}[s%x]"%i).format(j&3,j+1&3,j+2&3,j+3&3) for j in (0,3,2,1)) for i in ((0,1,2,3),(4,5,6,7),(8,9,10,11),(12,13,14,15))) -csl=["s%x"%(x*5&15) for x in range(16)] -csr=["s%x"%(x*-3&15) for x in range(16)] -box=",".join("s[%s]"%i for i in csl) -ibox=",".join("s[%s]^r%x"%i for i in zip(csr,range(16))) -xor=",".join("s[%s]^r%x"%i for i in zip(csl,range(16))) -xori=";".join("s%x^=r%x"%(i,i) for i in range(16)) -ciph="""def decipher_block(f,s): - g0,g1,g2,g3=galNI;ek=f._expanded_key;S=s+[0]*(16-len(s));s=sbox;R=ek[:16];X - for f in range(!16):R=ek[f:f+16];S=B;S=M - R=ek[f+16:] - return """.replace("S",ups).replace("R",upr).replace("X",xori) -class AESCipher: - def __init__(self,expanded_key): - self._expanded_key=expanded_key - self._Nr=len(expanded_key)-16 - exec(ciph.replace("g2,g3","").replace("dec","c").replace("!","16,f._Nr,").replace("B",box).replace("M",mix)+xor) - exec(ciph.replace("NI","I").replace(":16","f._Nr:").replace("f+16:",":16").replace("!","f._Nr-16,0,-").replace("sbox","i_sbox").replace("B",ibox).replace("M",imix)+ibox) -import unittest -class TestCipher(unittest.TestCase): - def test_cipher(self): - """Test AES cipher with all key lengths""" - import test_keys - import key_expander - test_data = test_keys.TestKeys() - for key_size in 128, 192, 256: - test_key_expander = key_expander.KeyExpander(key_size) - test_expanded_key = test_key_expander.expand(test_data.test_key[key_size]) - test_cipher = AESCipher(test_expanded_key) - test_result_ciphertext = test_cipher.cipher_block(test_data.test_block_plaintext) - self.assertEquals(len([i for i, j in zip(test_result_ciphertext, test_data.test_block_ciphertext_validated[key_size]) if i == j]), - 16,msg='Test %d bit cipher'%key_size) - test_result_plaintext = test_cipher.decipher_block(test_data.test_block_ciphertext_validated[key_size]) - self.assertEquals(len([i for i, j in zip(test_result_plaintext, test_data.test_block_plaintext) if i == j]), - 16,msg='Test %d bit decipher'%key_size) -if __name__ == "__main__": - unittest.main() \ No newline at end of file diff --git a/peepdf/aespython/aes_tables.py b/peepdf/aespython/aes_tables.py deleted file mode 100644 index bf6eb55..0000000 --- a/peepdf/aespython/aes_tables.py +++ /dev/null @@ -1,156 +0,0 @@ -""" -Instantiate AES tables for rcon,sbox,i_sbox,and galois_lookup. - -Copyright (c) 2010,Adam Newman http://www.caller9.com/ -Licensed under the MIT license http://www.opensource.org/licenses/mit-license.php -""" -__author__ = "Adam Newman" -rcon=( -0x8d,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,0x36,0x6c,0xd8,0xab,0x4d,0x9a, -0x2f,0x5e,0xbc,0x63,0xc6,0x97,0x35,0x6a,0xd4,0xb3,0x7d,0xfa,0xef,0xc5,0x91,0x39, -0x72,0xe4,0xd3,0xbd,0x61,0xc2,0x9f,0x25,0x4a,0x94,0x33,0x66,0xcc,0x83,0x1d,0x3a, -0x74,0xe8,0xcb,0x8d,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,0x36,0x6c,0xd8, -0xab,0x4d,0x9a,0x2f,0x5e,0xbc,0x63,0xc6,0x97,0x35,0x6a,0xd4,0xb3,0x7d,0xfa,0xef, -0xc5,0x91,0x39,0x72,0xe4,0xd3,0xbd,0x61,0xc2,0x9f,0x25,0x4a,0x94,0x33,0x66,0xcc, -0x83,0x1d,0x3a,0x74,0xe8,0xcb,0x8d,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b, -0x36,0x6c,0xd8,0xab,0x4d,0x9a,0x2f,0x5e,0xbc,0x63,0xc6,0x97,0x35,0x6a,0xd4,0xb3, -0x7d,0xfa,0xef,0xc5,0x91,0x39,0x72,0xe4,0xd3,0xbd,0x61,0xc2,0x9f,0x25,0x4a,0x94, -0x33,0x66,0xcc,0x83,0x1d,0x3a,0x74,0xe8,0xcb,0x8d,0x01,0x02,0x04,0x08,0x10,0x20, -0x40,0x80,0x1b,0x36,0x6c,0xd8,0xab,0x4d,0x9a,0x2f,0x5e,0xbc,0x63,0xc6,0x97,0x35, -0x6a,0xd4,0xb3,0x7d,0xfa,0xef,0xc5,0x91,0x39,0x72,0xe4,0xd3,0xbd,0x61,0xc2,0x9f, -0x25,0x4a,0x94,0x33,0x66,0xcc,0x83,0x1d,0x3a,0x74,0xe8,0xcb,0x8d,0x01,0x02,0x04, -0x08,0x10,0x20,0x40,0x80,0x1b,0x36,0x6c,0xd8,0xab,0x4d,0x9a,0x2f,0x5e,0xbc,0x63, -0xc6,0x97,0x35,0x6a,0xd4,0xb3,0x7d,0xfa,0xef,0xc5,0x91,0x39,0x72,0xe4,0xd3,0xbd, -0x61,0xc2,0x9f,0x25,0x4a,0x94,0x33,0x66,0xcc,0x83,0x1d,0x3a,0x74,0xe8,0xcb) -sbox=( -0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76, -0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0, -0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15, -0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75, -0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84, -0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf, -0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8, -0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2, -0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73, -0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb, -0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79, -0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08, -0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a, -0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e, -0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf, -0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16) -i_sbox=( -0x52,0x09,0x6a,0xd5,0x30,0x36,0xa5,0x38,0xbf,0x40,0xa3,0x9e,0x81,0xf3,0xd7,0xfb, -0x7c,0xe3,0x39,0x82,0x9b,0x2f,0xff,0x87,0x34,0x8e,0x43,0x44,0xc4,0xde,0xe9,0xcb, -0x54,0x7b,0x94,0x32,0xa6,0xc2,0x23,0x3d,0xee,0x4c,0x95,0x0b,0x42,0xfa,0xc3,0x4e, -0x08,0x2e,0xa1,0x66,0x28,0xd9,0x24,0xb2,0x76,0x5b,0xa2,0x49,0x6d,0x8b,0xd1,0x25, -0x72,0xf8,0xf6,0x64,0x86,0x68,0x98,0x16,0xd4,0xa4,0x5c,0xcc,0x5d,0x65,0xb6,0x92, -0x6c,0x70,0x48,0x50,0xfd,0xed,0xb9,0xda,0x5e,0x15,0x46,0x57,0xa7,0x8d,0x9d,0x84, -0x90,0xd8,0xab,0x00,0x8c,0xbc,0xd3,0x0a,0xf7,0xe4,0x58,0x05,0xb8,0xb3,0x45,0x06, -0xd0,0x2c,0x1e,0x8f,0xca,0x3f,0x0f,0x02,0xc1,0xaf,0xbd,0x03,0x01,0x13,0x8a,0x6b, -0x3a,0x91,0x11,0x41,0x4f,0x67,0xdc,0xea,0x97,0xf2,0xcf,0xce,0xf0,0xb4,0xe6,0x73, -0x96,0xac,0x74,0x22,0xe7,0xad,0x35,0x85,0xe2,0xf9,0x37,0xe8,0x1c,0x75,0xdf,0x6e, -0x47,0xf1,0x1a,0x71,0x1d,0x29,0xc5,0x89,0x6f,0xb7,0x62,0x0e,0xaa,0x18,0xbe,0x1b, -0xfc,0x56,0x3e,0x4b,0xc6,0xd2,0x79,0x20,0x9a,0xdb,0xc0,0xfe,0x78,0xcd,0x5a,0xf4, -0x1f,0xdd,0xa8,0x33,0x88,0x07,0xc7,0x31,0xb1,0x12,0x10,0x59,0x27,0x80,0xec,0x5f, -0x60,0x51,0x7f,0xa9,0x19,0xb5,0x4a,0x0d,0x2d,0xe5,0x7a,0x9f,0x93,0xc9,0x9c,0xef, -0xa0,0xe0,0x3b,0x4d,0xae,0x2a,0xf5,0xb0,0xc8,0xeb,0xbb,0x3c,0x83,0x53,0x99,0x61, -0x17,0x2b,0x04,0x7e,0xba,0x77,0xd6,0x26,0xe1,0x69,0x14,0x63,0x55,0x21,0x0c,0x7d) -galNI=(( -0x00,0x02,0x04,0x06,0x08,0x0a,0x0c,0x0e,0x10,0x12,0x14,0x16,0x18,0x1a,0x1c,0x1e, -0x20,0x22,0x24,0x26,0x28,0x2a,0x2c,0x2e,0x30,0x32,0x34,0x36,0x38,0x3a,0x3c,0x3e, -0x40,0x42,0x44,0x46,0x48,0x4a,0x4c,0x4e,0x50,0x52,0x54,0x56,0x58,0x5a,0x5c,0x5e, -0x60,0x62,0x64,0x66,0x68,0x6a,0x6c,0x6e,0x70,0x72,0x74,0x76,0x78,0x7a,0x7c,0x7e, -0x80,0x82,0x84,0x86,0x88,0x8a,0x8c,0x8e,0x90,0x92,0x94,0x96,0x98,0x9a,0x9c,0x9e, -0xa0,0xa2,0xa4,0xa6,0xa8,0xaa,0xac,0xae,0xb0,0xb2,0xb4,0xb6,0xb8,0xba,0xbc,0xbe, -0xc0,0xc2,0xc4,0xc6,0xc8,0xca,0xcc,0xce,0xd0,0xd2,0xd4,0xd6,0xd8,0xda,0xdc,0xde, -0xe0,0xe2,0xe4,0xe6,0xe8,0xea,0xec,0xee,0xf0,0xf2,0xf4,0xf6,0xf8,0xfa,0xfc,0xfe, -0x1b,0x19,0x1f,0x1d,0x13,0x11,0x17,0x15,0x0b,0x09,0x0f,0x0d,0x03,0x01,0x07,0x05, -0x3b,0x39,0x3f,0x3d,0x33,0x31,0x37,0x35,0x2b,0x29,0x2f,0x2d,0x23,0x21,0x27,0x25, -0x5b,0x59,0x5f,0x5d,0x53,0x51,0x57,0x55,0x4b,0x49,0x4f,0x4d,0x43,0x41,0x47,0x45, -0x7b,0x79,0x7f,0x7d,0x73,0x71,0x77,0x75,0x6b,0x69,0x6f,0x6d,0x63,0x61,0x67,0x65, -0x9b,0x99,0x9f,0x9d,0x93,0x91,0x97,0x95,0x8b,0x89,0x8f,0x8d,0x83,0x81,0x87,0x85, -0xbb,0xb9,0xbf,0xbd,0xb3,0xb1,0xb7,0xb5,0xab,0xa9,0xaf,0xad,0xa3,0xa1,0xa7,0xa5, -0xdb,0xd9,0xdf,0xdd,0xd3,0xd1,0xd7,0xd5,0xcb,0xc9,0xcf,0xcd,0xc3,0xc1,0xc7,0xc5, -0xfb,0xf9,0xff,0xfd,0xf3,0xf1,0xf7,0xf5,0xeb,0xe9,0xef,0xed,0xe3,0xe1,0xe7,0xe5), -(0x00,0x03,0x06,0x05,0x0c,0x0f,0x0a,0x09,0x18,0x1b,0x1e,0x1d,0x14,0x17,0x12,0x11, -0x30,0x33,0x36,0x35,0x3c,0x3f,0x3a,0x39,0x28,0x2b,0x2e,0x2d,0x24,0x27,0x22,0x21, -0x60,0x63,0x66,0x65,0x6c,0x6f,0x6a,0x69,0x78,0x7b,0x7e,0x7d,0x74,0x77,0x72,0x71, -0x50,0x53,0x56,0x55,0x5c,0x5f,0x5a,0x59,0x48,0x4b,0x4e,0x4d,0x44,0x47,0x42,0x41, -0xc0,0xc3,0xc6,0xc5,0xcc,0xcf,0xca,0xc9,0xd8,0xdb,0xde,0xdd,0xd4,0xd7,0xd2,0xd1, -0xf0,0xf3,0xf6,0xf5,0xfc,0xff,0xfa,0xf9,0xe8,0xeb,0xee,0xed,0xe4,0xe7,0xe2,0xe1, -0xa0,0xa3,0xa6,0xa5,0xac,0xaf,0xaa,0xa9,0xb8,0xbb,0xbe,0xbd,0xb4,0xb7,0xb2,0xb1, -0x90,0x93,0x96,0x95,0x9c,0x9f,0x9a,0x99,0x88,0x8b,0x8e,0x8d,0x84,0x87,0x82,0x81, -0x9b,0x98,0x9d,0x9e,0x97,0x94,0x91,0x92,0x83,0x80,0x85,0x86,0x8f,0x8c,0x89,0x8a, -0xab,0xa8,0xad,0xae,0xa7,0xa4,0xa1,0xa2,0xb3,0xb0,0xb5,0xb6,0xbf,0xbc,0xb9,0xba, -0xfb,0xf8,0xfd,0xfe,0xf7,0xf4,0xf1,0xf2,0xe3,0xe0,0xe5,0xe6,0xef,0xec,0xe9,0xea, -0xcb,0xc8,0xcd,0xce,0xc7,0xc4,0xc1,0xc2,0xd3,0xd0,0xd5,0xd6,0xdf,0xdc,0xd9,0xda, -0x5b,0x58,0x5d,0x5e,0x57,0x54,0x51,0x52,0x43,0x40,0x45,0x46,0x4f,0x4c,0x49,0x4a, -0x6b,0x68,0x6d,0x6e,0x67,0x64,0x61,0x62,0x73,0x70,0x75,0x76,0x7f,0x7c,0x79,0x7a, -0x3b,0x38,0x3d,0x3e,0x37,0x34,0x31,0x32,0x23,0x20,0x25,0x26,0x2f,0x2c,0x29,0x2a, -0x0b,0x08,0x0d,0x0e,0x07,0x04,0x01,0x02,0x13,0x10,0x15,0x16,0x1f,0x1c,0x19,0x1a)) -galI=( -(0x00,0x0e,0x1c,0x12,0x38,0x36,0x24,0x2a,0x70,0x7e,0x6c,0x62,0x48,0x46,0x54,0x5a, -0xe0,0xee,0xfc,0xf2,0xd8,0xd6,0xc4,0xca,0x90,0x9e,0x8c,0x82,0xa8,0xa6,0xb4,0xba, -0xdb,0xd5,0xc7,0xc9,0xe3,0xed,0xff,0xf1,0xab,0xa5,0xb7,0xb9,0x93,0x9d,0x8f,0x81, -0x3b,0x35,0x27,0x29,0x03,0x0d,0x1f,0x11,0x4b,0x45,0x57,0x59,0x73,0x7d,0x6f,0x61, -0xad,0xa3,0xb1,0xbf,0x95,0x9b,0x89,0x87,0xdd,0xd3,0xc1,0xcf,0xe5,0xeb,0xf9,0xf7, -0x4d,0x43,0x51,0x5f,0x75,0x7b,0x69,0x67,0x3d,0x33,0x21,0x2f,0x05,0x0b,0x19,0x17, -0x76,0x78,0x6a,0x64,0x4e,0x40,0x52,0x5c,0x06,0x08,0x1a,0x14,0x3e,0x30,0x22,0x2c, -0x96,0x98,0x8a,0x84,0xae,0xa0,0xb2,0xbc,0xe6,0xe8,0xfa,0xf4,0xde,0xd0,0xc2,0xcc, -0x41,0x4f,0x5d,0x53,0x79,0x77,0x65,0x6b,0x31,0x3f,0x2d,0x23,0x09,0x07,0x15,0x1b, -0xa1,0xaf,0xbd,0xb3,0x99,0x97,0x85,0x8b,0xd1,0xdf,0xcd,0xc3,0xe9,0xe7,0xf5,0xfb, -0x9a,0x94,0x86,0x88,0xa2,0xac,0xbe,0xb0,0xea,0xe4,0xf6,0xf8,0xd2,0xdc,0xce,0xc0, -0x7a,0x74,0x66,0x68,0x42,0x4c,0x5e,0x50,0x0a,0x04,0x16,0x18,0x32,0x3c,0x2e,0x20, -0xec,0xe2,0xf0,0xfe,0xd4,0xda,0xc8,0xc6,0x9c,0x92,0x80,0x8e,0xa4,0xaa,0xb8,0xb6, -0x0c,0x02,0x10,0x1e,0x34,0x3a,0x28,0x26,0x7c,0x72,0x60,0x6e,0x44,0x4a,0x58,0x56, -0x37,0x39,0x2b,0x25,0x0f,0x01,0x13,0x1d,0x47,0x49,0x5b,0x55,0x7f,0x71,0x63,0x6d, -0xd7,0xd9,0xcb,0xc5,0xef,0xe1,0xf3,0xfd,0xa7,0xa9,0xbb,0xb5,0x9f,0x91,0x83,0x8d), -(0x00,0x0b,0x16,0x1d,0x2c,0x27,0x3a,0x31,0x58,0x53,0x4e,0x45,0x74,0x7f,0x62,0x69, -0xb0,0xbb,0xa6,0xad,0x9c,0x97,0x8a,0x81,0xe8,0xe3,0xfe,0xf5,0xc4,0xcf,0xd2,0xd9, -0x7b,0x70,0x6d,0x66,0x57,0x5c,0x41,0x4a,0x23,0x28,0x35,0x3e,0x0f,0x04,0x19,0x12, -0xcb,0xc0,0xdd,0xd6,0xe7,0xec,0xf1,0xfa,0x93,0x98,0x85,0x8e,0xbf,0xb4,0xa9,0xa2, -0xf6,0xfd,0xe0,0xeb,0xda,0xd1,0xcc,0xc7,0xae,0xa5,0xb8,0xb3,0x82,0x89,0x94,0x9f, -0x46,0x4d,0x50,0x5b,0x6a,0x61,0x7c,0x77,0x1e,0x15,0x08,0x03,0x32,0x39,0x24,0x2f, -0x8d,0x86,0x9b,0x90,0xa1,0xaa,0xb7,0xbc,0xd5,0xde,0xc3,0xc8,0xf9,0xf2,0xef,0xe4, -0x3d,0x36,0x2b,0x20,0x11,0x1a,0x07,0x0c,0x65,0x6e,0x73,0x78,0x49,0x42,0x5f,0x54, -0xf7,0xfc,0xe1,0xea,0xdb,0xd0,0xcd,0xc6,0xaf,0xa4,0xb9,0xb2,0x83,0x88,0x95,0x9e, -0x47,0x4c,0x51,0x5a,0x6b,0x60,0x7d,0x76,0x1f,0x14,0x09,0x02,0x33,0x38,0x25,0x2e, -0x8c,0x87,0x9a,0x91,0xa0,0xab,0xb6,0xbd,0xd4,0xdf,0xc2,0xc9,0xf8,0xf3,0xee,0xe5, -0x3c,0x37,0x2a,0x21,0x10,0x1b,0x06,0x0d,0x64,0x6f,0x72,0x79,0x48,0x43,0x5e,0x55, -0x01,0x0a,0x17,0x1c,0x2d,0x26,0x3b,0x30,0x59,0x52,0x4f,0x44,0x75,0x7e,0x63,0x68, -0xb1,0xba,0xa7,0xac,0x9d,0x96,0x8b,0x80,0xe9,0xe2,0xff,0xf4,0xc5,0xce,0xd3,0xd8, -0x7a,0x71,0x6c,0x67,0x56,0x5d,0x40,0x4b,0x22,0x29,0x34,0x3f,0x0e,0x05,0x18,0x13, -0xca,0xc1,0xdc,0xd7,0xe6,0xed,0xf0,0xfb,0x92,0x99,0x84,0x8f,0xbe,0xb5,0xa8,0xa3), -(0x00,0x0d,0x1a,0x17,0x34,0x39,0x2e,0x23,0x68,0x65,0x72,0x7f,0x5c,0x51,0x46,0x4b, -0xd0,0xdd,0xca,0xc7,0xe4,0xe9,0xfe,0xf3,0xb8,0xb5,0xa2,0xaf,0x8c,0x81,0x96,0x9b, -0xbb,0xb6,0xa1,0xac,0x8f,0x82,0x95,0x98,0xd3,0xde,0xc9,0xc4,0xe7,0xea,0xfd,0xf0, -0x6b,0x66,0x71,0x7c,0x5f,0x52,0x45,0x48,0x03,0x0e,0x19,0x14,0x37,0x3a,0x2d,0x20, -0x6d,0x60,0x77,0x7a,0x59,0x54,0x43,0x4e,0x05,0x08,0x1f,0x12,0x31,0x3c,0x2b,0x26, -0xbd,0xb0,0xa7,0xaa,0x89,0x84,0x93,0x9e,0xd5,0xd8,0xcf,0xc2,0xe1,0xec,0xfb,0xf6, -0xd6,0xdb,0xcc,0xc1,0xe2,0xef,0xf8,0xf5,0xbe,0xb3,0xa4,0xa9,0x8a,0x87,0x90,0x9d, -0x06,0x0b,0x1c,0x11,0x32,0x3f,0x28,0x25,0x6e,0x63,0x74,0x79,0x5a,0x57,0x40,0x4d, -0xda,0xd7,0xc0,0xcd,0xee,0xe3,0xf4,0xf9,0xb2,0xbf,0xa8,0xa5,0x86,0x8b,0x9c,0x91, -0x0a,0x07,0x10,0x1d,0x3e,0x33,0x24,0x29,0x62,0x6f,0x78,0x75,0x56,0x5b,0x4c,0x41, -0x61,0x6c,0x7b,0x76,0x55,0x58,0x4f,0x42,0x09,0x04,0x13,0x1e,0x3d,0x30,0x27,0x2a, -0xb1,0xbc,0xab,0xa6,0x85,0x88,0x9f,0x92,0xd9,0xd4,0xc3,0xce,0xed,0xe0,0xf7,0xfa, -0xb7,0xba,0xad,0xa0,0x83,0x8e,0x99,0x94,0xdf,0xd2,0xc5,0xc8,0xeb,0xe6,0xf1,0xfc, -0x67,0x6a,0x7d,0x70,0x53,0x5e,0x49,0x44,0x0f,0x02,0x15,0x18,0x3b,0x36,0x21,0x2c, -0x0c,0x01,0x16,0x1b,0x38,0x35,0x22,0x2f,0x64,0x69,0x7e,0x73,0x50,0x5d,0x4a,0x47, -0xdc,0xd1,0xc6,0xcb,0xe8,0xe5,0xf2,0xff,0xb4,0xb9,0xae,0xa3,0x80,0x8d,0x9a,0x97), -(0x00,0x09,0x12,0x1b,0x24,0x2d,0x36,0x3f,0x48,0x41,0x5a,0x53,0x6c,0x65,0x7e,0x77, -0x90,0x99,0x82,0x8b,0xb4,0xbd,0xa6,0xaf,0xd8,0xd1,0xca,0xc3,0xfc,0xf5,0xee,0xe7, -0x3b,0x32,0x29,0x20,0x1f,0x16,0x0d,0x04,0x73,0x7a,0x61,0x68,0x57,0x5e,0x45,0x4c, -0xab,0xa2,0xb9,0xb0,0x8f,0x86,0x9d,0x94,0xe3,0xea,0xf1,0xf8,0xc7,0xce,0xd5,0xdc, -0x76,0x7f,0x64,0x6d,0x52,0x5b,0x40,0x49,0x3e,0x37,0x2c,0x25,0x1a,0x13,0x08,0x01, -0xe6,0xef,0xf4,0xfd,0xc2,0xcb,0xd0,0xd9,0xae,0xa7,0xbc,0xb5,0x8a,0x83,0x98,0x91, -0x4d,0x44,0x5f,0x56,0x69,0x60,0x7b,0x72,0x05,0x0c,0x17,0x1e,0x21,0x28,0x33,0x3a, -0xdd,0xd4,0xcf,0xc6,0xf9,0xf0,0xeb,0xe2,0x95,0x9c,0x87,0x8e,0xb1,0xb8,0xa3,0xaa, -0xec,0xe5,0xfe,0xf7,0xc8,0xc1,0xda,0xd3,0xa4,0xad,0xb6,0xbf,0x80,0x89,0x92,0x9b, -0x7c,0x75,0x6e,0x67,0x58,0x51,0x4a,0x43,0x34,0x3d,0x26,0x2f,0x10,0x19,0x02,0x0b, -0xd7,0xde,0xc5,0xcc,0xf3,0xfa,0xe1,0xe8,0x9f,0x96,0x8d,0x84,0xbb,0xb2,0xa9,0xa0, -0x47,0x4e,0x55,0x5c,0x63,0x6a,0x71,0x78,0x0f,0x06,0x1d,0x14,0x2b,0x22,0x39,0x30, -0x9a,0x93,0x88,0x81,0xbe,0xb7,0xac,0xa5,0xd2,0xdb,0xc0,0xc9,0xf6,0xff,0xe4,0xed, -0x0a,0x03,0x18,0x11,0x2e,0x27,0x3c,0x35,0x42,0x4b,0x50,0x59,0x66,0x6f,0x74,0x7d, -0xa1,0xa8,0xb3,0xba,0x85,0x8c,0x97,0x9e,0xe9,0xe0,0xfb,0xf2,0xcd,0xc4,0xdf,0xd6, -0x31,0x38,0x23,0x2a,0x15,0x1c,0x07,0x0e,0x79,0x70,0x6b,0x62,0x5d,0x54,0x4f,0x46)) \ No newline at end of file diff --git a/peepdf/aespython/cbc_mode.py b/peepdf/aespython/cbc_mode.py deleted file mode 100644 index f55e647..0000000 --- a/peepdf/aespython/cbc_mode.py +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/env python -""" -CBC Mode of operation - -Running this file as __main__ will result in a self-test of the algorithm. - -Algorithm per NIST SP 800-38A http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf - -Copyright (c) 2010, Adam Newman http://www.caller9.com/ -Licensed under the MIT license http://www.opensource.org/licenses/mit-license.php -""" -__author__ = "Adam Newman" - -class CBCMode: - """Perform CBC operation on a block and retain IV information for next operation""" - def __init__(self, block_cipher, block_size): - self._block_cipher = block_cipher - self._block_size = block_size - self._iv = [0] * block_size - - def set_iv(self, iv): - if len(iv) == self._block_size: - self._iv = iv - - def encrypt_block(self, plaintext): - iv=self._iv=self._block_cipher.cipher_block([i ^ j for i,j in zip (plaintext, self._iv)]) - return iv - - def decrypt_block(self, ciphertext): - plaintext = list(self._block_cipher.decipher_block(ciphertext)) - for i,v in enumerate(self._iv):plaintext[i]^=v - self._iv = ciphertext - return plaintext - -import unittest -class TestEncryptionMode(unittest.TestCase): - def test_mode(self): - #Self test - import key_expander - import aes_cipher - import test_keys - - test_data = test_keys.TestKeys() - - test_expander = key_expander.KeyExpander(256) - test_expanded_key = test_expander.expand(test_data.test_mode_key) - - test_cipher = aes_cipher.AESCipher(test_expanded_key) - - test_cbc = CBCMode(test_cipher, 16) - - test_cbc.set_iv(test_data.test_mode_iv) - for k in range(4): - self.assertEquals(len([i for i, j in zip(test_data.test_cbc_ciphertext[k],test_cbc.encrypt_block(test_data.test_mode_plaintext[k])) if i == j]), - 16, - msg='CBC encrypt test block %d'%k) - - test_cbc.set_iv(test_data.test_mode_iv) - for k in range(4): - self.assertEquals(len([i for i, j in zip(test_data.test_mode_plaintext[k],test_cbc.decrypt_block(test_data.test_cbc_ciphertext[k])) if i == j]), - 16, - msg='CBC decrypt test block %d'%k) - -if __name__ == "__main__": - unittest.main() \ No newline at end of file diff --git a/peepdf/aespython/cfb_mode.py b/peepdf/aespython/cfb_mode.py deleted file mode 100644 index 76e1971..0000000 --- a/peepdf/aespython/cfb_mode.py +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/env python -""" -CFB Mode of operation - -Running this file as __main__ will result in a self-test of the algorithm. - -Algorithm per NIST SP 800-38A http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf - -Copyright (c) 2010, Adam Newman http://www.caller9.com/ -Licensed under the MIT license http://www.opensource.org/licenses/mit-license.php -""" -__author__ = "Adam Newman" - -class CFBMode: - """Perform CFB operation on a block and retain IV information for next operation""" - def __init__(self, block_cipher, block_size): - self._block_cipher = block_cipher - self._block_size = block_size - self._iv = [0] * block_size - - def set_iv(self, iv): - if len(iv) == self._block_size: - self._iv = iv - - def encrypt_block(self, plaintext): - cipher_iv = self._block_cipher.cipher_block(self._iv) - iv = self._iv = [i ^ j for i,j in zip (plaintext, cipher_iv)] - return iv - - def decrypt_block(self, ciphertext): - cipher_iv = self._block_cipher.cipher_block(self._iv) - self._iv = ciphertext - return [i ^ j for i,j in zip (cipher_iv, ciphertext)] - -import unittest -class TestEncryptionMode(unittest.TestCase): - def test_mode(self): - #Self test - import key_expander - import aes_cipher - import test_keys - - test_data = test_keys.TestKeys() - - test_expander = key_expander.KeyExpander(256) - test_expanded_key = test_expander.expand(test_data.test_mode_key) - - test_cipher = aes_cipher.AESCipher(test_expanded_key) - - test_cfb = CFBMode(test_cipher, 16) - - test_cfb.set_iv(test_data.test_mode_iv) - for k in range(4): - self.assertEquals(len([i for i, j in zip(test_data.test_cfb_ciphertext[k],test_cfb.encrypt_block(test_data.test_mode_plaintext[k])) if i == j]), - 16, - msg='CFB encrypt test block' + str(k)) - - test_cfb.set_iv(test_data.test_mode_iv) - for k in range(4): - self.assertEquals(len([i for i, j in zip(test_data.test_mode_plaintext[k],test_cfb.decrypt_block(test_data.test_cfb_ciphertext[k])) if i == j]), - 16, - msg='CFB decrypt test block' + str(k)) - -if __name__ == "__main__": - unittest.main() diff --git a/peepdf/aespython/key_expander.py b/peepdf/aespython/key_expander.py deleted file mode 100644 index 0a20188..0000000 --- a/peepdf/aespython/key_expander.py +++ /dev/null @@ -1,97 +0,0 @@ -#!/usr/bin/env python - -""" -AES Key Expansion. - -Expands 128, 192, or 256 bit key for use with AES - -Running this file as __main__ will result in a self-test of the algorithm. - -Algorithm per NIST FIPS-197 http://csrc.nist.gov/publications/fips/fips197/fips-197.pdf - -Copyright (c) 2010, Adam Newman http://www.caller9.com/ -Licensed under the MIT license http://www.opensource.org/licenses/mit-license.php -""" -__author__ = "Adam Newman" - -#Normally use relative import. In test mode use local import. -try:from .aes_tables import sbox,rcon -except ValueError:from aes_tables import sbox,rcon -from operator import xor -class KeyExpander: - """Perform AES Key Expansion""" - - _expanded_key_length = {128 : 176, 192 : 208, 256 : 240} - - def __init__(self, key_length): - self._key_length = key_length - self._n = key_length>>3 - - if key_length in self._expanded_key_length: - self._b = self._expanded_key_length[key_length] - else: - raise LookupError('Invalid Key Size') - - def expand(self, new_key): - """ - Expand the encryption key per AES key schedule specifications - - http://en.wikipedia.org/wiki/Rijndael_key_schedule#Key_schedule_description - """ - #First n bytes are copied from key - len_new_key = len(new_key) - if len_new_key != self._n: - raise RuntimeError('expand(): key size is invalid') - rcon_iter = 1 - nex=new_key.extend - - #Grow the key until it is the correct length - while 1: - #Copy last 4 bytes of extended key, apply core, increment i(rcon_iter), - #core Append the list of elements 1-3 and list comprised of element 0 (circular rotate left) - #core For each element of this new list, put the result of sbox into output array. - #xor with 4 bytes n bytes from end of extended key - keyarr=[sbox[i] for i in new_key[-3:]+new_key[-4:-3]] - #First byte of output array is XORed with rcon(iter) - keyarr[0] ^= rcon[rcon_iter] - nex(map(xor,keyarr, new_key[-self._n:4-self._n])) - rcon_iter += 1 - len_new_key += 4 - - #Run three passes of 4 byte expansion using copy of 4 byte tail of extended key - #which is then xor'd with 4 bytes n bytes from end of extended key - for j in 0,1,2: - nex(map(xor,new_key[-4:], new_key[-self._n:4-self._n])) - len_new_key += 4 - if len_new_key >= self._b:return new_key - else: - #If key length is 256 and key is not complete, add 4 bytes tail of extended key - #run through sbox before xor with 4 bytes n bytes from end of extended key - if self._key_length == 256: - nex(map(xor,[sbox[x] for x in new_key[-4:]], new_key[-self._n:4-self._n])) - len_new_key += 4 - if len_new_key >= self._b:return new_key - - #If key length is 192 or 256 and key is not complete, run 2 or 3 passes respectively - #of 4 byte tail of extended key xor with 4 bytes n bytes from end of extended key - if self._key_length != 128: - for j in ((0,1) if self._key_length == 192 else (0,1,2)): - nex(map(xor,new_key[-4:], new_key[-self._n:4-self._n])) - len_new_key += 4 - if len_new_key >= self._b:return new_key - -import unittest -class TestKeyExpander(unittest.TestCase): - def test_keys(self): - """Test All Key Expansions""" - import test_keys - test_data = test_keys.TestKeys() - for key_size in 128, 192, 256: - test_expander = KeyExpander(key_size) - test_expanded_key = test_expander.expand(test_data.test_key[key_size]) - self.assertEqual (len([i for i, j in zip(test_expanded_key, test_data.test_expanded_key_validated[key_size]) if i == j]), - len(test_data.test_expanded_key_validated[key_size]), - msg='Key expansion ' + str(key_size) + ' bit') - -if __name__ == "__main__": - unittest.main() \ No newline at end of file diff --git a/peepdf/aespython/ofb_mode.py b/peepdf/aespython/ofb_mode.py deleted file mode 100644 index 84d6048..0000000 --- a/peepdf/aespython/ofb_mode.py +++ /dev/null @@ -1,63 +0,0 @@ -#!/usr/bin/env python -""" -OFB Mode of operation - -Running this file as __main__ will result in a self-test of the algorithm. - -Algorithm per NIST SP 800-38A http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf - -Copyright (c) 2010, Adam Newman http://www.caller9.com/ -Licensed under the MIT license http://www.opensource.org/licenses/mit-license.php -""" -__author__ = "Adam Newman" - -class OFBMode: - """Perform OFB operation on a block and retain IV information for next operation""" - def __init__(self, block_cipher, block_size): - self._block_cipher = block_cipher - self._block_size = block_size - self._iv = [0] * block_size - - def set_iv(self, iv): - if len(iv) == self._block_size: - self._iv = iv - - def encrypt_block(self, plaintext): - self._iv = cipher_iv = self._block_cipher.cipher_block(self._iv) - return [i ^ j for i,j in zip (plaintext, cipher_iv)] - - def decrypt_block(self, ciphertext): - self._iv = cipher_iv = self._block_cipher.cipher_block(self._iv) - return [i ^ j for i,j in zip (cipher_iv, ciphertext)] - -import unittest -class TestEncryptionMode(unittest.TestCase): - def test_mode(self): - #Self test - import key_expander - import aes_cipher - import test_keys - - test_data = test_keys.TestKeys() - - test_expander = key_expander.KeyExpander(256) - test_expanded_key = test_expander.expand(test_data.test_mode_key) - - test_cipher = aes_cipher.AESCipher(test_expanded_key) - - test_ofb = OFBMode(test_cipher, 16) - - test_ofb.set_iv(test_data.test_mode_iv) - for k in range(4): - self.assertEquals(len([i for i, j in zip(test_data.test_ofb_ciphertext[k],test_ofb.encrypt_block(test_data.test_mode_plaintext[k])) if i == j]), - 16, - msg='OFB encrypt test block' + str(k)) - - test_ofb.set_iv(test_data.test_mode_iv) - for k in range(4): - self.assertEquals(len([i for i, j in zip(test_data.test_mode_plaintext[k],test_ofb.decrypt_block(test_data.test_ofb_ciphertext[k])) if i == j]), - 16, - msg='OFB decrypt test block' + str(k)) - -if __name__ == "__main__": - unittest.main() \ No newline at end of file diff --git a/peepdf/aespython/test_keys.py b/peepdf/aespython/test_keys.py deleted file mode 100644 index fb28286..0000000 --- a/peepdf/aespython/test_keys.py +++ /dev/null @@ -1,119 +0,0 @@ -""" -Test keys and data for self-test operations. - -Test data from: -NIST SP 800-38A http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf -NIST FIPS-197 http://csrc.nist.gov/publications/fips/fips197/fips-197.pdf - -Copyright (c) 2010, Adam Newman http://www.caller9.com/ -Licensed under the MIT license http://www.opensource.org/licenses/mit-license.php -""" -__author__ = "Adam Newman" - -class TestKeys: - """Test data, keys, IVs, and output to use in self-tests""" - test_key = { - 128 : [ - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f] - , 192 : [ - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, - 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17] - , 256 : [ - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, - 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f] - } - - test_expanded_key_validated = { - 128 : [ - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, - 0xd6, 0xaa, 0x74, 0xfd, 0xd2, 0xaf, 0x72, 0xfa, 0xda, 0xa6, 0x78, 0xf1, 0xd6, 0xab, 0x76, 0xfe, - 0xb6, 0x92, 0xcf, 0x0b, 0x64, 0x3d, 0xbd, 0xf1, 0xbe, 0x9b, 0xc5, 0x00, 0x68, 0x30, 0xb3, 0xfe, - 0xb6, 0xff, 0x74, 0x4e, 0xd2, 0xc2, 0xc9, 0xbf, 0x6c, 0x59, 0x0c, 0xbf, 0x04, 0x69, 0xbf, 0x41, - 0x47, 0xf7, 0xf7, 0xbc, 0x95, 0x35, 0x3e, 0x03, 0xf9, 0x6c, 0x32, 0xbc, 0xfd, 0x05, 0x8d, 0xfd, - 0x3c, 0xaa, 0xa3, 0xe8, 0xa9, 0x9f, 0x9d, 0xeb, 0x50, 0xf3, 0xaf, 0x57, 0xad, 0xf6, 0x22, 0xaa, - 0x5e, 0x39, 0x0f, 0x7d, 0xf7, 0xa6, 0x92, 0x96, 0xa7, 0x55, 0x3d, 0xc1, 0x0a, 0xa3, 0x1f, 0x6b, - 0x14, 0xf9, 0x70, 0x1a, 0xe3, 0x5f, 0xe2, 0x8c, 0x44, 0x0a, 0xdf, 0x4d, 0x4e, 0xa9, 0xc0, 0x26, - 0x47, 0x43, 0x87, 0x35, 0xa4, 0x1c, 0x65, 0xb9, 0xe0, 0x16, 0xba, 0xf4, 0xae, 0xbf, 0x7a, 0xd2, - 0x54, 0x99, 0x32, 0xd1, 0xf0, 0x85, 0x57, 0x68, 0x10, 0x93, 0xed, 0x9c, 0xbe, 0x2c, 0x97, 0x4e, - 0x13, 0x11, 0x1d, 0x7f, 0xe3, 0x94, 0x4a, 0x17, 0xf3, 0x07, 0xa7, 0x8b, 0x4d, 0x2b, 0x30, 0xc5] - , 192 : [ - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, - 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x58, 0x46, 0xf2, 0xf9, 0x5c, 0x43, 0xf4, 0xfe, - 0x54, 0x4a, 0xfe, 0xf5, 0x58, 0x47, 0xf0, 0xfa, 0x48, 0x56, 0xe2, 0xe9, 0x5c, 0x43, 0xf4, 0xfe, - 0x40, 0xf9, 0x49, 0xb3, 0x1c, 0xba, 0xbd, 0x4d, 0x48, 0xf0, 0x43, 0xb8, 0x10, 0xb7, 0xb3, 0x42, - 0x58, 0xe1, 0x51, 0xab, 0x04, 0xa2, 0xa5, 0x55, 0x7e, 0xff, 0xb5, 0x41, 0x62, 0x45, 0x08, 0x0c, - 0x2a, 0xb5, 0x4b, 0xb4, 0x3a, 0x02, 0xf8, 0xf6, 0x62, 0xe3, 0xa9, 0x5d, 0x66, 0x41, 0x0c, 0x08, - 0xf5, 0x01, 0x85, 0x72, 0x97, 0x44, 0x8d, 0x7e, 0xbd, 0xf1, 0xc6, 0xca, 0x87, 0xf3, 0x3e, 0x3c, - 0xe5, 0x10, 0x97, 0x61, 0x83, 0x51, 0x9b, 0x69, 0x34, 0x15, 0x7c, 0x9e, 0xa3, 0x51, 0xf1, 0xe0, - 0x1e, 0xa0, 0x37, 0x2a, 0x99, 0x53, 0x09, 0x16, 0x7c, 0x43, 0x9e, 0x77, 0xff, 0x12, 0x05, 0x1e, - 0xdd, 0x7e, 0x0e, 0x88, 0x7e, 0x2f, 0xff, 0x68, 0x60, 0x8f, 0xc8, 0x42, 0xf9, 0xdc, 0xc1, 0x54, - 0x85, 0x9f, 0x5f, 0x23, 0x7a, 0x8d, 0x5a, 0x3d, 0xc0, 0xc0, 0x29, 0x52, 0xbe, 0xef, 0xd6, 0x3a, - 0xde, 0x60, 0x1e, 0x78, 0x27, 0xbc, 0xdf, 0x2c, 0xa2, 0x23, 0x80, 0x0f, 0xd8, 0xae, 0xda, 0x32, - 0xa4, 0x97, 0x0a, 0x33, 0x1a, 0x78, 0xdc, 0x09, 0xc4, 0x18, 0xc2, 0x71, 0xe3, 0xa4, 0x1d, 0x5d] - , 256 : [ - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, - 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, - 0xa5, 0x73, 0xc2, 0x9f, 0xa1, 0x76, 0xc4, 0x98, 0xa9, 0x7f, 0xce, 0x93, 0xa5, 0x72, 0xc0, 0x9c, - 0x16, 0x51, 0xa8, 0xcd, 0x02, 0x44, 0xbe, 0xda, 0x1a, 0x5d, 0xa4, 0xc1, 0x06, 0x40, 0xba, 0xde, - 0xae, 0x87, 0xdf, 0xf0, 0x0f, 0xf1, 0x1b, 0x68, 0xa6, 0x8e, 0xd5, 0xfb, 0x03, 0xfc, 0x15, 0x67, - 0x6d, 0xe1, 0xf1, 0x48, 0x6f, 0xa5, 0x4f, 0x92, 0x75, 0xf8, 0xeb, 0x53, 0x73, 0xb8, 0x51, 0x8d, - 0xc6, 0x56, 0x82, 0x7f, 0xc9, 0xa7, 0x99, 0x17, 0x6f, 0x29, 0x4c, 0xec, 0x6c, 0xd5, 0x59, 0x8b, - 0x3d, 0xe2, 0x3a, 0x75, 0x52, 0x47, 0x75, 0xe7, 0x27, 0xbf, 0x9e, 0xb4, 0x54, 0x07, 0xcf, 0x39, - 0x0b, 0xdc, 0x90, 0x5f, 0xc2, 0x7b, 0x09, 0x48, 0xad, 0x52, 0x45, 0xa4, 0xc1, 0x87, 0x1c, 0x2f, - 0x45, 0xf5, 0xa6, 0x60, 0x17, 0xb2, 0xd3, 0x87, 0x30, 0x0d, 0x4d, 0x33, 0x64, 0x0a, 0x82, 0x0a, - 0x7c, 0xcf, 0xf7, 0x1c, 0xbe, 0xb4, 0xfe, 0x54, 0x13, 0xe6, 0xbb, 0xf0, 0xd2, 0x61, 0xa7, 0xdf, - 0xf0, 0x1a, 0xfa, 0xfe, 0xe7, 0xa8, 0x29, 0x79, 0xd7, 0xa5, 0x64, 0x4a, 0xb3, 0xaf, 0xe6, 0x40, - 0x25, 0x41, 0xfe, 0x71, 0x9b, 0xf5, 0x00, 0x25, 0x88, 0x13, 0xbb, 0xd5, 0x5a, 0x72, 0x1c, 0x0a, - 0x4e, 0x5a, 0x66, 0x99, 0xa9, 0xf2, 0x4f, 0xe0, 0x7e, 0x57, 0x2b, 0xaa, 0xcd, 0xf8, 0xcd, 0xea, - 0x24, 0xfc, 0x79, 0xcc, 0xbf, 0x09, 0x79, 0xe9, 0x37, 0x1a, 0xc2, 0x3c, 0x6d, 0x68, 0xde, 0x36] - } - - test_block_ciphertext_validated = { - 128 : [ - 0x69, 0xc4, 0xe0, 0xd8, 0x6a, 0x7b, 0x04, 0x30, 0xd8, 0xcd, 0xb7, 0x80, 0x70, 0xb4, 0xc5, 0x5a] - , 192 : [ - 0xdd, 0xa9, 0x7c, 0xa4, 0x86, 0x4c, 0xdf, 0xe0, 0x6e, 0xaf, 0x70, 0xa0, 0xec, 0x0d, 0x71, 0x91] - , 256 : [ - 0x8e, 0xa2, 0xb7, 0xca, 0x51, 0x67, 0x45, 0xbf, 0xea, 0xfc, 0x49, 0x90, 0x4b, 0x49, 0x60, 0x89] - } - - test_block_plaintext = [ - 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff] - - #After initial validation, these deviated from test in SP 800-38A to use same key, iv, and plaintext on tests. - #Still valid, just easier to test with. - test_mode_key= [ - 0x60, 0x3d, 0xeb, 0x10, 0x15, 0xca, 0x71, 0xbe, 0x2b, 0x73, 0xae, 0xf0, 0x85, 0x7d, 0x77, 0x81, - 0x1f, 0x35, 0x2c, 0x07, 0x3b, 0x61, 0x08, 0xd7, 0x2d, 0x98, 0x10, 0xa3, 0x09, 0x14, 0xdf, 0xf4] - test_mode_iv = [ - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f] - test_mode_plaintext = [ - [0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a], - [0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51], - [0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11, 0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef], - [0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17, 0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10]] - test_cbc_ciphertext = [ - [0xf5, 0x8c, 0x4c, 0x04, 0xd6, 0xe5, 0xf1, 0xba, 0x77, 0x9e, 0xab, 0xfb, 0x5f, 0x7b, 0xfb, 0xd6], - [0x9c, 0xfc, 0x4e, 0x96, 0x7e, 0xdb, 0x80, 0x8d, 0x67, 0x9f, 0x77, 0x7b, 0xc6, 0x70, 0x2c, 0x7d], - [0x39, 0xf2, 0x33, 0x69, 0xa9, 0xd9, 0xba, 0xcf, 0xa5, 0x30, 0xe2, 0x63, 0x04, 0x23, 0x14, 0x61], - [0xb2, 0xeb, 0x05, 0xe2, 0xc3, 0x9b, 0xe9, 0xfc, 0xda, 0x6c, 0x19, 0x07, 0x8c, 0x6a, 0x9d, 0x1b]] - test_cfb_ciphertext = [ - [0xdc, 0x7e, 0x84, 0xbf, 0xda, 0x79, 0x16, 0x4b, 0x7e, 0xcd, 0x84, 0x86, 0x98, 0x5d, 0x38, 0x60], - [0x39, 0xff, 0xed, 0x14, 0x3b, 0x28, 0xb1, 0xc8, 0x32, 0x11, 0x3c, 0x63, 0x31, 0xe5, 0x40, 0x7b], - [0xdf, 0x10, 0x13, 0x24, 0x15, 0xe5, 0x4b, 0x92, 0xa1, 0x3e, 0xd0, 0xa8, 0x26, 0x7a, 0xe2, 0xf9], - [0x75, 0xa3, 0x85, 0x74, 0x1a, 0xb9, 0xce, 0xf8, 0x20, 0x31, 0x62, 0x3d, 0x55, 0xb1, 0xe4, 0x71]] - test_ofb_ciphertext = [ - [0xdc, 0x7e, 0x84, 0xbf, 0xda, 0x79, 0x16, 0x4b, 0x7e, 0xcd, 0x84, 0x86, 0x98, 0x5d, 0x38, 0x60], - [0x4f, 0xeb, 0xdc, 0x67, 0x40, 0xd2, 0x0b, 0x3a, 0xc8, 0x8f, 0x6a, 0xd8, 0x2a, 0x4f, 0xb0, 0x8d], - [0x71, 0xab, 0x47, 0xa0, 0x86, 0xe8, 0x6e, 0xed, 0xf3, 0x9d, 0x1c, 0x5b, 0xba, 0x97, 0xc4, 0x08], - [0x01, 0x26, 0x14, 0x1d, 0x67, 0xf3, 0x7b, 0xe8, 0x53, 0x8f, 0x5a, 0x8b, 0xe7, 0x40, 0xe4, 0x84]] - - def hex_output(self, list): - #Debugging output helper - result = '[' - for i in list[:-1]: - result += hex(i) + ',' - return result + hex(list[-1]) + ']' - - - \ No newline at end of file diff --git a/setup.py b/setup.py index 688b31f..7a4f7cb 100644 --- a/setup.py +++ b/setup.py @@ -6,6 +6,7 @@ author = "Jose Miguel Esparza", license = "GNU GPLv3", url = "http://eternal-todo.com", - install_requires = [ "jsbeautifier==1.6.2", "colorama" ], + install_requires = [ "jsbeautifier==1.6.2", "colorama", "pythonaes==1.0" ], + dependency_links = ["git+https://github.com/serprex/pythonaes.git@setup#egg=pythonaes-1.0"], packages = find_packages(), ) From fe22ea761654f488602fa0546bcc17b9f1bb6d73 Mon Sep 17 00:00:00 2001 From: Thibault Marquand Date: Mon, 30 May 2016 21:03:01 +0200 Subject: [PATCH 02/14] peepdf installed as command --- peepdf.py => bin/peepdf | 2 +- setup.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) rename peepdf.py => bin/peepdf (99%) diff --git a/peepdf.py b/bin/peepdf similarity index 99% rename from peepdf.py rename to bin/peepdf index d01e6da..0895277 100755 --- a/peepdf.py +++ b/bin/peepdf @@ -1,5 +1,5 @@ #!/usr/bin/env python - +# -*- coding: utf-8 -*- # # peepdf is a tool to analyse and modify PDF files # http://peepdf.eternal-todo.com diff --git a/setup.py b/setup.py index 7a4f7cb..e512dbc 100644 --- a/setup.py +++ b/setup.py @@ -5,6 +5,7 @@ version = "0.3-r235", author = "Jose Miguel Esparza", license = "GNU GPLv3", + scripts = [ "bin/peepdf" ], url = "http://eternal-todo.com", install_requires = [ "jsbeautifier==1.6.2", "colorama", "pythonaes==1.0" ], dependency_links = ["git+https://github.com/serprex/pythonaes.git@setup#egg=pythonaes-1.0"], From 56dd1be7e615b1409e205cf307dd21a181d753b8 Mon Sep 17 00:00:00 2001 From: Thibault Marquand Date: Fri, 3 Jun 2016 00:11:09 +0200 Subject: [PATCH 03/14] update README --- README | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/README b/README index 1408141..a823955 100644 --- a/README +++ b/README @@ -6,7 +6,6 @@ http://twitter.com/peepdf ** Dependencies ** -- jsbeautifier - In order to analyse Javascript code "PyV8" is needed: @@ -24,14 +23,17 @@ http://twitter.com/peepdf http://lxml.de/installation.html -- Included modules: lzw, colorama, ccitt, pythonaes (Thanks to all the developers!!) +- Included modules: lzw, ccitt (Thanks to all the developers!!) ** Installation ** -No installation is needed apart of the commented dependencies, just execute it! +Run + python setup.py install +The setup script handles the installation of jsbeautifier, colorama, pythonaes +It will also install `bin/peepdf` in your run path. ** Execution ** @@ -46,25 +48,25 @@ There are two important options when peepdf is executed: Shows the statistics of the file after being decoded/decrypted and analysed: - python peepdf.py [options] pdf_file + peepdf [options] pdf_file * Interactive console Executes the interactive console to let play with the PDF file: - python peepdf.py -i [options] pdf_file + peepdf -i [options] pdf_file If no PDF file is specified it's possible to use the decode/encode/js*/sctest commands and create a new PDF file: - python peepdf.py -i + peepdf -i * Batch execution It's possible to use a commands file to specify the commands to be executed in the batch mode. This type of execution is good to automatise analysis of several files: - python peepdf.py [options] -s commands_file pdf_file + peepdf [options] -s commands_file pdf_file @@ -72,7 +74,7 @@ It's possible to use a commands file to specify the commands to be executed in t Just type this and you will be updated to the latest version from the repository: - python peepdf.py -u + peepdf -u From 1946f52c5ea9668cac7eb3b5fe9b42a7c3b8b8d2 Mon Sep 17 00:00:00 2001 From: Thibault Marquand Date: Sat, 4 Jun 2016 18:44:30 +0200 Subject: [PATCH 04/14] introduce PDFOutput.py export code from bin/peepdf to PDFOutput.py --- bin/peepdf | 677 +++---------------------------------------- peepdf/PDFConsole.py | 168 +---------- peepdf/PDFOutput.py | 658 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 708 insertions(+), 795 deletions(-) create mode 100644 peepdf/PDFOutput.py diff --git a/bin/peepdf b/bin/peepdf index 1dbcc01..fa35742 100755 --- a/bin/peepdf +++ b/bin/peepdf @@ -30,330 +30,9 @@ import sys import os import optparse -import re -import urllib2 -import hashlib -import traceback -import json from datetime import datetime -from peepdf.PDFCore import PDFParser, vulnsDict -from peepdf.PDFUtils import vtcheck - -VT_KEY = 'fc90df3f5ac749a94a94cb8bf87e05a681a2eb001aef34b6a0084b8c22c97a64' - -try: - import PyV8 - JS_MODULE = True -except: - JS_MODULE = False -try: - import pylibemu - EMU_MODULE = True -except: - EMU_MODULE = False -try: - from colorama import init, Fore, Back, Style - COLORIZED_OUTPUT = True -except: - COLORIZED_OUTPUT = False - -try: - from PIL import Image - PIL_MODULE = True -except: - PIL_MODULE = False - - -def getRepPaths(url, path=''): - paths = [] - try: - browsingPage = urllib2.urlopen(url + path).read() - except: - sys.exit('[x] Connection error while getting browsing page "' + url + path + '"') - browsingPageObject = json.loads(browsingPage) - for file in browsingPageObject: - if file['type'] == 'file': - paths.append(file['path']) - elif file['type'] == 'dir': - dirPaths = getRepPaths(url, file['path']) - paths += dirPaths - return paths - - -def getLocalFilesInfo(filesList): - localFilesInfo = {} - print '[-] Getting local files information...' - for path in filesList: - absFilePath = os.path.join(absPeepdfRoot, path) - if os.path.exists(absFilePath): - content = open(absFilePath, 'rb').read() - shaHash = hashlib.sha256(content).hexdigest() - localFilesInfo[path] = [shaHash, absFilePath] - print '[+] Done' - return localFilesInfo - - -def getPeepXML(statsDict, version, revision): - root = etree.Element('peepdf_analysis', version=version + ' r' + revision, url='http://peepdf.eternal-todo.com', - author='Jose Miguel Esparza') - analysisDate = etree.SubElement(root, 'date') - analysisDate.text = datetime.today().strftime('%Y-%m-%d %H:%M') - basicInfo = etree.SubElement(root, 'basic') - fileName = etree.SubElement(basicInfo, 'filename') - fileName.text = statsDict['File'] - md5 = etree.SubElement(basicInfo, 'md5') - md5.text = statsDict['MD5'] - sha1 = etree.SubElement(basicInfo, 'sha1') - sha1.text = statsDict['SHA1'] - sha256 = etree.SubElement(basicInfo, 'sha256') - sha256.text = statsDict['SHA256'] - size = etree.SubElement(basicInfo, 'size') - size.text = statsDict['Size'] - detection = etree.SubElement(basicInfo, 'detection') - if statsDict['Detection']: - detectionRate = etree.SubElement(detection, 'rate') - detectionRate.text = '%d/%d' % (statsDict['Detection'][0], statsDict['Detection'][1]) - detectionReport = etree.SubElement(detection, 'report_link') - detectionReport.text = statsDict['Detection report'] - version = etree.SubElement(basicInfo, 'pdf_version') - version.text = statsDict['Version'] - binary = etree.SubElement(basicInfo, 'binary', status=statsDict['Binary'].lower()) - linearized = etree.SubElement(basicInfo, 'linearized', status=statsDict['Linearized'].lower()) - encrypted = etree.SubElement(basicInfo, 'encrypted', status=statsDict['Encrypted'].lower()) - if statsDict['Encryption Algorithms']: - algorithms = etree.SubElement(encrypted, 'algorithms') - for algorithmInfo in statsDict['Encryption Algorithms']: - algorithm = etree.SubElement(algorithms, 'algorithm', bits=str(algorithmInfo[1])) - algorithm.text = algorithmInfo[0] - updates = etree.SubElement(basicInfo, 'updates') - updates.text = statsDict['Updates'] - objects = etree.SubElement(basicInfo, 'num_objects') - objects.text = statsDict['Objects'] - streams = etree.SubElement(basicInfo, 'num_streams') - streams.text = statsDict['Streams'] - comments = etree.SubElement(basicInfo, 'comments') - comments.text = statsDict['Comments'] - errors = etree.SubElement(basicInfo, 'errors', num=str(len(statsDict['Errors']))) - for error in statsDict['Errors']: - errorMessageXML = etree.SubElement(errors, 'error_message') - errorMessageXML.text = error - advancedInfo = etree.SubElement(root, 'advanced') - for version in range(len(statsDict['Versions'])): - statsVersion = statsDict['Versions'][version] - if version == 0: - versionType = 'original' - else: - versionType = 'update' - versionInfo = etree.SubElement(advancedInfo, 'version', num=str(version), type=versionType) - catalog = etree.SubElement(versionInfo, 'catalog') - if statsVersion['Catalog'] is not None: - catalog.set('object_id', statsVersion['Catalog']) - info = etree.SubElement(versionInfo, 'info') - if statsVersion['Info'] is not None: - info.set('object_id', statsVersion['Info']) - objects = etree.SubElement(versionInfo, 'objects', num=statsVersion['Objects'][0]) - for id in statsVersion['Objects'][1]: - object = etree.SubElement(objects, 'object', id=str(id)) - if statsVersion['Compressed Objects'] is not None: - if id in statsVersion['Compressed Objects'][1]: - object.set('compressed', 'true') - else: - object.set('compressed', 'false') - if statsVersion['Errors'] is not None: - if id in statsVersion['Errors'][1]: - object.set('errors', 'true') - else: - object.set('errors', 'false') - streams = etree.SubElement(versionInfo, 'streams', num=statsVersion['Streams'][0]) - for id in statsVersion['Streams'][1]: - stream = etree.SubElement(streams, 'stream', id=str(id)) - if statsVersion['Xref Streams'] is not None: - if id in statsVersion['Xref Streams'][1]: - stream.set('xref_stream', 'true') - else: - stream.set('xref_stream', 'false') - if statsVersion['Object Streams'] is not None: - if id in statsVersion['Object Streams'][1]: - stream.set('object_stream', 'true') - else: - stream.set('object_stream', 'false') - if statsVersion['Encoded'] is not None: - if id in statsVersion['Encoded'][1]: - stream.set('encoded', 'true') - if statsVersion['Decoding Errors'] is not None: - if id in statsVersion['Decoding Errors'][1]: - stream.set('decoding_errors', 'true') - else: - stream.set('decoding_errors', 'false') - else: - stream.set('encoded', 'false') - jsObjects = etree.SubElement(versionInfo, 'js_objects') - if statsVersion['Objects with JS code'] is not None: - for id in statsVersion['Objects with JS code'][1]: - etree.SubElement(jsObjects, 'container_object', id=str(id)) - actions = statsVersion['Actions'] - events = statsVersion['Events'] - vulns = statsVersion['Vulns'] - elements = statsVersion['Elements'] - suspicious = etree.SubElement(versionInfo, 'suspicious_elements') - if events != None or actions != None or vulns != None or elements != None: - if events: - triggers = etree.SubElement(suspicious, 'triggers') - for event in events: - trigger = etree.SubElement(triggers, 'trigger', name=event) - for id in events[event]: - etree.SubElement(trigger, 'container_object', id=str(id)) - if actions: - actionsList = etree.SubElement(suspicious, 'actions') - for action in actions: - actionInfo = etree.SubElement(actionsList, 'action', name=action) - for id in actions[action]: - etree.SubElement(actionInfo, 'container_object', id=str(id)) - if elements: - elementsList = etree.SubElement(suspicious, 'elements') - for element in elements: - elementInfo = etree.SubElement(elementsList, 'element', name=element) - if vulnsDict.has_key(element): - vulnName = vulnsDict[element][0] - vulnCVEList = vulnsDict[element][1] - for vulnCVE in vulnCVEList: - cve = etree.SubElement(elementInfo, 'cve') - cve.text = vulnCVE - for id in elements[element]: - etree.SubElement(elementInfo, 'container_object', id=str(id)) - if vulns: - vulnsList = etree.SubElement(suspicious, 'js_vulns') - for vuln in vulns: - vulnInfo = etree.SubElement(vulnsList, 'vulnerable_function', name=vuln) - if vulnsDict.has_key(vuln): - vulnName = vulnsDict[vuln][0] - vulnCVEList = vulnsDict[vuln][1] - for vulnCVE in vulnCVEList: - cve = etree.SubElement(vulnInfo, 'cve') - cve.text = vulnCVE - for id in vulns[vuln]: - etree.SubElement(vulnInfo, 'container_object', id=str(id)) - urls = statsVersion['URLs'] - suspiciousURLs = etree.SubElement(versionInfo, 'suspicious_urls') - if urls != None: - for url in urls: - urlInfo = etree.SubElement(suspiciousURLs, 'url') - urlInfo.text = url - return etree.tostring(root, pretty_print=True) - - -def getPeepJSON(statsDict, version, revision): - # peepdf info - peepdfDict = {'version': version, - 'revision': revision, - 'author': 'Jose Miguel Esparza', - 'url': 'http://peepdf.eternal-todo.com'} - # Basic info - basicDict = {} - basicDict['filename'] = statsDict['File'] - basicDict['md5'] = statsDict['MD5'] - basicDict['sha1'] = statsDict['SHA1'] - basicDict['sha256'] = statsDict['SHA256'] - basicDict['size'] = int(statsDict['Size']) - basicDict['detection'] = {} - if statsDict['Detection'] != [] and statsDict['Detection'] is not None: - basicDict['detection']['rate'] = '%d/%d' % (statsDict['Detection'][0], statsDict['Detection'][1]) - basicDict['detection']['report_link'] = statsDict['Detection report'] - basicDict['pdf_version'] = statsDict['Version'] - basicDict['binary'] = bool(statsDict['Binary']) - basicDict['linearized'] = bool(statsDict['Linearized']) - basicDict['encrypted'] = bool(statsDict['Encrypted']) - basicDict['encryption_algorithms'] = [] - if statsDict['Encryption Algorithms']: - for algorithmInfo in statsDict['Encryption Algorithms']: - basicDict['encryption_algorithms'].append({'bits': algorithmInfo[1], 'algorithm': algorithmInfo[0]}) - basicDict['updates'] = int(statsDict['Updates']) - basicDict['num_objects'] = int(statsDict['Objects']) - basicDict['num_streams'] = int(statsDict['Streams']) - basicDict['comments'] = int(statsDict['Comments']) - basicDict['errors'] = [] - for error in statsDict['Errors']: - basicDict['errors'].append(error) - # Advanced info - advancedInfo = [] - for version in range(len(statsDict['Versions'])): - statsVersion = statsDict['Versions'][version] - if version == 0: - versionType = 'original' - else: - versionType = 'update' - versionInfo = {} - versionInfo['version_number'] = version - versionInfo['version_type'] = versionType - versionInfo['catalog'] = statsVersion['Catalog'] - versionInfo['info'] = statsVersion['Info'] - if statsVersion['Objects'] is not None: - versionInfo['objects'] = statsVersion['Objects'][1] - else: - versionInfo['objects'] = [] - if statsVersion['Compressed Objects'] is not None: - versionInfo['compressed_objects'] = statsVersion['Compressed Objects'][1] - else: - versionInfo['compressed_objects'] = [] - if statsVersion['Errors'] is not None: - versionInfo['error_objects'] = statsVersion['Errors'][1] - else: - versionInfo['error_objects'] = [] - if statsVersion['Streams'] is not None: - versionInfo['streams'] = statsVersion['Streams'][1] - else: - versionInfo['streams'] = [] - if statsVersion['Xref Streams'] is not None: - versionInfo['xref_streams'] = statsVersion['Xref Streams'][1] - else: - versionInfo['xref_streams'] = [] - if statsVersion['Encoded'] is not None: - versionInfo['encoded_streams'] = statsVersion['Encoded'][1] - else: - versionInfo['encoded_streams'] = [] - if versionInfo['encoded_streams'] and statsVersion['Decoding Errors'] is not None: - versionInfo['decoding_error_streams'] = statsVersion['Decoding Errors'][1] - else: - versionInfo['decoding_error_streams'] = [] - if statsVersion['Objects with JS code'] is not None: - versionInfo['js_objects'] = statsVersion['Objects with JS code'][1] - else: - versionInfo['js_objects'] = [] - elements = statsVersion['Elements'] - elementArray = [] - if elements: - for element in elements: - elementInfo = {'name': element} - if element in vulnsDict: - elementInfo['vuln_name'] = vulnsDict[element][0] - elementInfo['vuln_cve_list'] = vulnsDict[element][1] - elementInfo['objects'] = elements[element] - elementArray.append(elementInfo) - vulns = statsVersion['Vulns'] - vulnArray = [] - if vulns: - for vuln in vulns: - vulnInfo = {'name': vuln} - if vuln in vulnsDict: - vulnInfo['vuln_name'] = vulnsDict[vuln][0] - vulnInfo['vuln_cve_list'] = vulnsDict[vuln][1] - vulnInfo['objects'] = vulns[vuln] - vulnArray.append(vulnInfo) - versionInfo['suspicious_elements'] = {'triggers': statsVersion['Events'], - 'actions': statsVersion['Actions'], - 'elements': elementArray, - 'js_vulns': vulnArray, - 'urls': statsVersion['URLs']} - versionReport = {'version_info': versionInfo} - advancedInfo.append(versionReport) - jsonDict = {'peepdf_analysis': - {'peepdf_info': peepdfDict, - 'date': datetime.today().strftime('%Y-%m-%d %H:%M'), - 'basic': basicDict, - 'advanced': advancedInfo} - } - return json.dumps(jsonDict, indent=4, sort_keys=True) +import peepdf.PDFOutput as PDFOutput author = 'Jose Miguel Esparza' @@ -363,11 +42,6 @@ twitter = 'http://twitter.com/EternalTodo' peepTwitter = 'http://twitter.com/peepdf' version = '0.3' revision = '275' -stats = '' -pdf = None -fileName = None -statsDict = None -vtJsonDict = None newLine = os.linesep absPeepdfRoot = os.path.dirname(os.path.realpath(sys.argv[0])) errorsFile = os.path.join(absPeepdfRoot, 'errors.txt') @@ -380,47 +54,35 @@ peepdfHeader = versionHeader + newLine * 2 + \ author + newLine + \ twitter + newLine -argsParser = optparse.OptionParser(usage='Usage: peepdf.py [options] PDF_file', description=versionHeader) -argsParser.add_option('-i', '--interactive', action='store_true', dest='isInteractive', default=False, - help='Sets console mode.') -argsParser.add_option('-s', '--load-script', action='store', type='string', dest='scriptFile', - help='Loads the commands stored in the specified file and execute them.') -argsParser.add_option('-c', '--check-vt', action='store_true', dest='checkOnVT', default=False, - help='Checks the hash of the PDF file on VirusTotal.') -argsParser.add_option('-f', '--force-mode', action='store_true', dest='isForceMode', default=False, - help='Sets force parsing mode to ignore errors.') -argsParser.add_option('-l', '--loose-mode', action='store_true', dest='isLooseMode', default=False, - help='Sets loose parsing mode to catch malformed objects.') -argsParser.add_option('-m', '--manual-analysis', action='store_true', dest='isManualAnalysis', default=False, - help='Avoids automatic Javascript analysis. Useful with eternal loops like heap spraying.') -argsParser.add_option('-u', '--update', action='store_true', dest='update', default=False, - help='Updates peepdf with the latest files from the repository.') -argsParser.add_option('-g', '--grinch-mode', action='store_true', dest='avoidColors', default=False, - help='Avoids colorized output in the interactive console.') -argsParser.add_option('-v', '--version', action='store_true', dest='version', default=False, - help='Shows program\'s version number.') -argsParser.add_option('-x', '--xml', action='store_true', dest='xmlOutput', default=False, - help='Shows the document information in XML format.') -argsParser.add_option('-j', '--json', action='store_true', dest='jsonOutput', default=False, - help='Shows the document information in JSON format.') -argsParser.add_option('-C', '--command', action='append', type='string', dest='commands', - help='Specifies a command from the interactive console to be executed.') -(options, args) = argsParser.parse_args() -try: - # Avoid colors in the output - if not COLORIZED_OUTPUT or options.avoidColors: - warningColor = '' - errorColor = '' - alertColor = '' - staticColor = '' - resetColor = '' - else: - warningColor = Fore.YELLOW - errorColor = Fore.RED - alertColor = Fore.RED - staticColor = Fore.BLUE - resetColor = Style.RESET_ALL +if __name__ == '__main__': + argsParser = optparse.OptionParser(usage='Usage: peepdf.py [options] PDF_file', description=versionHeader) + argsParser.add_option('-i', '--interactive', action='store_true', dest='isInteractive', default=False, + help='Sets console mode.') + argsParser.add_option('-s', '--load-script', action='store', type='string', dest='scriptFile', + help='Loads the commands stored in the specified file and execute them.') + argsParser.add_option('-c', '--check-vt', action='store_true', dest='checkOnVT', default=False, + help='Checks the hash of the PDF file on VirusTotal.') + argsParser.add_option('-f', '--force-mode', action='store_true', dest='isForceMode', default=False, + help='Sets force parsing mode to ignore errors.') + argsParser.add_option('-l', '--loose-mode', action='store_true', dest='isLooseMode', default=False, + help='Sets loose parsing mode to catch malformed objects.') + argsParser.add_option('-m', '--manual-analysis', action='store_true', dest='isManualAnalysis', default=False, + help='Avoids automatic Javascript analysis. Useful with eternal loops like heap spraying.') + argsParser.add_option('-u', '--update', action='store_true', dest='update', default=False, + help='Updates peepdf with the latest files from the repository.') + argsParser.add_option('-g', '--grinch-mode', action='store_true', dest='avoidColors', default=False, + help='Avoids colorized output in the interactive console.') + argsParser.add_option('-v', '--version', action='store_true', dest='version', default=False, + help='Shows program\'s version number.') + argsParser.add_option('-x', '--xml', action='store_true', dest='xmlOutput', default=False, + help='Shows the document information in XML format.') + argsParser.add_option('-j', '--json', action='store_true', dest='jsonOutput', default=False, + help='Shows the document information in JSON format.') + argsParser.add_option('-C', '--command', action='append', type='string', dest='commands', + help='Specifies a command from the interactive console to be executed.') + (options, args) = argsParser.parse_args() + if options.version: print peepdfHeader elif options.update: @@ -445,9 +107,9 @@ try: else: print '[+] There are new updates!!' print '[-] Getting paths from the repository...' - pathNames = getRepPaths(repURL, '') + pathNames = PDFOutput.getRepPaths(repURL, '') print '[+] Done' - localFilesInfo = getLocalFilesInfo(pathNames) + localFilesInfo = PDFOutput.getLocalFilesInfo(pathNames) print '[-] Checking files...' for path in pathNames: try: @@ -477,274 +139,11 @@ try: message += ' to ' + newVersion print message - else: - if len(args) == 1: - fileName = args[0] - if not os.path.exists(fileName): - sys.exit('Error: The file "' + fileName + '" does not exist!!') - elif len(args) > 1 or (len(args) == 0 and not options.isInteractive): - sys.exit(argsParser.print_help()) - - if options.scriptFile is not None: - if not os.path.exists(options.scriptFile): - sys.exit('Error: The script file "' + options.scriptFile + '" does not exist!!') - - if fileName is not None: - pdfParser = PDFParser() - ret, pdf = pdfParser.parse(fileName, options.isForceMode, options.isLooseMode, options.isManualAnalysis) - if options.checkOnVT: - # Checks the MD5 on VirusTotal - md5Hash = pdf.getMD5() - ret = vtcheck(md5Hash, VT_KEY) - if ret[0] == -1: - pdf.addError(ret[1]) - else: - vtJsonDict = ret[1] - if vtJsonDict.has_key('response_code'): - if vtJsonDict['response_code'] == 1: - if vtJsonDict.has_key('positives') and vtJsonDict.has_key('total'): - pdf.setDetectionRate([vtJsonDict['positives'], vtJsonDict['total']]) - else: - pdf.addError('Missing elements in the response from VirusTotal!!') - if vtJsonDict.has_key('permalink'): - pdf.setDetectionReport(vtJsonDict['permalink']) - else: - pdf.setDetectionRate(None) - else: - pdf.addError('Bad response from VirusTotal!!') - statsDict = pdf.getStats() - - if options.xmlOutput: - try: - from lxml import etree - - xml = getPeepXML(statsDict, version, revision) - sys.stdout.write(xml) - except: - errorMessage = '*** Error: Exception while generating the XML file!!' - traceback.print_exc(file=open(errorsFile, 'a')) - raise Exception('PeepException', 'Send me an email ;)') - elif options.jsonOutput and not options.commands: - try: - jsonReport = getPeepJSON(statsDict, version, revision) - sys.stdout.write(jsonReport) - except: - errorMessage = '*** Error: Exception while generating the JSON report!!' - traceback.print_exc(file=open(errorsFile, 'a')) - raise Exception('PeepException', 'Send me an email ;)') - else: - if COLORIZED_OUTPUT and not options.avoidColors: - try: - init() - except: - COLORIZED_OUTPUT = False - if options.scriptFile is not None: - from peepdf.PDFConsole import PDFConsole - - scriptFileObject = open(options.scriptFile, 'rb') - console = PDFConsole(pdf, VT_KEY, options.avoidColors, stdin=scriptFileObject) - try: - console.cmdloop() - except: - errorMessage = '*** Error: Exception not handled using the batch mode!!' - scriptFileObject.close() - traceback.print_exc(file=open(errorsFile, 'a')) - raise Exception('PeepException', 'Send me an email ;)') - elif options.commands is not None: - from PDFConsole import PDFConsole - - console = PDFConsole(pdf, VT_KEY, options.avoidColors) - try: - for command in options.commands: - console.onecmd(command) - except: - errorMessage = '*** Error: Exception not handled using the batch commands!!' - traceback.print_exc(file=open(errorsFile, 'a')) - raise Exception('PeepException', 'Send me an email ;)') - else: - if statsDict is not None: - if COLORIZED_OUTPUT and not options.avoidColors: - beforeStaticLabel = staticColor - else: - beforeStaticLabel = '' - - if not JS_MODULE: - warningMessage = 'Warning: PyV8 is not installed!!' - stats += warningColor + warningMessage + resetColor + newLine - if not EMU_MODULE: - warningMessage = 'Warning: pylibemu is not installed!!' - stats += warningColor + warningMessage + resetColor + newLine - if not PIL_MODULE: - warningMessage = 'Warning: Python Imaging Library (PIL) is not installed!!' - stats += warningColor + warningMessage + resetColor + newLine - errors = statsDict['Errors'] - for error in errors: - if error.find('Decryption error') != -1: - stats += errorColor + error + resetColor + newLine - if stats != '': - stats += newLine - statsDict = pdf.getStats() - - stats += beforeStaticLabel + 'File: ' + resetColor + statsDict['File'] + newLine - stats += beforeStaticLabel + 'MD5: ' + resetColor + statsDict['MD5'] + newLine - stats += beforeStaticLabel + 'SHA1: ' + resetColor + statsDict['SHA1'] + newLine - stats += beforeStaticLabel + 'SHA256: ' + resetColor + statsDict['SHA256'] + newLine - stats += beforeStaticLabel + 'Size: ' + resetColor + statsDict['Size'] + ' bytes' + newLine - if options.checkOnVT: - if statsDict['Detection'] != []: - detectionReportInfo = '' - if statsDict['Detection'] != None: - detectionColor = '' - if COLORIZED_OUTPUT and not options.avoidColors: - detectionLevel = statsDict['Detection'][0] / (statsDict['Detection'][1] / 3) - if detectionLevel == 0: - detectionColor = alertColor - elif detectionLevel == 1: - detectionColor = warningColor - detectionRate = '%s%d%s/%d' % ( - detectionColor, statsDict['Detection'][0], resetColor, statsDict['Detection'][1]) - if statsDict['Detection report'] != '': - detectionReportInfo = beforeStaticLabel + 'Detection report: ' + resetColor + \ - statsDict['Detection report'] + newLine - else: - detectionRate = 'File not found on VirusTotal' - stats += beforeStaticLabel + 'Detection: ' + resetColor + detectionRate + newLine - stats += detectionReportInfo - stats += beforeStaticLabel + 'Version: ' + resetColor + statsDict['Version'] + newLine - stats += beforeStaticLabel + 'Binary: ' + resetColor + statsDict['Binary'] + newLine - stats += beforeStaticLabel + 'Linearized: ' + resetColor + statsDict['Linearized'] + newLine - stats += beforeStaticLabel + 'Encrypted: ' + resetColor + statsDict['Encrypted'] - if statsDict['Encryption Algorithms'] != []: - stats += ' (' - for algorithmInfo in statsDict['Encryption Algorithms']: - stats += algorithmInfo[0] + ' ' + str(algorithmInfo[1]) + ' bits, ' - stats = stats[:-2] + ')' - stats += newLine - stats += beforeStaticLabel + 'Updates: ' + resetColor + statsDict['Updates'] + newLine - stats += beforeStaticLabel + 'Objects: ' + resetColor + statsDict['Objects'] + newLine - stats += beforeStaticLabel + 'Streams: ' + resetColor + statsDict['Streams'] + newLine - stats += beforeStaticLabel + 'URIs: ' + resetColor + statsDict['URIs'] + newLine - stats += beforeStaticLabel + 'Comments: ' + resetColor + statsDict['Comments'] + newLine - stats += beforeStaticLabel + 'Errors: ' + resetColor + str(len(statsDict['Errors'])) + newLine * 2 - for version in range(len(statsDict['Versions'])): - statsVersion = statsDict['Versions'][version] - stats += beforeStaticLabel + 'Version ' + resetColor + str(version) + ':' + newLine - if statsVersion['Catalog'] != None: - stats += beforeStaticLabel + '\tCatalog: ' + resetColor + statsVersion['Catalog'] + newLine - else: - stats += beforeStaticLabel + '\tCatalog: ' + resetColor + 'No' + newLine - if statsVersion['Info'] != None: - stats += beforeStaticLabel + '\tInfo: ' + resetColor + statsVersion['Info'] + newLine - else: - stats += beforeStaticLabel + '\tInfo: ' + resetColor + 'No' + newLine - stats += beforeStaticLabel + '\tObjects (' + statsVersion['Objects'][ - 0] + '): ' + resetColor + str(statsVersion['Objects'][1]) + newLine - if statsVersion['Compressed Objects'] != None: - stats += beforeStaticLabel + '\tCompressed objects (' + statsVersion['Compressed Objects'][ - 0] + '): ' + resetColor + str(statsVersion['Compressed Objects'][1]) + newLine - if statsVersion['Errors'] != None: - stats += beforeStaticLabel + '\t\tErrors (' + statsVersion['Errors'][ - 0] + '): ' + resetColor + str(statsVersion['Errors'][1]) + newLine - stats += beforeStaticLabel + '\tStreams (' + statsVersion['Streams'][ - 0] + '): ' + resetColor + str(statsVersion['Streams'][1]) - if statsVersion['Xref Streams'] != None: - stats += newLine + beforeStaticLabel + '\t\tXref streams (' + statsVersion['Xref Streams'][ - 0] + '): ' + resetColor + str(statsVersion['Xref Streams'][1]) - if statsVersion['Object Streams'] != None: - stats += newLine + beforeStaticLabel + '\t\tObject streams (' + \ - statsVersion['Object Streams'][0] + '): ' + resetColor + str( - statsVersion['Object Streams'][1]) - if int(statsVersion['Streams'][0]) > 0: - stats += newLine + beforeStaticLabel + '\t\tEncoded (' + statsVersion['Encoded'][ - 0] + '): ' + resetColor + str(statsVersion['Encoded'][1]) - if statsVersion['Decoding Errors'] != None: - stats += newLine + beforeStaticLabel + '\t\tDecoding errors (' + \ - statsVersion['Decoding Errors'][0] + '): ' + resetColor + str( - statsVersion['Decoding Errors'][1]) - if statsVersion['URIs'] is not None: - stats += newLine + beforeStaticLabel + '\tObjects with URIs (' + \ - statsVersion['URIs'][0] + '): ' + resetColor + str(statsVersion['URIs'][1]) - if COLORIZED_OUTPUT and not options.avoidColors: - beforeStaticLabel = warningColor - if statsVersion['Objects with JS code'] != None: - stats += newLine + beforeStaticLabel + '\tObjects with JS code (' + \ - statsVersion['Objects with JS code'][0] + '): ' + resetColor + str( - statsVersion['Objects with JS code'][1]) - actions = statsVersion['Actions'] - events = statsVersion['Events'] - vulns = statsVersion['Vulns'] - elements = statsVersion['Elements'] - if events != None or actions != None or vulns != None or elements != None: - stats += newLine + beforeStaticLabel + '\tSuspicious elements:' + resetColor + newLine - if events != None: - for event in events: - stats += '\t\t' + beforeStaticLabel + event + ' (%d): ' % len(events[event]) + \ - resetColor + str(events[event]) + newLine - if actions != None: - for action in actions: - stats += '\t\t' + beforeStaticLabel + action + ' (%d): ' % len(actions[action]) + \ - resetColor + str(actions[action]) + newLine - if vulns != None: - for vuln in vulns: - if vulnsDict.has_key(vuln): - vulnName = vulnsDict[vuln][0] - vulnCVEList = vulnsDict[vuln][1] - stats += '\t\t' + beforeStaticLabel + vulnName + ' (' - for vulnCVE in vulnCVEList: - stats += vulnCVE + ',' - stats = stats[:-1] + ') (%d): ' % len(vulns[vuln]) + resetColor + str(vulns[vuln]) + newLine - else: - stats += '\t\t' + beforeStaticLabel + vuln + ' (%d): ' % len(vulns[vuln]) + \ - resetColor + str(vulns[vuln]) + newLine - if elements != None: - for element in elements: - if vulnsDict.has_key(element): - vulnName = vulnsDict[element][0] - vulnCVEList = vulnsDict[element][1] - stats += '\t\t' + beforeStaticLabel + vulnName + ' (' - for vulnCVE in vulnCVEList: - stats += vulnCVE + ',' - stats = stats[:-1] + '): ' + resetColor + str(elements[element]) + newLine - else: - stats += '\t\t' + beforeStaticLabel + element + ': ' + resetColor + str( - elements[element]) + newLine - if COLORIZED_OUTPUT and not options.avoidColors: - beforeStaticLabel = staticColor - urls = statsVersion['URLs'] - if urls != None: - stats += newLine + beforeStaticLabel + '\tFound URLs:' + resetColor + newLine - for url in urls: - stats += '\t\t' + url + newLine - stats += newLine * 2 - if fileName != None: - print stats - if options.isInteractive: - from peepdf.PDFConsole import PDFConsole + if len(args) == 1: + pdfName = args[0] + if not os.path.exists(pdfName): + sys.exit('Error: The file "' + pdfName + '" does not exist!!') + elif len(args) > 1 or (len(args) == 0 and not options.isInteractive): + sys.exit(argsParser.print_help()) - console = PDFConsole(pdf, VT_KEY, options.avoidColors) - while not console.leaving: - try: - console.cmdloop() - except KeyboardInterrupt as e: - sys.exit() - except: - errorMessage = '*** Error: Exception not handled using the interactive console!! Please, report it to the author!!' - print errorColor + errorMessage + resetColor + newLine - traceback.print_exc(file=open(errorsFile, 'a')) -except Exception as e: - if len(e.args) == 2: - excName, excReason = e.args - else: - excName = excReason = None - if excName == None or excName != 'PeepException': - errorMessage = '*** Error: Exception not handled!!' - traceback.print_exc(file=open(errorsFile, 'a')) - print errorColor + errorMessage + resetColor + newLine -finally: - if os.path.exists(errorsFile): - message = newLine + 'Please, don\'t forget to report the errors found:' + newLine * 2 - message += '\t- Sending the file "%s" to the author (mailto:peepdfREMOVETHIS@eternal-todo.com)%s' % ( - errorsFile, newLine) - message += '\t- And/Or creating an issue on the project webpage (https://github.com/jesparza/peepdf/issues)' + newLine - message = errorColor + message + resetColor - sys.exit(message) + PDFOutput.main(options, args, errorsFile, pdfName=pdfName) diff --git a/peepdf/PDFConsole.py b/peepdf/PDFConsole.py index 1923d39..ccac905 100644 --- a/peepdf/PDFConsole.py +++ b/peepdf/PDFConsole.py @@ -35,6 +35,7 @@ import traceback import jsbeautifier from peepdf.PDFUtils import * +from peepdf.PDFOutput import * from peepdf.PDFCrypto import * from peepdf.JSAnalysis import * from peepdf.PDFCore import * @@ -80,7 +81,7 @@ 'jbig2': '/JBIG2Decode', 'dct': '/DCTDecode', 'jpx': '/JPXDecode'} -class PDFConsole(cmd.Cmd): +class PDFConsole(cmd.Cmd, PDFOutput): ''' Class of the peepdf interactive console. To see details about commands: http://code.google.com/p/peepdf/wiki/Commands ''' @@ -88,28 +89,16 @@ class PDFConsole(cmd.Cmd): def __init__(self, pdfFile, vtKey, avoidOutputColors=False, stdin=None, batchMode=False, jsonOutput=False): global COLORIZED_OUTPUT cmd.Cmd.__init__(self, stdin=stdin) - self.warningColor = '' - self.errorColor = '' - self.alertColor = '' - self.staticColor = '' - self.resetColor = '' - if not COLORIZED_OUTPUT or avoidOutputColors: - self.avoidOutputColors = True - else: + PDFOutput.__init__(self, avoidOutputColors) + if COLORIZED_OUTPUT and not avoidOutputColors: try: - init() - self.warningColor = Fore.YELLOW - self.errorColor = Fore.RED - self.alertColor = Fore.RED - self.staticColor = Fore.BLUE self.promptColor = RL_PROMPT_START_IGNORE + Fore.GREEN + RL_PROMPT_END_IGNORE - self.resetColor = Style.RESET_ALL - self.avoidOutputColors = False + self.avoidColor = False except: - self.avoidOutputColors = True + self.avoidColor = True COLORIZED_OUTPUT = False - if not self.avoidOutputColors: + if not self.avoidColor: self.prompt = self.promptColor + 'PPDF> ' + RL_PROMPT_START_IGNORE + self.resetColor + RL_PROMPT_END_IGNORE else: self.prompt = 'PPDF> ' @@ -1408,151 +1397,19 @@ def do_info(self, argv): message = '*** Error: You must open a file!!' self.log_output('info ' + argv, message) return False - stats = '' args = self.parseArgs(argv) if args is None: message = '*** Error: The command line arguments have not been parsed successfully!!' self.log_output('info ' + argv, message) return False - if not self.avoidOutputColors: + if not self.avoidColor: beforeStaticLabel = self.staticColor else: beforeStaticLabel = '' + stats = "" if len(args) == 0: statsDict = self.pdfFile.getStats() - stats += beforeStaticLabel + 'File: ' + self.resetColor + statsDict['File'] + newLine - stats += beforeStaticLabel + 'MD5: ' + self.resetColor + statsDict['MD5'] + newLine - stats += beforeStaticLabel + 'SHA1: ' + self.resetColor + statsDict['SHA1'] + newLine - #stats += beforeStaticLabel + 'SHA256: ' + self.resetColor + statsDict['SHA256'] + newLine - stats += beforeStaticLabel + 'Size: ' + self.resetColor + statsDict['Size'] + ' bytes' + newLine - if statsDict['Detection'] != []: - detectionReportInfo = '' - if statsDict['Detection'] is not None: - detectionLevel = statsDict['Detection'][0] / (statsDict['Detection'][1] / 3) - if detectionLevel == 0: - detectionColor = self.alertColor - elif detectionLevel == 1: - detectionColor = self.warningColor - else: - detectionColor = '' - detectionRate = '%s%d%s/%d' % ( - detectionColor, statsDict['Detection'][0], self.resetColor, statsDict['Detection'][1]) - if statsDict['Detection report'] != '': - detectionReportInfo = beforeStaticLabel + 'Detection report: ' + self.resetColor + statsDict[ - 'Detection report'] + newLine - else: - detectionRate = 'File not found on VirusTotal' - stats += beforeStaticLabel + 'Detection: ' + self.resetColor + detectionRate + newLine - stats += detectionReportInfo - stats += beforeStaticLabel + 'Version: ' + self.resetColor + statsDict['Version'] + newLine - stats += beforeStaticLabel + 'Binary: ' + self.resetColor + statsDict['Binary'] + newLine - stats += beforeStaticLabel + 'Linearized: ' + self.resetColor + statsDict['Linearized'] + newLine - stats += beforeStaticLabel + 'Encrypted: ' + self.resetColor + statsDict['Encrypted'] - if statsDict['Encryption Algorithms'] != []: - stats += ' (' - for algorithmInfo in statsDict['Encryption Algorithms']: - stats += algorithmInfo[0] + ' ' + str(algorithmInfo[1]) + ' bits, ' - stats = stats[:-2] + ')' - stats += newLine - stats += beforeStaticLabel + 'Updates: ' + self.resetColor + statsDict['Updates'] + newLine - stats += beforeStaticLabel + 'Objects: ' + self.resetColor + statsDict['Objects'] + newLine - stats += beforeStaticLabel + 'Streams: ' + self.resetColor + statsDict['Streams'] + newLine - stats += beforeStaticLabel + 'URIs: ' + self.resetColor + statsDict['URIs'] + newLine - stats += beforeStaticLabel + 'Comments: ' + self.resetColor + statsDict['Comments'] + newLine - stats += beforeStaticLabel + 'Errors: ' + self.resetColor + str(len(statsDict['Errors'])) + newLine * 2 - for version in range(len(statsDict['Versions'])): - statsVersion = statsDict['Versions'][version] - stats += beforeStaticLabel + 'Version ' + self.resetColor + str(version) + ':' + newLine - if statsVersion['Catalog'] is not None: - stats += beforeStaticLabel + '\tCatalog: ' + self.resetColor + statsVersion['Catalog'] + newLine - else: - stats += beforeStaticLabel + '\tCatalog: ' + self.resetColor + 'No' + newLine - if statsVersion['Info'] is not None: - stats += beforeStaticLabel + '\tInfo: ' + self.resetColor + statsVersion['Info'] + newLine - else: - stats += beforeStaticLabel + '\tInfo: ' + self.resetColor + 'No' + newLine - stats += beforeStaticLabel + '\tObjects (' + statsVersion['Objects'][0] + '): ' + \ - self.resetColor + str(statsVersion['Objects'][1]) + newLine - if statsVersion['Compressed Objects'] is not None: - stats += beforeStaticLabel + '\tCompressed objects (' +\ - statsVersion['Compressed Objects'][0] + '): ' + self.resetColor + \ - str(statsVersion['Compressed Objects'][1]) + newLine - if statsVersion['Errors'] is not None: - stats += beforeStaticLabel + '\t\tErrors (' + statsVersion['Errors'][0] + '): ' + \ - self.resetColor + str(statsVersion['Errors'][1]) + newLine - stats += beforeStaticLabel + '\tStreams (' + statsVersion['Streams'][0] + '): ' + \ - self.resetColor + str(statsVersion['Streams'][1]) - if statsVersion['Xref Streams'] is not None: - stats += newLine + beforeStaticLabel + '\t\tXref streams (' + \ - statsVersion['Xref Streams'][0] + '): ' + self.resetColor + \ - str(statsVersion['Xref Streams'][1]) - if statsVersion['Object Streams'] is not None: - stats += newLine + beforeStaticLabel + '\t\tObject streams (' + \ - statsVersion['Object Streams'][0] + '): ' + self.resetColor + \ - str(statsVersion['Object Streams'][1]) - if int(statsVersion['Streams'][0]) > 0: - stats += newLine + beforeStaticLabel + '\t\tEncoded (' + statsVersion['Encoded'][0] + '): ' + \ - self.resetColor + str(statsVersion['Encoded'][1]) - if statsVersion['Decoding Errors'] is not None: - stats += newLine + beforeStaticLabel + '\t\tDecoding errors (' + \ - statsVersion['Decoding Errors'][0] + '): ' + self.resetColor + str( - statsVersion['Decoding Errors'][1]) - if statsVersion['URIs'] is not None: - stats += newLine + beforeStaticLabel + '\tObjects with URIs (' + \ - statsVersion['URIs'][0] + '): ' + self.resetColor + str(statsVersion['URIs'][1]) - if not self.avoidOutputColors: - beforeStaticLabel = self.warningColor - if statsVersion['Objects with JS code'] is not None: - stats += newLine + beforeStaticLabel + '\tObjects with JS code (' + \ - statsVersion['Objects with JS code'][0] + '): ' + \ - self.resetColor + str(statsVersion['Objects with JS code'][1]) - actions = statsVersion['Actions'] - events = statsVersion['Events'] - vulns = statsVersion['Vulns'] - elements = statsVersion['Elements'] - if events is not None or actions is not None or vulns is not None or elements is not None: - stats += newLine + beforeStaticLabel + '\tSuspicious elements:' + self.resetColor + newLine - if events is not None: - for event in events: - stats += '\t\t' + beforeStaticLabel + event + ' (%d): ' % len(events[event]) + \ - self.resetColor + str(events[event]) + newLine - if actions is not None: - for action in actions: - stats += '\t\t' + beforeStaticLabel + action + ' (%d): ' % len(actions[action]) + \ - self.resetColor + str(actions[action]) + newLine - if vulns is not None: - for vuln in vulns: - if vulnsDict.has_key(vuln): - vulnName = vulnsDict[vuln][0] - vulnCVEList = vulnsDict[vuln][1] - stats += '\t\t' + beforeStaticLabel + vulnName + ' (' - for vulnCVE in vulnCVEList: - stats += vulnCVE + ',' - stats = stats[:-1] + ') (%d): ' % len(vulns[vuln]) + self.resetColor + \ - str(vulns[vuln]) + newLine - else: - stats += '\t\t' + beforeStaticLabel + vuln + ' (%d): ' % len(vulns[vuln]) + \ - self.resetColor + str(vulns[vuln]) + newLine - if elements is not None: - for element in elements: - if vulnsDict.has_key(element): - vulnName = vulnsDict[element][0] - vulnCVEList = vulnsDict[element][1] - stats += '\t\t' + beforeStaticLabel + vulnName + ' (' - for vulnCVE in vulnCVEList: - stats += vulnCVE + ',' - stats = stats[:-1] + '): ' + self.resetColor + str(elements[element]) + newLine - else: - stats += '\t\t' + beforeStaticLabel + element + ': ' + self.resetColor + str( - elements[element]) + newLine - if not self.avoidOutputColors: - beforeStaticLabel = self.staticColor - urls = statsVersion['URLs'] - if urls is not None: - stats += newLine + beforeStaticLabel + '\tFound URLs:' + self.resetColor + newLine - for url in urls: - stats += '\t\t' + url + newLine - stats += newLine * 2 + stats = self.getPeepReport(statsDict) self.log_output('info ' + argv, stats) return False elif len(args) == 1: @@ -2863,8 +2720,7 @@ def do_open(self, argv): message = '*** Error: Opening document failed!!' self.pdfFile = None self.log_output('open ' + argv, message) - if not JS_MODULE: - print 'Warning: PyV8 is not installed!!' + newLine + print self.dependenciesWarning() if self.pdfFile is not None: self.do_info('') @@ -3727,7 +3583,7 @@ def do_vtcheck(self, argv): if args == []: self.pdfFile.setDetectionRate([jsonDict['positives'], jsonDict['total']]) self.pdfFile.setDetectionReport(jsonDict['permalink']) - if not self.avoidOutputColors: + if not self.avoidColor: detectionLevel = jsonDict['positives'] / (jsonDict['total'] / 3) if detectionLevel == 0: detectionColor = self.alertColor diff --git a/peepdf/PDFOutput.py b/peepdf/PDFOutput.py new file mode 100644 index 0000000..41b786b --- /dev/null +++ b/peepdf/PDFOutput.py @@ -0,0 +1,658 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# peepdf is a tool to analyse and modify PDF files +# http://peepdf.eternal-todo.com +# By Jose Miguel Esparza +# +# Copyright (C) 2011-2017 Jose Miguel Esparza +# +# This file is part of peepdf. +# +# peepdf 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 3 of the License, or +# (at your option) any later version. +# +# peepdf 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 peepdf. If not, see . +# + +import os +import sys +import re +import urllib2 +import hashlib +import traceback +import json +from peepdf.PDFCore import PDFParser, vulnsDict +from peepdf.PDFUtils import vtcheck + +try: + import PyV8 + JS_MODULE = True +except: + JS_MODULE = False +try: + import pylibemu + EMU_MODULE = True +except: + EMU_MODULE = False +try: + from colorama import init, Fore, Back, Style + COLORIZED_OUTPUT = True +except: + COLORIZED_OUTPUT = False + +try: + from PIL import Image + PIL_MODULE = True +except: + PIL_MODULE = False + + +def getLocalFilesInfo(filesList): + localFilesInfo = {} + print '[-] Getting local files information...' + for path in filesList: + absFilePath = os.path.join(absPeepdfRoot, path) + if os.path.exists(absFilePath): + content = open(absFilePath, 'rb').read() + shaHash = hashlib.sha256(content).hexdigest() + localFilesInfo[path] = [shaHash, absFilePath] + print '[+] Done' + return localFilesInfo + +def getRepPaths(url, path=''): + paths = [] + try: + browsingPage = urllib2.urlopen(url + path).read() + except: + sys.exit('[x] Connection error while getting browsing page "' + url + path + '"') + browsingPageObject = json.loads(browsingPage) + for file in browsingPageObject: + if file['type'] == 'file': + paths.append(file['path']) + elif file['type'] == 'dir': + dirPaths = getRepPaths(url, file['path']) + paths += dirPaths + return paths + + +def getPeepXML(statsDict, version, revision): + root = etree.Element('peepdf_analysis', version=version + ' r' + revision, url='http://peepdf.eternal-todo.com', + author='Jose Miguel Esparza') + analysisDate = etree.SubElement(root, 'date') + analysisDate.text = datetime.today().strftime('%Y-%m-%d %H:%M') + basicInfo = etree.SubElement(root, 'basic') + fileName = etree.SubElement(basicInfo, 'filename') + fileName.text = statsDict['File'] + md5 = etree.SubElement(basicInfo, 'md5') + md5.text = statsDict['MD5'] + sha1 = etree.SubElement(basicInfo, 'sha1') + sha1.text = statsDict['SHA1'] + sha256 = etree.SubElement(basicInfo, 'sha256') + sha256.text = statsDict['SHA256'] + size = etree.SubElement(basicInfo, 'size') + size.text = statsDict['Size'] + detection = etree.SubElement(basicInfo, 'detection') + if statsDict['Detection']: + detectionRate = etree.SubElement(detection, 'rate') + detectionRate.text = '%d/%d' % (statsDict['Detection'][0], statsDict['Detection'][1]) + detectionReport = etree.SubElement(detection, 'report_link') + detectionReport.text = statsDict['Detection report'] + version = etree.SubElement(basicInfo, 'pdf_version') + version.text = statsDict['Version'] + binary = etree.SubElement(basicInfo, 'binary', status=statsDict['Binary'].lower()) + linearized = etree.SubElement(basicInfo, 'linearized', status=statsDict['Linearized'].lower()) + encrypted = etree.SubElement(basicInfo, 'encrypted', status=statsDict['Encrypted'].lower()) + if statsDict['Encryption Algorithms']: + algorithms = etree.SubElement(encrypted, 'algorithms') + for algorithmInfo in statsDict['Encryption Algorithms']: + algorithm = etree.SubElement(algorithms, 'algorithm', bits=str(algorithmInfo[1])) + algorithm.text = algorithmInfo[0] + updates = etree.SubElement(basicInfo, 'updates') + updates.text = statsDict['Updates'] + objects = etree.SubElement(basicInfo, 'num_objects') + objects.text = statsDict['Objects'] + streams = etree.SubElement(basicInfo, 'num_streams') + streams.text = statsDict['Streams'] + comments = etree.SubElement(basicInfo, 'comments') + comments.text = statsDict['Comments'] + errors = etree.SubElement(basicInfo, 'errors', num=str(len(statsDict['Errors']))) + for error in statsDict['Errors']: + errorMessageXML = etree.SubElement(errors, 'error_message') + errorMessageXML.text = error + advancedInfo = etree.SubElement(root, 'advanced') + for version in range(len(statsDict['Versions'])): + statsVersion = statsDict['Versions'][version] + if version == 0: + versionType = 'original' + else: + versionType = 'update' + versionInfo = etree.SubElement(advancedInfo, 'version', num=str(version), type=versionType) + catalog = etree.SubElement(versionInfo, 'catalog') + if statsVersion['Catalog'] is not None: + catalog.set('object_id', statsVersion['Catalog']) + info = etree.SubElement(versionInfo, 'info') + if statsVersion['Info'] is not None: + info.set('object_id', statsVersion['Info']) + objects = etree.SubElement(versionInfo, 'objects', num=statsVersion['Objects'][0]) + for id in statsVersion['Objects'][1]: + object = etree.SubElement(objects, 'object', id=str(id)) + if statsVersion['Compressed Objects'] is not None: + if id in statsVersion['Compressed Objects'][1]: + object.set('compressed', 'true') + else: + object.set('compressed', 'false') + if statsVersion['Errors'] is not None: + if id in statsVersion['Errors'][1]: + object.set('errors', 'true') + else: + object.set('errors', 'false') + streams = etree.SubElement(versionInfo, 'streams', num=statsVersion['Streams'][0]) + for id in statsVersion['Streams'][1]: + stream = etree.SubElement(streams, 'stream', id=str(id)) + if statsVersion['Xref Streams'] is not None: + if id in statsVersion['Xref Streams'][1]: + stream.set('xref_stream', 'true') + else: + stream.set('xref_stream', 'false') + if statsVersion['Object Streams'] is not None: + if id in statsVersion['Object Streams'][1]: + stream.set('object_stream', 'true') + else: + stream.set('object_stream', 'false') + if statsVersion['Encoded'] is not None: + if id in statsVersion['Encoded'][1]: + stream.set('encoded', 'true') + if statsVersion['Decoding Errors'] is not None: + if id in statsVersion['Decoding Errors'][1]: + stream.set('decoding_errors', 'true') + else: + stream.set('decoding_errors', 'false') + else: + stream.set('encoded', 'false') + jsObjects = etree.SubElement(versionInfo, 'js_objects') + if statsVersion['Objects with JS code'] is not None: + for id in statsVersion['Objects with JS code'][1]: + etree.SubElement(jsObjects, 'container_object', id=str(id)) + actions = statsVersion['Actions'] + events = statsVersion['Events'] + vulns = statsVersion['Vulns'] + elements = statsVersion['Elements'] + suspicious = etree.SubElement(versionInfo, 'suspicious_elements') + if events != None or actions != None or vulns != None or elements != None: + if events: + triggers = etree.SubElement(suspicious, 'triggers') + for event in events: + trigger = etree.SubElement(triggers, 'trigger', name=event) + for id in events[event]: + etree.SubElement(trigger, 'container_object', id=str(id)) + if actions: + actionsList = etree.SubElement(suspicious, 'actions') + for action in actions: + actionInfo = etree.SubElement(actionsList, 'action', name=action) + for id in actions[action]: + etree.SubElement(actionInfo, 'container_object', id=str(id)) + if elements: + elementsList = etree.SubElement(suspicious, 'elements') + for element in elements: + elementInfo = etree.SubElement(elementsList, 'element', name=element) + if vulnsDict.has_key(element): + vulnName = vulnsDict[element][0] + vulnCVEList = vulnsDict[element][1] + for vulnCVE in vulnCVEList: + cve = etree.SubElement(elementInfo, 'cve') + cve.text = vulnCVE + for id in elements[element]: + etree.SubElement(elementInfo, 'container_object', id=str(id)) + if vulns: + vulnsList = etree.SubElement(suspicious, 'js_vulns') + for vuln in vulns: + vulnInfo = etree.SubElement(vulnsList, 'vulnerable_function', name=vuln) + if vulnsDict.has_key(vuln): + vulnName = vulnsDict[vuln][0] + vulnCVEList = vulnsDict[vuln][1] + for vulnCVE in vulnCVEList: + cve = etree.SubElement(vulnInfo, 'cve') + cve.text = vulnCVE + for id in vulns[vuln]: + etree.SubElement(vulnInfo, 'container_object', id=str(id)) + urls = statsVersion['URLs'] + suspiciousURLs = etree.SubElement(versionInfo, 'suspicious_urls') + if urls != None: + for url in urls: + urlInfo = etree.SubElement(suspiciousURLs, 'url') + urlInfo.text = url + return etree.tostring(root, pretty_print=True) + + +def getPeepJSON(statsDict, version, revision): + # peepdf info + peepdfDict = {'version': version, + 'revision': revision, + 'author': 'Jose Miguel Esparza', + 'url': 'http://peepdf.eternal-todo.com'} + # Basic info + basicDict = {} + basicDict['filename'] = statsDict['File'] + basicDict['md5'] = statsDict['MD5'] + basicDict['sha1'] = statsDict['SHA1'] + basicDict['sha256'] = statsDict['SHA256'] + basicDict['size'] = int(statsDict['Size']) + basicDict['detection'] = {} + if statsDict['Detection'] != [] and statsDict['Detection'] is not None: + basicDict['detection']['rate'] = '%d/%d' % (statsDict['Detection'][0], statsDict['Detection'][1]) + basicDict['detection']['report_link'] = statsDict['Detection report'] + basicDict['pdf_version'] = statsDict['Version'] + basicDict['binary'] = bool(statsDict['Binary']) + basicDict['linearized'] = bool(statsDict['Linearized']) + basicDict['encrypted'] = bool(statsDict['Encrypted']) + basicDict['encryption_algorithms'] = [] + if statsDict['Encryption Algorithms']: + for algorithmInfo in statsDict['Encryption Algorithms']: + basicDict['encryption_algorithms'].append({'bits': algorithmInfo[1], 'algorithm': algorithmInfo[0]}) + basicDict['updates'] = int(statsDict['Updates']) + basicDict['num_objects'] = int(statsDict['Objects']) + basicDict['num_streams'] = int(statsDict['Streams']) + basicDict['comments'] = int(statsDict['Comments']) + basicDict['errors'] = [] + for error in statsDict['Errors']: + basicDict['errors'].append(error) + # Advanced info + advancedInfo = [] + for version in range(len(statsDict['Versions'])): + statsVersion = statsDict['Versions'][version] + if version == 0: + versionType = 'original' + else: + versionType = 'update' + versionInfo = {} + versionInfo['version_number'] = version + versionInfo['version_type'] = versionType + versionInfo['catalog'] = statsVersion['Catalog'] + versionInfo['info'] = statsVersion['Info'] + if statsVersion['Objects'] is not None: + versionInfo['objects'] = statsVersion['Objects'][1] + else: + versionInfo['objects'] = [] + if statsVersion['Compressed Objects'] is not None: + versionInfo['compressed_objects'] = statsVersion['Compressed Objects'][1] + else: + versionInfo['compressed_objects'] = [] + if statsVersion['Errors'] is not None: + versionInfo['error_objects'] = statsVersion['Errors'][1] + else: + versionInfo['error_objects'] = [] + if statsVersion['Streams'] is not None: + versionInfo['streams'] = statsVersion['Streams'][1] + else: + versionInfo['streams'] = [] + if statsVersion['Xref Streams'] is not None: + versionInfo['xref_streams'] = statsVersion['Xref Streams'][1] + else: + versionInfo['xref_streams'] = [] + if statsVersion['Encoded'] is not None: + versionInfo['encoded_streams'] = statsVersion['Encoded'][1] + else: + versionInfo['encoded_streams'] = [] + if versionInfo['encoded_streams'] and statsVersion['Decoding Errors'] is not None: + versionInfo['decoding_error_streams'] = statsVersion['Decoding Errors'][1] + else: + versionInfo['decoding_error_streams'] = [] + if statsVersion['Objects with JS code'] is not None: + versionInfo['js_objects'] = statsVersion['Objects with JS code'][1] + else: + versionInfo['js_objects'] = [] + elements = statsVersion['Elements'] + elementArray = [] + if elements: + for element in elements: + elementInfo = {'name': element} + if element in vulnsDict: + elementInfo['vuln_name'] = vulnsDict[element][0] + elementInfo['vuln_cve_list'] = vulnsDict[element][1] + elementInfo['objects'] = elements[element] + elementArray.append(elementInfo) + vulns = statsVersion['Vulns'] + vulnArray = [] + if vulns: + for vuln in vulns: + vulnInfo = {'name': vuln} + if vuln in vulnsDict: + vulnInfo['vuln_name'] = vulnsDict[vuln][0] + vulnInfo['vuln_cve_list'] = vulnsDict[vuln][1] + vulnInfo['objects'] = vulns[vuln] + vulnArray.append(vulnInfo) + versionInfo['suspicious_elements'] = {'triggers': statsVersion['Events'], + 'actions': statsVersion['Actions'], + 'elements': elementArray, + 'js_vulns': vulnArray, + 'urls': statsVersion['URLs']} + versionReport = {'version_info': versionInfo} + advancedInfo.append(versionReport) + jsonDict = {'peepdf_analysis': + {'peepdf_info': peepdfDict, + 'date': datetime.today().strftime('%Y-%m-%d %H:%M'), + 'basic': basicDict, + 'advanced': advancedInfo} + } + return json.dumps(jsonDict, indent=4, sort_keys=True) + +class PDFOutput(object): + + def __init__(self, avoidOutputColors=False): + global COLORIZED_OUTPUT + self.warningColor = '' + self.errorColor = '' + self.alertColor = '' + self.staticColor = '' + self.resetColor = '' + if not COLORIZED_OUTPUT or avoidOutputColors: + self.avoidColor = True + else: + try: + init() + self.warningColor = Fore.YELLOW + self.errorColor = Fore.RED + self.alertColor = Fore.RED + self.staticColor = Fore.BLUE + self.resetColor = Style.RESET_ALL + self.avoidColor = False + except: + self.avoidColor = True + COLORIZED_OUTPUT = False + + self.newLine = os.linesep + + def dependenciesWarning(self, stats=""): + if not JS_MODULE: + warningMessage = 'Warning: PyV8 is not installed!!' + stats += self.warningColor + warningMessage + self.resetColor + self.newLine + if not EMU_MODULE: + warningMessage = 'Warning: pylibemu is not installed!!' + stats += self.warningColor + warningMessage + self.resetColor + self.newLine + if not PIL_MODULE: + warningMessage = 'Warning: Python Imaging Library (PIL) is not installed!!' + stats += self.warningColor + warningMessage + self.resetColor + self.newLine + if stats != '': + stats += self.newLine + return stats + + def decryptError(self, statsDict, stats=""): + errors = statsDict['Errors'] + for error in errors: + if error.find('Decryption error') != -1: + stats += self.errorColor + error + self.resetColor + self.newLine + if stats != '': + stats += self.newLine + return stats + + def getPeepReport(self, statsDict, stats=""): + + if not self.avoidColor: + beforeStaticLabel = self.staticColor + else: + beforeStaticLabel = '' + + stats += beforeStaticLabel + 'File: ' + self.resetColor + statsDict['File'] + self.newLine + stats += beforeStaticLabel + 'MD5: ' + self.resetColor + statsDict['MD5'] + self.newLine + stats += beforeStaticLabel + 'SHA1: ' + self.resetColor + statsDict['SHA1'] + self.newLine + stats += beforeStaticLabel + 'SHA256: ' + self.resetColor + statsDict['SHA256'] + self.newLine + stats += beforeStaticLabel + 'Size: ' + self.resetColor + statsDict['Size'] + ' bytes' + self.newLine + + if statsDict['Detection'] != []: + detectionReportInfo = '' + if statsDict['Detection'] is not None: + detectionColor = '' + if not self.avoidColor: + detectionLevel = statsDict['Detection'][0] / (statsDict['Detection'][1] / 3) + if detectionLevel == 0: + detectionColor = self.alertColor + elif detectionLevel == 1: + detectionColor = self.warningColor + detectionRate = '%s%d%s/%d' % ( + detectionColor, statsDict['Detection'][0], self.resetColor, statsDict['Detection'][1]) + if statsDict['Detection report'] != '': + detectionReportInfo = beforeStaticLabel + 'Detection report: ' + self.resetColor + \ + statsDict['Detection report'] + self.newLine + else: + detectionRate = 'File not found on VirusTotal' + stats += beforeStaticLabel + 'Detection: ' + self.resetColor + detectionRate + self.newLine + stats += detectionReportInfo + stats += beforeStaticLabel + 'Version: ' + self.resetColor + statsDict['Version'] + self.newLine + stats += beforeStaticLabel + 'Binary: ' + self.resetColor + statsDict['Binary'] + self.newLine + stats += beforeStaticLabel + 'Linearized: ' + self.resetColor + statsDict['Linearized'] + self.newLine + stats += beforeStaticLabel + 'Encrypted: ' + self.resetColor + statsDict['Encrypted'] + if statsDict['Encryption Algorithms'] != []: + stats += ' (' + for algorithmInfo in statsDict['Encryption Algorithms']: + stats += algorithmInfo[0] + ' ' + str(algorithmInfo[1]) + ' bits, ' + stats = stats[:-2] + ')' + stats += self.newLine + stats += beforeStaticLabel + 'Updates: ' + self.resetColor + statsDict['Updates'] + self.newLine + stats += beforeStaticLabel + 'Objects: ' + self.resetColor + statsDict['Objects'] + self.newLine + stats += beforeStaticLabel + 'Streams: ' + self.resetColor + statsDict['Streams'] + self.newLine + stats += beforeStaticLabel + 'URIs: ' + self.resetColor + statsDict['URIs'] + self.newLine + stats += beforeStaticLabel + 'Comments: ' + self.resetColor + statsDict['Comments'] + self.newLine + stats += beforeStaticLabel + 'Errors: ' + self.resetColor + str(len(statsDict['Errors'])) + self.newLine * 2 + for version in range(len(statsDict['Versions'])): + statsVersion = statsDict['Versions'][version] + stats += beforeStaticLabel + 'Version ' + self.resetColor + str(version) + ':' + self.newLine + if statsVersion['Catalog'] != None: + stats += beforeStaticLabel + '\tCatalog: ' + self.resetColor + statsVersion['Catalog'] + self.newLine + else: + stats += beforeStaticLabel + '\tCatalog: ' + self.resetColor + 'No' + self.newLine + if statsVersion['Info'] != None: + stats += beforeStaticLabel + '\tInfo: ' + self.resetColor + statsVersion['Info'] + self.newLine + else: + stats += beforeStaticLabel + '\tInfo: ' + self.resetColor + 'No' + self.newLine + stats += beforeStaticLabel + '\tObjects (' + statsVersion['Objects'][ + 0] + '): ' + self.resetColor + str(statsVersion['Objects'][1]) + self.newLine + if statsVersion['Compressed Objects'] != None: + stats += beforeStaticLabel + '\tCompressed objects (' + statsVersion['Compressed Objects'][ + 0] + '): ' + self.resetColor + str(statsVersion['Compressed Objects'][1]) + self.newLine + if statsVersion['Errors'] != None: + stats += beforeStaticLabel + '\t\tErrors (' + statsVersion['Errors'][ + 0] + '): ' + self.resetColor + str(statsVersion['Errors'][1]) + self.newLine + stats += beforeStaticLabel + '\tStreams (' + statsVersion['Streams'][ + 0] + '): ' + self.resetColor + str(statsVersion['Streams'][1]) + if statsVersion['Xref Streams'] != None: + stats += self.newLine + beforeStaticLabel + '\t\tXref streams (' + statsVersion['Xref Streams'][ + 0] + '): ' + self.resetColor + str(statsVersion['Xref Streams'][1]) + if statsVersion['Object Streams'] != None: + stats += self.newLine + beforeStaticLabel + '\t\tObject streams (' + \ + statsVersion['Object Streams'][0] + '): ' + self.resetColor + str( + statsVersion['Object Streams'][1]) + if int(statsVersion['Streams'][0]) > 0: + stats += self.newLine + beforeStaticLabel + '\t\tEncoded (' + statsVersion['Encoded'][ + 0] + '): ' + self.resetColor + str(statsVersion['Encoded'][1]) + if statsVersion['Decoding Errors'] != None: + stats += self.newLine + beforeStaticLabel + '\t\tDecoding errors (' + \ + statsVersion['Decoding Errors'][0] + '): ' + self.resetColor + str( + statsVersion['Decoding Errors'][1]) + if statsVersion['URIs'] is not None: + stats += self.newLine + beforeStaticLabel + '\tObjects with URIs (' + \ + statsVersion['URIs'][0] + '): ' + self.resetColor + str(statsVersion['URIs'][1]) + if not self.avoidColor: + beforeStaticLabel = self.warningColor + if statsVersion['Objects with JS code'] != None: + stats += self.newLine + beforeStaticLabel + '\tObjects with JS code (' + \ + statsVersion['Objects with JS code'][0] + '): ' + self.resetColor + str( + statsVersion['Objects with JS code'][1]) + actions = statsVersion['Actions'] + events = statsVersion['Events'] + vulns = statsVersion['Vulns'] + elements = statsVersion['Elements'] + if events != None or actions != None or vulns != None or elements != None: + stats += self.newLine + beforeStaticLabel + '\tSuspicious elements:' + self.resetColor + self.newLine + if events != None: + for event in events: + stats += '\t\t' + beforeStaticLabel + event + ' (%d): ' % len(events[event]) + \ + self.resetColor + str(events[event]) + self.newLine + if actions != None: + for action in actions: + stats += '\t\t' + beforeStaticLabel + action + ' (%d): ' % len(actions[action]) + \ + self.resetColor + str(actions[action]) + self.newLine + if vulns != None: + for vuln in vulns: + if vulnsDict.has_key(vuln): + vulnName = vulnsDict[vuln][0] + vulnCVEList = vulnsDict[vuln][1] + stats += '\t\t' + beforeStaticLabel + vulnName + ' (' + for vulnCVE in vulnCVEList: + stats += vulnCVE + ',' + stats = stats[:-1] + ') (%d): ' % len(vulns[vuln]) + self.resetColor + str(vulns[vuln]) + self.newLine + else: + stats += '\t\t' + beforeStaticLabel + vuln + ' (%d): ' % len(vulns[vuln]) + \ + self.resetColor + str(vulns[vuln]) + self.newLine + if elements != None: + for element in elements: + if vulnsDict.has_key(element): + vulnName = vulnsDict[element][0] + vulnCVEList = vulnsDict[element][1] + stats += '\t\t' + beforeStaticLabel + vulnName + ' (' + for vulnCVE in vulnCVEList: + stats += vulnCVE + ',' + stats = stats[:-1] + '): ' + self.resetColor + str(elements[element]) + self.newLine + else: + stats += '\t\t' + beforeStaticLabel + element + ': ' + self.resetColor + str( + elements[element]) + self.newLine + if not self.avoidColor: + beforeStaticLabel = self.staticColor + urls = statsVersion['URLs'] + if urls != None: + stats += self.newLine + beforeStaticLabel + '\tFound URLs:' + self.resetColor + self.newLine + for url in urls: + stats += '\t\t' + url + self.newLine + stats += self.newLine * 2 + return stats + +def main(options, args, errorsFile, pdfName=None): + try: + VT_KEY = 'fc90df3f5ac749a94a94cb8bf87e05a681a2eb001aef34b6a0084b8c22c97a64' + pdf = None + statsDict = None + vtJsonDict = None + pdfOutput = PDFOutput(options.avoidColors) + + if pdfName is not None: + pdfParser = PDFParser() + ret, pdf = pdfParser.parse(pdfName, options.isForceMode, options.isLooseMode, options.isManualAnalysis) + #ret is not check here, was it the case previous code ? + if options.checkOnVT: + # Checks the MD5 on VirusTotal + md5Hash = pdf.getMD5() + ret = vtcheck(md5Hash, VT_KEY) + if ret[0] == -1: + pdf.addError(ret[1]) + else: + vtJsonDict = ret[1] + if vtJsonDict.has_key('response_code'): + if vtJsonDict['response_code'] == 1: + if vtJsonDict.has_key('positives') and vtJsonDict.has_key('total'): + pdf.setDetectionRate([vtJsonDict['positives'], vtJsonDict['total']]) + else: + pdf.addError('Missing elements in the response from VirusTotal!!') + if vtJsonDict.has_key('permalink'): + pdf.setDetectionReport(vtJsonDict['permalink']) + else: + pdf.setDetectionRate(None) + else: + pdf.addError('Bad response from VirusTotal!!') + statsDict = pdf.getStats() + + if options.xmlOutput: + try: + from lxml import etree + + xml = getPeepXML(statsDict, version, revision) + sys.stdout.write(xml) + except: + errorMessage = '*** Error: Exception while generating the XML file!!' + print pdfOutput.errorColor + errorMessage + pdfOutput.resetColor + pdfOutput.newLine + traceback.print_exc(file=open(errorsFile, 'a')) + raise Exception('PeepException', 'Send me an email ;)') + elif options.jsonOutput and not options.commands: + try: + jsonReport = getPeepJSON(statsDict, version, revision) + sys.stdout.write(jsonReport) + except: + errorMessage = '*** Error: Exception while generating the JSON report!!' + print pdfOutput.errorColor + errorMessage + pdfOutput.resetColor + pdfOutput.newLine + traceback.print_exc(file=open(errorsFile, 'a')) + raise Exception('PeepException', 'Send me an email ;)') + elif options.isInteractive: + from peepdf.PDFConsole import PDFConsole + + console = PDFConsole(pdf, VT_KEY, options.avoidColors) + while not console.leaving: + try: + console.cmdloop() + except KeyboardInterrupt as e: + sys.exit() + except: + errorMessage = '*** Error: Exception not handled using the interactive console!! Please, report it to the author!!' + print pdfOutput.errorColor + errorMessage + pdfOutput.resetColor + pdfOutput.newLine + traceback.print_exc(file=open(errorsFile, 'a')) + else: + global COLORIZED_OUTPUT + if COLORIZED_OUTPUT and not options.avoidColors: + try: + init() + except: + COLORIZED_OUTPUT = False + if options.scriptFile is not None: + if not os.path.exists(options.scriptFile): + sys.exit('Error: The script file "' + options.scriptFile + '" does not exist!!') + from peepdf.PDFConsole import PDFConsole + + scriptFileObject = open(options.scriptFile, 'rb') + console = PDFConsole(pdf, VT_KEY, options.avoidColors, stdin=scriptFileObject) + try: + console.cmdloop() + except: + errorMessage = '*** Error: Exception not handled using the batch mode!!' + scriptFileObject.close() + traceback.print_exc(file=open(errorsFile, 'a')) + raise Exception('PeepException', 'Send me an email ;)') + elif options.commands is not None: + from PDFConsole import PDFConsole + + console = PDFConsole(pdf, VT_KEY, options.avoidColors) + try: + for command in options.commands: + console.onecmd(command) + except: + errorMessage = '*** Error: Exception not handled using the batch commands!!' + traceback.print_exc(file=open(errorsFile, 'a')) + raise Exception('PeepException', 'Send me an email ;)') + else: + if statsDict is not None: + stats = pdfOutput.decryptError(statsDict) + stats = pdfOutput.dependenciesWarning(stats=stats) + stats = pdfOutput.getPeepReport(statsDict, stats=stats) + print stats + except Exception as e: + if len(e.args) == 2: + excName, excReason = e.args + else: + excName = excReason = None + if excName == None or excName != 'PeepException': + errorMessage = '*** Error: Exception not handled!!' + traceback.print_exc(file=open(errorsFile, 'a')) + print pdfOutput.errorColor + errorMessage + pdfOutput.resetColor + pdfOutput.newLine + finally: + if os.path.exists(errorsFile): + message = pdfOutput.newLine + 'Please, don\'t forget to report the errors found:' + pdfOutput.newLine * 2 + message += '\t- Sending the file "%s" to the author (mailto:peepdfREMOVETHIS@eternal-todo.com)%s' % ( + errorsFile, pdfOutput.newLine) + message += '\t- And/Or creating an issue on the project webpage (https://github.com/jesparza/peepdf/issues)' + pdfOutput.newLine + message = pdfOutput.errorColor + message + pdfOutput.resetColor + sys.exit(message) From f29ff0ed176924d9b73c4afacf6793dabb43780b Mon Sep 17 00:00:00 2001 From: Thibault Marquand Date: Sat, 18 Jun 2016 14:57:28 +0200 Subject: [PATCH 05/14] Refactoring code from peepdf main script is dispatched around PDFCore, PDFOutput & PDFUtils The main script peepdf is now a wrapper to this moved code --- bin/peepdf | 203 ++++++++---- peepdf/PDFConsole.py | 55 +++- peepdf/PDFCore.py | 21 ++ peepdf/PDFOutput.py | 720 ++++++++++++++++++------------------------- peepdf/PDFUtils.py | 31 +- 5 files changed, 528 insertions(+), 502 deletions(-) diff --git a/bin/peepdf b/bin/peepdf index fa35742..6835148 100755 --- a/bin/peepdf +++ b/bin/peepdf @@ -30,9 +30,11 @@ import sys import os import optparse -from datetime import datetime +import traceback import peepdf.PDFOutput as PDFOutput +import peepdf.PDFUtils as PDFUtils +from peepdf.PDFCore import PDFParser author = 'Jose Miguel Esparza' @@ -44,7 +46,8 @@ version = '0.3' revision = '275' newLine = os.linesep absPeepdfRoot = os.path.dirname(os.path.realpath(sys.argv[0])) -errorsFile = os.path.join(absPeepdfRoot, 'errors.txt') + +VT_KEY = 'fc90df3f5ac749a94a94cb8bf87e05a681a2eb001aef34b6a0084b8c22c97a64' versionHeader = 'Version: peepdf ' + version + ' r' + revision peepdfHeader = versionHeader + newLine * 2 + \ @@ -83,67 +86,139 @@ if __name__ == '__main__': help='Specifies a command from the interactive console to be executed.') (options, args) = argsParser.parse_args() - if options.version: - print peepdfHeader - elif options.update: - updated = False - newVersion = '' - localVersion = 'v' + version + ' r' + revision - reVersion = 'version = \'(\d\.\d)\'\s*?revision = \'(\d+)\'' - repURL = 'https://api.github.com/repos/jesparza/peepdf/contents/' - rawRepURL = 'https://raw.githubusercontent.com/jesparza/peepdf/master/' - print '[-] Checking if there are new updates...' - try: - remotePeepContent = urllib2.urlopen(rawRepURL + 'peepdf.py').read() - except: - sys.exit('[x] Connection error while trying to connect with the repository') - repVer = re.findall(reVersion, remotePeepContent) - if repVer: - newVersion = 'v' + repVer[0][0] + ' r' + repVer[0][1] - else: - sys.exit('[x] Error getting the version number from the repository') - if localVersion == newVersion: - print '[+] No changes! ;)' - else: - print '[+] There are new updates!!' - print '[-] Getting paths from the repository...' - pathNames = PDFOutput.getRepPaths(repURL, '') - print '[+] Done' - localFilesInfo = PDFOutput.getLocalFilesInfo(pathNames) - print '[-] Checking files...' - for path in pathNames: - try: - fileContent = urllib2.urlopen(rawRepURL + path).read() - except: - sys.exit('[x] Connection error while getting file "' + path + '"') - if path in localFilesInfo: - # File exists - # Checking hash - shaHash = hashlib.sha256(fileContent).hexdigest() - if shaHash != localFilesInfo[path][0]: - open(localFilesInfo[path][1], 'wb').write(fileContent) - print '[+] File "' + path + '" updated successfully' - else: - # File does not exist - index = path.rfind('/') - if index != -1: - dirsPath = path[:index] - absDirsPath = os.path.join(absPeepdfRoot, dirsPath) - if not os.path.exists(absDirsPath): - print '[+] New directory "' + dirsPath + '" created successfully' - os.makedirs(absDirsPath) - open(os.path.join(absPeepdfRoot, path), 'wb').write(fileContent) - print '[+] New file "' + path + '" created successfully' - message = '[+] peepdf updated successfully' - if newVersion != '': - message += ' to ' + newVersion - print message + try: + pdfOutput = PDFOutput.PDFOutput(os.path.join(absPeepdfRoot, 'errors.txt'), options.avoidColors) + if options.scriptFile is not None and options.commands: + sys.stdout.write("scriptFile and commands options can't be used at the same time.\n") + sys.exit(argsParser.print_help()) + if options.version: + print peepdfHeader + elif options.update: + updated = False + newVersion = '' + localVersion = 'v' + version + ' r' + revision + reVersion = 'version = \'(\d\.\d)\'\s*?revision = \'(\d+)\'' + repURL = 'https://api.github.com/repos/jesparza/peepdf/contents/' + rawRepURL = 'https://raw.githubusercontent.com/jesparza/peepdf/master/' + print '[-] Checking if there are new updates...' + try: + remotePeepContent = urllib2.urlopen(rawRepURL + 'peepdf.py').read() + except: + sys.exit('[x] Connection error while trying to connect with the repository') + repVer = re.findall(reVersion, remotePeepContent) + if repVer: + newVersion = 'v' + repVer[0][0] + ' r' + repVer[0][1] + else: + sys.exit('[x] Error getting the version number from the repository') + if localVersion == newVersion: + print '[+] No changes! ;)' + else: + print '[+] There are new updates!!' + print '[-] Getting paths from the repository...' + pathNames = PDFUtils.getRepPaths(repURL, '') + print '[+] Done' + localFilesInfo = PDFUtils.getLocalFilesInfo(pathNames) + print '[-] Checking files...' + for path in pathNames: + try: + fileContent = urllib2.urlopen(rawRepURL + path).read() + except: + sys.exit('[x] Connection error while getting file "' + path + '"') + if path in localFilesInfo: + # File exists + # Checking hash + shaHash = hashlib.sha256(fileContent).hexdigest() + if shaHash != localFilesInfo[path][0]: + open(localFilesInfo[path][1], 'wb').write(fileContent) + print '[+] File "' + path + '" updated successfully' + else: + # File does not exist + index = path.rfind('/') + if index != -1: + dirsPath = path[:index] + absDirsPath = os.path.join(absPeepdfRoot, dirsPath) + if not os.path.exists(absDirsPath): + print '[+] New directory "' + dirsPath + '" created successfully' + os.makedirs(absDirsPath) + open(os.path.join(absPeepdfRoot, path), 'wb').write(fileContent) + print '[+] New file "' + path + '" created successfully' + message = '[+] peepdf updated successfully' + if newVersion != '': + message += ' to ' + newVersion + print message + + pdfName = None + if len(args) > 1 or (len(args) == 0 and not options.isInteractive): + sys.exit(argsParser.print_help()) + elif len(args) == 1: + pdfName = args[0] + if not os.path.exists(pdfName): + sys.exit('Error: The file "' + pdfName + '" does not exist!!') + pdf = None + statsDict = None + vtJsonDict = None - if len(args) == 1: - pdfName = args[0] - if not os.path.exists(pdfName): - sys.exit('Error: The file "' + pdfName + '" does not exist!!') - elif len(args) > 1 or (len(args) == 0 and not options.isInteractive): - sys.exit(argsParser.print_help()) + if pdfName is not None: + pdfParser = PDFParser() + ret, pdf = pdfParser.parse(pdfName, options.isForceMode, options.isLooseMode, options.isManualAnalysis) + #ret is not check here, was it the case previous code ? + if options.checkOnVT: + # Checks the MD5 on VirusTotal + pdf.getVtInfo(VT_KEY) + statsDict = pdf.getStats() - PDFOutput.main(options, args, errorsFile, pdfName=pdfName) + if options.xmlOutput: + xmlReport = pdfOutput.getXML(statsDict) + sys.stdout.write(xmlReport) + elif options.jsonOutput and not options.commands: + jsonReport = pdfOutput.getJSON(statsDict) + sys.stdout.write(jsonReport) + elif options.isInteractive: + from peepdf.PDFConsole import PDFConsole + + console = PDFConsole(pdf, VT_KEY, options.avoidColors) + console.runit() + elif options.scriptFile is not None: + if not os.path.exists(options.scriptFile): + sys.exit('Error: The script file "' + options.scriptFile + '" does not exist!!') + from peepdf.PDFConsole import PDFConsole + + console = PDFConsole(pdf, VT_KEY, options.avoidColors, scriptFile=options.scriptFile) + console.runit() + elif options.commands is not None: + from PDFConsole import PDFConsole + + commands = "\n".join(options.commands) + inputCommands = StringIO.StringIO(commands) + console = PDFConsole(pdf, VT_KEY, options.avoidColors, stdin=inputCommands) + console.runit() + elif statsDict is not None: + warnings = pdfOutput.getDecryptError(statsDict) + warnings += pdfOutput.getDependenciesWarning() + if warnings != "": + sys.stderr.write(warnings) + if options.jsonOutput: + format = "json" + elif options.xmlOutput: + format = "xml" + else: + format = "text" + stats = pdfOutput.getReport(statsDict, format) + sys.stdout.write(stats) + except Exception as e: + if len(e.args) == 2: + excName, excReason = e.args + else: + excName = excReason = None + if excName == None or excName != 'PeepException': + errorMessage = '*** Error: Exception not handled!!' + traceback.print_exc(file=open(pdfOutput.errorsFile, 'a')) + print pdfOutput.errorColor + errorMessage + pdfOutput.resetColor + pdfOutput.newLine + finally: + if os.path.exists(pdfOutput.errorsFile): + message = pdfOutput.newLine + 'Please, don\'t forget to report the errors found:' + pdfOutput.newLine * 2 + message += '\t- Sending the file "%s" to the author (mailto:peepdfREMOVETHIS@eternal-todo.com)%s' % ( + pdfOutput.errorsFile, pdfOutput.newLine) + message += '\t- And/Or creating an issue on the project webpage (https://github.com/jesparza/peepdf/issues)' + pdfOutput.newLine + message = pdfOutput.errorColor + message + pdfOutput.resetColor + sys.exit(message) diff --git a/peepdf/PDFConsole.py b/peepdf/PDFConsole.py index ccac905..dfde3ca 100644 --- a/peepdf/PDFConsole.py +++ b/peepdf/PDFConsole.py @@ -81,31 +81,41 @@ 'jbig2': '/JBIG2Decode', 'dct': '/DCTDecode', 'jpx': '/JPXDecode'} -class PDFConsole(cmd.Cmd, PDFOutput): +class PDFConsole(PDFOutput, cmd.Cmd): ''' Class of the peepdf interactive console. To see details about commands: http://code.google.com/p/peepdf/wiki/Commands ''' - def __init__(self, pdfFile, vtKey, avoidOutputColors=False, stdin=None, batchMode=False, jsonOutput=False): - global COLORIZED_OUTPUT - cmd.Cmd.__init__(self, stdin=stdin) - PDFOutput.__init__(self, avoidOutputColors) + def __init__(self, pdfFile, vtKey, avoidOutputColors=False, stdin=None, batchMode=False, jsonOutput=False, scriptFile=None): + """ + @batchMode: deprecated mode which purpose was to handle command on cli execution + @scriptFile: Script file path to execute. Overlap @stdin. + """ if COLORIZED_OUTPUT and not avoidOutputColors: try: self.promptColor = RL_PROMPT_START_IGNORE + Fore.GREEN + RL_PROMPT_END_IGNORE self.avoidColor = False except: self.avoidColor = True - COLORIZED_OUTPUT = False + PDFOutput.__init__(self, self.avoidColor) - if not self.avoidColor: - self.prompt = self.promptColor + 'PPDF> ' + RL_PROMPT_START_IGNORE + self.resetColor + RL_PROMPT_END_IGNORE - else: - self.prompt = 'PPDF> ' - self.use_rawinput = True - if stdin is not None: + if scriptFile is not None: + self.stdin = open(scriptFile, 'rb') + self.use_rawinput = False + self.prompt = '' + elif stdin is not None: + self.stdin = stdin self.use_rawinput = False self.prompt = '' + else: + self.stdin = stdin + self.use_rawinput = True + if not self.avoidColor: + self.prompt = self.promptColor + 'PPDF> ' + RL_PROMPT_START_IGNORE + self.resetColor + RL_PROMPT_END_IGNORE + else: + self.prompt = 'PPDF> ' + cmd.Cmd.__init__(self, stdin=self.stdin) + self.pdfFile = pdfFile self.variables = {'output_limit': [500, 500], 'malformed_options': [[], []], @@ -122,6 +132,27 @@ def __init__(self, pdfFile, vtKey, avoidOutputColors=False, stdin=None, batchMod self.outputVarName = None self.outputFileName = None + def runit(self): + while not self.leaving: + try: + self.cmdloop() + except KeyboardInterrupt as e: + sys.exit() + + except: + if self.stdin is not None: + errorMessage = '*** Error: Exception not handled using the batch mode!!' + traceback.print_exc(file=open(self.errorsFile, 'a')) + raise Exception('PeepException', 'Send me an email ;)') + else: + errorMessage = '*** Error: Exception not handled using the interactive console!! Please, report it to the author!!' + print pdfOutput.errorColor + errorMessage + pdfOutput.resetColor + pdfOutput.newLine + traceback.print_exc(file=open(self.errorsFile, 'a')) + finally: + if self.stdin is not None: + self.stdin.close() + + def emptyline(self): return diff --git a/peepdf/PDFCore.py b/peepdf/PDFCore.py index b9d8afb..9f46e9a 100644 --- a/peepdf/PDFCore.py +++ b/peepdf/PDFCore.py @@ -4771,6 +4771,27 @@ def __init__(self) : self.numDecodingErrors = 0 self.maxObjectId = 0 + def getVtInfo(self, vt_key): + ret = vtcheck(self.getMD5(), vt_key) + if ret[0] == -1: + self.addError(ret[1]) + else: + self.parseVtReport(ret[1]) + + def parseVtReport(self, vtJsonDict): + if vtJsonDict.has_key('response_code'): + if vtJsonDict['response_code'] == 1: + if vtJsonDict.has_key('positives') and vtJsonDict.has_key('total'): + self.setDetectionRate([vtJsonDict['positives'], vtJsonDict['total']]) + else: + self.addError('Missing elements in the response from VirusTotal!!') + if vtJsonDict.has_key('permalink'): + self.setDetectionReport(vtJsonDict['permalink']) + else: + self.setDetectionRate(None) + else: + self.addError('Bad response from VirusTotal!!') + def addBody(self, newBody): if newBody != None and isinstance(newBody,PDFBody): self.body.append(newBody) diff --git a/peepdf/PDFOutput.py b/peepdf/PDFOutput.py index 41b786b..341042c 100644 --- a/peepdf/PDFOutput.py +++ b/peepdf/PDFOutput.py @@ -30,6 +30,7 @@ import hashlib import traceback import json +from datetime import datetime from peepdf.PDFCore import PDFParser, vulnsDict from peepdf.PDFUtils import vtcheck @@ -56,298 +57,9 @@ PIL_MODULE = False -def getLocalFilesInfo(filesList): - localFilesInfo = {} - print '[-] Getting local files information...' - for path in filesList: - absFilePath = os.path.join(absPeepdfRoot, path) - if os.path.exists(absFilePath): - content = open(absFilePath, 'rb').read() - shaHash = hashlib.sha256(content).hexdigest() - localFilesInfo[path] = [shaHash, absFilePath] - print '[+] Done' - return localFilesInfo - -def getRepPaths(url, path=''): - paths = [] - try: - browsingPage = urllib2.urlopen(url + path).read() - except: - sys.exit('[x] Connection error while getting browsing page "' + url + path + '"') - browsingPageObject = json.loads(browsingPage) - for file in browsingPageObject: - if file['type'] == 'file': - paths.append(file['path']) - elif file['type'] == 'dir': - dirPaths = getRepPaths(url, file['path']) - paths += dirPaths - return paths - - -def getPeepXML(statsDict, version, revision): - root = etree.Element('peepdf_analysis', version=version + ' r' + revision, url='http://peepdf.eternal-todo.com', - author='Jose Miguel Esparza') - analysisDate = etree.SubElement(root, 'date') - analysisDate.text = datetime.today().strftime('%Y-%m-%d %H:%M') - basicInfo = etree.SubElement(root, 'basic') - fileName = etree.SubElement(basicInfo, 'filename') - fileName.text = statsDict['File'] - md5 = etree.SubElement(basicInfo, 'md5') - md5.text = statsDict['MD5'] - sha1 = etree.SubElement(basicInfo, 'sha1') - sha1.text = statsDict['SHA1'] - sha256 = etree.SubElement(basicInfo, 'sha256') - sha256.text = statsDict['SHA256'] - size = etree.SubElement(basicInfo, 'size') - size.text = statsDict['Size'] - detection = etree.SubElement(basicInfo, 'detection') - if statsDict['Detection']: - detectionRate = etree.SubElement(detection, 'rate') - detectionRate.text = '%d/%d' % (statsDict['Detection'][0], statsDict['Detection'][1]) - detectionReport = etree.SubElement(detection, 'report_link') - detectionReport.text = statsDict['Detection report'] - version = etree.SubElement(basicInfo, 'pdf_version') - version.text = statsDict['Version'] - binary = etree.SubElement(basicInfo, 'binary', status=statsDict['Binary'].lower()) - linearized = etree.SubElement(basicInfo, 'linearized', status=statsDict['Linearized'].lower()) - encrypted = etree.SubElement(basicInfo, 'encrypted', status=statsDict['Encrypted'].lower()) - if statsDict['Encryption Algorithms']: - algorithms = etree.SubElement(encrypted, 'algorithms') - for algorithmInfo in statsDict['Encryption Algorithms']: - algorithm = etree.SubElement(algorithms, 'algorithm', bits=str(algorithmInfo[1])) - algorithm.text = algorithmInfo[0] - updates = etree.SubElement(basicInfo, 'updates') - updates.text = statsDict['Updates'] - objects = etree.SubElement(basicInfo, 'num_objects') - objects.text = statsDict['Objects'] - streams = etree.SubElement(basicInfo, 'num_streams') - streams.text = statsDict['Streams'] - comments = etree.SubElement(basicInfo, 'comments') - comments.text = statsDict['Comments'] - errors = etree.SubElement(basicInfo, 'errors', num=str(len(statsDict['Errors']))) - for error in statsDict['Errors']: - errorMessageXML = etree.SubElement(errors, 'error_message') - errorMessageXML.text = error - advancedInfo = etree.SubElement(root, 'advanced') - for version in range(len(statsDict['Versions'])): - statsVersion = statsDict['Versions'][version] - if version == 0: - versionType = 'original' - else: - versionType = 'update' - versionInfo = etree.SubElement(advancedInfo, 'version', num=str(version), type=versionType) - catalog = etree.SubElement(versionInfo, 'catalog') - if statsVersion['Catalog'] is not None: - catalog.set('object_id', statsVersion['Catalog']) - info = etree.SubElement(versionInfo, 'info') - if statsVersion['Info'] is not None: - info.set('object_id', statsVersion['Info']) - objects = etree.SubElement(versionInfo, 'objects', num=statsVersion['Objects'][0]) - for id in statsVersion['Objects'][1]: - object = etree.SubElement(objects, 'object', id=str(id)) - if statsVersion['Compressed Objects'] is not None: - if id in statsVersion['Compressed Objects'][1]: - object.set('compressed', 'true') - else: - object.set('compressed', 'false') - if statsVersion['Errors'] is not None: - if id in statsVersion['Errors'][1]: - object.set('errors', 'true') - else: - object.set('errors', 'false') - streams = etree.SubElement(versionInfo, 'streams', num=statsVersion['Streams'][0]) - for id in statsVersion['Streams'][1]: - stream = etree.SubElement(streams, 'stream', id=str(id)) - if statsVersion['Xref Streams'] is not None: - if id in statsVersion['Xref Streams'][1]: - stream.set('xref_stream', 'true') - else: - stream.set('xref_stream', 'false') - if statsVersion['Object Streams'] is not None: - if id in statsVersion['Object Streams'][1]: - stream.set('object_stream', 'true') - else: - stream.set('object_stream', 'false') - if statsVersion['Encoded'] is not None: - if id in statsVersion['Encoded'][1]: - stream.set('encoded', 'true') - if statsVersion['Decoding Errors'] is not None: - if id in statsVersion['Decoding Errors'][1]: - stream.set('decoding_errors', 'true') - else: - stream.set('decoding_errors', 'false') - else: - stream.set('encoded', 'false') - jsObjects = etree.SubElement(versionInfo, 'js_objects') - if statsVersion['Objects with JS code'] is not None: - for id in statsVersion['Objects with JS code'][1]: - etree.SubElement(jsObjects, 'container_object', id=str(id)) - actions = statsVersion['Actions'] - events = statsVersion['Events'] - vulns = statsVersion['Vulns'] - elements = statsVersion['Elements'] - suspicious = etree.SubElement(versionInfo, 'suspicious_elements') - if events != None or actions != None or vulns != None or elements != None: - if events: - triggers = etree.SubElement(suspicious, 'triggers') - for event in events: - trigger = etree.SubElement(triggers, 'trigger', name=event) - for id in events[event]: - etree.SubElement(trigger, 'container_object', id=str(id)) - if actions: - actionsList = etree.SubElement(suspicious, 'actions') - for action in actions: - actionInfo = etree.SubElement(actionsList, 'action', name=action) - for id in actions[action]: - etree.SubElement(actionInfo, 'container_object', id=str(id)) - if elements: - elementsList = etree.SubElement(suspicious, 'elements') - for element in elements: - elementInfo = etree.SubElement(elementsList, 'element', name=element) - if vulnsDict.has_key(element): - vulnName = vulnsDict[element][0] - vulnCVEList = vulnsDict[element][1] - for vulnCVE in vulnCVEList: - cve = etree.SubElement(elementInfo, 'cve') - cve.text = vulnCVE - for id in elements[element]: - etree.SubElement(elementInfo, 'container_object', id=str(id)) - if vulns: - vulnsList = etree.SubElement(suspicious, 'js_vulns') - for vuln in vulns: - vulnInfo = etree.SubElement(vulnsList, 'vulnerable_function', name=vuln) - if vulnsDict.has_key(vuln): - vulnName = vulnsDict[vuln][0] - vulnCVEList = vulnsDict[vuln][1] - for vulnCVE in vulnCVEList: - cve = etree.SubElement(vulnInfo, 'cve') - cve.text = vulnCVE - for id in vulns[vuln]: - etree.SubElement(vulnInfo, 'container_object', id=str(id)) - urls = statsVersion['URLs'] - suspiciousURLs = etree.SubElement(versionInfo, 'suspicious_urls') - if urls != None: - for url in urls: - urlInfo = etree.SubElement(suspiciousURLs, 'url') - urlInfo.text = url - return etree.tostring(root, pretty_print=True) - - -def getPeepJSON(statsDict, version, revision): - # peepdf info - peepdfDict = {'version': version, - 'revision': revision, - 'author': 'Jose Miguel Esparza', - 'url': 'http://peepdf.eternal-todo.com'} - # Basic info - basicDict = {} - basicDict['filename'] = statsDict['File'] - basicDict['md5'] = statsDict['MD5'] - basicDict['sha1'] = statsDict['SHA1'] - basicDict['sha256'] = statsDict['SHA256'] - basicDict['size'] = int(statsDict['Size']) - basicDict['detection'] = {} - if statsDict['Detection'] != [] and statsDict['Detection'] is not None: - basicDict['detection']['rate'] = '%d/%d' % (statsDict['Detection'][0], statsDict['Detection'][1]) - basicDict['detection']['report_link'] = statsDict['Detection report'] - basicDict['pdf_version'] = statsDict['Version'] - basicDict['binary'] = bool(statsDict['Binary']) - basicDict['linearized'] = bool(statsDict['Linearized']) - basicDict['encrypted'] = bool(statsDict['Encrypted']) - basicDict['encryption_algorithms'] = [] - if statsDict['Encryption Algorithms']: - for algorithmInfo in statsDict['Encryption Algorithms']: - basicDict['encryption_algorithms'].append({'bits': algorithmInfo[1], 'algorithm': algorithmInfo[0]}) - basicDict['updates'] = int(statsDict['Updates']) - basicDict['num_objects'] = int(statsDict['Objects']) - basicDict['num_streams'] = int(statsDict['Streams']) - basicDict['comments'] = int(statsDict['Comments']) - basicDict['errors'] = [] - for error in statsDict['Errors']: - basicDict['errors'].append(error) - # Advanced info - advancedInfo = [] - for version in range(len(statsDict['Versions'])): - statsVersion = statsDict['Versions'][version] - if version == 0: - versionType = 'original' - else: - versionType = 'update' - versionInfo = {} - versionInfo['version_number'] = version - versionInfo['version_type'] = versionType - versionInfo['catalog'] = statsVersion['Catalog'] - versionInfo['info'] = statsVersion['Info'] - if statsVersion['Objects'] is not None: - versionInfo['objects'] = statsVersion['Objects'][1] - else: - versionInfo['objects'] = [] - if statsVersion['Compressed Objects'] is not None: - versionInfo['compressed_objects'] = statsVersion['Compressed Objects'][1] - else: - versionInfo['compressed_objects'] = [] - if statsVersion['Errors'] is not None: - versionInfo['error_objects'] = statsVersion['Errors'][1] - else: - versionInfo['error_objects'] = [] - if statsVersion['Streams'] is not None: - versionInfo['streams'] = statsVersion['Streams'][1] - else: - versionInfo['streams'] = [] - if statsVersion['Xref Streams'] is not None: - versionInfo['xref_streams'] = statsVersion['Xref Streams'][1] - else: - versionInfo['xref_streams'] = [] - if statsVersion['Encoded'] is not None: - versionInfo['encoded_streams'] = statsVersion['Encoded'][1] - else: - versionInfo['encoded_streams'] = [] - if versionInfo['encoded_streams'] and statsVersion['Decoding Errors'] is not None: - versionInfo['decoding_error_streams'] = statsVersion['Decoding Errors'][1] - else: - versionInfo['decoding_error_streams'] = [] - if statsVersion['Objects with JS code'] is not None: - versionInfo['js_objects'] = statsVersion['Objects with JS code'][1] - else: - versionInfo['js_objects'] = [] - elements = statsVersion['Elements'] - elementArray = [] - if elements: - for element in elements: - elementInfo = {'name': element} - if element in vulnsDict: - elementInfo['vuln_name'] = vulnsDict[element][0] - elementInfo['vuln_cve_list'] = vulnsDict[element][1] - elementInfo['objects'] = elements[element] - elementArray.append(elementInfo) - vulns = statsVersion['Vulns'] - vulnArray = [] - if vulns: - for vuln in vulns: - vulnInfo = {'name': vuln} - if vuln in vulnsDict: - vulnInfo['vuln_name'] = vulnsDict[vuln][0] - vulnInfo['vuln_cve_list'] = vulnsDict[vuln][1] - vulnInfo['objects'] = vulns[vuln] - vulnArray.append(vulnInfo) - versionInfo['suspicious_elements'] = {'triggers': statsVersion['Events'], - 'actions': statsVersion['Actions'], - 'elements': elementArray, - 'js_vulns': vulnArray, - 'urls': statsVersion['URLs']} - versionReport = {'version_info': versionInfo} - advancedInfo.append(versionReport) - jsonDict = {'peepdf_analysis': - {'peepdf_info': peepdfDict, - 'date': datetime.today().strftime('%Y-%m-%d %H:%M'), - 'basic': basicDict, - 'advanced': advancedInfo} - } - return json.dumps(jsonDict, indent=4, sort_keys=True) - class PDFOutput(object): - def __init__(self, avoidOutputColors=False): + def __init__(self, errorsFile, avoidOutputColors=False): global COLORIZED_OUTPUT self.warningColor = '' self.errorColor = '' @@ -370,29 +82,310 @@ def __init__(self, avoidOutputColors=False): COLORIZED_OUTPUT = False self.newLine = os.linesep + self.errorsFile = errorsFile - def dependenciesWarning(self, stats=""): + def getDependenciesWarning(self): + warnings = '' if not JS_MODULE: warningMessage = 'Warning: PyV8 is not installed!!' - stats += self.warningColor + warningMessage + self.resetColor + self.newLine + warnings += self.warningColor + warningMessage + self.resetColor + self.newLine if not EMU_MODULE: warningMessage = 'Warning: pylibemu is not installed!!' - stats += self.warningColor + warningMessage + self.resetColor + self.newLine + warnings += self.warningColor + warningMessage + self.resetColor + self.newLine if not PIL_MODULE: warningMessage = 'Warning: Python Imaging Library (PIL) is not installed!!' - stats += self.warningColor + warningMessage + self.resetColor + self.newLine - if stats != '': - stats += self.newLine - return stats + warnings += self.warningColor + warningMessage + self.resetColor + self.newLine + return warnings - def decryptError(self, statsDict, stats=""): + def getDecryptError(self, statsDict): errors = statsDict['Errors'] + warnings = '' for error in errors: if error.find('Decryption error') != -1: - stats += self.errorColor + error + self.resetColor + self.newLine - if stats != '': - stats += self.newLine - return stats + warnings += self.errorColor + error + self.resetColor + self.newLine + return warnings + + def getJSON(self, statsDict): + try: + return self.getPeepJSON(statsDict, version, revision) + except: + errorMessage = '*** Error: Exception while generating the JSON report!!' + print self.errorColor + errorMessage + self.resetColor + self.newLine + traceback.print_exc(file=open(self.errorsFile, 'a')) + raise Exception('PeepException', 'Send me an email ;)') + + def getPeepJSON(statsDict, version, revision): + # peepdf info + peepdfDict = {'version': version, + 'revision': revision, + 'author': 'Jose Miguel Esparza', + 'url': 'http://peepdf.eternal-todo.com'} + # Basic info + basicDict = {} + basicDict['filename'] = statsDict['File'] + basicDict['md5'] = statsDict['MD5'] + basicDict['sha1'] = statsDict['SHA1'] + basicDict['sha256'] = statsDict['SHA256'] + basicDict['size'] = int(statsDict['Size']) + basicDict['detection'] = {} + if statsDict['Detection'] != [] and statsDict['Detection'] is not None: + basicDict['detection']['rate'] = '%d/%d' % (statsDict['Detection'][0], statsDict['Detection'][1]) + basicDict['detection']['report_link'] = statsDict['Detection report'] + basicDict['pdf_version'] = statsDict['Version'] + basicDict['binary'] = bool(statsDict['Binary']) + basicDict['linearized'] = bool(statsDict['Linearized']) + basicDict['encrypted'] = bool(statsDict['Encrypted']) + basicDict['encryption_algorithms'] = [] + if statsDict['Encryption Algorithms']: + for algorithmInfo in statsDict['Encryption Algorithms']: + basicDict['encryption_algorithms'].append({'bits': algorithmInfo[1], 'algorithm': algorithmInfo[0]}) + basicDict['updates'] = int(statsDict['Updates']) + basicDict['num_objects'] = int(statsDict['Objects']) + basicDict['num_streams'] = int(statsDict['Streams']) + basicDict['comments'] = int(statsDict['Comments']) + basicDict['errors'] = [] + for error in statsDict['Errors']: + basicDict['errors'].append(error) + # Advanced info + advancedInfo = [] + for version in range(len(statsDict['Versions'])): + statsVersion = statsDict['Versions'][version] + if version == 0: + versionType = 'original' + else: + versionType = 'update' + versionInfo = {} + versionInfo['version_number'] = version + versionInfo['version_type'] = versionType + versionInfo['catalog'] = statsVersion['Catalog'] + versionInfo['info'] = statsVersion['Info'] + if statsVersion['Objects'] is not None: + versionInfo['objects'] = statsVersion['Objects'][1] + else: + versionInfo['objects'] = [] + if statsVersion['Compressed Objects'] is not None: + versionInfo['compressed_objects'] = statsVersion['Compressed Objects'][1] + else: + versionInfo['compressed_objects'] = [] + if statsVersion['Errors'] is not None: + versionInfo['error_objects'] = statsVersion['Errors'][1] + else: + versionInfo['error_objects'] = [] + if statsVersion['Streams'] is not None: + versionInfo['streams'] = statsVersion['Streams'][1] + else: + versionInfo['streams'] = [] + if statsVersion['Xref Streams'] is not None: + versionInfo['xref_streams'] = statsVersion['Xref Streams'][1] + else: + versionInfo['xref_streams'] = [] + if statsVersion['Encoded'] is not None: + versionInfo['encoded_streams'] = statsVersion['Encoded'][1] + else: + versionInfo['encoded_streams'] = [] + if versionInfo['encoded_streams'] and statsVersion['Decoding Errors'] is not None: + versionInfo['decoding_error_streams'] = statsVersion['Decoding Errors'][1] + else: + versionInfo['decoding_error_streams'] = [] + if statsVersion['Objects with JS code'] is not None: + versionInfo['js_objects'] = statsVersion['Objects with JS code'][1] + else: + versionInfo['js_objects'] = [] + elements = statsVersion['Elements'] + elementArray = [] + if elements: + for element in elements: + elementInfo = {'name': element} + if element in vulnsDict: + elementInfo['vuln_name'] = vulnsDict[element][0] + elementInfo['vuln_cve_list'] = vulnsDict[element][1] + elementInfo['objects'] = elements[element] + elementArray.append(elementInfo) + vulns = statsVersion['Vulns'] + vulnArray = [] + if vulns: + for vuln in vulns: + vulnInfo = {'name': vuln} + if vuln in vulnsDict: + vulnInfo['vuln_name'] = vulnsDict[vuln][0] + vulnInfo['vuln_cve_list'] = vulnsDict[vuln][1] + vulnInfo['objects'] = vulns[vuln] + vulnArray.append(vulnInfo) + versionInfo['suspicious_elements'] = {'triggers': statsVersion['Events'], + 'actions': statsVersion['Actions'], + 'elements': elementArray, + 'js_vulns': vulnArray, + 'urls': statsVersion['URLs']} + versionReport = {'version_info': versionInfo} + advancedInfo.append(versionReport) + jsonDict = {'peepdf_analysis': + {'peepdf_info': peepdfDict, + 'date': datetime.today().strftime('%Y-%m-%d %H:%M'), + 'basic': basicDict, + 'advanced': advancedInfo} + } + return json.dumps(jsonDict, indent=4, sort_keys=True) + + def getXML(self, statsDict): + try: + from lxml import etree + return self.getPeepXML(statsDict, __version__, revision) + except ImportError: + raise + except: + errorMessage = '*** Error: Exception while generating the XML file!!' + print self.errorColor + errorMessage + self.resetColor + self.newLine + traceback.print_exc(file=open(self.errorsFile, 'a')) + raise Exception('PeepException', 'Send me an email ;)') + + def getPeepXML(statsDict, version, revision): + root = etree.Element('peepdf_analysis', version=version + ' r' + revision, url='http://peepdf.eternal-todo.com', + author='Jose Miguel Esparza') + analysisDate = etree.SubElement(root, 'date') + analysisDate.text = datetime.today().strftime('%Y-%m-%d %H:%M') + basicInfo = etree.SubElement(root, 'basic') + fileName = etree.SubElement(basicInfo, 'filename') + fileName.text = statsDict['File'] + md5 = etree.SubElement(basicInfo, 'md5') + md5.text = statsDict['MD5'] + sha1 = etree.SubElement(basicInfo, 'sha1') + sha1.text = statsDict['SHA1'] + sha256 = etree.SubElement(basicInfo, 'sha256') + sha256.text = statsDict['SHA256'] + size = etree.SubElement(basicInfo, 'size') + size.text = statsDict['Size'] + detection = etree.SubElement(basicInfo, 'detection') + if statsDict['Detection']: + detectionRate = etree.SubElement(detection, 'rate') + detectionRate.text = '%d/%d' % (statsDict['Detection'][0], statsDict['Detection'][1]) + detectionReport = etree.SubElement(detection, 'report_link') + detectionReport.text = statsDict['Detection report'] + version = etree.SubElement(basicInfo, 'pdf_version') + version.text = statsDict['Version'] + binary = etree.SubElement(basicInfo, 'binary', status=statsDict['Binary'].lower()) + linearized = etree.SubElement(basicInfo, 'linearized', status=statsDict['Linearized'].lower()) + encrypted = etree.SubElement(basicInfo, 'encrypted', status=statsDict['Encrypted'].lower()) + if statsDict['Encryption Algorithms']: + algorithms = etree.SubElement(encrypted, 'algorithms') + for algorithmInfo in statsDict['Encryption Algorithms']: + algorithm = etree.SubElement(algorithms, 'algorithm', bits=str(algorithmInfo[1])) + algorithm.text = algorithmInfo[0] + updates = etree.SubElement(basicInfo, 'updates') + updates.text = statsDict['Updates'] + objects = etree.SubElement(basicInfo, 'num_objects') + objects.text = statsDict['Objects'] + streams = etree.SubElement(basicInfo, 'num_streams') + streams.text = statsDict['Streams'] + comments = etree.SubElement(basicInfo, 'comments') + comments.text = statsDict['Comments'] + errors = etree.SubElement(basicInfo, 'errors', num=str(len(statsDict['Errors']))) + for error in statsDict['Errors']: + errorMessageXML = etree.SubElement(errors, 'error_message') + errorMessageXML.text = error + advancedInfo = etree.SubElement(root, 'advanced') + for version in range(len(statsDict['Versions'])): + statsVersion = statsDict['Versions'][version] + if version == 0: + versionType = 'original' + else: + versionType = 'update' + versionInfo = etree.SubElement(advancedInfo, 'version', num=str(version), type=versionType) + catalog = etree.SubElement(versionInfo, 'catalog') + if statsVersion['Catalog'] is not None: + catalog.set('object_id', statsVersion['Catalog']) + info = etree.SubElement(versionInfo, 'info') + if statsVersion['Info'] is not None: + info.set('object_id', statsVersion['Info']) + objects = etree.SubElement(versionInfo, 'objects', num=statsVersion['Objects'][0]) + for id in statsVersion['Objects'][1]: + object = etree.SubElement(objects, 'object', id=str(id)) + if statsVersion['Compressed Objects'] is not None: + if id in statsVersion['Compressed Objects'][1]: + object.set('compressed', 'true') + else: + object.set('compressed', 'false') + if statsVersion['Errors'] is not None: + if id in statsVersion['Errors'][1]: + object.set('errors', 'true') + else: + object.set('errors', 'false') + streams = etree.SubElement(versionInfo, 'streams', num=statsVersion['Streams'][0]) + for id in statsVersion['Streams'][1]: + stream = etree.SubElement(streams, 'stream', id=str(id)) + if statsVersion['Xref Streams'] is not None: + if id in statsVersion['Xref Streams'][1]: + stream.set('xref_stream', 'true') + else: + stream.set('xref_stream', 'false') + if statsVersion['Object Streams'] is not None: + if id in statsVersion['Object Streams'][1]: + stream.set('object_stream', 'true') + else: + stream.set('object_stream', 'false') + if statsVersion['Encoded'] is not None: + if id in statsVersion['Encoded'][1]: + stream.set('encoded', 'true') + if statsVersion['Decoding Errors'] is not None: + if id in statsVersion['Decoding Errors'][1]: + stream.set('decoding_errors', 'true') + else: + stream.set('decoding_errors', 'false') + else: + stream.set('encoded', 'false') + jsObjects = etree.SubElement(versionInfo, 'js_objects') + if statsVersion['Objects with JS code'] is not None: + for id in statsVersion['Objects with JS code'][1]: + etree.SubElement(jsObjects, 'container_object', id=str(id)) + actions = statsVersion['Actions'] + events = statsVersion['Events'] + vulns = statsVersion['Vulns'] + elements = statsVersion['Elements'] + suspicious = etree.SubElement(versionInfo, 'suspicious_elements') + if events != None or actions != None or vulns != None or elements != None: + if events: + triggers = etree.SubElement(suspicious, 'triggers') + for event in events: + trigger = etree.SubElement(triggers, 'trigger', name=event) + for id in events[event]: + etree.SubElement(trigger, 'container_object', id=str(id)) + if actions: + actionsList = etree.SubElement(suspicious, 'actions') + for action in actions: + actionInfo = etree.SubElement(actionsList, 'action', name=action) + for id in actions[action]: + etree.SubElement(actionInfo, 'container_object', id=str(id)) + if elements: + elementsList = etree.SubElement(suspicious, 'elements') + for element in elements: + elementInfo = etree.SubElement(elementsList, 'element', name=element) + if vulnsDict.has_key(element): + vulnName = vulnsDict[element][0] + vulnCVEList = vulnsDict[element][1] + for vulnCVE in vulnCVEList: + cve = etree.SubElement(elementInfo, 'cve') + cve.text = vulnCVE + for id in elements[element]: + etree.SubElement(elementInfo, 'container_object', id=str(id)) + if vulns: + vulnsList = etree.SubElement(suspicious, 'js_vulns') + for vuln in vulns: + vulnInfo = etree.SubElement(vulnsList, 'vulnerable_function', name=vuln) + if vulnsDict.has_key(vuln): + vulnName = vulnsDict[vuln][0] + vulnCVEList = vulnsDict[vuln][1] + for vulnCVE in vulnCVEList: + cve = etree.SubElement(vulnInfo, 'cve') + cve.text = vulnCVE + for id in vulns[vuln]: + etree.SubElement(vulnInfo, 'container_object', id=str(id)) + urls = statsVersion['URLs'] + suspiciousURLs = etree.SubElement(versionInfo, 'suspicious_urls') + if urls != None: + for url in urls: + urlInfo = etree.SubElement(suspiciousURLs, 'url') + urlInfo.text = url + return etree.tostring(root, pretty_print=True) + def getPeepReport(self, statsDict, stats=""): @@ -533,126 +526,3 @@ def getPeepReport(self, statsDict, stats=""): stats += '\t\t' + url + self.newLine stats += self.newLine * 2 return stats - -def main(options, args, errorsFile, pdfName=None): - try: - VT_KEY = 'fc90df3f5ac749a94a94cb8bf87e05a681a2eb001aef34b6a0084b8c22c97a64' - pdf = None - statsDict = None - vtJsonDict = None - pdfOutput = PDFOutput(options.avoidColors) - - if pdfName is not None: - pdfParser = PDFParser() - ret, pdf = pdfParser.parse(pdfName, options.isForceMode, options.isLooseMode, options.isManualAnalysis) - #ret is not check here, was it the case previous code ? - if options.checkOnVT: - # Checks the MD5 on VirusTotal - md5Hash = pdf.getMD5() - ret = vtcheck(md5Hash, VT_KEY) - if ret[0] == -1: - pdf.addError(ret[1]) - else: - vtJsonDict = ret[1] - if vtJsonDict.has_key('response_code'): - if vtJsonDict['response_code'] == 1: - if vtJsonDict.has_key('positives') and vtJsonDict.has_key('total'): - pdf.setDetectionRate([vtJsonDict['positives'], vtJsonDict['total']]) - else: - pdf.addError('Missing elements in the response from VirusTotal!!') - if vtJsonDict.has_key('permalink'): - pdf.setDetectionReport(vtJsonDict['permalink']) - else: - pdf.setDetectionRate(None) - else: - pdf.addError('Bad response from VirusTotal!!') - statsDict = pdf.getStats() - - if options.xmlOutput: - try: - from lxml import etree - - xml = getPeepXML(statsDict, version, revision) - sys.stdout.write(xml) - except: - errorMessage = '*** Error: Exception while generating the XML file!!' - print pdfOutput.errorColor + errorMessage + pdfOutput.resetColor + pdfOutput.newLine - traceback.print_exc(file=open(errorsFile, 'a')) - raise Exception('PeepException', 'Send me an email ;)') - elif options.jsonOutput and not options.commands: - try: - jsonReport = getPeepJSON(statsDict, version, revision) - sys.stdout.write(jsonReport) - except: - errorMessage = '*** Error: Exception while generating the JSON report!!' - print pdfOutput.errorColor + errorMessage + pdfOutput.resetColor + pdfOutput.newLine - traceback.print_exc(file=open(errorsFile, 'a')) - raise Exception('PeepException', 'Send me an email ;)') - elif options.isInteractive: - from peepdf.PDFConsole import PDFConsole - - console = PDFConsole(pdf, VT_KEY, options.avoidColors) - while not console.leaving: - try: - console.cmdloop() - except KeyboardInterrupt as e: - sys.exit() - except: - errorMessage = '*** Error: Exception not handled using the interactive console!! Please, report it to the author!!' - print pdfOutput.errorColor + errorMessage + pdfOutput.resetColor + pdfOutput.newLine - traceback.print_exc(file=open(errorsFile, 'a')) - else: - global COLORIZED_OUTPUT - if COLORIZED_OUTPUT and not options.avoidColors: - try: - init() - except: - COLORIZED_OUTPUT = False - if options.scriptFile is not None: - if not os.path.exists(options.scriptFile): - sys.exit('Error: The script file "' + options.scriptFile + '" does not exist!!') - from peepdf.PDFConsole import PDFConsole - - scriptFileObject = open(options.scriptFile, 'rb') - console = PDFConsole(pdf, VT_KEY, options.avoidColors, stdin=scriptFileObject) - try: - console.cmdloop() - except: - errorMessage = '*** Error: Exception not handled using the batch mode!!' - scriptFileObject.close() - traceback.print_exc(file=open(errorsFile, 'a')) - raise Exception('PeepException', 'Send me an email ;)') - elif options.commands is not None: - from PDFConsole import PDFConsole - - console = PDFConsole(pdf, VT_KEY, options.avoidColors) - try: - for command in options.commands: - console.onecmd(command) - except: - errorMessage = '*** Error: Exception not handled using the batch commands!!' - traceback.print_exc(file=open(errorsFile, 'a')) - raise Exception('PeepException', 'Send me an email ;)') - else: - if statsDict is not None: - stats = pdfOutput.decryptError(statsDict) - stats = pdfOutput.dependenciesWarning(stats=stats) - stats = pdfOutput.getPeepReport(statsDict, stats=stats) - print stats - except Exception as e: - if len(e.args) == 2: - excName, excReason = e.args - else: - excName = excReason = None - if excName == None or excName != 'PeepException': - errorMessage = '*** Error: Exception not handled!!' - traceback.print_exc(file=open(errorsFile, 'a')) - print pdfOutput.errorColor + errorMessage + pdfOutput.resetColor + pdfOutput.newLine - finally: - if os.path.exists(errorsFile): - message = pdfOutput.newLine + 'Please, don\'t forget to report the errors found:' + pdfOutput.newLine * 2 - message += '\t- Sending the file "%s" to the author (mailto:peepdfREMOVETHIS@eternal-todo.com)%s' % ( - errorsFile, pdfOutput.newLine) - message += '\t- And/Or creating an issue on the project webpage (https://github.com/jesparza/peepdf/issues)' + pdfOutput.newLine - message = pdfOutput.errorColor + message + pdfOutput.resetColor - sys.exit(message) diff --git a/peepdf/PDFUtils.py b/peepdf/PDFUtils.py index 6423831..ba34c38 100644 --- a/peepdf/PDFUtils.py +++ b/peepdf/PDFUtils.py @@ -436,4 +436,33 @@ def vtcheck(md5, vtKey): jsonDict = json.loads(jsonResponse) except: return (-1, 'An error has occurred while parsing the JSON response from VirusTotal') - return (0, jsonDict) \ No newline at end of file + return (0, jsonDict) + + +def getLocalFilesInfo(filesList): + localFilesInfo = {} + print '[-] Getting local files information...' + for path in filesList: + absFilePath = os.path.join(absPeepdfRoot, path) + if os.path.exists(absFilePath): + content = open(absFilePath, 'rb').read() + shaHash = hashlib.sha256(content).hexdigest() + localFilesInfo[path] = [shaHash, absFilePath] + print '[+] Done' + return localFilesInfo + +def getRepPaths(url, path=''): + paths = [] + try: + browsingPage = urllib2.urlopen(url + path).read() + except: + sys.exit('[x] Connection error while getting browsing page "' + url + path + '"') + browsingPageObject = json.loads(browsingPage) + for file in browsingPageObject: + if file['type'] == 'file': + paths.append(file['path']) + elif file['type'] == 'dir': + dirPaths = getRepPaths(url, file['path']) + paths += dirPaths + return paths + From 7d838864e7902c9f49f59a475a8ee52a7aba695f Mon Sep 17 00:00:00 2001 From: Thibault Marquand Date: Sat, 18 Jun 2016 15:09:39 +0200 Subject: [PATCH 06/14] introduce intro attr to PDFConsole intro will show the report of the provided file This was normal behaviour before refactoring --- peepdf/PDFConsole.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/peepdf/PDFConsole.py b/peepdf/PDFConsole.py index dfde3ca..59ee0eb 100644 --- a/peepdf/PDFConsole.py +++ b/peepdf/PDFConsole.py @@ -117,6 +117,8 @@ def __init__(self, pdfFile, vtKey, avoidOutputColors=False, stdin=None, batchMod cmd.Cmd.__init__(self, stdin=self.stdin) self.pdfFile = pdfFile + if pdfFile is not None: + self.intro = self.getPeepReport(pdfFile.getStats()) self.variables = {'output_limit': [500, 500], 'malformed_options': [[], []], 'header_file': [None, None], From efb39d66f7351a5abadb55a2c8320b3dfe69baa7 Mon Sep 17 00:00:00 2001 From: Thibault Marquand Date: Sat, 18 Jun 2016 19:42:05 +0200 Subject: [PATCH 07/14] introduce constants.py constants file contains all constant variable like project info, author... --- bin/peepdf | 32 +++++++++++++------------------- peepdf/PDFOutput.py | 32 +++++++++++++++++++++----------- peepdf/constants.py | 23 +++++++++++++++++++++++ setup.py | 10 ++++++---- 4 files changed, 63 insertions(+), 34 deletions(-) create mode 100644 peepdf/constants.py diff --git a/bin/peepdf b/bin/peepdf index 6835148..10dc8c3 100755 --- a/bin/peepdf +++ b/bin/peepdf @@ -36,26 +36,20 @@ import peepdf.PDFOutput as PDFOutput import peepdf.PDFUtils as PDFUtils from peepdf.PDFCore import PDFParser +from peepdf.constants import PEEPDF_VERSION, PEEPDF_REVISION, AUTHOR, AUTHOR_EMAIL, \ + AUTHOR_TWITTER, PEEPDF_URL, GITHUB_URL, TWITTER_URL, PEEPDF_ROOT -author = 'Jose Miguel Esparza' -email = 'peepdf AT eternal-todo.com' -url = 'http://peepdf.eternal-todo.com' -twitter = 'http://twitter.com/EternalTodo' -peepTwitter = 'http://twitter.com/peepdf' -version = '0.3' -revision = '275' newLine = os.linesep -absPeepdfRoot = os.path.dirname(os.path.realpath(sys.argv[0])) VT_KEY = 'fc90df3f5ac749a94a94cb8bf87e05a681a2eb001aef34b6a0084b8c22c97a64' -versionHeader = 'Version: peepdf ' + version + ' r' + revision -peepdfHeader = versionHeader + newLine * 2 + \ - url + newLine + \ - peepTwitter + newLine + \ - email + newLine * 2 + \ - author + newLine + \ - twitter + newLine +versionHeader = 'Version: peepdf ' + PEEPDF_VERSION + ' r' + PEEPDF_REVISION +PeepdfHeader = versionHeader + newLine * 2 + \ + PEEPDF_URL + newLine + \ + TWITTER_URL + newLine + \ + AUTHOR_EMAIL + newLine * 2 + \ + AUTHOR + newLine + \ + AUTHOR_TWITTER + newLine if __name__ == '__main__': @@ -87,7 +81,7 @@ if __name__ == '__main__': (options, args) = argsParser.parse_args() try: - pdfOutput = PDFOutput.PDFOutput(os.path.join(absPeepdfRoot, 'errors.txt'), options.avoidColors) + pdfOutput = PDFOutput.PDFOutput(os.path.join(PEEPDF_ROOT, 'errors.txt'), options.avoidColors) if options.scriptFile is not None and options.commands: sys.stdout.write("scriptFile and commands options can't be used at the same time.\n") sys.exit(argsParser.print_help()) @@ -96,7 +90,7 @@ if __name__ == '__main__': elif options.update: updated = False newVersion = '' - localVersion = 'v' + version + ' r' + revision + localVersion = 'v' + PEEPDF_VERSION + ' r' + PEEPDF_REVISION reVersion = 'version = \'(\d\.\d)\'\s*?revision = \'(\d+)\'' repURL = 'https://api.github.com/repos/jesparza/peepdf/contents/' rawRepURL = 'https://raw.githubusercontent.com/jesparza/peepdf/master/' @@ -217,8 +211,8 @@ if __name__ == '__main__': finally: if os.path.exists(pdfOutput.errorsFile): message = pdfOutput.newLine + 'Please, don\'t forget to report the errors found:' + pdfOutput.newLine * 2 - message += '\t- Sending the file "%s" to the author (mailto:peepdfREMOVETHIS@eternal-todo.com)%s' % ( - pdfOutput.errorsFile, pdfOutput.newLine) + message += '\t- Sending the file "%s" to the author (mailto:%s)%s' % ( + pdfOutput.errorsFile, AUTHOR_EMAIL, pdfOutput.newLine) message += '\t- And/Or creating an issue on the project webpage (https://github.com/jesparza/peepdf/issues)' + pdfOutput.newLine message = pdfOutput.errorColor + message + pdfOutput.resetColor sys.exit(message) diff --git a/peepdf/PDFOutput.py b/peepdf/PDFOutput.py index 341042c..970a93a 100644 --- a/peepdf/PDFOutput.py +++ b/peepdf/PDFOutput.py @@ -34,6 +34,9 @@ from peepdf.PDFCore import PDFParser, vulnsDict from peepdf.PDFUtils import vtcheck +from peepdf.constants import AUTHOR, AUTHOR_EMAIL, PEEPDF_VERSION, PEEPDF_REVISION, \ + PEEPDF_URL, PEEPDF_ROOT + try: import PyV8 JS_MODULE = True @@ -56,6 +59,12 @@ except: PIL_MODULE = False +try: + from lxml import etree + LXML_MODULE = True +except: + LXML_MODULE = False + class PDFOutput(object): @@ -107,19 +116,19 @@ def getDecryptError(self, statsDict): def getJSON(self, statsDict): try: - return self.getPeepJSON(statsDict, version, revision) + return self.getPeepJSON(statsDict) except: errorMessage = '*** Error: Exception while generating the JSON report!!' print self.errorColor + errorMessage + self.resetColor + self.newLine traceback.print_exc(file=open(self.errorsFile, 'a')) raise Exception('PeepException', 'Send me an email ;)') - def getPeepJSON(statsDict, version, revision): + def getPeepJSON(self, statsDict): # peepdf info - peepdfDict = {'version': version, - 'revision': revision, - 'author': 'Jose Miguel Esparza', - 'url': 'http://peepdf.eternal-todo.com'} + peepdfDict = {'version': PEEPDF_VERSION, + 'revision': PEEPDF_REVISION, + 'author': AUTHOR, + 'url': PEEPDF_URL} # Basic info basicDict = {} basicDict['filename'] = statsDict['File'] @@ -228,8 +237,7 @@ def getPeepJSON(statsDict, version, revision): def getXML(self, statsDict): try: - from lxml import etree - return self.getPeepXML(statsDict, __version__, revision) + return self.getPeepXML(statsDict) except ImportError: raise except: @@ -238,9 +246,11 @@ def getXML(self, statsDict): traceback.print_exc(file=open(self.errorsFile, 'a')) raise Exception('PeepException', 'Send me an email ;)') - def getPeepXML(statsDict, version, revision): - root = etree.Element('peepdf_analysis', version=version + ' r' + revision, url='http://peepdf.eternal-todo.com', - author='Jose Miguel Esparza') + def getPeepXML(self, statsDict): + root = etree.Element('peepdf_analysis', + version=PEEPDF_VERSION + ' r' + PEEPDF_REVISION, + url=PEEPDF_URL, + author=AUTHOR) analysisDate = etree.SubElement(root, 'date') analysisDate.text = datetime.today().strftime('%Y-%m-%d %H:%M') basicInfo = etree.SubElement(root, 'basic') diff --git a/peepdf/constants.py b/peepdf/constants.py new file mode 100644 index 0000000..e3c9155 --- /dev/null +++ b/peepdf/constants.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Here is defined info about the project, its author +and some constants to customize + +""" + +import os +import sys + +PEEPDF_VERSION = '0.3' +PEEPDF_REVISION = '275' +AUTHOR = 'Jose Miguel Esparza' +AUTHOR_EMAIL = 'peepdf AT eternal-todo(dot)com' +AUTHOR_TWITTER = 'http://twitter.com/EternalTodo' +LICENCE = "GNU GPLv3" +PEEPDF_URL = 'http://peepdf.eternal-todo.com' +GITHUB_URL = 'https://github.com/jesparza/peepdf' +TWITTER_URL = 'http://twitter.com/peepdf' + +PEEPDF_ROOT = os.path.dirname(os.path.realpath(sys.argv[0])) diff --git a/setup.py b/setup.py index e512dbc..5fc9774 100644 --- a/setup.py +++ b/setup.py @@ -1,12 +1,14 @@ from setuptools import find_packages, setup +execfile("peepdf/constants.py") + setup( name = "peepdf", - version = "0.3-r235", - author = "Jose Miguel Esparza", - license = "GNU GPLv3", + version = PEEPDF_VERSION + ".post" + PEEPDF_REVISION, + author = AUTHOR, + license = LICENCE, scripts = [ "bin/peepdf" ], - url = "http://eternal-todo.com", + url = PEEPDF_URL, install_requires = [ "jsbeautifier==1.6.2", "colorama", "pythonaes==1.0" ], dependency_links = ["git+https://github.com/serprex/pythonaes.git@setup#egg=pythonaes-1.0"], packages = find_packages(), From 4ef9978d9e48d36807a1acf0ded5116a920a1a3f Mon Sep 17 00:00:00 2001 From: Thibault Marquand Date: Sun, 19 Jun 2016 13:33:06 +0200 Subject: [PATCH 08/14] PDFConsole: no intro if stdin is not None --- peepdf/PDFConsole.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/peepdf/PDFConsole.py b/peepdf/PDFConsole.py index 59ee0eb..1d919a5 100644 --- a/peepdf/PDFConsole.py +++ b/peepdf/PDFConsole.py @@ -98,6 +98,8 @@ def __init__(self, pdfFile, vtKey, avoidOutputColors=False, stdin=None, batchMod except: self.avoidColor = True PDFOutput.__init__(self, self.avoidColor) + if pdfFile is not None and stdin is None: + self.intro = self.getPeepReport(pdfFile.getStats()) if scriptFile is not None: self.stdin = open(scriptFile, 'rb') @@ -114,11 +116,10 @@ def __init__(self, pdfFile, vtKey, avoidOutputColors=False, stdin=None, batchMod self.prompt = self.promptColor + 'PPDF> ' + RL_PROMPT_START_IGNORE + self.resetColor + RL_PROMPT_END_IGNORE else: self.prompt = 'PPDF> ' + # if self.stdin is None, Cmd.__init__ set it to sys.stdin cmd.Cmd.__init__(self, stdin=self.stdin) self.pdfFile = pdfFile - if pdfFile is not None: - self.intro = self.getPeepReport(pdfFile.getStats()) self.variables = {'output_limit': [500, 500], 'malformed_options': [[], []], 'header_file': [None, None], From 1a054777d4e34db0caa017d579d13064a22ac890 Mon Sep 17 00:00:00 2001 From: Thibault Marquand Date: Sun, 19 Jun 2016 13:38:23 +0200 Subject: [PATCH 09/14] bin/peepdf: move scriptFile existence check before pdf parsing --- bin/peepdf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/peepdf b/bin/peepdf index 10dc8c3..0c1bb9e 100755 --- a/bin/peepdf +++ b/bin/peepdf @@ -148,6 +148,8 @@ if __name__ == '__main__': pdfName = args[0] if not os.path.exists(pdfName): sys.exit('Error: The file "' + pdfName + '" does not exist!!') + if options.scriptFile and not os.path.exists(options.scriptFile): + sys.exit('Error: The script file "' + options.scriptFile + '" does not exist!!') pdf = None statsDict = None vtJsonDict = None @@ -173,8 +175,6 @@ if __name__ == '__main__': console = PDFConsole(pdf, VT_KEY, options.avoidColors) console.runit() elif options.scriptFile is not None: - if not os.path.exists(options.scriptFile): - sys.exit('Error: The script file "' + options.scriptFile + '" does not exist!!') from peepdf.PDFConsole import PDFConsole console = PDFConsole(pdf, VT_KEY, options.avoidColors, scriptFile=options.scriptFile) From bb8152f2d1a5da72f57b23c2c89c3afd0e83760d Mon Sep 17 00:00:00 2001 From: Thibault Marquand Date: Sun, 19 Jun 2016 13:42:48 +0200 Subject: [PATCH 10/14] fix missing import & refactor import --- bin/peepdf | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/bin/peepdf b/bin/peepdf index 0c1bb9e..583cc8b 100755 --- a/bin/peepdf +++ b/bin/peepdf @@ -31,10 +31,12 @@ import sys import os import optparse import traceback +import StringIO import peepdf.PDFOutput as PDFOutput import peepdf.PDFUtils as PDFUtils from peepdf.PDFCore import PDFParser +from peepdf.PDFConsole import PDFConsole from peepdf.constants import PEEPDF_VERSION, PEEPDF_REVISION, AUTHOR, AUTHOR_EMAIL, \ AUTHOR_TWITTER, PEEPDF_URL, GITHUB_URL, TWITTER_URL, PEEPDF_ROOT @@ -170,18 +172,12 @@ if __name__ == '__main__': jsonReport = pdfOutput.getJSON(statsDict) sys.stdout.write(jsonReport) elif options.isInteractive: - from peepdf.PDFConsole import PDFConsole - console = PDFConsole(pdf, VT_KEY, options.avoidColors) console.runit() elif options.scriptFile is not None: - from peepdf.PDFConsole import PDFConsole - console = PDFConsole(pdf, VT_KEY, options.avoidColors, scriptFile=options.scriptFile) console.runit() elif options.commands is not None: - from PDFConsole import PDFConsole - commands = "\n".join(options.commands) inputCommands = StringIO.StringIO(commands) console = PDFConsole(pdf, VT_KEY, options.avoidColors, stdin=inputCommands) From fbdd8a145e80f36041f1b939d4a3473e8cbdd2da Mon Sep 17 00:00:00 2001 From: Thibault Marquand Date: Sun, 19 Jun 2016 14:03:28 +0200 Subject: [PATCH 11/14] PDFOutput: introduce method getReport() getReport is a wrapper to getPeepReport, getPeepJSON & getPeepXML --- bin/peepdf | 9 +-------- peepdf/PDFOutput.py | 20 ++++++++++++++++++-- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/bin/peepdf b/bin/peepdf index 583cc8b..1853f0b 100755 --- a/bin/peepdf +++ b/bin/peepdf @@ -154,7 +154,6 @@ if __name__ == '__main__': sys.exit('Error: The script file "' + options.scriptFile + '" does not exist!!') pdf = None statsDict = None - vtJsonDict = None if pdfName is not None: pdfParser = PDFParser() @@ -165,13 +164,7 @@ if __name__ == '__main__': pdf.getVtInfo(VT_KEY) statsDict = pdf.getStats() - if options.xmlOutput: - xmlReport = pdfOutput.getXML(statsDict) - sys.stdout.write(xmlReport) - elif options.jsonOutput and not options.commands: - jsonReport = pdfOutput.getJSON(statsDict) - sys.stdout.write(jsonReport) - elif options.isInteractive: + if options.isInteractive: console = PDFConsole(pdf, VT_KEY, options.avoidColors) console.runit() elif options.scriptFile is not None: diff --git a/peepdf/PDFOutput.py b/peepdf/PDFOutput.py index 970a93a..2799ddb 100644 --- a/peepdf/PDFOutput.py +++ b/peepdf/PDFOutput.py @@ -397,14 +397,14 @@ def getPeepXML(self, statsDict): return etree.tostring(root, pretty_print=True) - def getPeepReport(self, statsDict, stats=""): + def getPeepReport(self, statsDict): if not self.avoidColor: beforeStaticLabel = self.staticColor else: beforeStaticLabel = '' - stats += beforeStaticLabel + 'File: ' + self.resetColor + statsDict['File'] + self.newLine + stats = beforeStaticLabel + 'File: ' + self.resetColor + statsDict['File'] + self.newLine stats += beforeStaticLabel + 'MD5: ' + self.resetColor + statsDict['MD5'] + self.newLine stats += beforeStaticLabel + 'SHA1: ' + self.resetColor + statsDict['SHA1'] + self.newLine stats += beforeStaticLabel + 'SHA256: ' + self.resetColor + statsDict['SHA256'] + self.newLine @@ -536,3 +536,19 @@ def getPeepReport(self, statsDict, stats=""): stats += '\t\t' + url + self.newLine stats += self.newLine * 2 return stats + + def getReport(self, statsDict, format="text"): + """ + Wrapper to get a formatted report info. + @statsDict: dict with pdf info. + @format: expected output format. + Possible value are 'text', 'xml' and 'json' + """ + if format == "text": + return self.getPeepReport(statsDict) + elif format == "json": + return self.getPeepJSON(statsDict) + elif format == "xml": + return self.getPeepXML(statsDict) + else: + raise Exception("Format {} not handled".format(format)) From 25ae276d092496ad22d90ab4898654d0317136ee Mon Sep 17 00:00:00 2001 From: Thibault Marquand Date: Sun, 19 Jun 2016 20:37:40 +0200 Subject: [PATCH 12/14] PDFCore.py: remove useless value '0' return by parse() --- bin/peepdf | 3 +-- peepdf/PDFCore.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/bin/peepdf b/bin/peepdf index 1853f0b..d379436 100755 --- a/bin/peepdf +++ b/bin/peepdf @@ -157,8 +157,7 @@ if __name__ == '__main__': if pdfName is not None: pdfParser = PDFParser() - ret, pdf = pdfParser.parse(pdfName, options.isForceMode, options.isLooseMode, options.isManualAnalysis) - #ret is not check here, was it the case previous code ? + pdf = pdfParser.parse(pdfName, options.isForceMode, options.isLooseMode, options.isManualAnalysis) if options.checkOnVT: # Checks the MD5 on VirusTotal pdf.getVtInfo(VT_KEY) diff --git a/peepdf/PDFCore.py b/peepdf/PDFCore.py index 9f46e9a..e0b9960 100644 --- a/peepdf/PDFCore.py +++ b/peepdf/PDFCore.py @@ -7155,7 +7155,7 @@ def parse (self, fileName, forceMode = False, looseMode = False, manualAnalysis ret = pdfFile.decrypt() if ret[0] == -1: pdfFile.addError(ret[1]) - return (0,pdfFile) + return pdfFile def parsePDFSections(self, content, forceMode = False, looseMode = False): ''' From 611b351d3820d362d79e5ff68789f9580c0c999b Mon Sep 17 00:00:00 2001 From: Thibault Marquand Date: Thu, 23 Jun 2016 01:44:02 +0200 Subject: [PATCH 13/14] constants: ERROR_FILE & PDFOutput:fix avoidColor arg --- bin/peepdf | 27 +++++++++++++-------------- peepdf/PDFConsole.py | 14 +++++--------- peepdf/PDFOutput.py | 15 +++++++++++---- peepdf/constants.py | 5 ++++- 4 files changed, 33 insertions(+), 28 deletions(-) diff --git a/bin/peepdf b/bin/peepdf index d379436..0caa851 100755 --- a/bin/peepdf +++ b/bin/peepdf @@ -39,7 +39,7 @@ from peepdf.PDFCore import PDFParser from peepdf.PDFConsole import PDFConsole from peepdf.constants import PEEPDF_VERSION, PEEPDF_REVISION, AUTHOR, AUTHOR_EMAIL, \ - AUTHOR_TWITTER, PEEPDF_URL, GITHUB_URL, TWITTER_URL, PEEPDF_ROOT + AUTHOR_TWITTER, PEEPDF_URL, GITHUB_URL, TWITTER_URL, PEEPDF_ROOT, ERROR_FILE newLine = os.linesep @@ -70,7 +70,7 @@ if __name__ == '__main__': help='Avoids automatic Javascript analysis. Useful with eternal loops like heap spraying.') argsParser.add_option('-u', '--update', action='store_true', dest='update', default=False, help='Updates peepdf with the latest files from the repository.') - argsParser.add_option('-g', '--grinch-mode', action='store_true', dest='avoidColors', default=False, + argsParser.add_option('-g', '--grinch-mode', action='store_true', dest='avoidColor', default=False, help='Avoids colorized output in the interactive console.') argsParser.add_option('-v', '--version', action='store_true', dest='version', default=False, help='Shows program\'s version number.') @@ -83,7 +83,6 @@ if __name__ == '__main__': (options, args) = argsParser.parse_args() try: - pdfOutput = PDFOutput.PDFOutput(os.path.join(PEEPDF_ROOT, 'errors.txt'), options.avoidColors) if options.scriptFile is not None and options.commands: sys.stdout.write("scriptFile and commands options can't be used at the same time.\n") sys.exit(argsParser.print_help()) @@ -164,17 +163,18 @@ if __name__ == '__main__': statsDict = pdf.getStats() if options.isInteractive: - console = PDFConsole(pdf, VT_KEY, options.avoidColors) + console = PDFConsole(pdf, VT_KEY, options.avoidColor) console.runit() elif options.scriptFile is not None: - console = PDFConsole(pdf, VT_KEY, options.avoidColors, scriptFile=options.scriptFile) + console = PDFConsole(pdf, VT_KEY, options.avoidColor, scriptFile=options.scriptFile) console.runit() elif options.commands is not None: commands = "\n".join(options.commands) inputCommands = StringIO.StringIO(commands) - console = PDFConsole(pdf, VT_KEY, options.avoidColors, stdin=inputCommands) + console = PDFConsole(pdf, VT_KEY, options.avoidColor, stdin=inputCommands) console.runit() elif statsDict is not None: + pdfOutput = PDFOutput.PDFOutput(avoidColor=options.avoidColor) warnings = pdfOutput.getDecryptError(statsDict) warnings += pdfOutput.getDependenciesWarning() if warnings != "": @@ -194,13 +194,12 @@ if __name__ == '__main__': excName = excReason = None if excName == None or excName != 'PeepException': errorMessage = '*** Error: Exception not handled!!' - traceback.print_exc(file=open(pdfOutput.errorsFile, 'a')) - print pdfOutput.errorColor + errorMessage + pdfOutput.resetColor + pdfOutput.newLine + traceback.print_exc(file=open(ERROR_FILE, 'a')) + sys.stderr.write(errorMessage) finally: - if os.path.exists(pdfOutput.errorsFile): - message = pdfOutput.newLine + 'Please, don\'t forget to report the errors found:' + pdfOutput.newLine * 2 + if os.path.exists(ERROR_FILE): + message = newLine + 'Please, don\'t forget to report the errors found:' + newLine * 2 message += '\t- Sending the file "%s" to the author (mailto:%s)%s' % ( - pdfOutput.errorsFile, AUTHOR_EMAIL, pdfOutput.newLine) - message += '\t- And/Or creating an issue on the project webpage (https://github.com/jesparza/peepdf/issues)' + pdfOutput.newLine - message = pdfOutput.errorColor + message + pdfOutput.resetColor - sys.exit(message) + ERROR_FILE, AUTHOR_EMAIL, newLine) + message += '\t- And/Or creating an issue on the project webpage (https://github.com/jesparza/peepdf/issues)' + newLine + sys.stderr.write(message) diff --git a/peepdf/PDFConsole.py b/peepdf/PDFConsole.py index 1d919a5..cc54f8e 100644 --- a/peepdf/PDFConsole.py +++ b/peepdf/PDFConsole.py @@ -42,12 +42,8 @@ from base64 import b64encode, b64decode from PDFFilters import decodeStream, encodeStream from peepdf.jjdecode import JJDecoder +from colorama import Fore -try: - from colorama import init, Fore, Back, Style - COLORIZED_OUTPUT = True -except: - COLORIZED_OUTPUT = False try: import PyV8 JS_MODULE = True @@ -91,16 +87,16 @@ def __init__(self, pdfFile, vtKey, avoidOutputColors=False, stdin=None, batchMod @batchMode: deprecated mode which purpose was to handle command on cli execution @scriptFile: Script file path to execute. Overlap @stdin. """ - if COLORIZED_OUTPUT and not avoidOutputColors: + self.avoidColor = avoidOutputColors + PDFOutput.__init__(self, avoidColor=self.avoidColor) + if not self.avoidColor: try: self.promptColor = RL_PROMPT_START_IGNORE + Fore.GREEN + RL_PROMPT_END_IGNORE - self.avoidColor = False except: self.avoidColor = True - PDFOutput.__init__(self, self.avoidColor) + if pdfFile is not None and stdin is None: self.intro = self.getPeepReport(pdfFile.getStats()) - if scriptFile is not None: self.stdin = open(scriptFile, 'rb') self.use_rawinput = False diff --git a/peepdf/PDFOutput.py b/peepdf/PDFOutput.py index 2799ddb..78f0123 100644 --- a/peepdf/PDFOutput.py +++ b/peepdf/PDFOutput.py @@ -35,7 +35,7 @@ from peepdf.PDFUtils import vtcheck from peepdf.constants import AUTHOR, AUTHOR_EMAIL, PEEPDF_VERSION, PEEPDF_REVISION, \ - PEEPDF_URL, PEEPDF_ROOT + PEEPDF_URL, PEEPDF_ROOT, ERROR_FILE try: import PyV8 @@ -64,18 +64,25 @@ LXML_MODULE = True except: LXML_MODULE = False - + class PDFOutput(object): + """ + Class to get an output from PDFFile instance object + """ - def __init__(self, errorsFile, avoidOutputColors=False): + def __init__(self, errorsFile=ERROR_FILE, avoidColor=False): + """ + @errorsFile: path to the log error file + @avoidColor: Bool to prevent color on output + """ global COLORIZED_OUTPUT self.warningColor = '' self.errorColor = '' self.alertColor = '' self.staticColor = '' self.resetColor = '' - if not COLORIZED_OUTPUT or avoidOutputColors: + if avoidColor or not COLORIZED_OUTPUT: self.avoidColor = True else: try: diff --git a/peepdf/constants.py b/peepdf/constants.py index e3c9155..4018681 100644 --- a/peepdf/constants.py +++ b/peepdf/constants.py @@ -20,4 +20,7 @@ GITHUB_URL = 'https://github.com/jesparza/peepdf' TWITTER_URL = 'http://twitter.com/peepdf' -PEEPDF_ROOT = os.path.dirname(os.path.realpath(sys.argv[0])) +PEEPDF_ROOT = os.path.dirname( + os.path.realpath(os.path.join(sys.argv[0], "..")) +) +ERROR_FILE = os.path.join(PEEPDF_ROOT, "errors.txt") From ba52d296bbabd1927a5f1f03e4df40b84a75bff2 Mon Sep 17 00:00:00 2001 From: Thibault Marquand Date: Sun, 6 Nov 2016 19:44:34 +0100 Subject: [PATCH 14/14] revert 25ae276d092496ad22d90ab4898654d0317136ee --- peepdf/PDFCore.py | 2 +- peepdf/main.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/peepdf/PDFCore.py b/peepdf/PDFCore.py index ff48166..f1f0172 100644 --- a/peepdf/PDFCore.py +++ b/peepdf/PDFCore.py @@ -7192,7 +7192,7 @@ def parse(self, fileName, forceMode=False, looseMode=False, manualAnalysis=False ret = pdfFile.decrypt() if ret[0] == -1: pdfFile.addError(ret[1]) - return pdfFile + return (0, pdfFile) def parsePDFSections(self, content, forceMode=False, looseMode=False): ''' diff --git a/peepdf/main.py b/peepdf/main.py index 23f54f6..c5dc8c3 100644 --- a/peepdf/main.py +++ b/peepdf/main.py @@ -91,7 +91,7 @@ def main(): if pdfName is not None: pdfParser = PDFParser() - pdf = pdfParser.parse(pdfName, options.isForceMode, options.isLooseMode, options.isManualAnalysis) + ret, pdf = pdfParser.parse(pdfName, options.isForceMode, options.isLooseMode, options.isManualAnalysis) if options.checkOnVT: # Checks the MD5 on VirusTotal pdf.getVtInfo(VT_KEY)