1+ from pathlib import Path
12from typing import Optional
23
34import io
89from unblob .extractors .command import Command
910
1011from ...models import (
12+ Extractor ,
13+ ExtractResult ,
1114 File ,
1215 Handler ,
1316 HandlerDoc ,
2124
2225logger = get_logger ()
2326
27+
28+ class PEExtractor (Extractor ):
29+ """
30+ PEExtractor extracts files embedded within PEs such as installers.
31+ """
32+
33+ def extract (self , inpath : Path , outdir : Path ) -> Optional [ExtractResult ]:
34+ binary = lief .PE .parse (inpath )
35+ if not binary :
36+ return None
37+
38+ if not self .is_nsis (binary ):
39+ return None
40+
41+ return Command ("7z" , "x" , "-y" , "{inpath}" , "-o{outdir}" ).extract (inpath , outdir )
42+
43+
44+ def is_nsis (self , binary : lief .PE .Binary ) -> bool :
45+ """
46+ Test if binary appears to be a Nullsoft Installer self-extracting archive
47+
48+ TODO: this series of tests is possibly too strict
49+ """
50+
51+ return binary .has_resources and \
52+ binary .resources_manager .has_manifest and \
53+ "Nullsoft" in binary .resources_manager .manifest
54+
55+
56+
2457class PEHandler (Handler ):
2558 NAME = "pe"
2659
@@ -30,12 +63,18 @@ class PEHandler(Handler):
3063 // MZ header
3164 4d 5a
3265 """
66+ ),
67+ HexString (
68+ """
69+ // PE header
70+ 50 45 00 00
71+ """
3372 )
3473 ]
3574
36- EXTRACTOR = Command (
37- "7z" , "x" , "-y" , "{inpath}" , "-o{outdir}"
38- )
75+
76+ EXTRACTOR = PEExtractor ()
77+
3978
4079 DOC = HandlerDoc (
4180 name = "pe" ,
@@ -55,21 +94,6 @@ class PEHandler(Handler):
5594 limitations = [],
5695 )
5796
58- def is_nsis (self , binary : lief .PE .Binary ) -> bool :
59- """
60- Test if binary appears to be a Nullsoft Installer self-extracting archive
61-
62- TODO: this series of tests is possibly too strict
63- """
64-
65- if not binary .has_resources :
66- return False
67-
68- if not binary .resources_manager .has_manifest :
69- return False
70-
71- return "Nullsoft" in binary .resources_manager .manifest
72-
7397
7498 def calculate_chunk (self , file : File , start_offset : int ) -> Optional [ValidChunk ]:
7599 file .seek (start_offset , io .SEEK_SET )
@@ -78,9 +102,6 @@ def calculate_chunk(self, file: File, start_offset: int) -> Optional[ValidChunk]
78102 if not binary :
79103 return None
80104
81- if not self .is_nsis (binary ):
82- return None
83-
84105 return ValidChunk (
85106 start_offset = start_offset ,
86107 end_offset = start_offset + binary .original_size ,
0 commit comments