[comp.emacs] Postscript eexec tools in GNU Emacs

merlyn@intelob.intel.com (Randal L. Schwartz @ Stonehenge) (05/04/89)

Some time ago, Carsten Wiethoff
<cnwietho@immd4.informatik.uni-erlangen.de> published the algorithm
for the Postscript "undocumented" eexec operator in
comp.lang.postscript.  This operator apparently takes trivially
encrypted text on the indicated file (the top of stack parameter), and
decrypts it and then executes it.

I took his algorithm description and wrote the attached GNU Emacs
elisp code to play with it.  With the code, you can take an arbitrary
chunk of Postscript, 'eexec-encode' it, and feed the ugly-looking
result into a LaserWriter(tm) or any Postscript(tm) implementation
that supports eexec.  You can even send it to your friend, and he/she
won't be able to tell what it does until it comes out of the printer.
But, since this message is going out to thousands of machines
worldwide, don't count on secrecy for long... because the reverse is
supported...  take a file that has been encrypted, and decrypt it with
'eexec-decode'.

The 'seed' value apparently doesn't matter.  It worked for any of the
values I tried on a standard LaserWriter, but your mileage may vary.

I'd be interested to hear about bugs, feature enhancements, or
whatever.  Yeah, I know it is slow decoding... elisp has no fast hex
number manipulators.

Enjoy.  (This code is copyleft according to the GNU 'COPYING' file.)

/=Randal L. Schwartz, Stonehenge Consulting Services (503)777-0095===\
{ on contract to BiiN, Hillsboro, Oregon, USA, until 30 May 1989     }
{ <merlyn@intelob.intel.com> ...!uunet!tektronix!biin!merlyn         }
{ or try <merlyn@agora.hf.intel.com> after 30 May 1989               }
\=Cute quote: "Welcome to Oregon... home of the California Raisins!"=/

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; cut here ;;;;;;;;;;
;;; Postscript eexec support routines
;;; LastEditDate "Wed May  3 10:22:06 1989"
;;; Copyright (c) 1989 by Randal L. Schwartz. All Rights Reserved.
;;; This code may be freely distributed according to the GNU Public License

(defun hex-string-to-int (str)
  "Convert STRING to an integer by parsing it as a hexadecimal number."
  (let ((result 0) ch)
    (while (string-match "^[0-9a-fA-F]" str)
      (setq ch (string-to-char (substring str 0 1))
	    str (substring str 1)
	    result (+ (* 16 result)
		      (cond ((>= ch ?a) (+ (- ch ?a) 10))
			    ((>= ch ?A) (+ (- ch ?A) 10))
			    (t (- ch ?0))))))
    result))

(defun byte-to-hex-string (byte)
  "Convert BYTE to a string by printing it in hexadecimal."
  ;; quick and dirty
  (concat (char-to-string (aref "0123456789abcdef" (logand 15 (lsh byte -4))))
	  (char-to-string (aref "0123456789abcdef" (logand 15 byte)))))

(defconst eexec-const-init (hex-string-to-int "d971"))
(defconst eexec-const-mult (hex-string-to-int "ce6d"))
(defconst eexec-const-add (hex-string-to-int "58bf"))
(defconst eexec-const-seed (mapconcat (function (lambda (x)
						  (char-to-string
						   (hex-string-to-int x))))
				      '("17" "ec" "9c" "f3") ""))

(defun eexec-decode ()
  "Decode the first eexec string in the current (possibly narrowed) buffer.
Result is displayed in a temp buffer."
  (interactive)
  (with-output-to-temp-buffer "*eexec-decode-output*"
    (goto-char (point-min))
    (search-forward "eexec")
    (let ((str "") newstr)
      (while (re-search-forward "[ \t\n]*\\([0-9a-fA-F][0-9a-fA-F]\\)" nil t)
	(setq str (concat str
			  (char-to-string
			   (hex-string-to-int
			    (buffer-substring
			     (match-beginning 1)
			     (match-end 1)))))))
      (setq newstr (eexec-endecode str))
      (princ "Seed: ")
      (princ (byte-to-hex-string (aref newstr 0)))
      (princ (byte-to-hex-string (aref newstr 1)))
      (princ (byte-to-hex-string (aref newstr 2)))
      (princ (byte-to-hex-string (aref newstr 3)))
      (princ "\nText:\n")
      (princ (substring newstr 4)))))

(defun eexec-encode (start end &optional seed)
  "Encode text from START to END (region if interactive).
Result is displayed in a temp buffer.  If optional SEED is passed as
a four-character string, use it for initial state, else use the known
string from the uartpatch.ps file '17ec9cf3'."
  (interactive "r")
  (with-output-to-temp-buffer "*eexec-encode-output*"
    (let ((i 0))
      (princ "currentfile eexec\n")
      (mapcar (function (lambda (ch)
			  (princ (byte-to-hex-string ch))
			  (if (< (setq i (1+ i)) 32) nil
			    (princ "\n")
			    (setq i 0))))
	      (eexec-endecode (concat (or (and (stringp seed)
					       (= (length seed) 4)
					       seed)
					  eexec-const-seed)
				      (buffer-substring start end)) t))
      (if (> i 0) (princ "\n")))))

(defun eexec-endecode (str &optional encode)
  "Decode STR (or encode if optional ENCODE is non-nil), returning result.
If decoding, you will probably want to toss the first four bytes,
but they are returned anyway so that you may reencode a decoded string
for verification."
  (let ((state eexec-const-init) outbyte)
    (concat
     (mapcar
      (function
       (lambda (inbyte)
	 (setq outbyte (logxor inbyte (logand (lsh state -8)))
	       state (logand 65535 (+ state (if encode outbyte inbyte)))
	       state (logand 65535 (* state eexec-const-mult))
	       state (logand 65535 (+ state eexec-const-add)))
	 outbyte))
      str))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; cut here

rbj@dsys.icst.nbs.GOV (Root Boy Jim) (05/05/89)

? Reply-To: "Randal L. Schwartz @ Stonehenge" <merlyn@intelob.intel.com>

? (defun byte-to-hex-string (byte)
?   "Convert BYTE to a string by printing it in hexadecimal."
?   ;; quick and dirty
?   (concat (char-to-string (aref "0123456789abcdef" (logand 15 (lsh byte -4))))
? 	  (char-to-string (aref "0123456789abcdef" (logand 15 byte)))))

The above function can be written as:

	(defun byte-to-hex-string (byte)
	  "mumble"
	  (format "%x" byte))

Unfortunately, the reverse is not true. String-to-int should also take
an optional base argument.

Having said that, I will now quibble with your input scanner:

(defun hex-string-to-int (str)
  "Convert STRING to an integer by parsing it as a hexadecimal number."
  (let ((result 0) ch)
    (while (string-match "^[0-9a-fA-F]" str)
      (setq ch (string-to-char (substring str 0 1))
	    str (substring str 1)
	    result (+ (* 16 result)
		      (cond ((>= ch ?a) (+ (- ch ?a) 10))
			    ((>= ch ?A) (+ (- ch ?A) 10))
			    (t (- ch ?0))))))
    result))

Replace (string-to-char (substring str 0 1)) with (aref str 0).
A string is really a vector of characters; you used this fact later.

You can always replace (* 16 result) with (lsh result 4).

Since you have already verified that the digits match [0-9a-fA-F], you
can replace the cond statement with:

	(logand 15 (+ ch (if (> ch ?9) 9 0)))

Finally, towards the end you wrote ... (concat (mapcar ... )) ...
I'm not sure, but this looks awful similar to ... (mapconcat ...) ...

BTW, to the author of SIP (scroll-in-place): you wrote a function
whose point was to determine if two numbers were of same sign. The
definition looked something like:

	(defun same-sign (x y)
	  "Return t is X and Y are same sign, else nil"
	  (if (< x 0)
	      (< y 0)
	    (<= 0 y)))

How bout: (defun same-sign (x y) (<= 0 (logxor x y))) instead?
You could also use `natnump' instead of `<='.
If the function was different-sign, then replace `<=' with `>'.

Don't y'all just hate smart alecks :-?

	Root Boy Jim is what I am
	Are you what you are or what?