origami 1.0.4 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/README CHANGED
@@ -1,73 +1,64 @@
1
- ################################################################################
2
- # #
3
- # Origami - Ruby PDF manipulation framework #
4
- # #
5
- ################################################################################
1
+ NAME
6
2
 
7
- :: DESCRIPTION
8
- ==============
3
+ origami
9
4
 
10
- Origami is a framework written in Ruby designed to parse, analyze, and forge PDF
11
- documents.
12
- This is *NOT* a PDF rendering library, it aims at providing a scripting tool for
13
- generating and analyzing malicious PDF files. As well, it can be used to create
14
- on-the-fly customized PDFs, or to inject evil code into already existing
15
- documents.
5
+ DESCRIPTION
16
6
 
7
+ Origami is a framework written in Ruby designed to parse, analyze, and forge
8
+ PDF documents. This is not a PDF rendering library, it aims at providing a
9
+ scripting tool for generating and analyzing malicious PDF files. As well, it
10
+ can be used to create on-the-fly customized PDFs, or to inject evil code into
11
+ already existing documents.
17
12
 
18
- :: LICENSE
19
- ==========
13
+ VERSION
20
14
 
21
- This software is distributed under the LGPL license.
22
- See the COPYING.LESSER file for more details.
15
+ 1.1
23
16
 
17
+ DEPENDENCIES
24
18
 
25
- :: RELEASE
26
- ==========
19
+ - Ruby-GTK2 (only for GUI), http://ruby-gnome2.sourceforge.jp/
27
20
 
28
- - Current : Version 1.0.4
21
+ INSTALL
29
22
 
23
+ Stable: gem install origami
24
+ Devel: hg clone https://origami-pdf.googlecode.com/hg/ origami
30
25
 
31
- :: DEPENDENCIES
32
- ===============
26
+ DIRECTORIES
33
27
 
34
- - Ruby 1.8 (actually not very tested on 1.9)
35
- - Ruby-GTK2 (only for GUI), http://ruby-gnome2.sourceforge.jp/
28
+ ``origami/``
29
+ Core scripts used to parse a PDF file. All objects and features are
30
+ provided here.
36
31
 
32
+ ``samples/``
33
+ Many samples, mostly sorted to generate specially crafted PDFs.
37
34
 
38
- :: DIRECTORIES
39
- ==============
35
+ ``bin/``
36
+ Useful tools based on Origami.
40
37
 
41
- ``origami/``
42
- * Core scripts used to parse a PDF file. All objects and features are
43
- provided here.
38
+ ``tests/``
39
+ Test case units.
44
40
 
45
- ``samples/``
46
- * Many samples, mostly sorted to generate specially crafted PDFs.
41
+ ``doc/``
42
+ Automated RubyDoc HTML documentation.
47
43
 
48
- ``bin/``
49
- * Useful tools based on Origami.
44
+ HOMEPAGE
50
45
 
51
- ``tests/``
52
- * Test case units.
46
+ http://aslr.fr/pages/Origami
53
47
 
54
- ``doc/``
55
- * Automated RubyDoc HTML documentation.
48
+ CONTRIBUTORS
56
49
 
50
+ Guillaume Delugré <guillaume (at) security-labs.org> - Author
51
+ Frédéric Raynal <fred (at) security-labs.org> - Contributor
57
52
 
58
- :: CONTRIBUTORS
59
- ===============
53
+ LICENSE
60
54
 
61
- Guillaume Delugré <guillaume@security-labs.org> - Author
62
- Frédéric Raynal <fred@security-labs.org> - Contributor
55
+ This software is distributed under the LGPL license.
56
+ See the COPYING.LESSER file for more details.
63
57
 
58
+ NOTES
64
59
 
65
- :: NOTES
66
- ========
67
-
68
- If you encounter a problem, feel free to report it by mail at
69
- <guillaume [at] security-labs [dot] org>, with a short explanation of
70
- what you did and any necessary PDF documents.
71
-
72
- Thanks.
60
+ It contains many bugs and many incomplete features. If you encounter a
61
+ problem, feel free to report it by mail at <guillaume [at] security-labs
62
+ [dot] org>, with a short explanation of what you did and any necessary PDF
63
+ documents. Thanks.
73
64
 
@@ -36,7 +36,7 @@ module PDFWalker
36
36
  :version => Origami::VERSION,
37
37
  :copyright => "Copyright (C) 2010\nGuillaume Delugre, Sogeti-ESEC R&D <guillaume@security-labs.org>\nAll right reserved.",
38
38
  :comments => "A graphical PDF parser front-end",
39
- :license => File.read("#{File.dirname($0)}/../COPYING.LESSER")
39
+ :license => File.read("#{File.dirname(__FILE__)}/../../COPYING.LESSER")
40
40
  })
41
41
 
42
42
  end
@@ -37,7 +37,7 @@ require 'optparse'
37
37
 
38
38
  class OptParser
39
39
  BANNER = <<USAGE
40
- Usage: #{$0} [<PDF-file>] [-p <password>] [-c <cipher>] [-s <key-size>] [-o <output-file>]
40
+ Usage: #{$0} [<PDF-file>] [-p <password>] [-c <cipher>] [-s <key-size>] [--hardened] [-o <output-file>]
41
41
  Encrypts a PDF document. Supports RC4 40 to 128 bits, AES128, AES256.
42
42
  Bug reports or feature requests at: http://origami-pdf.googlecode.com/
43
43
 
@@ -64,6 +64,10 @@ USAGE
64
64
  options[:key_size] = s.to_i
65
65
  end
66
66
 
67
+ opts.on("--hardened", "Use stronger key validation scheme (only AES-256)") do
68
+ options[:hardened] = true
69
+ end
70
+
67
71
  opts.on_tail("-h", "--help", "Show this message") do
68
72
  puts opts
69
73
  exit
@@ -77,7 +81,8 @@ USAGE
77
81
  :output => STDOUT,
78
82
  :password => '',
79
83
  :cipher => 'aes',
80
- :key_size => 128
84
+ :key_size => 128,
85
+ :hardened => false
81
86
  }
82
87
 
83
88
  self.parser(options).parse!(args)
@@ -100,13 +105,14 @@ begin
100
105
  :user_password => @options[:password],
101
106
  :owner_password => @options[:password],
102
107
  :cipher => @options[:cipher],
103
- :key_size => @options[:key_size]
108
+ :key_size => @options[:key_size],
109
+ :hardened => @options[:hardened]
104
110
  )
105
111
  pdf.save(@options[:output], :noindent => true)
106
112
 
107
113
  rescue SystemExit
108
114
  rescue Exception => e
109
- STDERR.puts "#{e.class}: #{e.message}"
115
+ STDERR.puts "#{e.class}: #{e.message} #{e.backtrace}"
110
116
  exit 1
111
117
  end
112
118
 
@@ -112,7 +112,6 @@ module Origami
112
112
  def initialize(script)
113
113
  super(:JS => script)
114
114
  end
115
-
116
115
  end
117
116
 
118
117
  #
@@ -0,0 +1,96 @@
1
+ =begin
2
+
3
+ = File
4
+ docmdp.rb
5
+
6
+ = Info
7
+ This file is part of Origami, PDF manipulation framework for Ruby
8
+ Copyright (C) 2010 Guillaume Delugr� <guillaume@security-labs.org>
9
+ All right reserved.
10
+
11
+ Origami is free software: you can redistribute it and/or modify
12
+ it under the terms of the GNU Lesser General Public License as published by
13
+ the Free Software Foundation, either version 3 of the License, or
14
+ (at your option) any later version.
15
+
16
+ Origami is distributed in the hope that it will be useful,
17
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
18
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19
+ GNU Lesser General Public License for more details.
20
+
21
+ You should have received a copy of the GNU Lesser General Public License
22
+ along with Origami. If not, see <http://www.gnu.org/licenses/>.
23
+
24
+ =end
25
+
26
+ module Origami
27
+
28
+ class Null
29
+ def to_docmdp_str
30
+ "\000"
31
+ end
32
+ end
33
+
34
+ class Integer
35
+ def to_docmdp_str
36
+ [ 1, self.value & 0xFFFFFFFF ].pack("CN")
37
+ end
38
+ end
39
+
40
+ class Real
41
+ def to_docmdp_str
42
+ [ 2, self.value.round & 0xFFFFFFFF ].pack("CN")
43
+ end
44
+ end
45
+
46
+ class Boolean
47
+ def to_docmdp_str
48
+ [ 3, (self.false?) ? 0 : 1 ].pack("CN")
49
+ end
50
+ end
51
+
52
+ class Name
53
+ def to_docmdp_str
54
+ [ 4, self.to_s.length, self.to_s ].pack("CNA*")
55
+ end
56
+ end
57
+
58
+ class String
59
+ def to_docmdp_str
60
+ [ 5, self.to_s.length, self.to_s ].pack("CNA*")
61
+ end
62
+ end
63
+
64
+ class Dictionary
65
+ def to_docmdp_str(*fields)
66
+ if fields.empty?
67
+ self.each_pair { |key, value|
68
+
69
+ }
70
+ else
71
+
72
+ end
73
+ end
74
+ end
75
+
76
+ class Array
77
+ def to_docmdp_str
78
+ str = [ 7, self.length ].pack("CN")
79
+
80
+ self.each do |obj|
81
+ str << obj.to_docmdp_str
82
+ end
83
+
84
+ str
85
+ end
86
+ end
87
+
88
+ class Stream
89
+ def to_docmdp_str
90
+ [ 8, self.dictionary.size ].pack("CN") +
91
+ self.dictionary.to_docmdp_str(:DecodeParms, :F, :FDecodeParms, :FFilter, :Filter, :Length) +
92
+ [ self.rawdata.length, self.rawdata ].pack("NA*")
93
+ end
94
+ end
95
+
96
+ end
@@ -49,7 +49,6 @@ module Origami
49
49
 
50
50
  #
51
51
  # Decrypts the current document (only RC4 40..128 bits).
52
- # TODO: AESv3
53
52
  # _passwd_:: The password to decrypt the document.
54
53
  #
55
54
  def decrypt(passwd = "")
@@ -209,6 +208,7 @@ module Origami
209
208
  :owner_passwd => '',
210
209
  :cipher => 'rc4', # :RC4 or :AES
211
210
  :key_size => 128, # Key size in bits
211
+ :hardened => false, # Use newer password validation (since Reader X)
212
212
  :encrypt_metadata => true, # Metadata shall be encrypted?
213
213
  :permissions => Encryption::Standard::Permissions::ALL # Document permissions
214
214
  }.update(options)
@@ -234,7 +234,12 @@ module Origami
234
234
  if params[:key_size] == 128
235
235
  version = revision = 4
236
236
  elsif params[:key_size] == 256
237
- version = revision = 5
237
+ version = 5
238
+ if params[:hardened]
239
+ revision = 6
240
+ else
241
+ revision = 5
242
+ end
238
243
  else
239
244
  raise EncryptionError, "Invalid AES key length (Only 128 and 256 bits keys are supported)"
240
245
  end
@@ -1069,6 +1074,14 @@ module Origami
1069
1074
  field :Perms, :Type => String, :Version => '1.7', :ExtensionLevel => 3
1070
1075
  field :P, :Type => Integer, :Default => 0, :Required => true
1071
1076
  field :EncryptMetadata, :Type => Boolean, :Default => true, :Version => "1.5"
1077
+
1078
+ def pdf_version_required #:nodoc:
1079
+ if self.R > 5
1080
+ [ 1.7, 8 ]
1081
+ else
1082
+ super
1083
+ end
1084
+ end
1072
1085
 
1073
1086
  #
1074
1087
  # Computes the key that will be used to encrypt/decrypt the document contents with user password.
@@ -1099,7 +1112,12 @@ module Origami
1099
1112
  passwd = password_to_utf8(userpassword)
1100
1113
 
1101
1114
  uks = self.U[40, 8]
1102
- ukey = Digest::SHA256.digest(passwd + uks)
1115
+
1116
+ if self.R == 5
1117
+ ukey = Digest::SHA256.digest(passwd + uks)
1118
+ else
1119
+ ukey = compute_hardened_hash(passwd, uks)
1120
+ end
1103
1121
 
1104
1122
  iv = ::Array.new(AES::BLOCKSIZE, 0).pack("C*")
1105
1123
  AES.new(ukey, nil, false).decrypt(iv + self.UE.value)
@@ -1108,15 +1126,20 @@ module Origami
1108
1126
 
1109
1127
  #
1110
1128
  # Computes the key that will be used to encrypt/decrypt the document contents with owner password.
1111
- # Revision 5 only.
1129
+ # Revision 5 and above.
1112
1130
  #
1113
1131
  def compute_owner_encryption_key(ownerpassword)
1114
- if self.R == 5
1132
+ if self.R >= 5
1115
1133
  passwd = password_to_utf8(ownerpassword)
1116
1134
 
1117
1135
  oks = self.O[40, 8]
1118
- okey = Digest::SHA256.digest(passwd + oks)
1119
-
1136
+
1137
+ if self.R == 5
1138
+ okey = Digest::SHA256.digest(passwd + oks)
1139
+ else
1140
+ okey = compute_hardened_hash(passwd, oks)
1141
+ end
1142
+
1120
1143
  iv = ::Array.new(AES::BLOCKSIZE, 0).pack("C*")
1121
1144
  AES.new(okey, nil, false).decrypt(iv + self.OE.value)
1122
1145
  end
@@ -1136,21 +1159,32 @@ module Origami
1136
1159
  self.O = owner_key
1137
1160
  self.U = compute_user_password(userpassword, salt)
1138
1161
 
1139
- elsif self.R == 5
1162
+ else
1140
1163
  upass = password_to_utf8(userpassword)
1141
1164
  opass = password_to_utf8(ownerpassword)
1142
1165
 
1143
1166
  uvs, uks, ovs, oks = ::Array.new(4) { ::Array.new(8) { rand(255) }.pack("C*") }
1144
1167
  file_key = ::Array.new(32) { rand(256) }.pack("C*")
1145
1168
  iv = ::Array.new(AES::BLOCKSIZE, 0).pack("C*")
1146
-
1147
- ukey = Digest::SHA256.digest(upass + uks)
1148
- okey = Digest::SHA256.digest(opass + oks)
1149
1169
 
1170
+ if self.R == 5
1171
+ ukey = Digest::SHA256.digest(upass + uks)
1172
+ okey = Digest::SHA256.digest(opass + oks)
1173
+ else
1174
+ ukey = compute_hardened_hash(upass, uks)
1175
+ okey = compute_hardened_hash(upass, oks)
1176
+ end
1177
+
1150
1178
  self.UE = AES.new(ukey, iv, false).encrypt(file_key)[iv.size, 32]
1151
1179
  self.OE = AES.new(okey, iv, false).encrypt(file_key)[iv.size, 32]
1152
- self.U = Digest::SHA256.digest(upass + uvs) + uvs + uks
1153
- self.O = Digest::SHA256.digest(opass + ovs + self.U) + ovs + oks
1180
+
1181
+ if self.R == 5
1182
+ self.U = Digest::SHA256.digest(upass + uvs) + uvs + uks
1183
+ self.O = Digest::SHA256.digest(opass + ovs + self.U) + ovs + oks
1184
+ else
1185
+ self.U = compute_hardened_hash(upass, uvs) + uvs + uks
1186
+ self.O = compute_hardened_hash(opass, ovs, self.U) + ovs + oks
1187
+ end
1154
1188
 
1155
1189
  perms =
1156
1190
  [ self.P ].pack("V") + # 0-3
@@ -1168,7 +1202,7 @@ module Origami
1168
1202
  #
1169
1203
  # Checks user password.
1170
1204
  # For version 2,3 and 4, _salt_ is the document ID.
1171
- # For version 5, _salt_ is the User Key Salt.
1205
+ # For version 5 and 6, _salt_ is the User Key Salt.
1172
1206
  #
1173
1207
  def is_user_password?(pass, salt)
1174
1208
 
@@ -1179,6 +1213,9 @@ module Origami
1179
1213
  elsif self.R == 5
1180
1214
  uvs = self.U[32, 8]
1181
1215
  Digest::SHA256.digest(pass + uvs) == self.U[0, 32]
1216
+ elsif self.R == 6
1217
+ uvs = self.U[32, 8]
1218
+ compute_hardened_hash(pass, uvs) == self.U[0, 32]
1182
1219
  end
1183
1220
  end
1184
1221
 
@@ -1195,6 +1232,9 @@ module Origami
1195
1232
  elsif self.R == 5
1196
1233
  ovs = self.O[32, 8]
1197
1234
  Digest::SHA256.digest(pass + ovs + self.U) == self.O[0, 32]
1235
+ elsif self.R == 6
1236
+ ovs = self.O[32, 8]
1237
+ compute_hardened_hash(pass, ovs, self.U[0,48]) == self.O[0, 32]
1198
1238
  end
1199
1239
  end
1200
1240
 
@@ -1258,6 +1298,55 @@ module Origami
1258
1298
  user_key.ljust(32, "\xFF")
1259
1299
  end
1260
1300
  end
1301
+
1302
+ #
1303
+ # Computes hardened hash used in revision 6 (extension level 8).
1304
+ #
1305
+ def compute_hardened_hash(password, salt, vector = '')
1306
+ block_size = 32
1307
+ input = Digest::SHA256.digest(password + salt + vector) + "\x00" * 32
1308
+ key = input[0, 16]
1309
+ iv = input[16, 16]
1310
+ digest, aes, h, x = nil, nil, nil, nil
1311
+
1312
+ i = 0
1313
+ while i < 64 or i < x[-1].ord + 32
1314
+ j = 0
1315
+ block = input[0, block_size]
1316
+
1317
+ if Origami::OPTIONS[:use_openssl]
1318
+ aes = OpenSSL::Cipher::Cipher.new("aes-128-cbc").encrypt
1319
+ aes.iv = iv
1320
+ aes.key = key
1321
+ aes.padding = 0
1322
+ else
1323
+ fail "You need OpenSSL support to encrypt/decrypt documents with this method"
1324
+ end
1325
+
1326
+ 64.times do |j|
1327
+ x = aes.update(block)
1328
+ unless vector.empty?
1329
+ x += aes.update(vector)
1330
+ end
1331
+
1332
+ if j == 0
1333
+ block_size = 32 + (x.unpack("C16").inject(0) {|a,b| a+b} % 3) * 16
1334
+ digest = Digest::SHA2.new(block_size << 3)
1335
+ end
1336
+
1337
+ digest.update(x)
1338
+ end
1339
+
1340
+ h = digest.digest
1341
+ key = h[0, 16]
1342
+ input[0, block_size] = h[0, block_size]
1343
+ iv = h[16, 16]
1344
+
1345
+ i = i + 1
1346
+ end
1347
+
1348
+ h[0, 32]
1349
+ end
1261
1350
 
1262
1351
  def xor(str, byte) #:nodoc:
1263
1352
  str.split(//).map!{|c| (c[0].ord ^ byte).chr }.join