Skip to content

Commit 45a1e68

Browse files
authored
feat: improve obfus newline handling (#12)
Removed `sed` and pure `perl` for newline parsing/removing. Fixed edge cases like arrays, quotes, etc.
1 parent 76999ae commit 45a1e68

3 files changed

Lines changed: 141 additions & 35 deletions

File tree

.bin/obfus

Lines changed: 138 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ sub obfuscate {
146146
$var_index++;
147147
}
148148
my %vars=();
149-
while(my $line=<$ifh>) {
149+
START: while(my $line=<$ifh>) {
150150
if ($delete_blanks && (
151151
$line =~ m/^[ \t]*#.*/ || # [^!]
152152
$line =~ m/^[ \t]*$/ ||
@@ -156,7 +156,32 @@ sub obfuscate {
156156
next;
157157
}
158158

159+
# Flatten out the code
160+
# ignore
161+
# - open quotes (single or double)
162+
# - here documents
159163
if ($flatten) {
164+
if ($line =~ m/<<\s*['"]?(\w+)['"]?\s*$/) {
165+
my $end = $1;
166+
print $ofh $line;
167+
while(my $line=<$ifh>) {
168+
print $ofh $line;
169+
last if $line =~ m/$end/;
170+
}
171+
next;
172+
}
173+
# todo better handling of quotes
174+
# for my $q ("'", '"') {
175+
# my ($n) = scalar( @{[ $line=~/(?:(?:\\$q)|($q))/gi ]} );
176+
# if ($n % 2 == 1) {
177+
# do {
178+
# $line =~ s/\n/$q\$'\\n'$q/;
179+
# print $ofh $line;
180+
# $line = <$ifh>;
181+
# } while ($line !~ m/[^\\]$q(\s|\t)*(\n|;)/);
182+
# next START;
183+
# }
184+
# }
160185
$line =~ s/^[ \t]*//;
161186
}
162187
# clear ;-ending lines . This avoid bugs on aggressive mode
@@ -249,44 +274,125 @@ sub obfuscate {
249274
}
250275
close $ifh;
251276
close $ofh;
277+
}
278+
sub newlines {
279+
my $file=shift;
280+
281+
my $data = do {
282+
open my $fh, '<', $file or die "error opening $file: $!";
283+
local $/; <$fh>
284+
};
285+
open(my $ofh, ">", $file) or die "Couldn't open '".$file."' for writing because: ".$!;
286+
287+
# go through the file and remove all possible newlines
288+
# do not remove
289+
# - newlines in case statements
290+
# - new lines in here documents
291+
# replace
292+
# - new lines in quotes (single or double) with $'\n'
293+
# remove
294+
# - \ with newline at the end of the line
295+
# replace newlines with ; unless
296+
# - in array declaration
297+
# - || or && or | or { or ( at the end of the line
298+
# 'then' or 'do' or 'else' at the end of the line
299+
newline_process($data,$ofh);
300+
close $ofh;
301+
}
252302

253-
# aggressive
254-
if ($aggressive) {
255-
# say "Aggressive mode";
256-
my $var = <<EOS;
257-
1,\${ # from second line to the end
258-
:loop # label for loop behavior
259-
N # join next line with current, separating by \\n
260-
s/[\\]\\n//g
261-
s/\\(}\\|))\\|esac\\|done\\|fi\\)\\s*\\n/\\1;/g # line break to ';' on lines ending with '}', '))', 'esac','done' or 'fi'
262-
s/\\(do\\|{\\||\\|then\\|else\\)\\s*\\n/\\1 /g # line break to ' ' on lines ending with 'do', '{', '|', 'then' or 'else'
263-
s/\\n\\(function\\|while\\)/;\\1/g # line break to ';' on lines starting with 'function' or 'while'
264-
s/;;;/\\n;;/g # fix ;;; bug
265-
s/\\(\\([^);]\\);\\?\\n\\(if\\|else\\|done\\|\\[\\)\\)/\\2;\\3/g # lines beginning by if, else, done or [ preceded by line break from lines not ending with ^,;,], should change \\n into ;
266-
s/\\("[^"]\\+"\\)\\n/\\1;/g # lines ending with open and closed ", change \\n into ;
267-
s/\\n\\([a-z][a-z0-9]*=.*\\)\\n/;\\1;/g # var definition lines alone e.g. \\nvar=value\\n, change to ;var=value;
268-
s/expect { /expect {\\n/g
269-
s/\\(return[ ;0-9]*\\)\\n/\\1;/g # return with or without value and ending with ; or not
270-
s/\\(local [a-z0-9]+\\|>&[0-9]\\)\\n/\\1;/g # return with or without value and ending with ; or not
271-
s/\\([(]\\);/\\1/g
272-
s/\\n\\([.]\\|read\\|exec\\|return\\|echo\\|done\\|until\\|local\\|exit\\|if\\|fi\\|elif\\|else\\|[(!}[):]\\|mapfile\\|continue\\|declare\\|for\\|printf\\|[^);]+(;|\\n)\\)/;\\1/g
273-
s/\\([(]\\)\\n/\\1/g
274-
s/\\(continue\\|break\\|[")]\\|=1\\)\\n/\\1;/g
275-
# /^[^(]+[)]/! { s/\\n/;/g }
276-
b loop # repeat from loop label down
303+
sub newline_process {
304+
my $data=shift;
305+
my $ofh=shift;
306+
307+
my $handle;
308+
open $handle, '<', \$data;
309+
while(my $line=<$handle>) {
310+
# is this a here document?
311+
if ($line =~ m/<<\s*['"]?(\w+)['"]?\s*$/) {
312+
my $end = $1;
313+
print $ofh $line;
314+
while(my $line=<$handle>) {
315+
print $ofh $line;
316+
last if $line =~ m/$end/;
317+
}
318+
next;
277319
}
278-
EOS
320+
# is this a case statement?
321+
if ($line =~ m/^\s*case/) {
322+
print $ofh $line;
323+
while(my $line=<$handle>) {
324+
if ($line =~ m/esac(\s|\t|;)/) {
325+
$line =~ s/(\s|\t)*\n/;/;
326+
print $ofh $line;
327+
last;
328+
}
329+
# collect line between ) and ;; and then process it
330+
if ($line =~ m/^([^()]+\))(?:\s|\t)*(.*)/) {
331+
print $ofh $1;
332+
my $block = $2;
333+
my $i = 0;
334+
while ($line !~ m/^(.*);;/) {
335+
$line = <$handle>;
336+
$line =~ s/(\s|\t)*//;
337+
$line =~ s/(\s|\t)*\n/\n/;
338+
$block .= $line;
339+
$i++;
340+
last if $i > 10;
341+
}
342+
$block =~ s/(?:\s|\t)*;;(\s|\t|\n)*$//;
343+
newline_process($block,$ofh);
344+
print $ofh ";;\n";
345+
}
346+
}
347+
next;
348+
}
349+
350+
# replace newlines with ; unless in array declaration
351+
if ($line =~ m/=\([^)]*\n/) {
352+
while ($line !~ m/\)(?:\s|\t)*(\n|;)/) {
353+
$line =~ s/\n/ /;
354+
print $ofh $line;
355+
$line = <$handle>;
356+
}
357+
goto PRINT;
358+
}
359+
360+
# skip newlines
361+
goto PRINT if $line =~ m/^(?:\s|\t)*\n/;
279362

280-
open(my $ofh, ">", 'sed_aggressive.txt');
281-
print $ofh $var;
282-
close $ofh;
363+
# remove \ at the end of the line
364+
goto PRINT if $line =~ s/\\\n//;
365+
366+
# remove newlines for || , && , | , { , ( , ; at the end of the line
367+
goto PRINT if $line =~ s/([|&{(]{1,2}|;)(?:\s|\t)*\n/$1 /;
368+
goto PRINT if $line =~ m/^(?:\s|\t)*[)]/;
369+
370+
# remove newlines for then and do
371+
goto PRINT if $line =~ s/(?:\s|\t)*(then|do|else)(?:\s|\t)*\n/$1 /;
372+
373+
# is this a quote? (single or double) replace newlines with $'\n'
374+
for my $q ("'", '"') {
375+
my ($n) = scalar( @{[ $line=~/(?:(?:\\$q)|($q))/gi ]} );
376+
if ($n % 2 == 1) {
377+
do {
378+
$line =~ s/\n/$q\$'\\n'$q/;
379+
print $ofh $line;
380+
$line = <$handle>;
381+
} while ($line !~ m/[^\\]$q(\s|\t)*(\n|;)/);
382+
goto PRINT;
383+
}
384+
}
283385

284-
# apply 'aggressive' sed filters to output file
285-
system("sed -i -f sed_aggressive.txt $output_file; rm sed_aggressive.txt");
386+
PRINT:
387+
# replace the rest of the newlines with ;
388+
$line =~ s/(\s|\t)*\n/;/;
389+
print $ofh $line;
286390
}
391+
close $handle;
287392
}
288393

289394
my ($input_file,$output_file,$new_variable_prefix,$delete_blanks,$flatten,$aggressive)=&parse_cmd_args();
290395
my @parsed_vars=&parse_vars_from_file($input_file);
291396
my @sorted_vars = sort { length($b) <=> length($a) } @parsed_vars;
292-
&obfuscate($input_file,$output_file,$new_variable_prefix,$delete_blanks,$flatten,$aggressive,@sorted_vars);
397+
&obfuscate($input_file,$output_file,$new_variable_prefix,$delete_blanks,$flatten,$aggressive,@sorted_vars);
398+
&newlines($output_file);

Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,12 @@ RUN set -eux \
2121
&& apt update \
2222
&& apt install -y perl \
2323
&& rm -rf /var/lib/apt/lists/*
24-
COPY .bin/obfus /usr/local/bin/obfus
2524

2625
# docs
2726
RUN set -eux \
2827
&& apt update \
2928
&& apt install -y gawk \
3029
&& rm -rf /var/lib/apt/lists/*
31-
COPY .bin/shdoc /usr/local/bin/shdoc
3230

3331
# lint
3432
COPY --from=koalaman/shellcheck:stable /bin/shellcheck /usr/local/bin/shellcheck
@@ -41,6 +39,8 @@ RUN set -eux \
4139
&& rm -rf /var/lib/apt/lists/*
4240

4341
# argsh itself
42+
COPY .bin/obfus /usr/local/bin/obfus
43+
COPY .bin/shdoc /usr/local/bin/shdoc
4444
COPY ./argsh.min.sh /usr/local/bin/argsh
4545

4646
# docker

argsh.min.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
#!/usr/bin/env bash
22
# shellcheck disable=SC2178 disable=SC2120 disable=SC1090 disable=SC2046 disable=SC2155
33
set -euo pipefail; ARGSH_COMMIT_SHA="${commit_sha}"; ARGSH_VERSION="${version}"
4-
${data};[[ "${BASH_SOURCE[0]}" != "${0}" ]] || argsh::shebang "${@}"
4+
${data}[[ "${BASH_SOURCE[0]}" != "${0}" ]] || argsh::shebang "${@}"

0 commit comments

Comments
 (0)