Files
OSNCL/lib/pecoff-utilities.lisp

570 lines
23 KiB
Common Lisp

;;;; PMSF-Lib --- PMSF Common Lisp Utility Library
;;;; This is copyrighted software. See documentation for terms.
;;;;
;;;; pecoff-utilities.lisp --- Utilities concerned with PE/COFF
;;;;
;;;; $Id$
(cl:in-package #:pmsf-pecoff)
;;;; %File Description:
;;;;
;;;; This file contains utilities for PE/COFF file handling
;;;;
(defvar *pecoff-processing-pathname* nil)
(define-condition pecoff-processing-error (simple-error)
((pathname :initarg :pathname :initform *pecoff-processing-pathname*
:reader pecoff-processing-error-pathname))
(:report
(lambda (c s)
(with-standard-io-syntax
(let ((*print-readably* nil))
(format s
"Error processing PE/COFF file~@[ for ~A~]: ~?"
(pecoff-processing-error-pathname c)
(simple-condition-format-control c)
(simple-condition-format-arguments c)))))))
(defun pecoff-processing-error (datum &rest arguments)
(error 'pecoff-processing-error :format-control datum
:format-arguments arguments))
;;;
;;; PE/COFF Structures
;;;
(defclass file ()
((mz-stub :initarg :mz-stub :accessor file-mz-stub)
(signature :initarg :signature :accessor file-signature)
(header :initarg :header :accessor file-header)
(optional-header :initarg :optional-header
:accessor file-optional-header)
(sections :initarg :sections :accessor file-sections)
(raw-data :initarg :raw-data :accessor file-raw-data)))
(defclass mz-stub ()
((raw-magic :initarg :raw-magic :accessor mz-stub-raw-magic)
(pecoff-offset :initarg :pecoff-offset
:accessor mz-stub-pecoff-offset)
(raw-data :initarg :raw-data :accessor mz-stub-raw-data)))
(defconstant +header-machine-type-i386+ #x14c)
(defconstant +header-machine-type-amd64+ #x8664)
(defconstant +header-machine-type-arm+ #x1c0)
(defconstant +header-machine-type-thumb+ #x1c2)
(defconstant +header-machine-type-armnt+ #x1c4)
(defconstant +header-machine-type-arm64+ #xaa64)
(defclass header ()
((machine-type :initarg :machine-type
:accessor header-machine-type)
(number-of-sections :initarg :number-of-sections
:accessor header-number-of-sections)
(time-date-stamp :initarg :time-date-stamp
:accessor header-time-date-stamp)
(offset-to-symbol-table :initarg :offset-to-symbol-table
:accessor header-offset-to-symbol-table)
(number-of-symbols :initarg :number-of-symbols
:accessor header-number-of-symbols)
(size-of-optional-header :initarg :size-of-optional-header
:accessor header-size-of-optional-header)
(characteristics :initarg :characteristics
:accessor header-characteristics)))
(defclass optional-header ()
((linker-major-version :initarg :linker-major-version
:accessor optional-header-linker-major-version)
(linker-minor-version :initarg :linker-minor-version
:accessor optional-header-linker-minor-version)
(size-of-code :initarg :size-of-code
:accessor optional-header-size-of-code)
(size-of-initialized-data :initarg :size-of-initialized-data
:accessor optional-header-size-of-initialized-data)
(size-of-uninitialized-data :initarg :size-of-uninitialized-data
:accessor optional-header-size-of-uninitialized-data)
(address-of-entry-point :initarg :address-of-entry-point
:accessor optional-header-address-of-entry-point)
(base-of-code :initarg :base-of-code
:accessor optional-header-base-of-code)
;; Windows-specific part
(image-base :initarg :image-base
:accessor optional-header-image-base)
(section-alignment :initarg :section-alignment
:accessor optional-header-section-alignment)
(file-alignment :initarg :file-alignment
:accessor optional-header-file-alignment)
(major-os-version :initarg :major-os-version
:accessor optional-header-major-os-version)
(minor-os-version :initarg :minor-os-version
:accessor optional-header-minor-os-version)
(major-image-version :initarg :major-image-version
:accessor optional-header-major-image-version)
(minor-image-version :initarg :minor-image-version
:accessor optional-header-minor-image-version)
(major-subsystem-version :initarg :major-subsystem-version
:accessor optional-header-major-subsystem-version)
(minor-subsystem-version :initarg :minor-subsystem-version
:accessor optional-header-minor-subsystem-version)
(win32-version-value :initarg :win32-version-value
:accessor optional-header-win32-version-value)
(size-of-image :initarg :size-of-image
:accessor optional-header-size-of-image)
(size-of-headers :initarg :size-of-headers
:accessor optional-header-size-of-headers)
(checksum :initarg :checksum
:accessor optional-header-checksum)
(subsystem :initarg :subsystem
:accessor optional-header-subsystem)
(dll-characteristics :initarg :dll-characteristics
:accessor optional-header-dll-characteristics)
(size-of-stack-reserve :initarg :size-of-stack-reserve
:accessor optional-header-size-of-stack-reserve)
(size-of-stack-commit :initarg :size-of-stack-commit
:accessor optional-header-size-of-stack-commit)
(size-of-heap-reserve :initarg :size-of-heap-reserve
:accessor optional-header-size-of-heap-reserve)
(size-of-heap-commit :initarg :size-of-heap-commit
:accessor optional-header-size-of-heap-commit)
(loader-flags :initarg :loader-flags
:accessor optional-header-loader-flags)
(number-of-rva-and-sizes :initarg :number-of-rva-and-sizes
:accessor optional-header-number-of-rva-and-sizes)
(data-directories :initarg :data-directories
:accessor optional-header-data-directories)))
(defclass data-directory ()
((virtual-address :initarg :virtual-address
:accessor data-directory-virtual-address)
(size :initarg :size :accessor data-directory-size)))
(defclass optional-header-pe32 (optional-header)
((base-of-data :initarg :base-of-data
:accessor optional-header-base-of-data)))
(defclass optional-header-pe32+ (optional-header)
())
(defclass section ()
((name :initarg :name :accessor section-name)
(virtual-size :initarg :virtual-size :accessor section-virtual-size)
(virtual-address :initarg :virtual-address :accessor section-virtual-address)
(size-of-raw-data :initarg :size-of-raw-data :accessor section-size-of-raw-data)
(pointer-to-raw-data :initarg :pointer-to-raw-data :accessor section-pointer-to-raw-data)
(pointer-to-relocations :initarg :pointer-to-relocations :accessor section-pointer-to-relocations)
(pointer-to-line-numbers :initarg :pointer-to-line-numbers :accessor section-pointer-to-line-numbers)
(number-of-relocations :initarg :number-of-relocations :accessor section-number-of-relocations)
(number-of-line-numbers :initarg :number-of-line-numbers :accessor section-number-of-line-numbers)
(characteristics :initarg :characteristics :accessor section-characteristics)
(raw-data :initarg :raw-data :accessor section-raw-data)))
(defclass export-directory ()
((export-flags :initarg :export-flags
:accessor export-directory-export-flags)
(time-date-stamp :initarg :time-date-stamp
:accessor export-directory-time-date-stamp)
(major-version :initarg :major-version
:accessor export-directory-major-version)
(minor-version :initarg :minor-version
:accessor export-directory-minor-version)
(name-rva :initarg :name-rva
:accessor export-directory-name-rva)
(name :initarg :name
:accessor export-directory-name)
(ordinal-base :initarg :ordinal-base
:accessor export-directory-ordinal-name)
(address-table-entries :initarg :address-table-entries
:accessor export-directory-address-table-entries)
(name-table-entries :initarg :name-table-entries
:accessor export-directory-name-table-entries)
(address-table-rva :initarg :address-table-rva
:accessor export-directory-address-table-rva)
(name-table-rva :initarg :name-table-rva
:accessor export-directory-name-table-rva)
(ordinal-table-rva :initarg :ordinal-table-rva
:accessor export-directory-ordinal-table-rva)
(address-table :initarg :address-table
:accessor export-directory-address-table)
(name-table :initarg :name-table
:accessor export-directory-name-table)
(ordinal-table :initarg :ordinal-table
:accessor export-directory-ordinal-table)
(raw-data :initarg :raw-data
:accessor export-directory-raw-data)))
;;;
;;; Utility Accessors
;;;
(defun get-string-from-rva (file address)
(loop for section across (file-sections file)
for start = (section-virtual-address section)
for stop = (1- (+ start (section-virtual-size section)))
for raw-size = (section-size-of-raw-data section)
for raw-data = (section-raw-data section)
thereis
(when (and (<= start address stop)
(<= (- address start) raw-size))
(let ((end (position 0 raw-data :start (- address start))))
(make-array (list (1+ (- end (- address start))))
:element-type '(unsigned-byte 8)
:displaced-to raw-data
:displaced-index-offset (- address start))))))
(defun access-data-directory-contents (file index)
(let* ((opt-header (file-optional-header file))
(entry (aref (optional-header-data-directories opt-header)
index)))
(loop with address = (data-directory-virtual-address entry)
with size = (data-directory-size entry)
for section across (file-sections file)
for start = (section-virtual-address section)
for stop = (1- (+ start (section-virtual-size section)))
for raw-size = (section-size-of-raw-data section)
for raw-data = (section-raw-data section)
thereis
(when (and (<= start address stop)
(<= start (1- (+ address size)) stop)
(<= (- address start) raw-size))
(make-array (list size)
:element-type '(unsigned-byte 8)
:displaced-to raw-data
:displaced-index-offset (- address start))))))
;;;
;;; Basic COFF I/O
;;;
(defvar *current-source*)
(defvar *current-offset*)
(defvar *current-kind*)
(defmacro with-pecoff-reading (source &body body)
`(let ((*current-source* ,source)
(*current-offset* 0))
,@body))
(defmacro with-pecoff-reading-kind (kind &body body)
`(let ((*current-kind* ,kind))
,@body))
(defun read-coff-byte (&optional offset)
(when offset (setq *current-offset* offset))
(prog1 (aref *current-source* *current-offset*)
(incf *current-offset*)))
(defun read-coff-half (&optional offset)
(let ((lsb (read-coff-byte offset)))
(dpb (read-coff-byte) (byte 8 8) lsb)))
(defun read-coff-word (&optional offset)
(let* ((lsb (read-coff-byte offset))
(lsb2 (read-coff-byte))
(lsb3 (read-coff-byte))
(msb (read-coff-byte)))
(dpb msb (byte 8 24) (dpb lsb3 (byte 8 16) (dpb lsb2 (byte 8 8) lsb)))))
(defun read-coff-xword (&optional offset)
(let* ((lsb (read-coff-byte offset))
(lsb2 (read-coff-byte))
(lsb3 (read-coff-byte))
(lsb4 (read-coff-byte))
(lsb5 (read-coff-byte))
(lsb6 (read-coff-byte))
(lsb7 (read-coff-byte))
(msb (read-coff-byte)))
(dpb msb (byte 8 56)
(dpb lsb7 (byte 8 48)
(dpb lsb6 (byte 8 40)
(dpb lsb5 (byte 8 32)
(dpb lsb4 (byte 8 24)
(dpb lsb3 (byte 8 16)
(dpb lsb2 (byte 8 8) lsb)))))))))
(defun read-coff-word/xword (&optional offset)
(ecase *current-kind*
(:pe32 (read-coff-word offset))
(:pe32+ (read-coff-xword offset))))
(defun read-coff-sequence (length &optional offset)
(when offset (setq *current-offset* offset))
(let ((result (make-array (list length) :element-type '(unsigned-byte 8)
:displaced-to *current-source*
:displaced-index-offset *current-offset*)))
(incf *current-offset* length)
result))
;;;
;;; Header Handling and Reading
;;;
(defun check-pecoff-file-p ()
(and
;; MZ Stub
(eql (read-coff-byte 0) (char-code #\M))
(eql (read-coff-byte) (char-code #\Z))
(let ((pe-offset (read-coff-word #x3c)))
(and
(eql (read-coff-byte pe-offset) (char-code #\P))
(eql (read-coff-byte) (char-code #\E))
(eql (read-coff-byte) 0)
(eql (read-coff-byte) 0)))))
(defun read-mz-stub ()
(let ((identifier (read-coff-sequence 2 0))
(pecoff-offset (read-coff-word #x3c)))
(unless (every #'eql identifier (map 'list #'char-code "MZ"))
(pecoff-processing-error "Invalid MZ MAGIC:~{ 0x~2,'0X~}"
(coerce identifier 'list)))
(make-instance 'mz-stub
:raw-magic identifier
:pecoff-offset pecoff-offset
:raw-data
(read-coff-sequence pecoff-offset 0))))
(defun read-pecoff-signature (&optional offset)
(let ((signature (read-coff-sequence 4 offset)))
(unless (every #'eql signature
(map 'list #'char-code
(list #\P #\E #\NULL #\NULL)))
(pecoff-processing-error "Invalid PE Signature:~{ 0x~2,'0X~}"
(coerce signature 'list)))
signature))
(defun read-header (&optional offset)
(let ((machine-type (read-coff-half offset))
(number-of-sections (read-coff-half))
(time-date-stamp (read-coff-word))
(offset-to-symbol-table (read-coff-word))
(number-of-symbols (read-coff-word))
(size-of-optional-header (read-coff-half))
(characteristics (read-coff-half)))
(make-instance 'header
:machine-type machine-type
:number-of-sections number-of-sections
:time-date-stamp time-date-stamp
:offset-to-symbol-table offset-to-symbol-table
:number-of-symbols number-of-symbols
:size-of-optional-header size-of-optional-header
:characteristics characteristics)))
(defun read-optional-header (size &optional offset)
(unless (zerop size)
(let ((magic (read-coff-half offset)))
(cond
((eql magic #x010b)
(read-optional-header-pe32))
((eql magic #x020b)
(read-optional-header-pe32+))
(t
(pecoff-processing-error "Unsupported optional header magic 0x~4,'0X" magic))))))
(defun read-optional-header-pe32 ()
(apply
#'make-instance
'optional-header-pe32
:linker-major-version (read-coff-byte)
:linker-minor-version (read-coff-byte)
:size-of-code (read-coff-word)
:size-of-initialized-data (read-coff-word)
:size-of-uninitialized-data (read-coff-word)
:address-of-entry-point (read-coff-word)
:base-of-code (read-coff-word)
:base-of-data (read-coff-word)
(with-pecoff-reading-kind :pe32
(read-optional-header-windows))))
(defun read-optional-header-pe32+ ()
(apply
#'make-instance
'optional-header-pe32+
:linker-major-version (read-coff-byte)
:linker-minor-version (read-coff-byte)
:size-of-code (read-coff-word)
:size-of-initialized-data (read-coff-word)
:size-of-uninitialized-data (read-coff-word)
:address-of-entry-point (read-coff-word)
:base-of-code (read-coff-word)
(with-pecoff-reading-kind :pe32+
(read-optional-header-windows))))
(defun read-optional-header-windows ()
(list*
:image-base (read-coff-word/xword)
:section-alignment (read-coff-word)
:file-alignment (read-coff-word)
:major-os-version (read-coff-half)
:minor-os-version (read-coff-half)
:major-image-version (read-coff-half)
:minor-image-version (read-coff-half)
:major-subsystem-version (read-coff-half)
:minor-subsystem-version (read-coff-half)
:win32-version-value (read-coff-word)
:size-of-image (read-coff-word)
:size-of-headers (read-coff-word)
:checksum (read-coff-word)
:subsystem (read-coff-half)
:dll-characteristics (read-coff-half)
:size-of-stack-reserve (read-coff-word/xword)
:size-of-stack-commit (read-coff-word/xword)
:size-of-heap-reserve (read-coff-word/xword)
:size-of-heap-commit (read-coff-word/xword)
:loader-flags (read-coff-word)
(let ((entries (read-coff-word)))
(list :number-of-rva-and-sizes entries
:data-directories
(coerce
(loop repeat entries
collect
(make-instance 'data-directory
:virtual-address (read-coff-word)
:size (read-coff-word)))
'vector)))))
(defun read-section (&optional offset)
(let ((result
(make-instance
'section
:name (read-coff-sequence 8 offset)
:virtual-size (read-coff-word)
:virtual-address (read-coff-word)
:size-of-raw-data (read-coff-word)
:pointer-to-raw-data (read-coff-word)
:pointer-to-relocations (read-coff-word)
:pointer-to-line-numbers (read-coff-word)
:number-of-relocations (read-coff-half)
:number-of-line-numbers (read-coff-half)
:characteristics (read-coff-word))))
(unless (zerop (section-size-of-raw-data result))
(let ((*current-offset* *current-offset*))
(setf (section-raw-data result)
(read-coff-sequence (section-size-of-raw-data result)
(section-pointer-to-raw-data result)))))
result))
;;;
;;; Parsing of directories
;;;
(defun parse-export-directory (file)
(let ((raw-data (access-data-directory-contents file 0)))
(with-pecoff-reading raw-data
(let* ((export-flags (read-coff-word))
(time-date-stamp (read-coff-word))
(major-version (read-coff-half))
(minor-version (read-coff-half))
(name-rva (read-coff-word))
(ordinal-base (read-coff-word))
(address-table-entries (read-coff-word))
(name-table-entries (read-coff-word))
(address-table-rva (read-coff-word))
(name-table-rva (read-coff-word))
(ordinal-table-rva (read-coff-word)))
(make-instance
'export-directory
:export-flags export-flags
:time-date-stamp time-date-stamp
:major-version major-version
:minor-version minor-version
:name-rva name-rva
:ordinal-base ordinal-base
:address-table-entries address-table-entries
:name-table-entries name-table-entries
:address-table-rva address-table-rva
:name-table-rva name-table-rva
:ordinal-table-rva ordinal-table-rva
:raw-data raw-data
:address-table
(loop with result = (make-array address-table-entries)
for i from 0 below address-table-entries
do (setf (aref result i) (read-coff-word))
finally (return result))
:name-table
(loop with result = (make-array name-table-entries)
for i from 0 below name-table-entries
for rva = (read-coff-word)
do (setf (aref result i)
(get-string-from-rva file rva))
finally (return result))
:ordinal-table
(loop with result = (make-array address-table-entries)
for i from 0 below address-table-entries
do (setf (aref result i) (read-coff-half))
finally (return result)))))))
;;;
;;; Checksum Updates
;;;
(defun calculate-checksum-offset (file)
(+ (mz-stub-pecoff-offset (file-mz-stub file))
4
20
64))
(defun calculate-checksum (file)
(loop with raw-data = (file-raw-data file)
with checksum-offset = (calculate-checksum-offset file)
with checksum = 0
for offset upfrom 0 below (length raw-data) by 2
do
(unless (<= checksum-offset offset (+ 2 checksum-offset))
(incf checksum
(dpb (aref raw-data (1+ offset))
(byte 8 8)
(aref raw-data offset)))
(setq checksum
(+ (ldb (byte 16 16) checksum)
(ldb (byte 16 0) checksum))))
finally
(return
(ldb (byte 32 0)
(+
(ldb (byte 16 0)
(+ (ldb (byte 16 16) checksum)
checksum))
(length raw-data))))))
(defun update-checksum (file)
(let ((raw-data (file-raw-data file))
(checksum (calculate-checksum file))
(offset (calculate-checksum-offset file)))
(setf (aref raw-data offset) (ldb (byte 8 0) checksum)
(aref raw-data (+ offset 1)) (ldb (byte 8 8) checksum)
(aref raw-data (+ offset 2)) (ldb (byte 8 16) checksum)
(aref raw-data (+ offset 3)) (ldb (byte 8 24) checksum))))
;;;
;;; Main Entry Points
;;;
(defun read-image-file (pathname)
(with-open-file (in pathname :element-type '(unsigned-byte 8))
(let ((raw-data (make-array (list (file-length in))
:element-type '(unsigned-byte 8))))
(read-sequence raw-data in)
(with-pecoff-reading raw-data
(unless (check-pecoff-file-p)
(pecoff-processing-error "File is not in PE/COFF format!"))
(let* ((mz-stub (read-mz-stub))
(signature (read-pecoff-signature))
(header (read-header))
(optional-header (read-optional-header (header-size-of-optional-header header)))
(sections (coerce
(loop repeat (header-number-of-sections header)
collect (read-section))
'vector)))
(make-instance 'file
:mz-stub mz-stub
:signature signature
:header header
:optional-header optional-header
:sections sections
:raw-data raw-data))))))
(defun write-image-file (file pathname)
(with-open-file (out pathname :element-type '(unsigned-byte 8)
:direction :output)
(write-sequence (file-raw-data file) out)))