作者:scz@綠盟科技
來源:綠盟科技博客
文中演示了3種數據映射方案,有更多其他編解碼方案,這3種夠用了。前面介紹的都是bin與txt的相互轉換,各種編碼、解碼。假設數據傳輸通道只有一個弱shell,有回顯,可以通過copy/paste無損傳輸可打印字符。為了將不可打印字節傳輸過去,只能通過編解碼進行數據映射。
od+xxd
2000年時我和tt在一臺遠程主機上想把其中一個ELF弄回本地來逆向工程,目標只在23/TCP上開了服務,其他端口不可達。遠程主機上可用命令少得可憐,xxd、base64、uuencode之類的都沒有,但意外發現有個od。后來靠od把這個ELF從遠程弄回了本地。
為了便于演示說明,生造一個二進制文件:
$ printf -v escseq \\%o {0..255}
$ printf "$escseq" > some
這是bash語法,ash不支持。
$ xxd -g 1 some
00000000: 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f ................
00000010: 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f ................
00000020: 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f !"#$%&'()*+,-./
00000030: 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 0123456789:;?
00000040: 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f @ABCDEFGHIJKLMNO
00000050: 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f PQRSTUVWXYZ[\]^_
00000060: 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f `abcdefghijklmno
00000070: 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f pqrstuvwxyz{|}~.
00000080: 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f ................
00000090: 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f ................
000000a0: a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af ................
000000b0: b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf ................
000000c0: c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf ................
000000d0: d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df ................
000000e0: e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef ................
000000f0: f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff ................
$ xxd -p some
000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d
1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b
3c3d3e3f404142434445464748494a4b4c4d4e4f50515253545556575859
5a5b5c5d5e5f606162636465666768696a6b6c6d6e6f7071727374757677
78797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495
969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3
b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1
d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeef
f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff
xxd在Linux上很常見,但在其他非Linux的*nix環境中,od可能更常見。
$ od -An -tx1 -v --width=30 some &> some.txt
some.txt形如:
00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d
1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b
3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59
在遠程主機上顯示some.txt,設法把其中的內容原封不動地弄回本地來,比如錄屏、開啟終端日志等等。然后在本地處理some.txt,恢復出some。
$ sed "s/ //g" some.txt &> some.tmp
如果遠程主機上有sed,上面這步可以在遠程主機進行,減少通過網絡傳輸的文本數據量。
some.tmp內容形如:
000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d
1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b
3c3d3e3f404142434445464748494a4b4c4d4e4f50515253545556575859
some.tmp的格式就是“xxx -p”的輸出格式。
$ xxd -r -p some.tmp some
od本身只有數據轉儲功能,沒有數據恢復功能。上面用“xxd -r”恢復出binary。
有人Ctrl-U斷在U-Boot中,進行hexdump,然后恢復binary,本質是一樣的。
xxd
如果遠程主機有xxd,整個過程類似。
1) 方案1
$ xxd -p some &> some.txt
some.txt形如:
000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d
1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b
3c3d3e3f404142434445464748494a4b4c4d4e4f50515253545556575859
xxd生成的some.txt已經是最精簡形式,不需要sed再處理。
$ xxd -r -p some.txt some
2) 方案2
方案2演示xxd的其他參數,性價比不如方案1。
$ xxd -g 1 some some.txt
some.txt形如:
00000000: 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f ................
00000010: 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f ................
00000020: 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f !"#$%&'()*+,-./
$ xxd -r -s 0 some.txt some
base64
https://en.wikipedia.org/wiki/Base64
原始數據
01 02 03
二進制表示
00000001 00000010 00000011
從8-bits一組變成6-bits一組
000000 010000 001000 000011
16進制表示
00 10 08 03
查表后轉成:
A Q I D
上面是base64編碼基本原理,沒有考慮需要填充的情形。
如果遠程主機可以對binary進行base64編碼,就沒什么好說的了。
$ base64 some > some.txt
some.txt形如:
AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4
OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3Bx
cnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmq
q6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj
5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==
$ base64 -d some.txt > some
本文假設針對*nix環境,不考慮vbscript、jscript這些存在。
base64編碼比“xxd -p”省空間,前者一個字符代表6-bits,后者一個字符代表4-bits。
uuencode/uudecode
https://en.wikipedia.org/wiki/Uuencoding
begin
...
`
end
is a character indicating the number of data bytes which have been encoded on that line. This is an ASCII character determined by adding 32 to the actual byte count, with the sole exception of a grave accent “`” (ASCII code 96) signifying zero bytes. All data lines except the last (if the data was not divisible by 45), have 45 bytes of encoded data (60 characters after encoding). Therefore, the vast majority of length values is ‘M’, (32 + 45 = ASCII code 77 or “M”).
If the source is not divisible by 3 then the last 4-byte section will contain padding bytes to make it cleanly divisible. These bytes are subtracted from the line’s so that the decoder does not append unwanted characters to the file.
uu編碼如今已不多見。
1) uu編碼
$ uuencode some some > some.txt
some.txt形如:
begin 644 some
M``$"`P0%!@<("0H+#`T.#Q`1$A,4%187&!D:&QP='A\@(2(C)"4F)R@I*BLL
M+2XO,#$R,S0U-C<X.3H[/#T^/T!!0D-$149'2$E*2TQ-3D]045)35%565UA9
M6EM<75Y?8&%B8V1E9F=H:6IK;&UN;W!Q'EZ>WQ]?G^`@8*#A(6&
MAXB)BHN,C8Z/D)&2DY25EI>8F9J;G)V>GZ"AHJ.DI::GJ*FJJZRMKJ^PL;*S
MM+6VM[BYNKN\O;Z_P,'"P\3%QL?(R+CY.7FY^CIZNOL[>[O\/'R\_3U]O?X^?K[_/W^_P``
`
end
這是傳統的uuencode編碼
$ uudecode -o some some.txt
2) base64編碼
某些uuencode命令支持base64
$ uuencode -m some some > some.txt
some.txt形如:
begin-base64 644 some
AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKiss
LS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZ
WltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWG
h4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKz
tLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g
4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==
====
$ uudecode -o some some.txt
解碼時不需要額外參數,靠第一行識別base64編碼。“uuencode -m”產生的內容相比base64產生的內容,多了第一行及最后一行:
begin-base64 644 some
====
把這兩行刪除后,就可以用”base64 -d”解碼。
awk
我們并不只考慮從遠程主機下載binary,也考慮向遠程主機上傳binary。
如果目標環境有gcc,就弄個C代碼實現base64編解碼。本文不考慮寬松環境,像perl、python、gcc之類的都不考慮。考慮目標環境存在awk。
1) base64decode.awk
https://github.com/shane-kerr/AWK-base64decode/blob/master/base64decode.awk
# base64decode.awk
#
# Introduction
# ============
# Decode Base64-encoded strings.
#
# Invocation
# ==========
# Typically you run the script like this:
#
# $ awk -f base64decode.awk [file1 [file2 [...]]] > output
# The script implements Base64 decoding, based on RFC 3548:
#
# https://tools.ietf.org/html/rfc3548
# create our lookup table
BEGIN {
# load symbols based on the alphabet
for (i=0; i<26; i++) {
BASE64[sprintf("%c", i+65)] = i
BASE64[sprintf("%c", i+97)] = i+26
}
# load our numbers
for (i=0; i= 4) {
g0 = BASE64[substr(encoded, 1, 1)]
g1 = BASE64[substr(encoded, 2, 1)]
g2 = BASE64[substr(encoded, 3, 1)]
g3 = BASE64[substr(encoded, 4, 1)]
if (g0 == "") {
printf("Unrecognized character %c in Base 64 encoded string\n",
g0) >> "/dev/stderr"
exit 1
}
if (g1 == "") {
printf("Unrecognized character %c in Base 64 encoded string\n",
g1) >> "/dev/stderr"
exit 1
}
if (g2 == "") {
printf("Unrecognized character %c in Base 64 encoded string\n",
g2) >> "/dev/stderr"
exit 1
}
if (g3 == "") {
printf("Unrecognized character %c in Base 64 encoded string\n",
g3) >> "/dev/stderr"
exit 1
}
# we don't have bit shifting in AWK, but we can achieve the same
# results with multiplication, division, and modulo arithmetic
result[n++] = (g0 * 4) + int(g1 / 16)
if (g2 != -1) {
result[n++] = ((g1 * 16) % 256) + int(g2 / 4)
if (g3 != -1) {
result[n++] = ((g2 * 64) % 256) + g3
}
}
encoded = substr(encoded, 5)
}
if (length(encoded) != 0) {
printf("Extra characters at end of Base 64 encoded string: \"%s\"\n",
encoded) >> "/dev/stderr"
exit 1
}
}
# our main text processing
{
# Decode what we have read.
base64decode($0, result)
# Output the decoded string.
#
# We cannot output a NUL character using BusyBox AWK. See:
# https://stackoverflow.com/a/32302711
#
# So we collect our result into an octal string and use the
# shell "printf" command to create the actual output.
#
# This also helps with gawk, which gets confused about the
# non-ASCII output if localization is used unless this is
# set via LC_ALL=C or via "--characters-as-bytes".
printf_str = ""
for (i=1; i in result; i++) {
printf_str = printf_str sprintf("\\%03o", result[i])
if (length(printf_str) >= 1024) {
system("printf '" printf_str "'")
printf_str = ""
}
delete result[i]
}
system("printf '" printf_str "'")
}
$ base64 some > some.txt
$ awk -f base64decode.awk some.txt > some
$ busybox awk -f base64decode.awk some.txt > some
busybox不一定有nc,如果有awk就可以用前面這招。awk腳本執行效率很低,極端情況下聊勝于無。base64decode.awk在一個很弱的busybox環境下成功解碼。
2) base64.awk
Danny Chouinard的原實現在做base64編碼時沒有正確處理結尾的=,他固定添加“==”。
這個問題不大,原腳本產生的編碼輸出可以被原腳本有效解碼,但用其他工具解碼原腳本產生的編碼輸出時可能容錯度不夠。比如“scz@nsfocus”經原腳本編碼產生“c2N6QG5zZm9jdXM==”,結尾多了一個=。
如果上下文都只用Danny Chouinard的原腳本,它的實現是最精簡的。
下面是改過的版本,確保base64編碼輸出符合規范,以便與其他工具混合使用。其base64編碼功能無法直接處理binary,只能處理“xxd -p”這類輸入,允許出現空格。
暫時沒有找到用awk直接處理binary的辦法。
#!/usr/bin/awk -f
#
# Author : Danny Chouinard
# Modify : scz@nsfocus
#
function base64encode ()
{
o = 0;
bits = 0;
n = 0;
count = 0;
while ( getline )
{
for ( c = 0; c < length( $0 ); c++ )
{
h = index( "0123456789abcdef", substr( $0, c+1, 1 ) );
if ( h-- )
{
count++;
for( i = 0; i < 4; i++ )
{
o = o * 2 + int( h / 8 )
h = ( h * 2 ) % 16;
if( ++bits == 6 )
{
printf substr( base64table, o+1, 1 );
if( ++n >= maxn )
{
printf( "\n" );
n = 0;
}
o = 0;
bits = 0;
}
}
}
}
}
if ( bits )
{
while ( bits++ < 6 )
{
o = o * 2;
}
printf substr( base64table, o+1, 1 );
if( ++n >= maxn )
{
printf( "\n" );
n = 0;
}
}
count = int( count / 2 ) % 3;
if ( count )
{
for ( i = 0; i < 3 - count; i++ )
{
printf( "=" );
if( ++n >= maxn )
{
printf( "\n" );
n = 0;
}
}
}
if ( n )
{
printf( "\n" );
}
}
function base64decode ()
{
o = 0;
bits = 0;
while( getline < "/dev/stdin" )
{
for ( i = 0; i < length( $0 ); i++ )
{
c = index( base64table, substr( $0, i+1, 1 ) );
if ( c-- )
{
for ( b = 0; b < 6; b++ )
{
o = o * 2 + int( c / 32 );
c = ( c * 2 ) % 64;
if( ++bits == 8 )
{
printf( "%c", o );
o = 0;
bits = 0;
}
}
}
}
}
}
BEGIN \
{
base64table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
maxn = 76;
if ( ARGV[1] == "d" )
{
base64decode();
}
else
{
base64encode();
}
}
$ xxd -p some | awk -f base64.awk > some.txt
$ base64 some > some.txt
這兩個輸出完全相同。
base64解碼時,必須關閉%c的UTF-8支持,下面兩種辦法均可:
$ LANG=C awk -f base64.awk d some
$ awk --characters-as-bytes -f base64.awk d some
base64.awk直接使用awk的printf()。如果這個awk實際是由busybox提供的,此時可能無法輸出0x00,這點需要在目標環境實測:
可以調用shell的printf輸出0x00,UTF-8困撓一并被規避,參看uudecode_ash.awk。
3) uudecode.awk
busybox提供的awk可能無法輸出0x00,本腳本不適用于busybox環境。
#!/usr/bin/awk -f
function looktable ( l, p )
{
uutable = "!\"#$%&'()*+,-./0123456789:;?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_";
return index( uutable, substr( l, p+1, 1 ) );
}
/^[^be]/ \
{
len = looktable( $0, 0 );
for ( i = 1; len > 0; i += 4 )
{
a = looktable( $0, i );
b = looktable( $0, i+1 );
c = looktable( $0, i+2 );
d = looktable( $0, i+3 );
printf( "%c", a * 4 + b / 16 );
if ( len > 1 )
{
printf( "%c", b * 16 + c / 4 );
if ( len > 2 )
{
printf( "%c", c * 64 + d );
}
}
len -= 3;
}
}
$ uuencode some some > some.txt
$ LANG=C awk -f uudecode.awk some
$ awk --characters-as-bytes -f uudecode.awk some
4) uudecode_ash.awk
#!/bin/awk -f
function looktable ( l, p )
{
uutable = "!\"#$%&'()*+,-./0123456789:;?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_";
return index( uutable, substr( l, p+1, 1 ) );
}
/^[^be]/ \
{
len = looktable( $0, 0 );
n = 1;
for ( i = 1; len > 0; i += 4 )
{
a = looktable( $0, i );
b = looktable( $0, i+1 );
c = looktable( $0, i+2 );
d = looktable( $0, i+3 );
ret[n++] = ( a * 4 + b / 16 ) % 256;
if ( len > 1 )
{
ret[n++] = ( b * 16 + c / 4 ) % 256;
if ( len > 2 )
{
ret[n++] = ( c * 64 + d ) % 256;
}
}
len -= 3;
}
escseq = "";
for ( i = 1; i in ret; i++ )
{
escseq = escseq sprintf( "\\x%02x", ret[i] );
delete ret[i];
}
system( "printf \"" escseq "\"" );
}
$ uuencode some some > some.txt
$ busybox awk -f uudecode_ash.awk some
此處不需要LANG=C,并且可以輸出0x00,適用于busybox環境。
5) base64_ash.awk
從base64.awk移植出一個可以在busybox(ash+awk)中使用的版本。
#!/bin/awk -f
#
# Author : Danny Chouinard
# Modify : scz@nsfocus
#
function base64encode ()
{
o = 0;
bits = 0;
n = 0;
count = 0;
while ( getline )
{
for ( c = 0; c < length( $0 ); c++ )
{
h = index( "0123456789abcdef", substr( $0, c+1, 1 ) );
if ( h-- )
{
count++;
for( i = 0; i < 4; i++ )
{
o = o * 2 + int( h / 8 )
h = ( h * 2 ) % 16;
if( ++bits == 6 )
{
printf substr( base64table, o+1, 1 );
if( ++n >= maxn )
{
printf( "\n" );
n = 0;
}
o = 0;
bits = 0;
}
}
}
}
}
if ( bits )
{
while ( bits++ < 6 )
{
o = o * 2;
}
printf substr( base64table, o+1, 1 );
if( ++n >= maxn )
{
printf( "\n" );
n = 0;
}
}
count = int( count / 2 ) % 3;
if ( count )
{
for ( i = 0; i < 3 - count; i++ )
{
printf( "=" );
if( ++n >= maxn )
{
printf( "\n" );
n = 0;
}
}
}
if ( n )
{
printf( "\n" );
}
}
function base64decode ()
{
o = 0;
bits = 0;
while( getline < "/dev/stdin" )
{
n = 1;
for ( i = 0; i < length( $0 ); i++ )
{
c = index( base64table, substr( $0, i+1, 1 ) );
if ( c-- )
{
for ( b = 0; b < 6; b++ )
{
o = o * 2 + int( c / 32 );
c = ( c * 2 ) % 64;
if( ++bits == 8 )
{
ret[n++] = o;
o = 0;
bits = 0;
}
}
}
}
escseq = "";
for ( i = 1; i in ret; i++ )
{
escseq = escseq sprintf( "\\x%02x", ret[i] );
delete ret[i];
}
system( "printf \"" escseq "\"" );
}
}
BEGIN \
{
base64table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
maxn = 76;
if ( ARGV[1] == "d" )
{
base64decode();
}
else
{
base64encode();
}
}
$ busybox od -An -tx1 -v some | busybox awk -f base64_ash.awk > some.txt
$ busybox awk -f base64_ash.awk d < some.txt > some
此處不需要LANG=C,并且可以輸出0x00,適用于busybox環境。
bash
1) xxd.sh
這個腳本要求bash 4.3或更高版本,充斥著bash的各種奇技淫巧,如果讀不懂,請看bash(1)。
#!/bin/bash
#
# Read a file by bytes in BASH
# https://stackoverflow.com/questions/13889659/read-a-file-by-bytes-in-bash
#
# Author : F. Hauri
# : 2016-09
#
# Modify : scz@nsfocus
# : 2018-10-08
# : 2018-10-11 15:12
#
function hexdump ()
{
printf -v escseq \\%o {32..126}
printf -v asciitable "$escseq"
printf -v ctrltable %-20sE abtnvfr
if [ "$1" == "-p" ] ; then
printf -v spaceline %30s
fmt=${spaceline// /%02x}
else
printf -v spaceline %16s
fmt=${spaceline// / %02x}
fi
offset=0
hexarray=()
asciidump=
while LANG=C IFS= read -r -d '' -n 1 byte
do
if [ "$byte" ] ; then
printf -v escchar "%q" "$byte"
case ${#escchar} in
1|2 )
index=${asciitable%$escchar*}
hexarray+=($((${#index}+0x20)))
asciidump+=$byte
;;
5 )
tmp=${escchar#*\'\\}
index=${ctrltable%${tmp%\'}*}
hexarray+=($((${#index}+7)))
asciidump+=.
;;
7 )
tmp=${escchar#*\'\\}
hexarray+=($((8#${tmp%\'})))
asciidump+=.
;;
* )
echo >&2 Error: "[$escchar]"
;;
esac
else
hexarray+=(0)
asciidump+=.
fi
if [ "$1" == "-p" ] ; then
if [ ${#hexarray[@]} -gt 29 ] ; then
printf "$fmt\n" ${hexarray[@]}
((offset+=30))
hexarray=()
asciidump=
fi
else
if [ ${#hexarray[@]} -gt 15 ] ; then
printf "%08x:$fmt %s\n" $offset ${hexarray[@]} "$asciidump"
((offset+=16))
hexarray=()
asciidump=
fi
fi
done
if [ "$hexarray" ] ; then
if [ "$1" == "-p" ] ; then
fmt="${fmt:0:${#hexarray[@]}*4}"
printf "$fmt\n" ${hexarray[@]}
else
fmt="${fmt:0:${#hexarray[@]}*5}%$((48-${#hexarray[@]}*3))s"
printf "%08x:$fmt %s\n" $offset ${hexarray[@]} " " "$asciidump"
fi
fi
}
function revert ()
{
hextable="0123456789abcdef"
two=0
hh=
while LANG=C IFS= read -r -d '' -n 1 byte
do
if [ "$byte" ] ; then
printf -v escchar "%q" "$byte"
case ${#escchar} in
1 )
index=${hextable%${escchar,,}*}
index=${#index}
if [[ $index != 16 ]] ; then
((two+=1))
hh+=$escchar
if [[ $two == 2 ]] ; then
printf -v escseq "\\\\x%s" $hh
printf $escseq
two=0
hh=
fi
fi
;;
* )
;;
esac
fi
done
}
if [ "$1" != "-r" ] ; then
hexdump $1
else
revert
fi
腳本中的-d ”很重要,否則讀取\n時,\n被自動轉成\0。
$ ./xxd.sh -p some.txt
$ xxd -p some > some.txt
這兩個輸出完全相同
$ ./xxd.sh -r some
$ xxd -r -p some.txt some
這兩個輸出完全相同
“xxd.sh -r”的輸入允許出現空格、換行等一切非16進制數字的字符,它們將被丟棄。
16進制數字大小寫不敏感。
2) xxd_mini.sh
#!/bin/bash
#
# Author : scz@nsfocus
# : 2018-10-08
# : 2018-10-12 11:58
#
hexdump ()
{
count=0
while LANG=C IFS= read -r -d '' -n 1 byte
do
LANG=C printf '%02x' "'$byte"
let count+=1
if [ $count -eq 30 ] ; then
printf "\n"
count=0
fi
done
if [ $count -ne 0 ] ; then
printf "\n"
fi
}
revert ()
{
hextable="0123456789abcdef"
two=0
hh=
while LANG=C IFS= read -r -n 1 byte
do
if [ "$byte" ] ; then
index=${hextable%${byte}*}
index=${#index}
if [[ $index != 16 ]] ; then
let two+=1
hh=$hh$byte
if [[ $two == 2 ]] ; then
printf "\x"$hh
two=0
hh=
fi
fi
fi
done
}
if [ "$1" != "-r" ] ; then
hexdump $1
else
revert
fi
這個腳本不支持帶ascii區的hexdump,即不支持”xxd -g 1″的效果,但支持”xxd -p”、”xxd -r”的效果,作為上傳、下載工具,足矣。
相比xxd.sh,xxd_mini.sh的語法有些陳舊,這是為了兼容ash,參看xxd.ash的說明。
$ ./xxd_mini.sh < some
$ xxd -p some
這兩個輸出完全相同
$ ./xxd_mini.sh < some | ./xxd_mini.sh -r | xxd -g 1
$ xxd -p some | xxd -r -p | xxd -g 1
這兩個輸出完全相同
3) base64.sh
#!/bin/bash
#
# Author : scz@nsfocus
# : 2018-10-08
# : 2018-10-16 17:46
#
function base64encode ()
{
printf -v escseq \\%o {32..126}
printf -v asciitable "$escseq"
printf -v ctrltable %-20sE abtnvfr
base64table="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
o=0
bits=0
n=0
maxn=76
count=0
while LANG=C IFS= read -r -d '' -n 1 byte
do
if [ "$byte" ] ; then
printf -v escchar "%q" "$byte"
case ${#escchar} in
1|2 )
index=${asciitable%$escchar*}
((hh=${#index}+0x20))
;;
5 )
tmp=${escchar#*\'\\}
index=${ctrltable%${tmp%\'}*}
((hh=${#index}+7))
;;
7 )
tmp=${escchar#*\'\\}
((hh=8#${tmp%\'}))
;;
* )
echo >&2 Error: "[$escchar]"
;;
esac
else
hh=0
fi
((count++))
for ((i=0;i<8;i++))
do
((o=o*2+hh/128))
((hh=hh*2%256))
((bits++))
if [[ $bits == 6 ]] ; then
printf ${base64table:$o:1}
((n++))
if [ $n -ge $maxn ] ; then
printf "\n"
n=0
fi
o=0
bits=0
fi
done
done
if [[ $bits != 0 ]] ; then
while [ $bits -lt 6 ]
do
((bits++))
((o*=2))
done
printf ${base64table:$o:1}
((n++))
if [ $n -ge $maxn ] ; then
printf "\n"
n=0
fi
fi
((count=count%3))
if [[ $count != 0 ]] ; then
for ((i=0;i<3-count;i++))
do
printf "="
((n++))
if [ $n -ge $maxn ] ; then
printf "\n"
n=0
fi
done
fi
if [ $n -ne 0 ] ; then
printf "\n"
fi
}
function base64decode ()
{
base64table="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
o=0
bits=0
while LANG=C IFS= read -r -d '' -n 1 byte
do
if [ "$byte" ] ; then
c=${base64table%${byte}*}
c=${#c}
if [[ $c != 64 ]] ; then
#
# printf "%#x\n" $c
# continue
#
for ((b=0;b<6;b++))
do
((o=o*2+c/32))
((c=c*2%64))
((bits++))
if [[ $bits == 8 ]] ; then
printf -v escseq \\x5cx%x $o
printf $escseq
o=0
bits=0
fi
done
fi
fi
done
}
if [ "$1" != "-d" ] ; then
base64encode
else
base64decode
fi
$ echo -n -e "scz@nsfocus" | ./base64.sh
c2N6QG5zZm9jdXM=
$ ./base64.sh some.txt
$ ./base64.sh -d some
ash
bash很強大,而我們面臨的很可能是busybox提供的ash,ash要比bash弱不少。
1) xxd.ash
#!/bin/ash
#
# Author : scz@nsfocus
# : 2018-10-08
# : 2018-10-12 11:58
#
hexdump ()
{
count=0
while LANG=C IFS= read -r -n 1 byte
do
LANG=C printf '%02x' "'$byte"
let count+=1
if [ $count -eq 30 ] ; then
printf "\n"
count=0
fi
done
if [ $count -ne 0 ] ; then
printf "\n"
fi
}
revert ()
{
hextable="0123456789abcdef"
two=0
hh=
while LANG=C IFS= read -r -n 1 byte
do
if [ "$byte" ] ; then
index=${hextable%${byte}*}
index=${#index}
if [[ $index != 16 ]] ; then
let two+=1
hh=$hh$byte
if [[ $two == 2 ]] ; then
printf "\x"$hh
two=0
hh=
fi
fi
fi
done
}
if [ "$1" != "-r" ] ; then
hexdump $1
else
revert
fi
xxd.ash實際就是xxd_mini.sh,編寫后者時已經充分考慮了ash與bash的兼容性。
為了進行遞增操作,使用了let關鍵字,ash很可能不支持(())。
不要寫function關鍵字,busybox v1.19.3不認,v1.27.2才認。
ash不支持-d、-N,因此xxd.ash中read時刪除了-d ”,這導致腳本無法正確讀取\n,讀進來時被自動轉換成\0,在ash中找不到規避辦法。
ash不支持${parameter,,pattern},無法將輸入自動轉換成小寫,xxd.ash只能處理全小寫的some.txt。
xxd.ash的revert()可用,hexdump()不能正確轉儲\n。如果some中不包含\n,則可使用xxd.ash的hexdump()。如果非要在弱環境中進行hexdump(),可以先用revert()上傳一個靜態鏈接的ELF,此處不展開討論。
2) echohelper.c
busybox的ash支持“echo -n -e”,這可能是最笨的上傳binary方案。寫個輔助C程序將指定binary轉換成一系列echo命令。
/*
* gcc -Wall -pipe -O3 -s -o echohelper echohelper.c
*/
#include
#include
#include
#include
#include
#include
int main ( int argc, char * argv[] )
{
int ret = EXIT_FAILURE;
int fd, i, n;
unsigned char buf[16];
fd = open( argv[1], O_RDONLY, 0 );
if ( fd 0 )
{
printf( "echo -n -e \"" );
for ( i = 0; i some.ash
$ busybox ash some.ash > some
some.ash形如:
echo -n -e "\x0\x1\x2\x3\x4\x5\x6\x7\x8\x9\xa\xb\xc\xd\xe\xf"
echo -n -e "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
echo -n -e "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f"
echo -n -e "\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f"
echo -n -e "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f"
echo -n -e "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f"
echo -n -e "\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f"
echo -n -e "\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f"
echo -n -e "\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f"
echo -n -e "\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f"
echo -n -e "\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf"
echo -n -e "\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf"
echo -n -e "\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf"
echo -n -e "\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf"
echo -n -e "\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef"
echo -n -e "\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
3) base64.ash
base64編碼時不直接處理binary,處理“xxd -p”、“od -An -tx1 -v –width=30″這類輸入,允許出現空格,只支持小寫[a-f]。如果busybox沒有提供od,base64.ash無法進行base64編碼。base64.ash不直接處理binary,主要因為busybox的ash不支持-d,無法有效讀取\n。
base64.ash進行base64解碼時僅依賴busybox的ash,但效率極其低下。
#!/bin/ash
#
# Author : scz@nsfocus
# : 2018-10-08
# : 2018-10-16 16:12
#
base64encode ()
{
base64table="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
hextable="0123456789abcdef"
o=0
bits=0
n=0
maxn=76
count=0
while LANG=C IFS= read -r -n 1 byte
do
if [ "$byte" ] ; then
h=${hextable%${byte}*}
h=${#h}
if [[ $h != 16 ]] ; then
let count+=1
i=0
while [ $i -lt 4 ]
do
let o=o*2+h/8
let h=h*2%16
let bits+=1
if [[ $bits == 6 ]] ; then
printf ${base64table:$o:1}
let n+=1
if [ $n -ge $maxn ] ; then
printf "\n"
n=0
fi
o=0
bits=0
fi
let i+=1
done
fi
fi
done
if [[ $bits != 0 ]] ; then
while [ $bits -lt 6 ]
do
let bits+=1
let o*=2
done
printf ${base64table:$o:1}
let n+=1
if [ $n -ge $maxn ] ; then
printf "\n"
n=0
fi
fi
let count=count/2%3
if [[ $count != 0 ]] ; then
i=0
let t=3-count
while [ $i -lt $t ]
do
printf "="
let n+=1
if [ $n -ge $maxn ] ; then
printf "\n"
n=0
fi
let i+=1
done
fi
if [ $n -ne 0 ] ; then
printf "\n"
fi
}
base64decode ()
{
base64table="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
o=0
bits=0
while LANG=C IFS= read -r -n 1 byte
do
if [ "$byte" ] ; then
c=${base64table%${byte}*}
c=${#c}
if [[ $c != 64 ]] ; then
b=0
while [ $b -lt 6 ]
do
let o=o*2+c/32
let c=c*2%64
let bits+=1
if [[ $bits == 8 ]] ; then
escseq=$(printf "\x%02x" $o)
printf $escseq
o=0
bits=0
fi
let b+=1
done
fi
fi
done
}
if [ "$1" != "-d" ] ; then
base64encode
else
base64decode
fi
$ echo -n -e "scz@nsfocus" | xxd -p | busybox ash base64.ash
$ busybox od -An -tx1 -v some | busybox ash base64.ash > some.txt
$ busybox ash base64.ash -d some
openssl
openssl可以進行base64編解碼。一般不考慮目標環境存在openssl,列于此處只是出于完備性考慮。
$ openssl enc -base64 -e -in some -out some.txt
$ openssl enc -base64 -d -in some.txt -out some
$ base64 -d some.txt > some
小結
至此為止,前面介紹的都是bin與txt的相互轉換,各種編碼、解碼。假設數據傳輸通道只有一個弱shell,有回顯,可以通過copy/paste無損傳輸可打印字符。為了將不可打印字節傳輸過去,只能通過編解碼進行數據映射。前文只演示了3種數據映射方案,有更多其他編解碼方案,但沒必要,這3種夠用了。
弱環境使得無法用C代碼完成編解碼,只能用一些受限的現有工具完成,為此上場了各種奇技淫巧。
后面的內容是一些相關發散。
perl
1) nc.pl
nc.pl實現nc部分功能。
#!/usr/bin/perl
use IO::Socket;
$SIG{PIPE} = 'IGNORE';
$buflen = 102400;
die "Usage: $0 \n" unless ($host = shift) && ($port = shift);
die "connect to $host:$port: $!\n" unless
$sock = new IO::Socket::INET
(
PeerAddr => $host,
PeerPort => $port,
proto => 'tcp'
);
while ( ( $count = sysread( STDIN, $buffer, $buflen ) ) > 0 )
{
die "socket write error: $!\n" unless syswrite( $sock, $buffer, $count ) == $count;
}
die "socket read error: $!\n" if $count < 0;
die "close socket: $!\n " unless close( $sock );
本文最初沒打算把perl牽扯進來,一般有perl的環境都不算弱環境。事實上前面主要考慮沒有網絡的串口登錄shell,而且優先考慮惡劣的弱busybox環境。
后來想起曾經處理過一臺x64/Solaris,當時需要取證,不允許在上面額外安裝二進制工具,系統中沒有nc,但有perl解釋器。雖然這個場景不夠惡劣,也算有所限制。
$ dd if=/dev/dsk/cNtNdNs2 | nc.pl
用這個辦法把硬盤dd走了。
SecureCRT
這里介紹ZMODEM/YMODEM/XMODEM/KERMIT方案,某些場景用得上,包括U-Boot,但舉例時用了Windows和Linux。
1) 從Windows向Linux上傳文件
1.1) ZMODEM(推薦)
在Linux中安裝lrzsz包:
$ aptitude install lrzsz
假設在Windows中用SecureCRT SSH登錄Linux,在Linux的當前shell中切換到用于存放上傳文件的目錄,比如:
$ cd /tmp/modem/
在Windows中操作SecureCRT:
Transfer->Zmodem Upload List->選擇多個待上傳文件->Start Upload
之后在/tmp/modem中將出現被上傳文件。
整個過程會在Linux中隱式執行rz:
$ rz
rz waiting to receive.
Starting zmodem transfer. Press Ctrl+C to cancel.
Transferring <file>...
第二種操作方式,SecureCRT SSH登錄Linux,在Linux中切換目錄,在Windows中用鼠標拖放待上傳文件到SecureCRT SSH會話窗口,此時會彈出一個小窗口,在其中選擇“Send Zmodem”。
第三種操作方式,SecureCRT SSH登錄Linux,在Linux中切換目錄,在Linux中執行rz命令,在SecureCRT中彈出界面讓你選擇文件,確定后完成上傳。
1.2) YMODEM
相比ZMODEM,YMODEM、XMODEM沒有優勢,這里只是演示,并不推薦。
在Linux中執行:
$ rb -b
在Windows中操作SecureCRT:
Options->Session Options->Terminal->X/Y/Zmodem->X/Ymodem send packet size
128 bytes // 缺省值
1024 bytes (Xmodem-1k/Ymodem-1k) // 選這個
Transfer->Send Ymodem->選擇文件
或者用鼠標拖放文件到相應SecureCRT會話窗口。YMODEM比ZMODEM慢,在Debian中居然需要用Ctrl-C結束,不過不影響上傳數據。
1.3) XMODEM(不推薦)
在Linux中執行:
$ rx -b some
rx一次只能接收一個文件。
在Windows中操作SecureCRT:
Options->Session Options->Terminal->X/Y/Zmodem->X/Ymodem send packet size->1024 bytes (Xmodem-1k/Ymodem-1k)
Transfer->Send Xmodem->選擇文件
待上傳文件在Windows中的名字不要求是some,但到了Linux中將被重命名為some。在Debian中同樣可能需要用Ctrl-C結束,但不影響上傳數據。
相比ZMODEM、YMODEM,XMODEM有個大問題,在man中寫道:
Up to 1023 garbage characters may be added to the received file.
尾部填充導致不宜用XMODEM上傳binary,盡管可以用dd切掉尾部填充。ZMODEM、YMODEM無此問題。
1.4) KERMIT
介紹ZMODEM的文章很多,介紹KERMIT的較少,看到過標題說是介紹KERMIT內容實際是ZMODEM的文章,真扯淡。
在Linux中安裝ckermit包:
$ aptitude install ckermit
在Linux中執行:
$ kermit -i -r
在Windows中操作SecureCRT:
Transfer->Send Kermit->選擇文件(可以多選)
或者用鼠標拖放文件到相應SecureCRT會話窗口,在彈出窗口中選擇”Send Kermit”。
2) 從Linux向Windows下載文件
2.1) ZMODEM(推薦)
假設在Windows中用SecureCRT SSH登錄Linux
在Windows中操作SecureCRT:
Options->Session Options->Terminal->X/Y/Zmodem->Directories->Download->指定用于存放下載文件的目錄
不必理會Upload的設置
在Linux中執行:
$ sz -b zmodem.bin other.bin
rz
Starting zmodem transfer. Press Ctrl+C to cancel.
Transferring zmodem.bin...
...
Transferring other.bin...
...
在Windows中檢查Download目錄,已經出現被下載文件。
SecureCRT對sz的支持比較智能,沒有想像中的:
Transfer->Receive Zmodem
這帶來一些兼容性問題。某遠程主機是一臺嵌入式ARM/Linux,上面有個3.48版sz,遠程執行“sz -b <file>”后,SecureCRT這邊沒反應,但用YMODEM下載成功。后來把源自Debian 9的lrzsz 0.12.21-10交叉編譯出靜態鏈接版本弄到前述ARM/Linux上,用ZMODEM下載成功。
2.2) YMODEM
在Linux中執行:
$ sb -b -k ymodem.bin other.bin
在Windows中操作SecureCRT:
Options->Session Options->Terminal->X/Y/Zmodem->Directories->Download->指定用于存放下載文件的目錄
Options->Session Options->Terminal->X/Y/Zmodem->X/Ymodem send packet size->1024 bytes (Xmodem-1k/Ymodem-1k)
Transfer->Receive Ymodem
在Windows中檢查Download目錄,已經出現被下載文件。
2.3) XMODEM(不推薦)
在Linux中執行:
$ sx -b -k xmodem.bin
sx一次只能傳送一個文件。
在Windows中操作SecureCRT:
Options->Session Options->Terminal->X/Y/Zmodem->Directories->Download->指定用于存放下載文件的目錄
Options->Session Options->Terminal->X/Y/Zmodem->X/Ymodem send packet size->1024 bytes (Xmodem-1k/Ymodem-1k)
Transfer->Receive Xmodem
與2.2小節不同,此處彈出文件對話框,讓你選擇輸出目錄,還可以指定輸出文件名。
1.3小節提到的尾部填充(0x1a)并不是Linux版rx命令的獨有表現,應該是XMODEM規范。
SecureCRT通過XMODEM接收文件時,同樣會進行尾部填充。填充什么數據,填充多少字節,可以看rx源碼,我已經打定主意不用XMODEM,不深究。
2.4) KERMIT
在Linux中執行:
$ kermit -I -P -i -s kermit.bin other.bin
指定-P,否則文件下載到Windows后文件名變成全大寫。
在Windows中操作SecureCRT:
Options->Session Options->Terminal->X/Y/Zmodem->Directories->Download->指定用于存放下載文件的目錄
Transfer->Receive Kermit
在Windows中檢查Download目錄,已經出現被下載文件。SecureCRT沒有單獨為KERMIT配置下載目錄的地方,KERMIT與ZMODEM共用同一個下載目錄。
zssh
若A、B都是Linux,也可以用rz/sz上傳下載,此時需要zssh。zssh是”Zmodem SSH”的縮寫,Debian有這個包,直接裝就是。
$ apt-cache search zssh
$ dpkg -L zssh | grep "/bin/"
/usr/bin/zssh
/usr/bin/ztelnet
man手冊里有:
zssh is an interactive wrapper for ssh used to switch the ssh connection between the remote shell and file transfers. This is achieved by using another tty/pty pair between the user and the local ssh process to plug either the user’s tty (remote shell mode) or another process (file transfer mode) on the ssh connection.
ztelnet behaves similarly to zssh, except telnet is used instead of ssh.It is equivalent to ‘zssh -s “telnet -8 -E”‘
$ zssh <user>@<ip>
登錄后,在遠程shell里執行:
$ sz zmodem.bin other.bin
**B00000000000000
按下zssh的”escape squence”,缺省是Ctrl-@(或Ctrl-2)。這將進入另一個提示符,在其中輸入rz
zssh > rz
即可完成下載。此處有坑,假設是在C中用SecureCRT遠程登錄A,該會話啟用ZMODEM,前述操作原始意圖是從B向A提供文件,實際效果是從B向C提供文件;這種場景下,為了達成原始意圖,必須先禁用C與A之間的ZMODEM。
上傳更簡單,在”zssh >”提示符下執行sz:
zssh > sz zmodem.bin other.bin
上傳時跟SecureCRT一樣”智能”,不需要在遠程shell里顯式執行rz來配合。
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/723/
暫無評論