From 132750867fab3a9129c06ecbddd3de06b804f6e2 Mon Sep 17 00:00:00 2001 From: c0decave Date: Thu, 7 Nov 2019 12:51:30 +0100 Subject: [PATCH] release version 0.4.9 --- Readme.md | 49 +++++++---- docs/Changelog | 14 +++ libs/libgoogle.py | 31 ++++++- libs/liblog.py | 8 +- libs/libreport.py | 173 +++++++++++++++++++++++++++++++++++++ libs/librequest.py | 162 +++++++++++++++++++++++++++++++++++ libs/pdf_png.py | 5 ++ pdfgrab.py | 195 ++++++++++++------------------------------ supply/pdf.png | Bin 0 -> 24483 bytes supply/pdf_base64.png | 1 + 10 files changed, 477 insertions(+), 161 deletions(-) create mode 100644 libs/libreport.py create mode 100644 libs/librequest.py create mode 100644 libs/pdf_png.py create mode 100644 supply/pdf.png create mode 100644 supply/pdf_base64.png diff --git a/Readme.md b/Readme.md index b096be9..ff0dc62 100644 --- a/Readme.md +++ b/Readme.md @@ -1,6 +1,6 @@ # pdfgrab -* Version 0.4.8-Pre +* Version 0.4.9 ## What is it? @@ -9,21 +9,18 @@ Basically it analyses PDF files for Metadata. You can direct it to a file or dir You can show it the url of a pdf or use the integrated googlesearch (thanx to mario vilas class) to search for pdfs at target site, download and analyse them. -## What is new in 0.4.8 bug fix pre-release? +## What is new in 0.4.9? -* catching google error at too many requests -* catching dns resolve urlopen error at googlelib -* fixing annoying bug in regard of pdfs behind urls like http://host/pdf/ -* fixing zero size pdf error(online linked pdfs which are not accessable) -* added some logging - -## What is new in 0.4.7 release? - -* Added support for html output file, this will be placed in the outdir path and is more clear then a text or json file -* Added basic logging support, logfile is placed in pdfgrab.py directory -* Reordered Codebase, exported functionality to some libraries -* PDF XMP Metadata is grabbed now as well, but not yet saved in output files -* added docs/ section with Changelog and Todo +* exported reporting methods to libreport.py +* added optargs for disabling different report methods +* made the html report a bit more shiny +* added function for generating html report after analysis +* exported requests and storing data to new library +* code fixes and more clear error handling +* removed necessary site: parameter at search flag -s +* updated readme +* -s flag now acceppts several domains +* console logging more clean ## What information can be gathered? @@ -132,7 +129,7 @@ Will analyse all pdf's in that directory ### Google Search Mode ``` -# ./pdfgrab.py -s site:kernel.org +# ./pdfgrab.py -s kernel.org ``` Result: ``` @@ -164,6 +161,26 @@ File: pdfgrab/bpf_global_data_and_static_keys.pdf /PTEX.Fullbanner This is pdfTeX, Version 3.14159265-2.6-1.40.17 (TeX Live 2016) kpathsea version 6.2.2 ``` +### Google Search Mode, several domains +``` +# ./pdfgrab.py -s example.com,example.us +``` + +### Reporting + +pdfgrab outputs the information in different formats. If not disabled by one of the reporting flags (see -h) you will +find in the output directory: + +* html report +* text report +* text url list +* json data +* json url list + +### Logging + +pdfgrab creates a logfile in the running directory called "pdfgrab.log" + ## Google * Search: filetype:pdf site:com diff --git a/docs/Changelog b/docs/Changelog index e85582b..45488af 100644 --- a/docs/Changelog +++ b/docs/Changelog @@ -1,6 +1,20 @@ Changelog ========= +Version 4.9 +----------- + +* exported reporting methods to libreport.py +* added optargs for disabling different report methods +* made the html report a bit more shiny +* added function for generating html report after analysis +* exported requests and storing data to new library +* code fixes and more clear error handling +* removed necessary site: parameter at search flag -s +* updated readme +* -s flag now acceppts several domains +* console logging more clean + Version 4.8 Bugfix-PreRelease ----------------------------- diff --git a/libs/libgoogle.py b/libs/libgoogle.py index e658c63..4d23847 100644 --- a/libs/libgoogle.py +++ b/libs/libgoogle.py @@ -5,19 +5,44 @@ from libs.libhelper import * def get_random_agent(): return (gs.get_random_user_agent()) -def search_pdf(search, args): +def hits_google(search, args): + ''' the function where googlesearch from mario vilas + is called + ''' + s = search.split(',') + query = 'filetype:pdf' + + + try: + hits = gs.hits(query, domains=s,user_agent=gs.get_random_user_agent()) + + except urllib.error.HTTPError as e: + return False,e + + except urllib.error.URLError as e: + return False,e + + except IndexError as e: + return False,e + + return True,hits + + +def search_google(search, args): ''' the function where googlesearch from mario vilas is called ''' + s = search.split(',') search_stop = args.search_stop - query = '%s filetype:pdf' % search + query = 'filetype:pdf' + #query = 'site:%s filetype:pdf' % search # print(query) urls = [] try: - for url in gs.search(query, num=20, stop=search_stop, user_agent=gs.get_random_user_agent()): + for url in gs.search(query, num=20, domains=s,stop=search_stop, user_agent=gs.get_random_user_agent()): #print(url) urls.append(url) diff --git a/libs/liblog.py b/libs/liblog.py index 2c01d25..66d19cd 100644 --- a/libs/liblog.py +++ b/libs/liblog.py @@ -8,10 +8,12 @@ file_handler = logging.FileHandler('pdfgrab.log') console_handler = logging.StreamHandler() console_handler.setLevel(logging.WARNING) -formatter = logging.Formatter('%(asctime)s:%(name)s:%(levelname)s:%(message)s') +file_formatter = logging.Formatter('%(asctime)s:%(name)s:%(levelname)s:%(message)s') +console_formatter = logging.Formatter('%(levelname)s:%(message)s') -file_handler.setFormatter(formatter) -console_handler.setFormatter(formatter) + +file_handler.setFormatter(file_formatter) +console_handler.setFormatter(console_formatter) logger.addHandler(file_handler) logger.addHandler(console_handler) diff --git a/libs/libreport.py b/libs/libreport.py new file mode 100644 index 0000000..3bb5679 --- /dev/null +++ b/libs/libreport.py @@ -0,0 +1,173 @@ +import os +import sys +import json +from json2html import * +from libs.pdf_png import get_png_base64 + +def prepare_analysis_dict(ana_queue): + '''params: ana_queue - queue with collected information + ''' + # initiate analysis dictionary + analysis_dict = {} + + # move analysis dictionary in queue back to dictionary + while ana_queue.empty() == False: + item = ana_queue.get() + # print('item ', item) + analysis_dict.update(item) + + # ana_q is empty now return the newly created dictionary + return analysis_dict + +def create_txt_report(analysis_dict, outdir, out_filename): + ''' create a txt report in the output directory + ''' + + # draw seperator lines + sep = '-' * 80 + '\n' + + # create output filepath + txtout = "%s/%s.txt" % (outdir, out_filename) + + # open the file and return filedescriptor + fwtxt = open(txtout, 'w') + + # get the keys of the dict + for k in analysis_dict.keys(): + # write seperator + fwtxt.write(sep) + + # build entry filename of the pdf + fname = 'File: %s\n' % (analysis_dict[k]['filename']) + + # build data entry + ddata = analysis_dict[k]['data'] + + # write the filename + fwtxt.write(fname) + + # write the metadata + for kdata in ddata.keys(): + metatxt = '%s:%s\n' % (kdata, ddata[kdata]) + fwtxt.write(metatxt) + + # write seperator + fwtxt.write(sep) + + # close the file + fwtxt.close() + + return True + +def create_json_report(analysis_dict, outdir, out_filename): + ''' create a jsonfile report in the output directory + ''' + + # build json output name + jsonout = "%s/%s.json" % (outdir, out_filename) + + # open up json output file + fwjson = open(jsonout, 'w') + + # convert dictionary to json data + jdata = json.dumps(analysis_dict) + + # write json data to file + fwjson.write(jdata) + + # close file + fwjson.close() + + return True + +def create_html_report(analysis_dict, outdir, out_filename): + ''' create a html report from json file using json2html in the output directory + ''' + + # build up path for html output file + htmlout = "%s/%s.html" % (outdir, out_filename) + + # open htmlout filedescriptor + fwhtml = open(htmlout,'w') + + # some html stuff + pdfpng=get_png_base64('supply/pdf_base64.png') + html_style ='\n' + html_head = 'pdfgrab - {0} item/s{1}\n'.format(len(analysis_dict),html_style) + html_pdf_png = '


pdfgrab - grab and analyse pdf files

'.format(pdfpng) + html_body = '{0}\n'.format(html_pdf_png) + html_end = '\n

pdfgrab by dash

\n' + + # some attributes + attr = 'id="meta-data" class="table table-bordered table-hover", border=1, cellpadding=3 summary="Metadata"' + + # convert dictionary to json data + # in this mode each finding gets its own table there are other possibilities + # but now i go with this + html_out = '' + for k in analysis_dict.keys(): + trans = analysis_dict[k] + jdata = json.dumps(trans) + html = json2html.convert(json = jdata, table_attributes=attr) + html_out = html_out + html + "\n" + #html_out = html_out + "

" + html + "

\n" + #jdata = json.dumps(analysis_dict) + + # create html + #html = json2html.convert(json = jdata, table_attributes=attr) + + # write html + fwhtml.write(html_head) + fwhtml.write(html_body) + fwhtml.write(html_out) + fwhtml.write(html_end) + + # close html file + fwhtml.close() + +def create_url_json(url_d, outdir, out_filename): + ''' create a json url file in output directory + ''' + + # create url savefile + jsonurlout = "%s/%s_url.json" % (outdir, out_filename) + + # open up file for writting urls down + fwjson = open(jsonurlout, 'w') + + # convert url dictionary to json + jdata = json.dumps(url_d) + + # write json data to file + fwjson.write(jdata) + + # close filedescriptor + fwjson.close() + + return True + +def create_url_txt(url_d, outdir, out_filename): + ''' create a txt url file in output directory + ''' + # build up txt out path + txtout = "%s/%s_url.txt" % (outdir, out_filename) + + # open up our url txtfile + fwtxt = open(txtout, 'w') + + # iterating through the keys of the url dictionary + for k in url_d.keys(): + + # get the entry + ddata = url_d[k] + + # create meta data for saving + metatxt = '%s:%s\n' % (ddata['url'], ddata['filename']) + + # write metadata to file + fwtxt.write(metatxt) + + # close fd + fwtxt.close() + + return True diff --git a/libs/librequest.py b/libs/librequest.py new file mode 100644 index 0000000..a8df76b --- /dev/null +++ b/libs/librequest.py @@ -0,0 +1,162 @@ +import os +import sys +import json +import socket +import requests + +from libs.liblog import logger +from libs.libhelper import * +from libs.libgoogle import get_random_agent + +def store_file(url, data, outdir): + ''' storing the downloaded data to a file + params: url - is used to create the filename + data - the data of the file + outdir - to store in which directory + returns: dict { "code":, "data":,"error":} - the status code, the savepath, the errorcode + ''' + + logger.info('Store file {0}'.format(url)) + name = find_name(url) + + # only allow stored file a name with 50 chars + if len(name) > 50: + name = name[:49] + + # build up the save path + save = "%s/%s" % (outdir, name) + + try: + f = open(save, "wb") + + except OSError as e: + logger.warning('store_file {0}'.format(e)) + # return ret_dict + return {"code":False,"data":save,"error":e} + + # write the data and return the written bytes + ret = f.write(data) + + # check if bytes are zero + if ret == 0: + logger.warning('Written {0} bytes for file: {1}'.format(ret,save)) + + else: + # log to info that bytes and file has been written + logger.info('Written {0} bytes for file: {1}'.format(ret,save)) + + # close file descriptor + f.close() + + # return ret_dict + return {"code":True,"data":save,"error":False} + + +def download_file(url, args, header_data): + ''' downloading the file for later analysis + params: url - the url + args - argparse args namespace + header_data - pre-defined header data + returns: ret_dict + ''' + + # check the remote tls certificate or not? + cert_check = args.cert_check + + # run our try catch routine + try: + # request the url and save the response in req + # give header data and set verify as delivered by args.cert_check + req = requests.get(url, headers=header_data, verify=cert_check) + + except requests.exceptions.SSLError as e: + logger.warning('download file {0}{1}'.format(url,e)) + + # return retdict + return {"code":False,"data":req,"error":e} + + except requests.exceptions.InvalidSchema as e: + logger.warning('download file {0}{1}'.format(url,e)) + + # return retdict + return {"code":False,"data":False,"error":e} + + except socket.gaierror as e: + logger.warning('download file, host not known {0} {1}'.format(url,e)) + return {"code":False,"data":False,"error":e} + + except: + logger.warning('download file, something wrong with remote server? {0}'.format(url)) + # return retdict + if not req in locals(): + req = False + + return {"code":False,"data":req,"error":True} + + #finally: + # lets close the socket + #req.close() + + # return retdict + return {"code":True,"data":req,"error":False} + +def grab_run(url, args, outdir): + ''' function keeping all the steps for the user call of grabbing + just one and analysing it + ''' + header_data = {'User-Agent': get_random_agent()} + rd_download = download_file(url, args, header_data) + code_down = rd_download['code'] + + # is code True download of file was successfull + if code_down: + rd_evaluate = evaluate_response(rd_download) + code_eval = rd_evaluate['code'] + # if code is True, evaluation was also successful + if code_eval: + # get the content from the evaluate dictionary request + content = rd_evaluate['data'].content + + # call store file + rd_store = store_file(url, content, outdir) + + # get the code + code_store = rd_store['code'] + + # get the savepath + savepath = rd_store['data'] + + # if code is True, storing of file was also successfull + if code_store: + return {"code":True,"data":savepath,"error":False} + + return {"code":False,"data":False,"error":True} + +def evalute_content(ret_dict): + pass + +def evaluate_response(ret_dict): + ''' this method comes usually after download_file, + it will evaluate what has happened and if we even have some data to process + or not + params: data - is the req object from the conducted request + return: {} + returns: dict { "code":, "data":,"error":} - the status code, the savepath, the errorcode + ''' + # extract data from ret_dict + req = ret_dict['data'] + + # get status code + url = req.url + status = req.status_code + reason = req.reason + + # ahh everything is fine + if status == 200: + logger.info('download file, {0} {1} {2}'.format(url,reason,status)) + return {"code":True,"data":req,"error":False} + + # nah something is not like it should be + else: + logger.warning('download file, {0} {1} {2}'.format(url,reason,status)) + return {"code":False,"data":req,"error":True} diff --git a/libs/pdf_png.py b/libs/pdf_png.py new file mode 100644 index 0000000..9ec8052 --- /dev/null +++ b/libs/pdf_png.py @@ -0,0 +1,5 @@ + +def get_png_base64(filename): + fr = open(filename,'r') + buf = fr.read() + return buf diff --git a/pdfgrab.py b/pdfgrab.py index cb5ed69..d620653 100755 --- a/pdfgrab.py +++ b/pdfgrab.py @@ -22,12 +22,14 @@ from PyPDF2 import pdf from libs.liblog import logger from libs.libhelper import * from libs.libgoogle import * +from libs.libreport import * +from libs.librequest import grab_run from IPython import embed # some variables in regard of the tool itself name = 'pdfgrab' -version = '0.4.8-Pre' +version = '0.4.9' author = 'dash' date = 'November 2019' @@ -243,72 +245,6 @@ def check_encryption(filename): return True - -def download_pdf(url, args, header_data): - ''' downloading the pdfile for later analysis ''' - - # check the remote tls certificate or not? - cert_check = args.cert_check - - try: - req = requests.get(url, headers=header_data, verify=cert_check) - # req = requests.get(url,headers=header_data,verify=False) - data = req.content - status_code = req.status_code - - except requests.exceptions.SSLError as e: - logger.warning('download pdf {0}{1}'.format(url,e)) - return -1 - - except: - logger.warning('download pdf, something wrong with remote server? {0}'.format(url)) - return -1 - - if status_code == 403: - logger.warning('download pdf, 403 Forbidden {0}'.format(url)) - return -1 - - # print(len(data)) - return data - - -def store_pdf(url, data, outdir): - ''' storing the downloaded pdf data - ''' - - logger.info('Store pdf {0}'.format(url)) - name = find_name(url) - #logger.warning(url) - #logger.warning(name) - #logger.warning(outdir) - - # only allow stored file a name with 50 chars - if len(name) > 50: - name = name[:49] + '.pdf' - # print(len(name)) - - save = "%s/%s" % (outdir, name) - - try: - f = open(save, "wb") - except OSError as e: - logger.warning('store_pdf {0}'.format(e)) - return -1 - - ret = f.write(data) - logger.info('Written {0} bytes for file: {1}'.format(ret,save)) - f.close() - - if ret == 0: - logger.warning('Written {0} bytes for file: {1}'.format(ret,save)) - return save - #return -1 - - - # return the savepath - return save - - def _parse_pdf(filename): ''' the real parsing function ''' @@ -320,26 +256,18 @@ def _parse_pdf(filename): logger.warning('Filesize is 0 bytes at file: {0}'.format(filename)) return False - -def grab_url(url, args, outdir): - ''' function keeping all the steps for the user call of grabbing - just one pdf and analysing it - ''' - header_data = {'User-Agent': get_random_agent()} - data = download_pdf(url, args, header_data) - if data != -1: - savepath = store_pdf(url, data, outdir) - _parse_pdf(savepath) - - return - - def seek_and_analyse(search, args, outdir): ''' function for keeping all the steps of searching for pdfs and analysing them together ''' + # check how many hits we got + # seems like the method is broken in googlsearch library :( + #code, hits = hits_google(search,args) + #if code: + # print('Got {0} hits'.format(hits)) + # use the search function of googlesearch to get the results - code, values=search_pdf(search, args) + code, values=search_google(search, args) if not code: if values.code == 429: logger.warning('[-] Too many requests, time to change ip address or use proxychains') @@ -362,7 +290,11 @@ def seek_and_analyse(search, args, outdir): item = url_q.get() # print(item) url = item['url'] - grab_url(url, args, outdir) + rd_grabrun = grab_run(url, args, outdir) + code = rd_grabrun['code'] + savepath = rd_grabrun['data'] + if code: + _parse_pdf(savepath) return True @@ -372,6 +304,9 @@ def run(args): # initialize logger logger.info('{0} Started'.format(name)) + # create some variables + + # outfile name if args.outfile: out_filename = args.outfile @@ -381,6 +316,7 @@ def run(args): # specify output directory outdir = args.outdir + # create output directory make_directory(outdir) @@ -417,68 +353,43 @@ def run(args): fpath = '%s/%s' % (directory, f) _parse_pdf(fpath) + # simply generate html report from json outfile + elif args.gen_html_report: + fr = open(args.gen_html_report,'r') + analysis_dict = json.loads(fr.read()) + if create_html_report(analysis_dict, outdir,out_filename): + logger.info('Successfully created html report') + sys.exit(0) + else: + sys.exit(1) + else: print('[-] Dunno what to do, bro. Use help. {0} -h'.format(sys.argv[0])) + sys.exit(1) - # move analysis dictionary in queue back to dictionary - analysis_dict = {} - while ana_q.empty() == False: - item = ana_q.get() - # print('item ', item) - analysis_dict.update(item) + # creating the analysis dictionary for reporting + analysis_dict = prepare_analysis_dict(ana_q) - #print('dict:',analysis_dict) - # ana_q is empty now + # lets go through the different reporting types + if args.report_txt: + if create_txt_report(analysis_dict, outdir,out_filename): + logger.info('Successfully created txt report') - # create txt output - sep = '-' * 80 + '\n' - txtout = "%s/%s.txt" % (outdir, out_filename) - fwtxt = open(txtout, 'w') - # print(analysis_dict) - for k in analysis_dict.keys(): - fwtxt.write(sep) - fname = 'File: %s\n' % (analysis_dict[k]['filename']) - ddata = analysis_dict[k]['data'] - fwtxt.write(fname) - for kdata in ddata.keys(): - metatxt = '%s:%s\n' % (kdata, ddata[kdata]) - fwtxt.write(metatxt) - fwtxt.write(sep) - fwtxt.close() + if args.report_json: + if create_json_report(analysis_dict, outdir,out_filename): + logger.info('Successfully created json report') - # create json output - jsonout = "%s/%s.json" % (outdir, out_filename) - fwjson = open(jsonout, 'w') + if args.report_html: + if create_html_report(analysis_dict, outdir,out_filename): + logger.info('Successfully created html report') - # print(analysis_dict) - jdata = json.dumps(analysis_dict) - fwjson.write(jdata) - fwjson.close() + if args.report_url_txt: + if create_url_txt(url_d, outdir,out_filename): + logger.info('Successfully created txt url report') - # create html from json - htmlout = "%s/%s.html" % (outdir, out_filename) - fwhtml = open(htmlout,'w') - #print(jdata) - html = json2html.convert(json = jdata) - fwhtml.write(html) - fwhtml.close() - - - # create url savefile - # print('url_d: ', url_d) - jsonurlout = "%s/%s_url.json" % (outdir, out_filename) - fwjson = open(jsonurlout, 'w') - jdata = json.dumps(url_d) - fwjson.write(jdata) - fwjson.close() - - txtout = "%s/%s_url.txt" % (outdir, out_filename) - fwtxt = open(txtout, 'w') - for k in url_d.keys(): - ddata = url_d[k] - metatxt = '%s:%s\n' % (ddata['url'], ddata['filename']) - fwtxt.write(metatxt) - fwtxt.close() + if args.report_url_json: + if create_url_json(url_d, outdir,out_filename): + logger.info('Successfully created json url report') return 42 @@ -504,8 +415,14 @@ def main(): help="specify domain or tld to scrape for pdf-files", default=None) parser.add_argument('-sn', '--search-number', action='store', dest='search_stop', required=False, help="specify how many files are searched", default=10, type=int) - parser.add_argument('-z', '--disable-cert-check', action='store_false', dest='cert_check', required=False, - help="if the target domain(s) run with old or bad certificates", default=True) + parser.add_argument('-z', '--disable-cert-check', action='store_false', dest='cert_check', required=False,help="if the target domain(s) run with old or bad certificates", default=True) + + parser.add_argument('-ghr', '--gen-html-report', action='store', dest='gen_html_report', required=False,help="If you want to generate the html report after editing the json outfile (parameter: pdfgrab_analysis.json)") + parser.add_argument('-rtd', '--report-text-disable', action='store_false', dest='report_txt', required=False,help="Disable txt report",default=True) + parser.add_argument('-rjd', '--report-json-disable', action='store_false', dest='report_json', required=False,help="Disable json report",default=True) + parser.add_argument('-rhd', '--report-html-disable', action='store_false', dest='report_html', required=False,help="Disable html report",default=True) + parser.add_argument('-rutd', '--report-url-text-disable', action='store_false', dest='report_url_txt', required=False,help="Disable url txt report",default=True) + parser.add_argument('-rujd', '--report-url-json-disable', action='store_false', dest='report_url_json', required=False,help="Disable url json report",default=True) if len(sys.argv)<2: parser.print_help(sys.stderr) diff --git a/supply/pdf.png b/supply/pdf.png new file mode 100644 index 0000000000000000000000000000000000000000..769b4078a208ba47caa05b608eb326d61c4aae88 GIT binary patch literal 24483 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