github repo for sample: https://github.com/desmondshih/embedtoshell
前言
工作中遇到一個需求是需要在 linux 套件(deb, rpm)中的 pre/post, install/rm 執行比較複雜性的檢查,而這個檢查是由 golang 撰寫.並且希望此 binary 不要被解到環境上,以下是完成這個目標所記錄的紀錄和練習.
完成目標中主要分為兩個部分
- 如何把 binary embed 到 shell script 中
- 如何執行 embeded binary in memory 並且取得結果回傳值
如何把 binary embed 到 shell script 中
- 先做一個 binary, 這邊使用 golang 寫一個簡單的例子, 接收 args integer 方便指定 return code 方便後續測試
package main
import (
"fmt"
"os"
"strconv"
)
func main() {
var rc int
if len(os.Args) == 2 {
r, err := strconv.Atoi(os.Args[1])
if err != nil {
panic("args should be integer for return code, err: %s")
}
rc = r
} else {
rc = 0
}
fmt.Println("Hello go bin")
os.Exit(rc)
}
-> % go build ./... && ls # check go output embedtoshell
embed_binary_test.sh embeded_binary_output embedtoshell go.mod main.go
-> % ./embedtoshell
Hello go bin
-> % echo $?
0
-> % ./embedtoshell 87
Hello go bin
-> % echo $?
87
- 在 shell script 中放置 binary, 建立一個測試 script "embed_binary_test.sh"
#!/bin/bash
sed "1,/^\#\#\/\*\*__PAYLOAD_BEGINS__\*\*\/\#\#$/ d" $0 | base64 -d | gzip -d -c > embed_binary_test_output
exit 0
##/**__PAYLOAD_BEGINS__**/##
- 這邊 dump to raw string 使用 gzip 壓縮並存進 script tag "PAYLOAD_BEGIN" 位置後面
gzip -9 -c < embedtoshell | base64 >> embed_binary_test.sh
- 執行 script: 會把前一步驟 dump string 讀取解壓然後存進 file "embeded_binary_output", 再來檢查內容,並執行來驗證正確性
-> % ./embed_binary_test.sh
-> % ls -lh
total 5.3M
-rwxrwxr-x 1 desmond desmond 1.4M Aug 15 10:30 embed_binary_test.sh
-rw-rw-r-- 1 desmond desmond 2.0M Aug 15 16:39 embed_binary_test_output
-rwxrwxr-x 1 desmond desmond 2.0M Aug 15 16:31 embedtoshell
-rw-rw-r-- 1 desmond desmond 29 Aug 7 15:31 go.mod
-rw-rw-r-- 1 desmond desmond 294 Aug 15 10:11 main.go
-> % md5sum embedtoshell embed_binary_test_output
b34d1c4cbb965ae26949695ccfc59b99 embedtoshell
b34d1c4cbb965ae26949695ccfc59b99 embed_binary_test_output
-> % chmod +x embed_binary_test_output
-> % ./embed_binary_test_output
Hello go bin
如何執行 embeded binary in memory 並且取得結果回傳值
- 因為這次的 task 是要在 package preinstall 做一些操作, 不希望在這個階段產生檔案在環境中.所以這邊決定使用 linux memfd 的方式 run in memory
- 因為 bash 上沒辦法直接做 memfd 相關操作,這邊用 perl & python 分別做的例子
- perl sample
#!/bin/bash
unset POSIXLY_CORRECT
run_payload() {
read -r pid mfd < <(perl -e '
require qw/syscall.ph/;
# Create memfd
my $name = "";
my $fd = syscall(SYS_memfd_create(), $fn, 0);
if (-1 == $fd) { die "memfd_create: $!"; }
$pid = fork();
if ( $pid == 0 ) {
sleep();
}
print "$pid $fd\n";
select()->flush();
')
memfd=/proc/$pid/fd/$mfd
sed "1,/^\#\#\/\*\*__PAYLOAD_BEGINS__\*\*\/\#\#$/ d" $0 | base64 -d | gzip -d -c > ${memfd}
${memfd} 87
PAYLOAD_RC=$?
kill ${pid}
}
run_payload
echo "run elf string in memfd and get return code:" $PAYLOAD_RC
exit
##/**__PAYLOAD_BEGINS__**/##
- python sample
#!/bin/bash
unset POSIXLY_CORRECT
run_payload() {
ARG1=$1
read -r pid mfd < <(python3 -c '
import os
import sys
import signal
fd = os.memfd_create("bin", 0)
if fd < 0:
sys.exit(-1)
pid = os.fork()
if pid == 0:
signal.pause()
print("%d %d" % (pid, fd))
sys.stdout.flush()
')
if ! [[ ${pid} =~ ^[0-9]+$ ]] || ! [[ ${mfd} =~ ^[0-9]+$ ]]; then
PAYLOAD_RC=1
return
fi
memfd=/proc/$pid/fd/$mfd
sed "1,/^\#\#\/\*\*__PAYLOAD_BEGINS__\*\*\/\#\#$/ d" $0 | base64 -d | gzip -d -c > ${memfd}
${memfd} 87
PAYLOAD_RC=$?
kill ${pid}
}
run_payload
echo "run elf string in memfd and get return code:" $PAYLOAD_RC
exit
##/**__PAYLOAD_BEGINS__**/##
- 測試 script
-> % gzip -9 -c < embedtoshell | base64 >> embed_binary_perl.sh
-> % ./embed_binary_perl.sh
Hello go bin
run elf string in memfd and get return code: 87
-> % gzip -9 -c < embedtoshell | base64 >> embed_binary_python.sh
-> % ./embed_binary_python.sh
Hello go bin
run elf string in memfd and get return code: 87
References