Shell/Bash script 学习笔记

上一篇写 Bash 的文章 ,只是介绍了 terminal 下面的一些快捷键,并没有对 Bash 和 Shell 本身展开多少的介绍,因此正好趁此机会。

sh(或者叫做 Shell Command Language), 是由 POSIX 标准 定义的一门语言。 它有很多实现(ksh88,dash…)等等, bash 同样可以认为是 sh 的一个实现。1

因为 sh 是一个规范,而不是实现,所以在大部分的 POSIX 系统上 /bin/sh 是一个 symlink。比如在 Debian/Ubtuntu 中, sh 便指向 dash.

bash 在创造初期是作为 sh 兼容的实现,但是随着时间发展,延伸出了很多扩展功能。而许多新功能改变了原来 POSIX shell 的设计。因此, bash 自身并不是正宗的 POSIX shell,而是 POSIX shell 的一种方言。

bash 支持 --posix 模式,用来兼容 POSIX shell。很长一段时间来, 在 GNU/Linux 系统中 /bin/sh 都指向 /bin/bash,久而久之人们便忘记了这两者之间的区别。

一些非常著名的系统 /bin/sh 并不指向 /bin/bash 的例子(甚至在有些系统中 /bin/bash 甚至不存在):

  1. Debian/Ubuntu 系统中 sh 默认指向 dash
  2. BusyBox 使用 ash 来作为 sh 的实现
  3. BSDs, OpenBSD 使用 pdksh .

到此,基本也就解释了 sh 和 bash 的重要区别,但是就目前的学习, bash 因为实现比较流行,几乎绝大部分 shell 脚本都交由 bash 解释执行,并且 bash 相比较于 sh,功能更加强大,语法更加简洁。

Shell 实现

  • sh sh 由 Steve Bourne 开发,是 Bourne Shell 的缩写,sh 是 Unix 标准默认的 shell。
  • ash shell 是由 Kenneth Almquist 编写的,Linux 中占用系统资源最少的一个小 shell,它只包含 24 个内部命令。
  • csh 它由以 William Joy 为代表的共计 47 位作者编成。它产生于 Unix 第六版 /bin/sh, 是 Bourne Shell 的前身。
  • ksh 是 Korn shell 的缩写,由 David G. Korn 编写。该 shell 最大的优点是几乎和商业发行版的 ksh 完全兼容,这样就可以在不用花钱购买商业版本的情况下尝试商业版本的性能了。
  • bash 由 Brian Fox 和 Chet Ramey 共同完成,是 BourneAgain Shell 的缩写。

下面的命令和使用基于 bash。

Shell 适用

因为 Shell 似乎是各 UNIX 系统之间通用的功能,并且经过了 POSIX 的标准化。因此,Shell 脚本只要“用心写”一次,即可应用到很多系统上。因此,之所以要使用 Shell 脚本是基于:

  • 简单性:Shell 是一个高级语言;通过它,你可以简洁地表达复杂的操作。
  • 可移植性:使用 POSIX 所定义的功能,可以做到脚本无须修改就可在不同的系统上执行。
  • 开发容易:可以在短时间内完成一个功能强大又妤用的脚本。

但是,考虑到 Shell 脚本的命令限制和效率问题,下列情况一般不使用 Shell:

  • 资源密集型的任务,尤其在需要考虑效率时(比如排序,hash 等等)。
  • 需要处理大任务的数学操作,尤其是浮点运算,精确运算,或者复杂的算术运算(这种情况一般使用 C++ 或 FORTRAN 来处理)。
  • 有跨平台(操作系统)移植需求(一般使用 C 或 Java)。
  • 复杂的应用,在必须使用结构化编程的时候(需要变量的类型检查,函数原型,等等)。
  • 对于影响系统全局性的关键任务应用。
  • 对于安全有很高要求的任务,比如你需要一个健壮的系统来防止入侵、破解、恶意破坏等等。
  • 项目由连串的依赖的各个部分组成。
  • 需要大规模的文件操作。
  • 需要多维数组的支持。
  • 需要数据结构的支持,比如链表或数等数据结构。
  • 需要产生或操作图形化界面 GUI。
  • 需要直接操作系统硬件。
  • 需要 I/O 或 socket 接口。
  • 需要使用库或者遗留下来的老代码的接口。
  • 私人的、闭源的应用

如果你的应用符合上面的任意一条,那么就考虑一下更强大的语言吧——或许是 Perl、Python、Ruby——或者是编译语言比如 C/C++,或者是 Java。

初识 Shell

#!/bin/bash
echo "Hello World !"

#! 是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行。通常这一行被称作 Shebang。常见的有 #!/bin/bash 是用 Bash Shell, #!/bin/sh 使用 Bourne Shell。

chmod +x ./test.sh  #使脚本具有执行权限
./test.sh  #执行脚本

Linux 系统会去 PATH 里寻找有没有叫 test.sh 的,而只有 /bin, /sbin, /usr/bin,/usr/sbin 等在 PATH 里 要用 ./test.sh 告诉系统说,就在当前目录找。

再一个稍微复杂一点的脚本:

echo "What is your name?"
read PERSON
echo "Hello, $PERSON"

变量

学习每一门编程语言都是从变量开始。

定义

变量定义时,变量名不加 $,如

variableName="value"

变量名和等号之间不能有空格,变量名需遵循规则:

  • 首字符必须为 [^a-zA-z]
  • 中间不能有空格,可是有下划线 _
  • 不能使用标点
  • 不能使用 bash 关键字, help 命令可查看保留关键字

引用

使用定义过的变量,在变量名之前添加美元符号 ${variableName}

yourName="einverne"
echo $yourName
echo ${yourName}

花括号是可选的,用来识别变量边界,建议每次都加上。

重新定义变量

不加美元符号,直接重新赋值即可

yourName="einverne"
echo "your name is ${yourName} !!"
yourName="Ein Verne"
echo "your name is ${yourName} !!"

只读变量

yourName="einverne"
echo "your name is ${yourName} !!"
readonly yourName
yourName="Ein Verne"

会发生错误,

your name is einverne !!
./demo.sh: line 25: yourName: readonly variable

删除变量

使用 unset 命令删除变量

unset variableName

unset 不能删除只读变量

变量类型

同时存在三种变量:

  1. 局部变量,字脚本或者命令中定义,仅在当前 shell 实例中有效,其他启动的程序不能访问
  2. 环境变量,所有陈旭,包括 shell 启动的程序都能访问,有些程序需要依赖环境变量 PATH 来保证其正常运行
  3. shell 变量

有 shell 程序设置的特殊变量

shell 特殊变量

变量 含义
$$ 当前 Shell 进程 ID , PID
$0 当前脚本的文件名
$n 传递给脚本或函数的参数, n 是数字,表示第几个参数
$# 传递给脚本或函数的参数个数
$* 传递给脚本的所有参数
$@ 传递给脚本的所有参数,被双引号包围稍有不同
$? 上个命令的退出状态,或者函数的返回值

$*$@ 都表示传递给函数或脚本的所有参数,不被双引号 (“ “) 包含时,都以$1 $2$n 的形式输出所有参数。

但是当它们被双引号 (“ “) 包含时,$* 会将所有的参数作为一个整体,以$1 $2 … $n的形式输出所有参数;$@ 会将各个参数分开,以$1 $2$n 的形式输出所有参数。

测试脚本

set -o nounset                              # Treat unset variables as an error

echo "File name is $0"
echo "First parameter : $1"
echo "First parameter : $2"
echo "This script have $# parameters"
echo "They are $*"
echo "They are $@"

for var in "$*"
do
    echo "${var}"
done

for var in "$@"
do
    echo "${var}"
done

结果:

./demo.sh p1 p2
File name is ./demo.sh
First parameter : p1
First parameter : p2
This script have 2 parameters
They are p1 p2
They are p1 p2
p1 p2
p1
p2

转义

转义表

转义字符 含义
\ 反斜杠
\a 报警
\b 退格
\f 换页,将当前位置移到下页开头
\n 换行
\r 回车
\t Tab
\v 垂直制表符

使用 echo -e "value is ${variableName} \n" 中的 -e 选项来进行转义,否则会原样输出。

echo 的 -E 禁止转义,默认不转义, -n 来禁止插入换行符。

结果暂存

将输出结果保存到变量中,在合适的地方使用

Data=`date`
echo "Date is ${Date}"

变量替换

根据变量的状态,来改变变量的值

形式 说明
${var} 变量原本值
${var:-word} 如果变量 var 为空或已被删除 (unset),那么返回 word,但不改变 var 的值。
${var:=word} 如果变量 var 为空或已被删除 (unset),那么返回 word,并将 var 的值设置为 word。
${var:?message} 如果变量 var 为空或已被删除 (unset),那么将消息 message 送到标准错误输出,可以用来检测变量 var 是否可以被正常赋值。
若此替换出现在 Shell 脚本中,那么脚本将停止运行。
${var:+word} 如果变量 var 被定义,那么返回 word,但不改变 var 的值。

表达式

基本表达式

#!/bin/sh

a=10
b=20
val=`expr $a + $b`
echo "a + b : $val"

val=`expr $a - $b`
echo "a - b : $val"

val=`expr $a \* $b`
echo "a * b : $val"

val=`expr $b / $a`
echo "b / a : $val"

val=`expr $b % $a`
echo "b % a : $val"

if [ $a == $b ]
then
   echo "a is equal to b"
fi

if [ $a != $b ]
then
   echo "a is not equal to b"
fi

基本解释

  • 表达式和运算符之间要有空格,例如 2+2 是不对的,必须写成 2 + 2
  • 乘号 (*) 前边必须加反斜杠 (\) 才能实现乘法运算
  • 条件表达式要放在方括号之间,并且要有空格,例如 [$a==$b] 是错误的,必须写成 [ $a == $b ]

关系运算符

关系运算符只能用于数字,或者字符串中只能包含数字

运算符 说明
-eq 相等返回 true
-ne 不相等返回 true
-gt great than 返回 true
-lt less than 返回 true
-ge great or equal than , 返回 true
-le less or equal than , 返回 true

布尔运算

运算符 说明
!
-o 或 or
-a 与 and

字符串

# 单引号中的任何字符都原样输出
# 单引号中不能出现单引号
echo '${hi}: "einverne"\t\n\r'

# 双引号可以有变量
# 双引号可以出现转义字符

yourName='einverne'
hi="hi, ${yourName} !\n"

# 字符长度
echo "your name length: ${#yourName}"

echo "first two character of your name ${yourName:0:2}"

text="0123456789-0123456789"
echo $text
echo '${text:position}' "${text:1}"
echo '${text:position:length}' "${text:1:3}"
# 从变量 $string 的开头,删除最短匹配 $substring 的子串
echo '${text#01}' "${text#01}"
# 从变量 $string 的开头,删除最长匹配 $substring 的子串
echo '${text##01}' "${text##01}"
# 从变量 $string 的结尾,删除最短匹配 $substring 的子串
echo '${text%89}' "${text%89}"
# 从变量 $string 的结尾,删除最长匹配 $substring 的子串
echo '${text%%89}' "${text%%89}"

# 使用 $replacement, 来代替第一个匹配的 $substring
echo '${text/substring/replacement} ${text/01/ab}' "${text/01/ab}"
# 使用 $replacement, 代替所有匹配的 $substring
echo '${text//substring/replacement} ${text//01/ab}' "${text//01/ab}"

# 如果 $string 的前缀匹配 $substring, 那么就用 $replacement 来代替匹配到的 $substring
echo '${text/#substring/replacement} ${text/#01/ab}' "${text/#01/ab}"

# 如果 $string 的后缀匹配 $substring, 那么就用 $replacement 来代替匹配到的 $substring
echo '${text/%substring/replacement} ${text/%01/ab}' "${text/%01/ab}"

数组

array_name=(value0 value1 value2 value3)
# 或者
array_name=(
value0
value1
value2
value3
)
# 或者
array_name[0]=value0
array_name[1]=value1
array_name[2]=value2
# 读取数组
valuen=${array_name[2]}
# 获取所有元素
${array_name[*]}
${array_name[@]}
# 长度
# 取得数组元素的个数
length=${#array_name[@]}
# 或者
length=${#array_name[*]}
# 取得数组单个元素的长度
lengthn=${#array_name[n]}

条件语句

IF

条件判断有两种格式,test EXPRESSION[ EXPRESSION ] , test 判断语句实际应用比较少, [] 判断应用比较广。

基本格式

test EXPRESSION

test 是关键词,表判断, EXPRESSION 被判断语句。

表达式 说明
test -d File 判断 File 是不是目录
test -f File 判断 File 是不是普通文件
test -L File 判断 File 是不是符号链接
test -r File 判断 File 是不是可读
test -s File 判断 File 是不是文件长度大于 0、非空
test -w File 判断 File 是不是可写
test -u File 判断 File 是不是有 suid 位设置
test -x File 判断 File 是不是可执行

格式

[ EXPRESSION ]

中括号左右必须要有空格隔开

表达式 说明
[ (expr) ] expr 为真
[ !expr ] expr 为假
[ expr1 -a expr2 ] expr1 和 expr2 同时为真
[ expr1 -o expr2 ] expr1 或 expr2 为真
[ -n string ] string 的长度不为 0
[ -z string ] string 的长度为 0
[ string1 = string2 ] 两个字符串 string1 和 string2 相等
[ string1 != string2 ] 两个字符串 string1 和 string2 不等
[ integer1 -eq integer2 ] 两个 integer1 和 integer2 整数相等
[ integer1 -ne integer2 ] integer1 不等于 integer2
[ integer1 -ge integer2 ] integer1 大于或等于 integer2
[ integer1 -gt integer2 ] integer1 大于 integer2
[ integer1 -le integer2 ] integer1 小于或等于 integer2
[ integer1 -lt integer2 ] integer1 小于 integer2
[ file1 -ef file2 ] 文件 file1 和 file2 有相同的 device 和 inode 数目
[ file1 -nt file2 ] file1 的修改事件早于 file2
[ file1 -ot file2 ] file1 的修改事件晚于 file2
[ -b file ] file 是块设备
[ -c file ] file 是字符设备
[ -d file ] file 是文件夹
[ -e file ] file 是存在
[ -f file ] file 是普通文件
[ -g file ] file 存在,且有 group-ID
[ -G file ] file 存在,且 group-ID 是有效的
[ -h file ] file 存在,且是一个硬链接
[ -L file ] file 存在,且是一个软链接
[ -r file ] file 存在,且可读
[ -w file ] file 存在,且可写
[ -x file ] file 存在,且可执行

下面是一些例子:

if [ expression ]
then
   Statement(s) to be executed if expression is true
fi


if [ expression ]
then
   Statement(s) to be executed if expression is true
else
   Statement(s) to be executed if expression is not true
fi

# 更复杂
if [ expression 1 ]
then
   Statement(s) to be executed if expression 1 is true
elif [ expression 2 ]
then
   Statement(s) to be executed if expression 2 is true
elif [ expression 3 ]
then
   Statement(s) to be executed if expression 3 is true
else
   Statement(s) to be executed if no expression is true
fi

CASE

CASE 语句为多选择语句,CASE 语句匹配一个值与一个模式,如果匹配成功就执行相应的命令。

echo 'Input a number between 1 to 4'
echo 'Your number is:\c'
read aNum
case $aNum in
    1)  echo 'You select 1'
    ;;
    2)  echo 'You select 2'
    ;;
    3)  echo 'You select 3'
    ;;
    4)  echo 'You select 4'
    ;;
    *)  echo 'You do not select a number between 1 to 4'
    ;;
esac

循环语句

for

for 变量 in 列表
do
    command1
    command2
    ...
    commandN
done

举例

sum=0
for ((i=1; i < 10; i++))
do
((sum=$sum+$i))
done

echo "sum=$sum"

while

while 语句

while condition
do
   Statement(s) to be executed if command is true
done

举例

i=0
sum=0
while [ $i -lt 10 ]
do
    (( i++ ))
    (( sum=$sum+$i ))
    echo "i=$i"
done

echo "sum=$sum"

until

until 语句

until condition
command 1
...
done

举例

i=0
sum=0
until [ $i -ge 10 ]
do
    (( i++ ))
    (( sum=$sum+$i ))
    echo "i=$i"
done

echo "sum=$sum"

循环当中有 break 调出循环,和 continue 跳过本次循环这两个关键字,和其他编程语言一样,就不多说了。

函数

function function_name () {
    list of commands
    [ return value ]
}

reference


2017-03-01 linux , shell , bash , sh , zsh

在 Ubuntu/Mint 下快速创建桌面快捷方式

Mint 下有些应用下载之后,解压就能使用,但是这样每次启动该应用时就需要到这个目录中来启动,没有可快捷方式可以直接在桌面上,或者启动器中快速启动。 Mint/Ubuntu 中,gnome 桌面的快捷方式都存在 ~/.local/share/applications 目录下。

系统中还有另外一个目录 /usr/share/applications 也会存放桌面快捷方式。

使用 gnome-desktop-item-edit ~/.local/share/applications --create-new 即可创建新的快捷方式。如果该命令不存在记得安装

sudo apt-get install --no-install-recommends gnome-panel

运行上面的命令之后会出现一个对话框,然后选择相应的图标,可执行文件地址,起名字即可。这样以后就可以从启动器中启动该应用。Wiznote 就是使用该方法来启动的 .AppImages 文件。

创建的 Desktop 文件 (.desktop) 类似下面

[Desktop Entry]
Encoding=UTF-8
Name=eclipse
Comment=Eclipse IDE
Exec=/usr/local/eclipse/eclipse  #根据软件的具体执行路径修改
Icon=/usr/local/eclipse/icon.xpm  #根据软件的具体执行路径修改
Terminal=false  #软件打开时是否启动终端
StartupNotify=false
Type=Application
Categories=Application;Development;

另外系统的 desktop 默认地址是 /usr/share/applications 。将文件创建在该文件夹中也能够达到效果。


2017-03-01 linux , desktop , desktop-icon , short-cut , gnome

Linux Mint 下使用 electron wechat

在 Ubuntu 下明明非常好用的 sudo snap install electronic-wechat 到了 Mint 下竟然就不管用了,出现了如下错误

cannot perform readlinkat() on the mount namespace file descriptor of the init process: Permission denied

无奈只能寻求另外的方法,手动编译安装果然可以。看 electronic-wechatGitHub 代码知道该项目使用 electron

安装 nodejs ,然后安装

git clone https://github.com/geeeeeeeeek/electronic-wechat.git
npm install
npm run build:linux

然后在桌面上右击创建一个图标,名字,执行路径都填写清楚,然后就可以快速启动 electronic wechat 了。


2017-02-28 wechat , linux , mint , npm , nodejs , node

使用 mitmproxy 抓包

在之前的文章 Android 抓包 中介绍过 Mac 下 [[Charles]] 进行客户端的抓包,那篇文章中最后介绍其他工具的时候提到了 [[mitmproxy]] 这样一款命令行工具,最近使用来看,也是非常强大的工具。这里就简单记录一下。

mitmproxy 是用 Python 和 C 开发的一款支持 HTTP(S) 的中间人代理软件(man-in-the-middle proxy),不同于 Fiddler2,burpsuite 等类似功能工具,mitmproxy 可在终端下运行并且可以用来拦截、修改、重放和保存 HTTP/HTTPS 请求。mitmproxy 可以辅助 WEB 及客户端调试、开发和测试,是一个渗透测试的工具。

安装

sudo apt-get install python3-pip python3-dev libffi-dev libssl-dev libtiff5-dev libjpeg8-dev zlib1g-dev libwebp-dev
sudo pip3 install mitmproxy  # or pip3 install --user mitmproxy

或者从源里面拉,但是可能不是最新版本

sudo apt install mitmproxy

我从源里面拉的版本为 0.15 版,而 pip3 安装的为 1.02 版本,相差版本足有一年。

或者熟悉 Docker 可以使用官方打包好的镜像

工作原理

mitmproxy 实现原理:

  1. 客户端发起一个到 mitmproxy 的连接,并且发出 HTTP CONNECT 请求,
  2. mitmproxy 作出响应 (200),模拟已经建立了 CONNECT 通信管道,
  3. 客户端确信它正在和远端服务器会话,然后启动 SSL 连接。在 SSL 连接中指明了它正在连接的主机名 (SNI),
  4. mitmproxy 连接服务器,然后使用客户端发出的 SNI 指示的主机名建立 SSL 连接,
  5. 服务器以匹配的 SSL 证书作出响应,这个 SSL 证书里包含生成的拦截证书所必须的通用名 (CN) 和服务器备用名 (SAN),
  6. mitmproxy 生成拦截证书,然后继续进行与第 3 步暂停的客户端 SSL 握手,
  7. 客户端通过已经建立的 SSL 连接发送请求,
  8. mitmproxy 通过第 4 步建立的 SSL 连接传递这个请求给服务器。

how-mitmproxy-works-transparent-https

mitmproxy 工作步骤:

  1. 设置系统、浏览器、终端等的代理地址和端口为同一局域网中 mitmproxy 所在电脑的 IP 地址,比如我的 PC 开启 mitmproxy 之后,设置 8080 端口,本地 IP 为 192.168.1.130,那么设置 Android HTTP 代理为 192.168.1.130:8080
  2. 浏览器或移动端访问 mitm.it 来安装 mitmproxy 提供的证书
  3. 在 mitmproxy 提供的命令行下,或者 mitmweb 提供的浏览器界面中就能看到 Android 端发出的请求。

如果遇到 iOS 11 以上,访问网页时出现 This Connection is Not private

mitmproxy error

在确保证书安装的前提下,需要到 Settings > General > About > Certificate Trust Setting 开启证书信任

官方提供的安装方式:http://mitmproxy.org/doc/certinstall.html

三个命令

在完成 mitmproxy 的安装之后,mitm 提供的三个命令

  • mitmproxy 会提供一个在终端下的图形界面,具有修改请求和响应,流量重放等功能,具体操作方式有点 vim 的风格
  • mitmdump 可设定规则保存或重放请求和响应,mitmdump 的特点是支持 inline 脚本,由于拥有可以修改 request 和 response 中每一个细节的能力,批量测试,劫持等都可以轻松实现
  • mitmweb 提供的一个简单 web 界面,简单实用,初学者或者对终端命令行不熟悉的可以用 mitmweb 界面

mitmproxy 基本使用

可以使用 mitmproxy -h 来查看 mitmproxy 的参数及使用方法。常用的几个命令参数:

  1. -p PORT, --port PORT 设置 mitmproxy 的代理端口
  2. -T, --transparent 设置透明代理
  3. --socks 设置 SOCKS5 代理
  4. -s "script.py --bar", --script "script.py --bar" 来执行脚本,通过双引号来添加参数
  5. -t FILTER 过滤参数

在 mitmproxy 命令模式下,在终端显示请求流,可以通过 Shift + ? 来开启帮助查看当前页面可用的命令。

基本快捷键

b  保存请求 / 返回头
C  将请求内容导出到粘贴板,按 C 之后会有选择导出哪一部分
d  删除 flow 请求
E  将 flow 导出到文件
w  保存所有 flow 或者该 flow
W  保存该 flow
L  加载保存的 Flow
m  添加 / 取消 Mark 标记,会在请求列表该请求前添加红色圆圈
z  清空 flow list 和 eventlog
/  在详情界面,可以使用 / 来搜索,大小写敏感
i  开启 interception pattern 拦截请求

移动

j, k       上下
h, l        左右
g, G   go to beginning, end
space    下一页
pg up/down   上一页 / 下一页
ctrl+b/ctrl+f    上一页 / 下一页
arrows 箭头     上下左右


全局快捷键
q   退出,或者后退
Q  不提示直接退出

同样在 mitmproxy 中不同界面中使用 ? 可以获取不同的帮助,在请求详细信息中 m 快捷键的作用就完全不同 m 在响应结果中,输入 m 可以选择 body 的呈现方式,比如 json,xml 等 e 编辑请求、响应 a 发送编辑后的请求、响应。 因此在熟悉使用 ? 之后,多次使用并熟悉快捷键即可。就如同在 Linux 下要熟悉使用 man 命令一样,在不懂地方请教 Google 一样,应该是习惯性动作。多次反复之后就会变得非常数量。

使用脚本

使用 s 参数制定 inline 脚本

mitmproxy -s script.py

比如将指定 url 的请求指向新的地址

用于调试 Android 或者 iOS 客户端,打包比较复杂的时候,强行将客户端请求从线上地址指向本地调试地址。可以使用 mitmproxy scripting API mitmproxy 提供的事件驱动接口。

加上将线上地址,指向本地 8085 端口,文件为 redirect_request.py

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
def request(flow):
    if flow.request.pretty_host == 'api.github.com':
        flow.request.host = '127.0.0.1'
        flow.request.port = 8085

则使用 mitmweb -s redirect_request.py 来调用此脚本,则通过 mitm 的请求都会指向本地 http://127.0.0.1:8085。

更多的脚本可以参考

一个完整的 HTTP flow 会依次触发 requestheaders, request, responseheadersresponse

启用 SOCKS5 代理

添加参数 --socks 可以使用 mitmproxy 的 SOCK5 代理

透明代理

透明代理是指将网络流量直接重定向到网络端口,不需要客户端做任何设置。这个特性使得透明代理非常适合不能对客户端进行配置的时候,比如说 Android 应用等等。

几个问题

对于 Openwrt 的路由器,在无线接口配置中一定要注意查看,“禁止客户端之间的通信”这个选项不启用。或者在 /etc/config/wireless 配置中, wifi-iface 配置中

option isolate 0

为关闭状态,否则无法让无线局域网中的设备之间通信。

在 Linux 下让 Chrome 信任 mitmproxy 的 CA 证书

主要是

sudo apt-get install libnss3-tools
certutil -d sql:$HOME/.pki/nssdb -A -t C -n mitmproxy -i ~/.mitmproxy/mitmproxy-ca-cert.pem

From: github

reference


2017-02-27 mitmproxy , android , proxy , linux , http , debug , reverse

我的一加 3 手机设置

最近屏幕摔碎了一次,维修过程中无奈抹掉了所有数据,于是只能从头开始开搞。这里只是简单的记录一下,以免哪一天又有同样的悲剧发生,当然不可能再有下次了。

刷回原厂设置

对于 Nexus 机型来说,Google 都提供了原厂镜像,一加同样也是,可以从官网下载 ,不过亲身经历一加官网的好多链接都挂掉了,直接从 xda 找反而要方便许多。官网的链接使用迅雷下载还遭遇了掉包,所以对于刷机包,一定要验证 md5,一定一定。

刷 Oxygen OS (氧 OS) , 有很多方法,如果使用的 stock recovery ,可以使用 adb sideload full-rom.zip 来刷入(官网有详细教程)。 而如果安装了 TWRP 或者 twrp-3.0.2-0_blu_spark_v11-op3.img 等等第三方的 recovery , 可以直接在 recovery 中 install ROM。虽然间隔遇到了一些问题,在使用 recovery 刷机的时候遇到 “this package is for OnePlus 3 devices this is one plus 3”,这样奇怪的错误。但是最后找原因还是因为 recovery 的关系,使用 TWRP 3.0.2 及以前的版本即可,或者 blu spark 的版本都可以完美解决。

root

使用下面地址给出的 superSu, systemless 可以完美 root。

https://forum.xda-developers.com/oneplus-3/how-to/oneplus-3-how-to-unlock-bootloader-t3398733

xda 终究是一切的开端,很多内容都是从这边开始,然后被无数人复制粘贴搬运到互联网的各个角落。因此,遇到任何刷机或者 root,或者 Android 相关的任何折腾的问题,用英语关键词来这里搜索有的时候比直接在 Google 搜索要灵得多。这有的时候就像是要查找某一款产品的型号或者功能时,直接在 jd.com 或者 淘宝搜索要来的快得多。或者垂直搜索领域确实值得深入发展一下,感觉这也是 Google 越来越担心亚马逊存在的原因之一吧。

修改 DPI

在 root 之后,开机进入,就会发现一加默认的 DPI 实在感人,远看像老人机,每一个图标都老大老大。Oxygen OS 默认的 DPI 是 480,实际使用感受设置在 420 到 440 最佳。基本上 Launcher 中一行 5 个 图标,原始设置一行只能放下 4 个图标。

一加 3 修改 DPI ,可以有很多方法,最最简单的方法就是下载 Le DPI Changer 这个应用,直接在应用中修改(记得上一次可以修改成功,但是这一次差点将系统搞坏,这里要注意,修改 build.prop 文件一定要备份,否则有可能遇到无法开机的情况。幸亏这一次在系统中找到了 build.prop 的备份,在 recovery 中恢复了之前的设置)。

另外一种情况就是,直接修改 /system/build.prop 系统目录下的 build.prop 文件,直接修改文件内容。

ro.sf.lcd_density=420

同样也可以利用 BuildProp Editor 应用来修改,开启应用,右上角设置,中添加,键为 ro.sf.lcd_density ,值为 420 的条目。重启手机便可以生效。记住修改 build.prop 文件,以及任何系统文件一定要备份系统,或者可以在 recovery 中能够恢复修改的内容,否则有可能无法开机。建议使用 BuildProp Editor 这个应用来修改。

Magisk

从 Play Store 中下载 Magisk Manager,安装打开,保证 ROOT,此时从 Magisk 中更新组件,重启,即可。

下文中的 boot.img 不要轻易刷入,其他未经验证的 boot.img 也不要轻易刷入。

  • https://forum.xda-developers.com/oneplus-3/how-to/guide-oneplus-3-magisk-install-android-t3433093
  • https://forum.xda-developers.com/apps/magisk/official-magisk-v7-universal-systemless-t3473445

Xposed

在安装好 Magisk 之后,从 Magisk 中可以选择安装 Xposed ,然后参照之前整理过的文章 我使用的 Xposed Module 安装一遍。还很快。

如果不想使用 Systemless 的 Xposed ,可以使用官网的地址,直接从 recovery 中刷入。

Kernel

一直使用的 ElementalX 的 kernel ,直接从 Play Store 上下载 ElementalX Manager,在应用中安装即可,应用会自动选择相应的版本。

中间遇到的几个问题

The dm-verity is not started in enforcing mode

原厂给我刷了 Android 7.0 的系统,在开机的时候除了解锁 bootloader 的提醒之外,还多了这个提醒,猜测可能是 Android 7.0 导致的,在刷完 Oxygen OS 3.2.8 (Android 6.0.1) 之后,这个提示就消失了。

Adblocker 被移除

在原本的 Xposed 列表中我曾经写过, Adblocker 这样一款广告去除 Module,但是这个 module 莫名其妙被 Xposed 仓库和 xda 移除,没有任何消息。有人说是因为有木马导致的,但是我更加觉得是因为去广告有用,得罪了不少人才导致下架的。

更多的细节可以参考下面的文章:

  • https://forum.xda-developers.com/xposed/happened-to-adblocker-t3453144
  • https://forum.xda-developers.com/android/general/guide-ad-blocking-t3218167
  • https://www.reddit.com/r/xposed/comments/50s0ow/discussion_the_adblocker_module_has_been_removed/

不过还是能从 Coolapk.com 来下载到下架版本。个人感觉还是能够屏蔽很多广告的。

至此所有系统级别设置都已经完成。附上一张桌面

OnePlus3 Wallpaper

reference


2017-02-26 oneplus , android , xposed , magisk , root

终端使用 sock5 代理

为了解决 Linux Mint/Ubuntu 下安装 Dropbox 的问题,而认识了 proxychains。proxychains 是 Linux 下的代理工具,他允许其他程序通过其代理上网,支持 HTTP, SOCKS4 和 SOCKS5 类型的代理服务器,并可配置多个代理方式。

一直使用的 Dropbox 因为其被屏蔽,所以安装及使用必须通过代理,而 Shadowsocks 作为天然的 socks5 代理成为了最佳选择,在安装 Dropbox(Linux) 之后,设置中可以设置 Socks5 代理。于是剩下的问题便是,如何安装 Dropbox 了。在 Dropbox 官网下载的安装程序会自动下载相关组件,但是因为没有走代理,几乎在直连的情况下无法成功。所以在解决这个问题的时候知道了 proxychains。在了解其作用之后,发现也可以利用其特性来达到在 Linux 终端下让任何命令走代理的目的。

安装 Proxychains

安装 proxychains 可以有很多方法,最简单的方式就是使用 apt

sudo apt install proxychains

如果想要使用最新版,也可以自己手动编译源码

# 安装
git clone https://github.com/rofl0r/proxychains-ng.git
./configure --prefix=/usr --sysconfdir=/etc
make
sudo make install
sudo make install-config

配置

在安装完成之后,一般在 /etc/proxychains.conf 处会有默认配置文件,编辑该文件

sudo vim /etc/proxychains.conf
# 然后将如下内容添加到文件末
socks5 127.0.0.1 1080

# 开启本地 socks5 代理

然后使用

proxychains dropbox start -i

就可以启动 Dropbox 安装程序。安装成功之后使用设置中内置的代理即可。

或者使用

proxychains wget https://www.google.com

使用 curl ip.gs 来验证是否成功

$ curl ip.gs 
当前 IP:124.xxx.xxx.xxx 来自:中国北京北京 电信

$ proxychains curl ip.gs
ProxyChains-3.1 (http://proxychains.sf.net)
|DNS-request| ip.gs 
|S-chain|-<>-127.0.0.1:1080-<><>-4.2.2.2:53-<><>-OK
|DNS-response| ip.gs is 45.116.12.10
|S-chain|-<>-127.0.0.1:1080-<><>-45.116.12.10:80-<><>-OK
当前 IP:106.xxx.xxx.xxx 来自:日本东京都东京linode.com kddi.com

来现在任何内容

延伸

在配置完 proxychains 之后,在终端如果任何命令无法连接成功时,在其前加上 proxychains 就可以走代理方式来执行该命令。

在网上查阅的时候同样发现还有其他类似的工具,比如 polipo,这是一个 socks5 转 http 的代理,设置之后可以使用 export http_proxy=http://localhost:port 来让终端走代理。

polipo 的使用也非常简单

sudo apt-get install polipo

然后再修改其配置

sudo vim /etc/polipo/config

放置以下配置:

logSyslog = true
logFile = /var/log/polipo/polipo.log

proxy:socksParentProxy = "localhost:1080"
socksProxyType = socks5

启用 polipo

sudo /etc/init.d/polipo start

默认端口为 8123 使用即可。

不过需要注意的是 Polipo 作者已经在 GitHub 宣布项目不再更新。推荐还是使用 proxychains。

reference


2017-02-25 Socks5 , Linux , Proxy , Ternimal

fastboot and adb 工具

早之前曾经写过过一篇文章 总结 adb 的常用命令,这次刷机也复习了一些命令,这里再补充一下刷机中必不可少的 fastboot 命令。

就像之前 adb 中说过的那样, adb 用来向开机的 Android 设备发送指令,而 fastboot 则是向开机的 Android bootloader 来发送指令,因此在刷机过程中,如果手机重启进入 bootloader 基本之后使用的 命令都离不开 fastboot。比如在安装第三方 recovery 时,要用到 fastboot flash recovery recovery.img 这样的指令。虽然不能依靠此命令来刷入 ROM,但是 fastboot 也能够做一些 adb 命令无法实现的功能。

安装

sudo apt-get install android-tools-fastboot

检查设备是否 bootloader 解锁

常用的命令如下:

adb devices
adb reboot bootloader
fastboot devices
fastboot oem device-info

结果:

❯ fastboot oem device-info
...
(bootloader) Verity mode: true
(bootloader) Device unlocked: true
(bootloader) Device critical unlocked: true
(bootloader) Charger screen enabled: false
OKAY [  0.006s]
finished. total time: 0.007s

bootloader

关于 bootloader 部分可以参考 之前的文章

fastboot

adb 和 fastboot 两个工具都是跟随着 Android SDK 分发的,但是由于这两个工具需求过高,Google 单独提供了两个工具集的下载

在下载之后,需要将文件路径添加到系统环境变量中,以便于在各个路径下访问。

假设加压后的路径为 ~/Android/Sdk/platform-tools/, 那需要在 ~/.bashrc 或者 ~/.zshrc 中配置。

export PATH="$PATH:/home/einverne/Android/Sdk/platform-tools"

WIndows 同理,在系统配置环境变量。

然后在终端下使用 adb devices ,就能够查看连接的设备。同样在 bootloader 下,也可以使用 fastboot devices 来查看连接设备,如果未出现已经 USB 连接的设备,查看驱动是否安装完好即可。

查看设备

fastboot devices

用 boot.img 启动手机

用当前目录下的 boot.img 启动手机,在手机 boot 分区损坏的情况下可以用这个正常进入系统

fastboot boot boot.img

同理可以使用

fastboot boot recovery.img

来启动 recovery 模式

和手机上现有的系统完全无关,只要 PC 的 boot.img 或者 recovery.img 是可以正常工作的就可以。

修改分区

刷机的人经常会遇到下面一些场景。

将当前目录下的 boot.img 刷入手机的 boot 分区:

fastboot flash boot boot.img

将当前目录下的 recovery.img 刷入系统的 recovery 分区:

fastboot flash recovery recovery.img

将当前目录下的 system.img 刷入系统的 system 分区:

fastboot flash system system.img

同理,刷入 data 分区

fastboot flash userdata userdata.img

清理分区的命令,慎用!!!

fastboot erase system
fastboot erase cache
fastboot erase config
fastboot erase data
fastboot erase logs
fastboot erase factory

最后重启手机

fastboot reboot

2017-02-25 Android , Fastboot , adb

Android 反编译

本文介绍 Android 反编译工具,只介绍工具名字及工具简单使用,详细开来再具体讲吧。本文主要包含工具的作用,工作的简单用法,以及反编译的基本步骤。

概述

反编译 Android APK 主要需要依靠如下几个工具:

  • apktool:A tool for reverse engineering Android apk files 查看 APK 包中的 AndroidManifest.xml 等 XML 资源文件
    • apk 本质上是一种压缩包,可以通过解压的方式获得资源文件,但是如果直接解压 manifest 和 XML 都是乱码,所以需要 apktool 工具来获得资源文件
  • dex2jar:Tools to work with android .dex and java .class files 将 APK 包中的 Dalvik 字节码文件(.dex)转换为 .jar 文件
  • JD-GUI:Java Decompiler is a tools to decompile and analyze Java 5 “byte code” and the later versions 查看 .jar 文件的 Java 源码
    • jd-gui 可以用来查看上一步中从 dex 文件得到的 jar

相关项目及工具地址后文贴出。

使用方法

apktool

Apktook 是一个反编译(reverse engineering) 工具,可以用来反编译Android APK。几乎可以将APK中的 resources.arsc, classes.dex9.png 以及 XMLs 等等源文件反编译得到。

安装需要:

  1. JRE 1.7 及以上
  2. 关于 Android SDK, AAPT 以及 smali 的基本知识

地址: https://ibotpeaches.github.io/Apktool/

apktool 安装

各平台的安装指南,如果使用 Linux 可以使用如下简易步骤:

  1. 右击链接 将 Linux 的脚本保存为 apktool,并移动到 /usr/local/bin/ 目录下
  2. 下载最新版本 apktool.jar (https://bitbucket.org/iBotPeaches/apktool/downloads/)
  3. 确保 Linux 64 位系统中安装了 ia32-libs,使用 apt search ia32 ,然后 apt install ia32-libs 安装,可跳过
  4. 将下载的 apktool-x.x.x.jar 重命名为 apktool.jar
  5. apktool.jarapktool 移动到 /usr/local/bin/ 目录中,需要 root 权限
  6. 给予以上两者执行权限 chmod +x /usr/local/bin/apktool
  7. 在终端执行 apktool

apktool 使用

  1. 拿到 APK 安装包,比如 xxx.apk
  2. 在APK同目录下,执行 apktool d xxx.apk
  3. 目录下会多一个与 APK 同名的文件夹,是解压后的 APK,其中的 XML 资源文件和各种 drawable 图片资源等可以直接看, 不想看 XML 文件的话,可以不用 apktool,直接将 APK 后缀改为 .zip 后解压即可,此时得到的解压目录中的结构和 apktool 解的是一样的,但是 XML 都处在压缩状态不能看。

Apktool 其实还可以用来做另外一件事情,就是汉化,或者将语言包替换之后,重新打包,此时需要使用 apktool b xxx.apk 来重新打包 APK。

dex2jar

dex2jar 能够将 dex 转换为 jar

地址:https://github.com/pxb1988/dex2jar

  1. 不使用 apktool,直接修改后缀解压 APK,将解压后 APK 中的 .dex 文件复制到 dex2jar 目录下
  2. 进入该目录执行 ./d2j-dex2jar.sh xxx.dex(注意赋予该 shell 可执行权限 chmod +x d2j-dex2jar.sh)

JD GUI

JD GUI 用来反编译源文件jar包,查看源代码

地址:http://jd.benow.ca/

  1. dex2jar 目录下会多一个 .jar 文件,用 GUI 工具 JD-GUI 打开看就可以了(当然,混淆过的代码中变量名都是 a, b, c, d)

总结

总的来说 apktool 可以让你轻松的拿到应用的资源文件,包括图片,xml等,而 dex2jar 和 JD GUI 可以反编译源代码,看到一些基础的代码结构。

Update

后来 Google 搞了一个 ClassyShark,看起来不错,不过还没来得及尝试

http://classyshark.com/

还有一个 不错的 smali 查看插件看起来也不错,还未尝试

https://github.com/JesusFreke/smali

另外有一个 MAC 专属的一键反编译工具,可以一试:

https://github.com/Jermic/Android-Crack-Tool

又一个查看 dex 到 jar 的反编译工具:

https://github.com/skylot/jadx


2017-02-22 Android , Java , Apktook , Google

HBase 如何设计 Table Schema

我们都知道 HBase 的基本结构由 rowkey,column, timestamp 组成。列存储数据不同于关系型数据库,MySQL 一旦建表,需要修改表结构时则需要执行对应的修改语句,而 HBase 在建完表之后,对于列的增加则不需要修改建表语句,但这并不意味着 HBase 的建表就可以随意建。

首先来看看 HBase 的结构

  • Table, 表,HBase 按照表来组织数据
  • Row, 行,Table 可以有多 Row,Table 按照 RowKey 进行索引
  • Column Family,列族,一行可以分为多个列族,列族需要在建表 Schema 中指定
  • Column Qualifier,和列族一起确定唯一列,一个列族可以有任意多个 Qualifier
  • TimeStamp,支持多版本,用时间戳来确定数据版本

通过以上 Row, Column Family, Column Qualifier, TimeStamp 可以唯一确定一个基本存储单元 Cell。

关系型 vs 列存储

并没有关系型数据库到列存储数据库一对一的设计迁移,在关系型数据库中,设计的重点是描述实体和其他实体之间的关系,查询和索引可以在之后设计。

而在 HBase 中,遵循着查询优先的设计模式,所有可能的查询都需要在设计中体现,因此 Schema 的设计方式也因此而来。考虑查询的方式,然后设计 Schema,这样可以使得数据能够快速被查询到。另外需要注意 HBase 被设计用来在大数据集群中使用,当预计数据量能够到达一个层级的时候再考虑使用。

rowkey 设计

rowkey 是 Table 的”主键”,设计时需要确保唯一性,数据按照 rowkey 大小顺序存储,rowkey 不是 schema 的一部分,但需要仔细设计:

  • 类型:rowkey 为 byte[],HBase 提供 String/int/short/long/float/double/boolean 向 byte[] 转化的函数。
  • 随机读:根据 rowkey 的随机读会有索引的支持,效率很高。
  • 顺序读:数据按 rowkey 大小顺序存储,按照 rowkey 范围 [startKey, endKey) 的顺序读,吞吐量很高。
  • RowKey 字段的设计需要参考最高频的查询场景

rowKey 设计热点问题,rowKey 要尽量随机,不要出现连续 rowKey。

列族设计

建议 HBase 设计为高表,不宜使用过多列族。

列和列族名字不宜太长,HBase 会在内存中对列和列族做索引和缓存。


2017-02-21 hbase , schema , database , column-database , apache

Docker 使用 nginx-proxy 来架设多个网站

我们知道如果使用 Nginx 可以使用 Virtual HOST 来 HOST 多个域名下的网站到同一台机器,那么如果使用 Docker 架设了一个 WordPress,还想用 Docker 架设一个新的网站,那么该怎么办呢?

有一种解决办法就是使用 Nginx 转发请求,比如一个网站监听了 81 端口,一个网站监听了 82 端口,那么使用 Nginx 的代理功能,将对应的流量转发给对应的服务器处理即可。因此 nginx-proxy 这个镜像的作用就如上面所述,让我们将不同的流量转发给不同的 Docker 容器进行处理。

启动 nginx-proxy

有两种方式可以启动 nginx-proxy 容器,一种是通过 docker 命令,另一种是使用 docker-compose。不过用这两种方式之前,先创建一个 Docker network,将多个容器关联起来

docker network create nginx-proxy

然后创建容器

docker run -d -p 80:80 --name nginx-proxy --net nginx-proxy -v /var/run/docker.sock:/tmp/docker.sock jwilder/nginx-proxy

或者创建 docker-compose.yml

version: "3"
services:
  nginx-proxy:
    image: jwilder/nginx-proxy
    container_name: nginx-proxy
    ports:
      - "80:80"
    volumes:
      - /var/run/docker.sock:/tmp/docker.sock:ro

networks:
  default:
    external:
      name: nginx-proxy

然后在同目录下 docker-compose up -d

Nginx Proxy Manager 的管理面板在 81 端口,默认的用户名和密码是 [email protected]changeme

nginx-proxy 对外暴露 80 端口,并且监听 80 端口,允许 80 端口的流量流入。而 /var/run/docker.sock:/tmp/docker.sock 这一行则表示着允许该容器访问宿主机器的 Docker socket,这也就意味着有新容器加入,或者新容器关闭时都会通知到 nginx-proxy。

这样每一次添加容器,nginx-proxy 就会通过 socket 来接收到事件,自动创建对应的配置文件,然后重启 nginx 来生效。nginx-proxy 会寻找带有 VIRTUAL_HOST 环境变量的容器,然后依照配置进行。

另外 --net nginx-proxy 和 docker compose 中 networks 块的配置,让所有的容器通过 Docker network 进行通讯。

增加容器

比如增加一个 WordPress 容器

docker run -d --name blog --expose 80 --net nginx-proxy -e VIRTUAL_HOST=blog.DOMAIN.TLD wordpress
  • --expose 80 会允许流量从 80 端口流入
  • --net nginx-proxy 保证 Docker 使用同一个网络
  • -e VIRTUAL_HOST=blog.DOMAIN.TLD 开启 nginx-proxy 创建对应的配置文件将流量转发给 WordPress 容器

如果使用 Docker compose

version: "3"

services:
   db_node_domain:
     image: mysql:5.7
     volumes:
       - db_data:/var/lib/mysql
     restart: always
     environment:
       MYSQL_ROOT_PASSWORD: PASSWORD
       MYSQL_DATABASE: wordpress
       MYSQL_USER: wordpress
       MYSQL_PASSWORD: PASSWORD
     container_name: wordpress_db

   wordpress:
     depends_on:
       - db_node_domain
     image: wordpress:latest
     expose:
       - 80
     restart: always
     environment:
       VIRTUAL_HOST: blog.DOMAIN.TLD
       WORDPRESS_DB_HOST: db_node_domain:3306
       WORDPRESS_DB_USER: wordpress
       WORDPRESS_DB_PASSWORD: PASSWORD
     container_name: wordpress
volumes:
    db_data:

networks:
  default:
    external:
      name: nginx-proxy

不过这里需要注意的是会创建一个数据库容器,一个 WordPress app 容器。

扩展

如果想要支持 SSL,那么 nginx-proxy 有一个对应的项目 letsencrypt-nginx-proxy-companion,他可以自动创建和续签 Let’s Encrypt 的证书。

延展阅读

reference


2017-02-20 docker , nginx , proxy , dockerfile

电子书

本站提供服务

最近文章

  • AI Shell 让 AI 在命令行下提供 Shell 命令 AI Shell 是一款在命令行下的 AI 自动补全工具,当你想要实现一个功能,敲一大段命令又记不住的时候,使用自然语言让 AI 给你生成一个可执行的命令,然后确认之后执行。
  • 最棒的 Navidrome 音乐客户端 Sonixd(Feishin) Sonixd 是一款跨平台的音乐播放器,可以使用 [[Subsonic API]],兼容 Jellyfin,[[Navidrome]],Airsonic,Airsonic-Advanced,Gonic,Astiga 等等服务端。 Sonixd 是一款跨平台的音乐播放器,可以使用 [[Subsonic API]],兼容 Jellyfin,[[Navidrome]],Airsonic,Airsonic-Advanced,Gonic,Astiga 等等服务端。
  • 中心化加密货币交易所 Gate 注册以及认证 Gate.io 是一个中心化的加密货币交易所。Gate 中文通常被称为「芝麻开门」,Gate 创立于 2013 年,前身是比特儿,是一家致力于安全、稳定的数字货币交易所,支持超过 1600 种数字货币的交易,提供超过 2700 个交易对。
  • 不重启的情况下重新加载 rTorrent 配置文件 因为我在 Screen 下使用 rTorrent,最近经常调试修改 rtorrent.rc 配置文件,所以想要找一个方法可以在不重启 rTorrent 的情况重新加载配置文件,网上调查了一下之后发现原来挺简单的。
  • Go 语言编写的网络穿透工具 chisel chisel 是一个在 HTTP 协议上的 TCP/UDP 隧道,使用 Go 语言编写,10.9 K 星星。