-
Notifications
You must be signed in to change notification settings - Fork 29
/
unpack_repack_bin.sh
executable file
·1245 lines (1177 loc) · 39.1 KB
/
unpack_repack_bin.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/bin/bash
#
# This file is part of asafw.
# Copyright (c) 2017, Aaron Adams <aaron.adams(at)nccgroup(dot)trust>
# Copyright (c) 2017, Cedric Halbronn <cedric.halbronn(at)nccgroup(dot)trust>
#
# This script is used to unpack all Cisco ASA routers .bin firmwares,
# modify some files in order to start gdb after booting the OS.
# It can be used for anything related to modifying files in the filesystem.
#
# This is useful when debugging ASAv firmware in GNS3. Indeed, some firmware do
# not have GDB so we need to add it. Also, we can enable/disable that GDB starts
# at boot and wait for us to attach to the GDB server by commenting/uncommenting
# the appropriate line. Finally, it can be used for any custom modification we want
# to do (for testing purpose)
#
# This script can also be used to extract files from Cisco ASA routers .bin firmwares.
# It supports asa8**-k8.bin, asa9**-k8.bin firmware in both 32-bit and 64-bit.
# It does the following:
# - extract each .bin firmware into a directory with the same name (.extracted) in the same directory
# - tries to extract lina executable if -l is specified
#
# TODO:
# - remove temporary files? or order them
# - It does not support asa7**-k8.bin firmware yet because they don't contain
# a rootfs as other versions.
# - have a safe way to delete temporary files by specifying option from the command line
# (commented for now - use at your own risks)
#
# Dependencies
# - binwalk > 2.0
# sudo apt-get install binwalk
# eg: from the source: https://github.com/devttys0/binwalk
# - 7z
# sudo apt-get install p7zip-full
SCRIPTNAME="unpack_repack_bin"
# FUNCTIONS
log()
{
echo -n "[${SCRIPTNAME}] "
echo "$@"
}
dbglog()
{
if [ ! -z ${DEBUG} ]; then
log DEBUG: $@
fi
}
usage()
{
echo "Usage:"
echo "./unpack_repack_bin.sh -i <firmware_file> -o <out_dir> [-f -g -G -a -A -m -b -r -u -l <linabin_dir> -d -e -k]"
echo " -h, --help This help menu"
echo " -i, --input <firmware_file> What firmware bin to operate on. This option is always required"
echo " -o, --output <out_dir> Where to write new firmware"
echo " -f, --free-space Remove space from .bin to ensure injections fit"
echo " -g, --enable-gdb Set gdb to start on boot"
echo " -G, --disable-gdb Stop gdb from starting on boot"
echo " -a, --enable-aslr Turn on ASLR"
echo " -A, --disable-aslr Turn off ASLR"
echo " -m, --inject-gdb Inject gdbserver for firmware lacking this file"
echo " -b, --debug-shell Inject ssh-triggered debug shell"
echo " -B, --serial-shell Configure a serial shell on ASA 2nd serial port"
echo " -H, --lina-hook Inject hooks for monitor lina heap (requires -b)"
echo " -r, --root Root the bin to get a rootshell on boot"
echo " -c, --custom Custom functionality you can add yourself"
# XXX - Unused. Likely delete
# echo " -q, --gns3-fixup Gns?"
echo " -u, --unpack-only Unpack the firmware and nothing else"
echo " -l, --linabins <linabin_dir> Destination folder to save lina binaries"
echo " -d, --delete-extracted Delete files extracted during modification"
echo " -e, --delete-original-bin Delete the original firmware being modified"
echo " -k, --keep-rootfs Keep the extracted rootfs on disk"
echo " -s, --simple-name Use a simple name for the output .bin with just appended '-repacked'"
echo " -R, --repack-only Repack an existing unpacked dir. Requires --original-firmware"
echo " --replace-linamonitor <path> Use a simple name for the output .bin with just appended '-repacked'"
echo " --original-firmware <name> Name of original firmware file. for use with --repack-only"
echo " --bin-with-asa-to-inject <firmware_file> Additional firmware bin file to take /asa folder from and inject into the one specified with -i"
echo " -v, --verbose Display debug messages"
echo "Examples:"
echo " # Unpack and repack a firmware file, freeing space, enabling gdb, and injecting gdbserver bin. Output modifications to firmware_repacked dir"
echo " ./unpack_repack_bin.sh -i /home/user/firmware -o /home/user/firmware_repacked --free-space --enable-gdb --inject-gdb"
echo " # Unpack and repack a firmware file, freeing space, enabling gdb, and injecting gdbserver bin"
echo " ./unpack_repack_bin.sh -i /home/user/firmware/asa961-smp-k8.bin -f -g -m"
echo " # Unpack a firmware file and copy the lina and lina_monitor file in to linabins dir"
echo " ./unpack_repack_bin.sh -u -i /home/user/firmware -l /home/user/linabins"
echo " # Unpack a firmware file and keep the rootfs on disk for analysis"
echo " ./unpack_repack_bin.sh -u -i /home/user/firmware/asa924-k8.bin -k"
echo " # Repack an already unpacked firmware dir, freeing space and patching lina_monitor to bypass checksum validation"
echo " ./unpack_repack_bin.sh --repack-only -i _asa924-smp-k8.bin.extracted --output-bin asa924-smp-k8-repacked.bin --original-firmware /home/user/firmware/asa924-smp-k8.bin --free-space --replace-linamonitor /home/user/firmware/lina_monitor_patched"
echo " # Unpack and repack a firmware file, freeing space, enabling gdb, debug shell and linahook"
echo " ./unpack_repack_bin.sh -i asa924-smp-k8.bin -f -g -b -H hat"
exit 1
}
# determine_rootfs_name()
#
# Arguments:
# None
#
# Description:
# versions < 8.2.3 don't have the rootfs in rootfs.img but in a filename
# containing digits only ASAv961 has a a .gz additional extension in rootfs
# image name
##
determine_rootfs_name()
{
ROOTFS=rootfs.img
if [ -f ${ROOTFS} ]; then
log "Firmware uses regular rootfs/ dir"
elif [ -f "rootfs.img.gz" ]; then
ROOTFS=rootfs.img.gz
log "Firmware uses regular rootfs.img.gz file"
else
for EXTRACTFILE in $(find * -maxdepth 0 -type f);
do
TMP=`file ${EXTRACTFILE}`
if [[ $TMP == *"ASCII cpio archive"* ]];
then
ROOTFS=${EXTRACTFILE}
log "Firmware uses ${ROOTFS} rootfs file"
break
fi
done
fi
}
# extract_bin()
#
# Arguments:
# None
#
# Required Globals:
# FWFILE
#
# Notes:
# Expects current folder being the dirname of $FWFILE
#
# Description:
# Extracts the contents of a firmware .bin file using binwalk. The files are
# written to a directory called _<bin name>.extracted
#
# Can be called independent of other bin functions.
##
extract_bin()
{
log "extract_bin: $FWFILE"
if [ -z ${DEBUG} ]; then
${BINWALK} -e ${FWFILE} > /dev/null
else
${BINWALK} -e ${FWFILE}
fi
if [ $? != 0 ];
then
log "ERROR: Binwalk failed. Exiting"
exit 1
fi
FWFOLDER=$(pwd)/_${FWFILE}.extracted
if [ ! -d "${FWFOLDER}" ]; then
log "ERROR: binwalk extraction failed. Didn't find ${FWFOLDER}"
return
fi
cd ${FWFOLDER}
# better safe than sorry. If we can't go in, when we go out later
# we will go back in the arborescence and do bad stuff, such as delete
# files, etc...
if [ $? != 0 ];
then
log "ERROR: Couldn't enter ${FWFOLDER} for some reason. Exiting"
exit 1
fi
log "Extracted firmware to ${FWFOLDER}"
determine_rootfs_name
# we create a directory to avoid extracting everything
# in the middle of other files
if [ ! -d "rootfs" ]; then
mkdir rootfs
fi
cd rootfs
log "Extracting ${FWFOLDER}/rootfs/${ROOTFS} into $(pwd)"
# We really need --no-absolute-filenames as otherwise we may corrupt
# our own filesystem...
${CPIO} -id --no-absolute-filenames > /dev/null 2>&1 < ../${ROOTFS}
LINA=${FWFOLDER}/rootfs/asa/bin/lina
LINA_MONITOR=${FWFOLDER}/rootfs/asa/bin/lina_monitor
if [[ ! -z $LINABINDIR && -d $LINABINDIR ]]
then
mkdir ${LINABINDIR}/${FWFILE}
cp ${LINA} ${LINABINDIR}/${FWFILE}/
cp ${LINA_MONITOR} ${LINABINDIR}/${FWFILE}/
fi
cd .. # leave rootfs
cd .. # leave ${FWFOLDER}
# we need space here...
if [[ "$KEEP_ROOTFS" == "YES" ]]
then
log "Keeping rootfs"
# we only keep the rootfs directory which is not a regular file
for F in $(find ${FWFOLDER} -maxdepth 1 -type f);
do
log "Deleting \"${F}\""
rm -f "${F}"
done
fi
if [[ "$DELETE_EXTRACTED" == "YES" ]]
then
log "Deleting extracted files"
# We only delete folders of the following format _*.extracted
# The reason we do that is we don't want to rm -Rf arbitrary
# folders
log "Deleting \"${FWFOLDER}\""
rm -Rf ${FWFOLDER}
fi
if [[ "$DELETE_BIN" == "YES" ]]
then
log "Deleting original firmware bin"
log "Deleting \"${FWFILE}\""
rm ${FWFILE}
fi
}
# unpack_repack_bin()
#
# Arguments:
# None
#
# Required Globals:
# FWFILE
#
# Notes:
# Expects current folder being the dirname of $FWFILE
#
# Description:
# Primary workhorse function. Will attempt to unpack, modify, and repack as
# necessary.
##
unpack_repack_bin()
{
if [[ "${UNPACK_ONLY}" == "YES" ]]
then
extract_bin
else
# root is needed so the repacked version has the right uid/gid
if [ "$(whoami)" != "root" ]; then
log "You need to be root so repacked version has the right uid/gid"
log "NOTE: Use sudo -E if you sourced env.sh"
exit 1
fi
unpack_bin
# unpack_bin creates "work/" directory and extracts files into it
modify_bin "work/"
repack_bin "work/"
fi
}
# unpack_bin()
#
# Arguments:
# None
#
# Required Globals:
# FWFILE
#
# Notes:
# Expects current folder being the dirname of $FWFILE
#
# Description:
# Extracts a .bin using our asafw bin.py script. Creates a 'work/' directory
# which contains the extracted contenst of the rootfs.img
unpack_bin()
{
log "unpack_bin: $FWFILE"
INFILE=$(pwd)/${FWFILE}
# get filename without extension and extension
OUTFILE=$(basename "$FWFILE")
EXTFILE=${FWFILE##*.}
if [ ! -z ${FWFILE_WITH_ASA_TO_INJECT} ]
then
OUTFILE_PREFIX=${FWFILE_WITH_ASA_TO_INJECT}-in-${OUTFILE%.*}
else
OUTFILE_PREFIX=${OUTFILE%.*}
fi
OUTFILE_SUFFIX=
if [[ "${SIMPLE_NAME}" != "YES" ]]; then
# the more complex filename we could get is something like
# "asaXXX-smp-k8-in-asa921-smp-k8-noaslr-debugshell-hooked-gdbserver.bin"
if [[ "${DISABLE_ASLR}" == "YES" ]]
then
OUTFILE_SUFFIX=$OUTFILE_SUFFIX-noaslr
fi
if [[ "${DEBUGSHELL}" == "YES" ]]
then
OUTFILE_SUFFIX=$OUTFILE_SUFFIX-debugshell
fi
if [[ ! -z "${LINAHOOK}" ]]
then
OUTFILE_SUFFIX=$OUTFILE_SUFFIX-hooked
fi
fi
if [[ "${OUTFILE_SUFFIX}" == "" ]]
then
OUTFILE_SUFFIX=-repacked
fi
if [[ "${ROOT}" == "YES" ]]
then
OUTFILE_SUFFIX=$OUTFILE_SUFFIX-rooted
fi
if [[ "${ENABLE_GDB}" == "YES" ]]
then
OUTFILE_SUFFIX=$OUTFILE_SUFFIX-gdbserver
fi
OUTFILE_SUFFIX=$OUTFILE_SUFFIX.${EXTFILE}
OUTFILE=${OUTDIR}/${OUTFILE_PREFIX}${OUTFILE_SUFFIX}
# get filename without extension
BASEFWFILE=$(basename "$INFILE")
FOLDERFWFILE=$(dirname "$INFILE")
BASEFWFILE_NOEXT=${BASEFWFILE%.*}
GZIP_ORIGINAL=${FOLDERFWFILE}/${BASEFWFILE_NOEXT}-initrd-original.gz
CPIO_ORIGINAL=${FOLDERFWFILE}/${BASEFWFILE_NOEXT}-initrd-original.cpio
# we should not really care about the name of the gzip. However, if we want to re-unpack
# the file that we are repacking, we need that it uses the "rootfs.img" as this is what
# we use to locate the rootfs.img inside the .bin in bin.py
#GZIP_MODIFIED=${FOLDERFWFILE}/${BASEFWFILE_NOEXT}-initrd-modified.gz
# Actually looks like this is not even enough. Well, we should not need to do that several times
# anyway. We can always come back to the original one to do all in once :)
GZIP_MODIFIED=${FOLDERFWFILE}/rootfs.img.gz
VMLINUZ_ORIGINAL=${FOLDERFWFILE}/${BASEFWFILE_NOEXT}-vmlinuz
${FWTOOL} -u -f "$INFILE"
if [ $? != 0 ];
then
log "ERROR: ${FWTOOL} -u -f "$INFILE" failed"
exit 1
fi
${GUNZIP} -c "$GZIP_ORIGINAL" > "$CPIO_ORIGINAL"
if [ $? != 0 ];
then
log "ERROR: ${GUNZIP} -c $GZIP_ORIGINAL > $CPIO_ORIGINAL failed"
exit 1
fi
rm -Rf work
mkdir work
cd work
# We really need --no-absolute-filenames as otherwise we may corrupt
# our own filesystem...
${CPIO} -id --no-absolute-filenames > /dev/null 2>&1 < "$CPIO_ORIGINAL"
cd .. # leave work
}
# free_space()
#
# Arguments:
# None
#
# Notes:
# Expects $PWD to be an extracted rootfs directory
#
# Description:
# Attempts to free room in the firmware by rm'ing unused large files. This is
# done to accomodate patches or new files that might otherwise make the
# rootfs.img.gz file larger than the original, which would prevent injection.
##
free_space()
{
# free some space
if [[ "$FREE_SPACE" == "YES" ]]
then
log "Freeing space in extracted .bin"
rm -Rf usr/test/*
if [[ -e 'usr/bin/qemu-system-x86_64' ]]; then
rm usr/bin/qemu-system-x86_64 # 5.2 MB
fi
if [[ -e 'asa/html/dd/fdd.swf' ]]; then
rm asa/html/dd/fdd.swf # 1.3 MB
fi
fi
}
# disable_aslr()
#
# Arguments:
# None
#
# Notes:
# Expects $PWD to be an extracted rootfs directory
#
# Description:
# Configures the ASA kernel to disable ASLR. Useful for debugging 64-bit
# systems.
##
disable_aslr()
{
# disable ASLR
# /etc/sysctl.conf -> /etc/sysctl.conf.props so we follow symlink otherwise it will modify our host one :/
if [[ "$DISABLE_ASLR" == "YES" ]]
then
log "DISABLE ASLR"
# we can't just add the following line
#echo "kernel.randomize_va_space = 0" >> etc/sysctl.conf.procps
# because it looks like rcS.common overrides our value later in the boot process
# so we just make the modification in rcS.common :)
# deal with case when no randomize_va_space in asa/scripts/rcS.common, such as asav9101.qcow2
VASPACE=$(grep randomize_va_space "asa/scripts/rcS.common")
DISABLE_ASLR_ARGS=
if [ -n "$VASPACE" ]
then
sed -i 's/echo 2 > \/proc\/sys\/kernel\/randomize_va_space/echo 0 > \/proc\/sys\/kernel\/randomize_va_space/' asa/scripts/rcS.common
else
# use kernel parameter 'norandmaps' instead
DISABLE_ASLR_ARGS=--disable-aslr
fi
fi
}
# enable_gdb()
#
# Arguments:
# None
#
# Required Globals:
# FWFILE - name of firmware bieng modified
# FIRMWAREDIR - directory holding firmware images
#
# Notes:
# Expects $PWD to be an extracted rootfs directory
#
# Description:
# Enables gdb support in the current rootfs
##
enable_gdb()
{
# enable gdb at boot
if [[ "$ENABLE_GDB" == "YES" ]]
then
log "ENABLE GDB"
if [[ "$FWFILE" == *"asa803"* ]]
then
log "Using asa803 ASA gdb patching method and patching serial port in lina_monitor"
sed -i 's/\(\/asa\/bin\/lina_monitor\)/\1 -g -s \/dev\/ttyS0 -d/' etc/init.d/rcS
# XXX - This assumption about the ${FIRMWAREDIR} contents is
# error prone. If we require it, we should document it. We could
# consider include thihs _asa803/lina_monitor_patched file in asafw
cp ${FIRMWAREDIR}/_asa803/lina_monitor_patched $(pwd)/asa/bin/lina_monitor
elif [[ "$FWFILE" == *"asa804"* ]]
then
# XXX - untested - do we need to patch lina_monitor too?
log "Using asa804 ASA gdb patching method"
sed -i 's/\(\/asa\/bin\/lina_monitor\)/\1 -g -s \/dev\/ttyS0 -d/' asa/scripts/rcS
else
log "Using recent ASA gdb patching method"
sed -i 's/#\(.*\)ttyUSB0\(.*\)/\1ttyS0\2/' asa/scripts/rcS
# Don't output anything on the tty, as this breaks gdb with some Linux kernel setups
sed -i 's/ttyS0::once:\/tmp\/run_cmd/tty0::once:\/tmp\/run_cmd/' etc/inittab
fi
fi
}
# disable_gdb()
#
# Arguments:
# None
#
# Required Globals:
#
# Notes:
# Expects $PWD to be an extracted rootfs directory
#
# Description:
# Disables GDB in the current rootfs.
##
disable_gdb()
{
# disable gdb at boot
if [[ "$DISABLE_GDB" == "YES" ]]
then
log "DISABLE GDB"
sed -i 's/echo\(.*\)ttyUSB0\(.*\)/#echo\1ttyS0\2/' asa/scripts/rcS
fi
}
# fix_gns3_interface()
#
# Arguments:
# None
#
# Required Globals:
# None
#
# Notes:
# Expects $PWD to be an extracted rootfs directory
#
# Description:
# XXX - Think this was going to be for fixing interfaces for emulated 32-bit
# firmwares. We can probably remove it
##
fix_gns3_interface()
{
# fix GNS3 network interface
if [[ "$FIX_GNS3_INTERFACE" == "YES" ]]
then
log "FIXING GNS3 INTERFACE"
log "ERROR: Not implemented yet"
#sed -i 's/echo\(.*\)ttyUSB0\(.*\)/#echo\1ttyS0\2/' asa/scripts/rcS
fi
}
# inject_gdb()
#
# Arguments:
# None
#
# Required Globals:
# FWFILE - name of current firmware being worked on
# FIRMWAREDIR - directory holding collection of firmware
#
# Notes:
# Expects $PWD to be an extracted rootfs directory
# XXX - Makes undocumented assumptions about FIRMWAREDIR layout that will
# sometimes break. We should include those gdb files in asafw repo and
# copy them from somewhere static
#
# Description:
# Injects a gdbserver binary from a separate firmware in FIRMWAREDIR into the
# firmware being worked on. This is relative only to 64-bit ASA images or old
# 32-bit ASA devices.
##
inject_gdb()
{
# inject gdbserver from other firmware
if [[ "$INJECT_GDB" == "YES" ]]
then
if [[ -e 'usr/bin/gdbserver' ]]; then
log "WARNING: This firmware already has a gdbserver."
log "WARNING: Injecting another gdbserver might cause issues."
log "WARNING: Injecting is only relevant to 64-bit ASA or really old 32-bit ASA devices"
log "WARNING: Are you sure? (ctrl-c if not. enter if yes)"
read CMD
fi
log "INJECT OTHER GDB"
if [[ "$FWFILE" == *"asa803"* ]]; then
FIRMWARE_WITH_GDB="asa924-k8.bin"
else
FIRMWARE_WITH_GDB="asa931-smp-k8.bin"
fi
log "Using gdbserver from ${FIRMWARE_WITH_GDB}"
if [ ! -d "${FIRMWAREDIR}/_${FIRMWARE_WITH_GDB}.extracted" ]; then
log "Didn't find ${FIRMWAREDIR}/_${FIRMWARE_WITH_GDB}.extracted"
log "Need to binwalk ${FIRMWARE_WITH_GDB} to allow file stealing"
if [ ! -e "${FIRMWAREDIR}/${FIRMWARE_WITH_GDB}" ]; then
log "ERROR: Can't find ${FIRMWAREDIR}/${FIRMWARE_WITH_GDB} so can't binwalk it"
exit 1
fi
LASTDIR=$(pwd)
cd ${FIRMWAREDIR}
${BINWALK} -e ${FIRMWARE_WITH_GDB}
cd ${LASTDIR}
fi
if [[ "$FWFILE" == *"asa803"* ]]; then
# On ASA803, the gdbserver is not able to "info proc cmdline" or "info proc mappings"
# The gdbserver from ASA924 is better in that we can at least "info proc cmdline"....
cp ${FIRMWAREDIR}/_${FIRMWARE_WITH_GDB}.extracted/rootfs/usr/bin/gdbserver bin/
else
cp ${FIRMWAREDIR}/_${FIRMWARE_WITH_GDB}.extracted/rootfs/usr/bin/gdbserver usr/bin/
cp ${FIRMWAREDIR}/_${FIRMWARE_WITH_GDB}.extracted/rootfs/lib64/libthread_db-1.0.so lib64/
# we should copy the symlink instead of copying the file but does the job for now
cp ${FIRMWAREDIR}/_${FIRMWARE_WITH_GDB}.extracted/rootfs/lib64/libthread_db.so.1 lib64/
fi
fi
}
# inject_asa_folder()
#
# Arguments:
# None
#
# Required Globals:
# FWFILE_WITH_ASA_TO_INJECT - custom firmware to take /asa from
# FWFILE - name of current firmware being worked on to inject new /asa (needs to be asa921-*.bin)
# FIRMWAREDIR - directory holding collection of firmware
#
# Notes:
# Expects $PWD to be an extracted rootfs directory
#
# Description:
# Injects an /asa/ folder from a separate firmware in FIRMWAREDIR into the
# firmware being worked on.
#
# Firmware older than 921 have their gdb broken so we could not debug them :(
# Workaround is to use the asa921-k8.bin or asa921-smp-k8.bin as a container and inject the asa/
# folder from the older firmware in order to be able to debug it
##
inject_asa_folder()
{
if [ ! -z ${FWFILE_WITH_ASA_TO_INJECT} ]
then
log "INJECT OTHER ASA FOLDER"
if [[ "$FWFILE" == *"asa921"* ]]; then
log "Using ${FWFILE} as container to inject /asa from ${FWFILE_WITH_ASA_TO_INJECT}"
else
log "ERROR: ${FWFILE} is not supported as container to inject /asa from ${FWFILE_WITH_ASA_TO_INJECT}. You need either asa921-k8.bin or asa921-smp-k8.bin"
exit 1
fi
log "Checking ${FWFILE_WITH_ASA_TO_INJECT}"
if [ ! -d "${FIRMWAREDIR}/_${FWFILE_WITH_ASA_TO_INJECT}.extracted" ]; then
log "Didn't find ${FIRMWAREDIR}/_${FWFILE_WITH_ASA_TO_INJECT}.extracted"
log "Need to binwalk ${FWFILE_WITH_ASA_TO_INJECT} to allow file stealing"
if [ ! -e "${FIRMWAREDIR}/${FWFILE_WITH_ASA_TO_INJECT}" ]; then
log "ERROR: Can't find ${FIRMWAREDIR}/${FWFILE_WITH_ASA_TO_INJECT} so can't binwalk it"
exit 1
fi
LASTDIR=$(pwd)
cd ${FIRMWAREDIR}
${BINWALK} -e ${FWFILE_WITH_ASA_TO_INJECT}
cd ${LASTDIR}
fi
log "Using /asa from ${FWFILE_WITH_ASA_TO_INJECT}"
rm -Rf ./asa
CMD="cp -Rf ${FIRMWAREDIR}/_${FWFILE_WITH_ASA_TO_INJECT}.extracted/rootfs/asa ."
${CMD}
if [ $? != 0 ];
then
log "ERROR: '${CMD}' failed"
exit 1
fi
fi
}
# replace_lina_monitor()
#
# Arguments:
# None
#
# Required Globals:
#
# Notes:
# Expects $PWD to be an extracted rootfs directory
##
replace_lina_monitor()
{
if [[ ! -z "${REPLACE_LINAMONITOR}" ]]
then
log "REPLACING LINA_MONITOR"
cp ${REPLACE_LINAMONITOR} asa/bin/lina_monitor
fi
}
# inject_debugshell()
#
# Arguments:
# None
#
# Required Globals:
#
# Notes:
# Expects $PWD to be an extracted rootfs directory
##
inject_debugshell()
{
# On ASAv 64-bit, patching in a debugshell to lina has the implicit requirement
# of patching lina_monitor to bypass boot verification of lina.
if [[ "$DEBUGSHELL" == "YES" ]]
then
FWFILE_WITH_ASA=${FWFILE}
if [ ! -z ${FWFILE_WITH_ASA_TO_INJECT} ]
then
FWFILE_WITH_ASA=${FWFILE_WITH_ASA_TO_INJECT}
log "debug shell: overriding firmware with ${FWFILE_WITH_ASA} to patch lina correctly"
fi
ADDITIONAL_ARGS=""
if [[ ! -z "${LINAHOOK}" ]]
then
ADDITIONAL_ARGS="--hook ${LINAHOOK}"
fi
CBPORT="4444"
if [[ "$FWFILE_WITH_ASA" == *"asav"* ]]
then
log "debug shell: using 64-bit ASAv firmware"
CBHOST=${ATTACKER_GNS3}
else
log "debug shell: using 32-bit / 64-bit firmware for real hardware"
CBHOST=${ATTACKER_ASA}
fi
log "Adding debug shell for $CBHOST:$CBPORT"
# If it is a 32-bit firmware, it should not contain the lib64 path so
# it is safe to search in this order
LIBC=$(find ${PWD} -regex ".*/lib64/libc.so.6")
if [[ "$LIBC" == "" ]]
then
LIBC=$(find ${PWD} -regex ".*/lib/libc.so.6")
fi
# NOTE: we pass as many arguments as possible to LINA_LINUXSHELL and it is up to that script to know
# if libc is used for malloc()/etc. and if lina_monitor needs to be patched.
CMD="${LINA_LINUXSHELL} -b ${FWFILE_WITH_ASA} -F ${PWD}/asa/bin/lina_monitor -O ${PWD}/asa/bin/lina_monitor -f ${PWD}/asa/bin/lina -o ${PWD}/asa/bin/lina -c $CBHOST -p $CBPORT -d ${ASADBG_DB} ${ADDITIONAL_ARGS} --libc-input ${LIBC} --libc-output ${LIBC}"
log "Using command: '${CMD}'"
# XXX - fix fact that we use -b to specify the bin_name but it would not work if the name is not one of the original Cisco ones (such as asa924-k8.bin)
${CMD}
if [ $? != 0 ];
then
log "ERROR: '${CMD}' failed"
exit 1
fi
fi
}
# setup_serialshell()
#
# Arguments:
# None
#
# Required Globals:
#
# Notes:
# Expects $PWD to be an extracted rootfs directory
#
# Description
# This setups a Linux shell on 2nd serial. E.g. add this to qemu options in GNS3: ASAv instance > Configure
# then Advanced settings > Additional settings: "-serial telnet:127.0.0.1:15002,server,nowait"
#
# Tested with asav962-7.qcow2
##
setup_serialshell()
{
if [[ "$SERIALSHELL" == "YES" ]]
then
FWFILE_WITH_ASA=${FWFILE}
if [ ! -z ${FWFILE_WITH_ASA_TO_INJECT} ]
then
FWFILE_WITH_ASA=${FWFILE_WITH_ASA_TO_INJECT}
log "serial shell: overriding firmware with ${FWFILE_WITH_ASA} to patch rcS correctly"
fi
if [[ "$FWFILE_WITH_ASA" == *"asav"* ]]
then
sed -i '/# regular startup/i # serial shell specifics' asa/scripts/rcS
# we redirect stdin/out/err to the 2nd serial
# bashrc does not seem to be loaded automatically so we force it to load with --rcfile
log "Exposing a Linux shell on 2nd serial (GNS3 only?)"
sed -i '/# regular startup/i \/bin\/bash --rcfile \/root\/bashrc < \/dev\/ttyS1 > \/dev\/ttyS1 2> \/dev\/ttyS1 &' asa/scripts/rcS
# Not working properly yet so needs to be executed manually
#log "Starting lina at boot"
#sed -i 's/echo "$CGEXEC \/asa\/bin\/lina_monitor.*"/ echo "\/asa\/scripts\/lina_start.sh"/' asa/scripts/rcS
log "Not starting lina at boot"
sed -i 's/echo "$CGEXEC \/asa\/bin\/lina_monitor.*"/echo ""/' asa/scripts/rcS
# Avoids reaching the end of rcS script which triggers a reboot
#sed -i '/# Explicitly call reboot here for consistency across target rcS files/a echo "while true; do echo in while true loop; sleep 1; done" >> \/tmp\/run_cmd' asa/scripts/rcS
sed -i '/# Explicitly call reboot here for consistency across target rcS files/a echo "read -p \\"[asafw] Press enter to reboot\\"" >> \/tmp\/run_cmd' asa/scripts/rcS
else
log "Not starting lina at boot"
sed -i 's/echo "$CGEXEC \/asa\/bin\/lina_monitor.*"/echo "echo \\"[asafw run_cmd] Not starting lina at boot\\""/' asa/scripts/rcS
log "Skipping setting baudrate on /dev/ttyUSB0 in serial_init"
sed -i 's/ if \[ -e \/dev\/ttyUSB0 \]; then stty -F \/dev\/ttyUSB0 115200; fi/ echo "[asafw serial_init] Skipping setting baudrate on \/dev\/ttyUSB0"/' asa/scripts/serial_init
# XXX - does not work yet - spawning the shell after resets that :( so may need to be done manually anyway
#log "Adding sourcing bashrc before spawning shell"
#sed -i '/echo "\/sbin\/reboot -d 3"/i echo "[asafw rcS] Sourcing bashrc"' asa/scripts/rcS
#sed -i '/echo "\/sbin\/reboot -d 3"/i source /root/bashrc' asa/scripts/rcS
log "Spawning shell at the end of rcS"
sed -i '/echo "\/sbin\/reboot -d 3"/i echo "[asafw rcS] End of rcS reached, spawning a shell instead"' asa/scripts/rcS
sed -i '/echo "\/sbin\/reboot -d 3"/i \/bin\/sh < \/dev\/ttyS0 > \/dev\/ttyS0 2> \/dev\/ttyS0 &' asa/scripts/rcS
log "Not rebooting in /tmp/run_cmd"
sed -i 's/echo "\/sbin\/reboot -d 3"/echo "echo \\"[asafw run_cmd] Do nothing instead of rebooting\\""/' asa/scripts/rcS
fi
declare -a scripts_list=("lstart.sh" "ldebug.sh" "lattach.sh" "lkill.sh" "lclean.sh" "ltrap.sh")
for file in "${scripts_list[@]}"
do
log "Copying ${file} script"
CMD="cp ${TOOLDIR}/binfs/${file} asa/scripts/${file}"
${CMD}
if [ $? != 0 ];
then
log "ERROR: '${CMD}' failed"
exit 1
fi
CMD="chmod +x asa/scripts/${file}"
${CMD}
if [ $? != 0 ];
then
log "ERROR: '${CMD}' failed"
exit 1
fi
done
file="bashrc"
log "Copying ${file} script"
CMD="cp ${TOOLDIR}/binfs/${file} root/${file}"
${CMD}
if [ $? != 0 ];
then
log "ERROR: '${CMD}' failed"
exit 1
fi
CMD="chmod +x root/${file}"
${CMD}
if [ $? != 0 ];
then
log "ERROR: '${CMD}' failed"
exit 1
fi
fi
}
# custom()
#
# Arguments:
# None
#
# Required Globals:
#
# Notes:
# Expects $PWD to be an extracted rootfs directory
#
# Description
# This function is for testing or doing whatever you want if you want to test
# making modifications, etc.
##
custom()
{
# custom: can be used for testing purpose before we add something useful as a real command line option
if [[ "$CUSTOM" == "YES" ]]
then
log "DOING CUSTOM STUFF: fill it yourself :)"
# ...
fi
}
# cleanup()
#
# Arguments:
# None
#
# Referenced Globals:
# GZIP_ORIGINAL - set only by unpack_bin
# CPIO_ORIGINAL - set only by unpack_bin
# GZIP_MODIFIED - set by unpack_bin and repack_bin
# VMLINUZ_ORIGINAL - set only by unpack_bin
#
# Notes:
# Expects $PWD to be an extracted rootfs directory
#
# Description
# This function deletes unneeded files generated by various tools. It is okay
# if some of the reference globals having been set
##
cleanup()
{
# cleanup
if [[ "$NO_CLEANUP" == "NO" ]]
then
log "CLEANUP"
dbglog "Removing $GZIP_ORIGINAL $CPIO_ORIGINAL $GZIP_MODIFIED $VMLINUZ_ORIGINAL"
rm $GZIP_ORIGINAL $CPIO_ORIGINAL $GZIP_MODIFIED $VMLINUZ_ORIGINAL
fi
}
# modify_bin()
#
# Arguments:
# $1 (required) - working directory
#
# Required Globals:
#
# Notes:
# Modify extracted files (typically the files from the ramdisk)
# in preparation for later repacking.
#
# Most functionality is influenced by command-line arguments.
##
modify_bin()
{
log "modify_bin: $FWFILE"
# Enter rootfs folder told to us
dbglog "Entering ${1}"
OLDDIR=${PWD}
cd ${1}
inject_asa_folder # early so all other modifications are done on the right /asa files
disable_aslr
enable_gdb
disable_gdb
fix_gns3_interface
free_space
inject_gdb
replace_lina_monitor
inject_debugshell
setup_serialshell
custom
# Return to original folder
dbglog "Returning to ${OLDDIR}"
cd ${OLDDIR}
}
# repack_bin()
#
# Arguments:
# $1 (required) - working directory
# $2 (optional) - output filename of repacked bin we'll create
# $3 (optional) - original firmware file from which this file was taken
#
# Globals Required:
# CPIO
#
# Notes:
# If $2 is specified, then $3 must also be specified.
#
# TODO:
# - It would be nice if we just derive $3 from $1
# - Instead of inferring GZIP_MODIFIED, OUTFILE and FWFILE based on arg count,
# we could just have them passed.
##
repack_bin()
{
# Enter working directory. Usually either work/ or <extracted dir>/rootfs/
OLDDIR=${PWD}
dbglog "Entering ${1}"
cd $1
if [[ $# > 1 ]]; then
log "beep"
# If we get additional args it means we are repacking an extracted
# directory and unpack_bin wasn't called. That means we have to set
# these ourself
OUTFILE="${2}"
FWFILE="${3}"
GZIP_MODIFIED="${PWD}/../rootfs.img.gz"
fi
log "repack_bin: $FWFILE"
find . | ${CPIO} -o -H newc 2>/dev/null | gzip -9 > "$GZIP_MODIFIED"
# Leave working directory
dbglog "Returning to ${OLDDIR}"
cd ${OLDDIR}
# cd ..
#ls -l *.gz
if [[ "${ROOT}" == "YES" ]]
then
ROOTARGS=--root
else
ROOTARGS=
fi
dbglog ${FWTOOL} -r -f "$FWFILE" -g "$GZIP_MODIFIED" -o "$OUTFILE" $ROOTARGS $DISABLE_ASLR_ARGS
${FWTOOL} -r -f "$FWFILE" -g "$GZIP_MODIFIED" -o "$OUTFILE" $ROOTARGS $DISABLE_ASLR_ARGS
if [ $? != 0 ];
then
log "${FWTOOL} -r -f "$FWFILE" -g "$GZIP_MODIFIED" -o "$OUTFILE" $ROOTARGS $DISABLE_ASLR_ARGS failed"
exit 1
fi
echo -n "[unpack_repack_bin] MD5: "
md5sum "${OUTFILE}"
cleanup
}
# START OF EXECUTION
if [ -z "${ASATOOLS}" ]; then
log "This tool relies on env.sh which has not been sourced"
log "NOTE: Use sudo -E if you already sourced env.sh"
exit 1
fi
BINWALK=$(which binwalk)
if [ -z "${BINWALK}" ]; then
log "ERROR: binwalk not found. Required for extract_repack_bin.sh usage"
log "ERROR: NOTE: binwalk must be > v2.0"
exit 1
fi
GUNZIP=gunzip
CPIO=cpio
# http://stackoverflow.com/questions/192249/how-do-i-parse-command-line-arguments-in-bash
# XXX - Could switch this to an associative array and pass it around instead
# of relying on globals
INPUT=