File Coverage

blib/lib/B/Deobfuscate.pm
Criterion Covered Total %
statement 44 243 18.1
branch 2 104 1.9
condition 0 28 0.0
subroutine 13 28 46.4
pod 1 14 7.1
total 60 417 14.3


line stmt bran cond sub pod time code
1             package B::Deobfuscate;
2 1     1   4847 use strict;
  1         2  
  1         26  
3 1     1   5 use warnings;
  1         1  
  1         28  
4 1     1   4 use vars qw( @ISA $VERSION );
  1         10  
  1         39  
5 1     1   3 use B qw( main_cv main_root main_start );
  1         2  
  1         63  
6 1     1   3 use B::Deparse;
  1         2  
  1         44  
7              
8             BEGIN {
9 1     1   13 @ISA = 'B::Deparse';
10 1         1 $VERSION = '0.20';
11              
12 1         2 for my $func (qw( begin_av init_av check_av end_av )) {
13              
14             ## no critic
15 1     1   4 no strict 'refs';
  1         1  
  1         123  
16 4 50       4 if ( defined &{"B::$func"} ) {
  4         14  
17 4         72 B->import($func);
18             }
19             else {
20              
21             # If I couldn't create it, I'll just declare it to keep lint happy.
22 0         0 eval "sub $func;";
23             }
24             }
25              
26             # B::perlstring was added in 5.8.0
27 1 50       3 if ( defined &B::perlstring ) {
28 1         37 B->import('perlstring');
29             }
30             else {
31 0         0 *perlstring = sub { '"' . quotemeta( shift @_ ) . '"' };
  0         0  
32             }
33              
34             }
35 1     1   820 use B::Keywords qw( @Barewords @Symbols );
  1         1095  
  1         102  
36              
37 1     1   4 use Carp 'confess';
  1         2  
  1         41  
38 1     1   806 use IO::Handle ();
  1         6394  
  1         22  
39 1     1   663 use YAML qw( LoadFile Dump );
  1         9380  
  1         262  
40              
41             # use Data::Postponed 'postpone_forever';
42 0     0 0   sub postpone_forever { return shift @_ }
43              
44             sub load_keywords {
45 0     0 0   my $self = shift @_;
46 0           my $p = $self->{ +__PACKAGE__ };
47              
48 0           return $p->{keywords} = {
49 0           map { $_, undef } @Barewords,
50              
51             # Snip the sigils.
52 0           map { substr $_, 1 } @Symbols
53             };
54             }
55              
56             sub load_unknown_dict {
57 0     0 0   my $self = shift @_;
58 0           my $p = $self->{ +__PACKAGE__ };
59              
60 0           my $dict_data;
61              
62             # slurp the entire dictionary at once
63 0 0         if ( defined( my $dict_file = $p->{unknown_dict_file} ) ) {
64 0 0         open my $fh, '<', $dict_file
65             or confess "Cannot open dictionary $dict_file: $!";
66 0           local $/; ## no critic
67 0           $dict_data = [<$fh>];
68             }
69             else {
70             LOAD_DICTIONARY_MODULE:
71 0           for my $module ( $p->{unknown_dict_module}, 'PGPHashKeywords',
72             'Flowers' )
73             {
74 0 0         next if not defined $module;
75 0           eval "require B::Deobfuscate::Dict::$module"; ## no critic
76 0 0         next if $@;
77              
78 1     1   8 no strict 'refs'; ## no critic
  1         15  
  1         2096  
79 0           $dict_data = ${"B::Deobfuscate::Dict::$module"};
  0            
80 0           last LOAD_DICTIONARY_MODULE;
81             }
82             }
83              
84 0 0         unless ($dict_data) {
85 0           confess "The symbol dictionary was empty!";
86             }
87              
88 0           my $k = $self->load_keywords;
89              
90 0 0         $p->{unknown_dict_data} = [
91 0   0       sort { length $a <=> length $b or $a cmp $b }
92 0           grep { not( /\W/ or exists $k->{$_} ) }
93             split /\n/,
94             $dict_data
95             ];
96              
97 0 0         unless ( scalar @{ $p->{'unknown_dict_data'} } ) {
  0            
98 0           confess "The symbol dictionary is empty!";
99             }
100              
101 0           return;
102             }
103              
104             sub next_short_dict_symbol {
105 0     0 0   my $self = shift @_;
106 0           my $p = $self->{ +__PACKAGE__ };
107              
108 0           my $sym = shift @{ $p->{unknown_dict_data} };
  0            
109 0           push @{ $p->{used_symbols} }, $sym;
  0            
110              
111 0 0         unless ($sym) {
112 0           confess "The symbol dictionary has run out and is now empty";
113             }
114              
115 0           return $sym;
116             }
117              
118             sub next_long_dict_symbol {
119 0     0 0   my $self = shift @_;
120 0           my $p = $self->{ +__PACKAGE__ };
121              
122 0           my $sym = pop @{ $p->{unknown_dict_data} };
  0            
123 0           push @{ $p->{used_symbols} }, $sym;
  0            
124              
125 0 0         unless ($sym) {
126 0           confess "The symbol dictionary has run out and is now empty";
127             }
128              
129 0           return $sym;
130             }
131              
132             sub load_user_config {
133 0     0 0   my $self = shift @_;
134 0           my $p = $self->{ +__PACKAGE__ };
135 0           my $config_file = $p->{user_config};
136              
137 0 0         return unless $config_file;
138              
139 0 0         unless ( -f $config_file ) {
140 0           confess "Configuration file $config_file doesn't exist";
141             }
142              
143 0           my $config = ( LoadFile($config_file) )[0];
144 0           $p->{globals_to_ignore} = $config->{globals_to_ignore};
145 0           $p->{pad_symbols} = $config->{lexicals};
146 0           $p->{gv_symbols} = $config->{globals};
147 0 0         if ( $config->{dictionary} ) {
148 0           $p->{unknown_dict_file} = $config->{dictionary};
149             }
150 0 0         if ( $config->{global_regex} ) {
151 0           $p->{global_regex} = qr/$config->{global_regex}/;
152             }
153              
154             # Symbols that are listed with an undef value actually
155             # just aren't renamed at all.
156 0           for my $symt_nym (qw/pad gv/) {
157 0           my $symt = $p->{ $symt_nym . "_symbols" };
158 0           for my $symt_key ( keys %$symt ) {
159 0 0         if ( not defined $symt->{$symt_key} ) {
160 0           $symt->{$symt_key} = $symt_key;
161             }
162             }
163             }
164              
165 0           return;
166             }
167              
168             sub gv_should_be_renamed {
169 0     0 0   my ( $self, $sigil, $name ) = @_;
170 0           my $p = $self->{ +__PACKAGE__ };
171 0           my $k = $p->{keywords};
172              
173 0 0         confess("Undefined sigil") unless defined $sigil;
174 0 0         confess("Undefined name") unless defined $name;
175              
176             # Bug 24334: $1 gets passed in w/o a sigil. Dunno why. That's wrong and broke the previous version of
177             # the regexp which read m{^\$\d+\z}
178              
179             # Ignore keywords.
180             return
181 0 0 0       if exists $k->{$name}
182             or "$sigil$name" =~ m{^\$?\d+\z};
183              
184 0 0 0       if ( exists $p->{gv_symbols}{$name}
185             or $name =~ $p->{gv_match} )
186             {
187 0           return 1;
188             }
189 0           return;
190             }
191              
192             sub rename_pad {
193 0     0 0   my ( $self, $name ) = @_;
194 0           my $p = $self->{ +__PACKAGE__ };
195              
196 0 0         my ($sigil) = $name =~ m{^(\W+)}
197             or confess "Invalid pad variable name $name";
198              
199 0           my $dict = $p->{pad_symbols};
200 0 0         return $dict->{$name} if $dict->{$name};
201              
202             # $dict->{$name} = $name;
203 0           $dict->{$name} = postpone_forever $sigil . $self->next_short_dict_symbol;
204              
205 0 0         unless ( $dict->{$name} ) {
206 0           confess "The suggested name for the lexical variable $name is empty";
207             }
208 0           return $dict->{$name};
209             }
210              
211             sub lookup_sigil {
212 0     0 0   my $rv = shift @_;
213              
214 0 0         return $rv =~ /(?:gv|pad|rv2)sv\z/ ? '$'
    0          
    0          
    0          
    0          
215             : $rv =~ /(?:gvav|padav|av2arylen|rv2av|aelemfast|aelem|aslice)\z/
216             ? '@'
217             : $rv =~ /(?:padhv|rv2hv|helem|hslice)\z/ ? '%'
218             : $rv =~ /rv2cv\z/ ? '&'
219             : $rv =~ /(?:gv|gelem|rv2gv)\z/ ? ''
220             :
221              
222             # Nothing valid;
223             ();
224             }
225              
226             sub rename_gv {
227 0     0 0   my ( $self, $name ) = @_;
228 0           my $p = $self->{ +__PACKAGE__ };
229              
230 0           my $sigil_debug = '';
231 0           my $sigil;
232             FIND_SIGIL: {
233 0           for ( my $cx = 0; not defined $sigil; ++$cx ) {
  0            
234 0           my ( undef, undef, undef, $rv ) = caller $cx;
235 0 0         if ( not $rv ) {
236 0           confess
237             "No sigil could be found. Please report the following text:\n$sigil_debug\n";
238             }
239              
240 0           $sigil = lookup_sigil($rv);
241              
242 0           $sigil_debug .= "$cx = $rv\n";
243             }
244             }
245              
246 0 0         unless ( defined $sigil ) {
247 0           confess
248             "No sigil could be found. Please report the following text:\n$sigil_debug\n";
249             }
250              
251 0 0         return $name unless $self->gv_should_be_renamed( $sigil, $name );
252              
253 0           my $dict = $p->{gv_symbols};
254              
255 0           my $sname = "$sigil$name";
256 0 0         return $dict->{$sname} if exists $dict->{$sname};
257 0           $dict->{$sname} = postpone_forever $self->next_long_dict_symbol;
258              
259 0 0         unless ( $dict->{$sname} ) {
260 0           confess "$sname could not be renamed.";
261             }
262              
263 0           return $dict->{$sname};
264             }
265              
266             ## OVERRIDE METHODS FROM B::Deparse
267              
268             sub new {
269 0     0 1   my $class = shift @_;
270 0           my $self = $class->SUPER::new(@_);
271 0           my $p = $self->{ +__PACKAGE__ } = {};
272 0           $p->{unknown_dict_file} = undef;
273 0           $p->{unknown_dict_module} = undef;
274 0           $p->{unknown_dict_data} = undef;
275 0           $p->{user_config} = undef;
276 0           $p->{gv_match} = qw/^[[:lower:][:digit:]_]+\z/;
277 0           $p->{pad_symbols} = {};
278 0           $p->{gv_symbols} = {};
279 0           $p->{output_yaml} = 0;
280 0           $p->{output_fh} = \*STDOUT;
281              
282 0           while ( my $arg = shift @_ ) {
283             ## no critic
284 0 0         if ( $arg =~ m{^-d([^,]+)} ) {
    0          
    0          
    0          
    0          
285 0           $p->{unknown_dict_file} = $1;
286             }
287             elsif ( $arg =~ m{^-D([^,]+)} ) {
288 0           $p->{unknown_dict_module} = $1;
289             }
290             elsif ( $arg =~ m{^-c([^,]+)} ) {
291 0           $p->{user_config} = $1;
292             }
293             elsif ( $arg =~ m{^-m/([^/]+)/} ) {
294 0           $p->{gv_match} = $1;
295             }
296             elsif ( $arg =~ m{^-y} ) {
297 0           $p->{output_yaml} = 1;
298             }
299             }
300              
301 0           $self->load_user_config;
302 0           $self->load_unknown_dict;
303              
304 0           return $self;
305             }
306              
307             sub compile { ## no critic Complex
308 0     0 0   my (@args) = @_;
309              
310             return sub {
311 0     0     my $source = '';
312 0           my $self = __PACKAGE__->new(@args);
313              
314             # First deparse command-line args
315 0 0         if ( defined $^I ) { # deparse -i
316 0           $source .= q(BEGIN { $^I = ) . perlstring($^I) . qq(; }\n);
317             }
318 0 0         if ($^W) { # deparse -w
319 0           $source .= qq(BEGIN { \$^W = $^W; }\n);
320             }
321             ## no critic PackageVar
322 0 0 0       if ( $/ ne "\n" or defined $O::savebackslash ) { # deparse -l -0
323 0   0       my $fs = perlstring($/) || 'undef';
324 0   0       my $bs = perlstring($O::savebackslash) || 'undef';
325 0           $source .= qq(BEGIN { \$/ = $fs; \$\\ = $bs; }\n);
326             }
327              
328             # I need to do things differently depending on the perl
329             # version.
330 0 0         if ( $] >= 5.008 ) {
331 0 0 0       if ( defined &begin_av
332             and begin_av->isa('B::AV') )
333             {
334 0           $self->todo( $_, 0 ) for begin_av->ARRAY;
335             }
336 0 0 0       if ( defined &check_av
337             and check_av->isa('B::AV') )
338             {
339 0           $self->todo( $_, 0 ) for check_av->ARRAY;
340             }
341 0 0 0       if ( defined &init_av
342             and init_av->isa('B::AV') )
343             {
344 0           $self->todo( $_, 0 ) for init_av->ARRAY;
345             }
346 0 0 0       if ( defined &end_av
347             and end_av->isa('B::AV') )
348             {
349 0           $self->todo( $_, 0 ) for end_av->ARRAY;
350             }
351              
352 0           $self->stash_subs;
353 0           $self->{curcv} = main_cv;
354 0           $self->{curcvlex} = undef;
355             }
356             else {
357              
358             # 5.6.x
359 0           $self->stash_subs('main');
360 0           $self->{curcv} = main_cv;
361 0           $self->walk_sub( main_cv, main_start );
362             }
363              
364 0           $source .= join "\n", $self->print_protos;
365 0           @{ $self->{subs_todo} }
  0            
366 0           = sort { $a->[0] <=> $b->[0] } @{ $self->{subs_todo} };
  0            
367 0 0         $source .= join "\n", $self->indent( $self->deparse( main_root, 0 ) ),
368             "\n"
369             unless B::Deparse::null main_root;
370 0           my @text;
371 0           while ( scalar @{ $self->{subs_todo} } ) {
  0            
372 0           push @text, $self->next_todo;
373             }
374 0 0         $source .= join "\n", $self->indent( join "", @text ), "\n"
375             if @text;
376              
377             # Print __DATA__ section, if necessary
378 0 0         my $laststash
379             = defined $self->{curcop}
380             ? $self->{curcop}->stash->NAME
381             : $self->{curstash};
382             {
383             ## no critic
384 1     1   5 no strict 'refs';
  1         2  
  1         315  
  0            
385             ## use critic
386 0 0         if ( defined *{ $laststash . "::DATA" } ) {
  0            
387 0 0         if ( eof *{ $laststash . "::DATA" } ) {
  0            
388              
389             # I think this only happens when using B::Deobfuscate
390             # on itself.
391             {
392 0           local $/ = "__DATA__\n";
  0            
393 0           seek *{ $laststash . "::DATA" }, 0, 0;
  0            
394 0           readline *{ $laststash . "::DATA" };
  0            
395             }
396             }
397              
398 0           $source .= "__DATA__\n";
399 0           $source .= join '', readline *{ $laststash . "::DATA" };
  0            
400             }
401             }
402              
403 0           my $p = $self->{ +__PACKAGE__ };
404 0           my %dump = (
405             lexicals => $p->{pad_symbols},
406             globals => $p->{gv_symbols},
407             dictionary => $p->{unknown_dict_file},
408             global_regex => $p->{gv_match}
409             );
410              
411 0 0         if ( $p->{output_yaml} ) {
412 0           $p->{output_fh}->print( Dump( \%dump, $source ) );
413             }
414             else {
415 0           $p->{output_fh}->print($source);
416             }
417              
418 0           return;
419 0           };
420             }
421              
422             sub padname {
423 0     0 0   my $self = shift @_;
424 0           my $padname = $self->SUPER::padname(@_);
425              
426 0           return $self->rename_pad($padname);
427             }
428              
429             sub gv_name {
430 0     0 0   my $self = shift @_;
431 0           my $gv_name = $self->SUPER::gv_name(@_);
432              
433 0           return $self->rename_gv($gv_name);
434             }
435              
436             # BEGIN {
437             # ## no critic
438             # no strict 'refs';
439             # for my $sub ( grep defined &$_, keys %B::Deobfuscate:: ) {
440             # my $orig = \&$sub;
441             # *$sub = sub {
442             # print "$sub\n";
443             # &$orig;
444             # };
445             # }
446             # }
447              
448             1;
449              
450             ## Local Variables:
451             ## perl-lint-bin: "/home/josh/bin/perl/5.9.4/bin/perl5.9.4"
452             ## eval: (setenv "/home/josh/src/B-Deobfuscate/lib" "PERL5LIB")
453             ## End: