简书链接:逆向学习法无源码通过两个源码比较研究路由器2天搞定ngrok离线插件开发
文章字数:2515,阅读全文大约需要10分钟
首先,我没有多少linux,基础,对路由器 linux底层还是缺少系统的理解的,那么我这种程序员初次玩这个路由器系统是怎么个学习法呢?
对linux shell这些的脚本也不是很了解,
作为插件开发,如果你是设计者,设计一个插件 提供一个网页,网页点击后是不是应该交互调用具体的真实shell命令,
我虽然没有开发过路由器脚本,但是我对安卓的xposed插件,以及架构 机器人js插件大概有一定的了解,当js脚本里面执行原生代码我这边会hook执行具体的逻辑,那么 我想这大概也是一样的.

官方所有源码下载地址
https://codeload.github.com/koolshare/armsoft/zip/refs/heads/master
离线安装需要taz包,window可以用7z打包成tar,然而再次打包gzip.就可以了.

定位里面两个插件的源码,发现里面的规范写法 区别,就是从模板代码里面修改install_now()
image.png

前者是开发一个内网穿透的工具 后者只是一个小公举,所以前者通常会启动一个进程,以此卸载的时候也需要killall 进程
image.png
l另外里面的文件目录存放结构大概得出一下结论
基本上也是按模块名来起名的.
网页 配置文件会放在固定的文件夹下,

image.png

从这文件看,大概就是知道请求路由器自身的域名/_api 发送postData从而执行对应脚本,.

也就是说在_api这个服务器会执行一些固定的执行脚本操作.
初次尝试
image.png

image.png

模拟真实请求
设置Cookie和user-agent
image.png

结果

image.png
最后我做了一个修改版的ngrok 内网映射插件但是上传后提示插件里面找不到web文件夹,于是只能根据源码进行分析了
根据抓包分析得出文件名,然后linux查找文件 ,

image.png

image.png

通过比对两个插件的源码,知道大概应该改什么?

之后拷贝一份frp ,因为frp 和,ngrok,内网穿透程序相似,

大概把里面的结构改一下
比对发现其实大部分内容是高度一致的,只有部分不一样.

image.png
大概熟悉了下后
拷贝一份frp
至于里面怎么打包的结构,
需要抓包分析,
根据错误提示查找定位到文件
image.png

路由器 使用 sftp,或者ssh 拷贝下来,
怎么找到文件呢?

linux命令
ls find -name "*.asp"
额,这个上面图片已经展示过了
根据提示分析为啥错了,之前我是没看懂linux命令

image.png

这句话反人类-a,虽然我熟悉很多种语言开发,但是从没看到这么写的, 这么简单的一个字-a 没有学过就不懂了

后面查阅才知道

等于```&&```\
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
```-o```等于```||```
笔记
![image.png](https://upload-images.jianshu.io/upload_images/2815884-a9ab89ffcb145414.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

那么这个文件没找到是啥问题呢,打包我是选择ngrok文件夹打包的那么解压后是ngrok/webs/Module_ngrok.asp 理论上没错,后面发现改名后没有把frp.asp改名为ngrok.asp,改完之后重新打包 ,终于成功了.
最后附开发完成的截图

![image.png](https://upload-images.jianshu.io/upload_images/2815884-37cdf8f6e254ac02.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![image.png](https://upload-images.jianshu.io/upload_images/2815884-e3c2c55ed7f28d77.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


![image.png](https://upload-images.jianshu.io/upload_images/2815884-2e33374864fbad3d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

研究2天时间,从星期天整天到星期一晚上以及早上12点...
还增加了日志实时显示功能,frpc的插件源码可能压根没那么强大,所以我借鉴另外一个插件实现了实时执行日志的监听. 但是ngrok命令执行实际上不是直接管道显示的,怎么说呢 就是```echo "aa">file ```这种方法只能dump到刚开始闪现的东西,反正怎么解决这个思路我不会.


所以大概实现思路可能只能sleep几秒 然后访问一个web inteface 也就是127.0.0.1:4040 取出访问内容来大致判断是否成功,

![image.png](https://upload-images.jianshu.io/upload_images/2815884-603a9e7cf78edd68.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


最后我发现jffs分区这种东西空间不足,于是折腾用u盘代替 ,
![image.png](https://upload-images.jianshu.io/upload_images/2815884-c07f428c55264b6d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

install.sh

#!/bin/sh
source /koolshare/scripts/base.sh
alias echo_date=’echo 【$(TZ=UTC-8 date -R +%Y年%m月%d日\ %X)】:’
MODEL=
UI_TYPE=ASUSWRT
FW_TYPE_CODE=
FW_TYPE_NAME=
DIR=$(cd $(dirname $0); pwd)
module=${DIR##*/}

get_model(){
local ODMPID=$(nvram get odmpid)
local PRODUCTID=$(nvram get productid)
if [ -n “${ODMPID}” ];then
MODEL=”${ODMPID}”
else
MODEL=”${PRODUCTID}”
fi
}

get_fw_type() {
local KS_TAG=$(nvram get extendno|grep koolshare)
if [ -d “/koolshare” ];then
if [ -n “${KS_TAG}” ];then
FW_TYPE_CODE=”2”
FW_TYPE_NAME=”koolshare官改固件”
else
FW_TYPE_CODE=”4”
FW_TYPE_NAME=”koolshare梅林改版固件”
fi
else
if [ “$(uname -o|grep Merlin)” ];then
FW_TYPE_CODE=”3”
FW_TYPE_NAME=”梅林原版固件”
else
FW_TYPE_CODE=”1”
FW_TYPE_NAME=”华硕官方固件”
fi
fi
}

platform_test(){
local LINUX_VER=$(uname -r|awk -F”.” ‘{print $1$2}’)
if [ -d “/koolshare” -a -f “/usr/bin/skipd” -a “${LINUX_VER}” -eq “26” ];then
echo_date 机型:”${MODEL} ${FW_TYPE_NAME} 符合安装要求,开始安装插件!”
else
exit_install 1
fi
}

get_ui_type(){
# default value
[ “${MODEL}” == “RT-AC86U” ] && local ROG_RTAC86U=0
[ “${MODEL}” == “GT-AC2900” ] && local ROG_GTAC2900=1
[ “${MODEL}” == “GT-AC5300” ] && local ROG_GTAC5300=1
[ “${MODEL}” == “GT-AX11000” ] && local ROG_GTAX11000=1
[ “${MODEL}” == “GT-AXE11000” ] && local ROG_GTAXE11000=1
local KS_TAG=$(nvram get extendno|grep koolshare)
local EXT_NU=$(nvram get extendno)
local EXT_NU=$(echo ${EXT_NU%_*} | grep -Eo “^[0-9]{1,10}$”)
local BUILDNO=$(nvram get buildno)
[ -z “${EXT_NU}” ] && EXT_NU=”0”
# RT-AC86U
if [ -n “${KS_TAG}” -a “${MODEL}” == “RT-AC86U” -a “${EXT_NU}” -lt “81918” -a “${BUILDNO}” != “386” ];then
# RT-AC86U的官改固件,在384_81918之前的固件都是ROG皮肤,384_81918及其以后的固件(包括386)为ASUSWRT皮肤
ROG_RTAC86U=1
fi
# GT-AC2900
if [ “${MODEL}” == “GT-AC2900” ] && [ “${FW_TYPE_CODE}” == “3” -o “${FW_TYPE_CODE}” == “4” ];then
# GT-AC2900从386.1开始已经支持梅林固件,其UI是ASUSWRT
ROG_GTAC2900=0
fi
# GT-AX11000
if [ “${MODEL}” == “GT-AX11000” -o “${MODEL}” == “GT-AX11000_BO4” ] && [ “${FW_TYPE_CODE}” == “3” -o “${FW_TYPE_CODE}” == “4” ];then
# GT-AX11000从386.2开始已经支持梅林固件,其UI是ASUSWRT
ROG_GTAX11000=0
fi
# ROG UI
if [ “${ROG_GTAC5300}” == “1” -o “${ROG_RTAC86U}” == “1” -o “${ROG_GTAC2900}” == “1” -o “${ROG_GTAX11000}” == “1” -o “${ROG_GTAXE11000}” == “1” ];then
# GT-AC5300、RT-AC86U部分版本、GT-AC2900部分版本、GT-AX11000部分版本、GT-AXE11000全部版本,骚红皮肤
UI_TYPE=”ROG”
fi
# TUF UI
if [ “${MODEL}” == “TUF-AX3000” ];then
# 官改固件,橙色皮肤
UI_TYPE=”TUF”
fi
}

exit_install(){
local state=$1
case $state in
1)
echo_date “本插件适用于【koolshare merlin armv7l 384/386】固件平台!”
echo_date “你的固件平台不能安装!!!”
echo_date “本插件支持机型/平台:https://github.com/koolshare/armsoft#armsoft"
echo_date “退出安装!”
rm -rf /tmp/${module}* >/dev/null 2>&1
exit 1
;;
0|)
rm -rf /tmp/${module}
>/dev/null 2>&1
exit 0
;;
esac
}

install_ui(){
# intall different UI
get_ui_type
if [ “${UI_TYPE}” == “ROG” ];then
echo_date “安装ROG皮肤!”
sed -i ‘/asuscss/d’ /koolshare/webs/Module_${module}.asp >/dev/null 2>&1
fi
if [ “${UI_TYPE}” == “TUF” ];then
echo_date “安装TUF皮肤!”
sed -i ‘/asuscss/d’ /koolshare/webs/Module_${module}.asp >/dev/null 2>&1
sed -i ‘s/3e030d/3e2902/g;s/91071f/92650F/g;s/680516/D0982C/g;s/cf0a2c/c58813/g;s/700618/74500b/g;s/530412/92650F/g’ /koolshare/webs/Module_${module}.asp >/dev/null 2>&1
fi
if [ “${UI_TYPE}” == “ASUSWRT” ];then
echo_date “安装ASUSWRT皮肤!”
sed -i ‘/rogcss/d’ /koolshare/webs/Module_${module}.asp >/dev/null 2>&1
fi
}

install_now(){
# default value
local TITLE=”ngrok内网穿透”
local DESCR=”支持多种协议的内网穿透软件”
local PLVER=$(cat ${DIR}/version)

# stop first
local ENABLE=$(dbus get ${module}_enable)
local PID=$(pidof ngrok)
if [ -n "${PID}" ];then
    echo_date "安装前先关闭${TITLE}插件,以保证更新成功!"
    killall ngrok >/dev/null 2>&1
fi

# isntall file
echo_date "安装插件相关文件..."
cd /tmp
cp -rf /tmp/${module}/bin/* /koolshare/bin/
cp -rf /tmp/${module}/res/* /koolshare/res/
cp -rf /tmp/${module}/scripts/* /koolshare/scripts/
cp -rf /tmp/${module}/webs/* /koolshare/webs/
cp -rf /tmp/${module}/uninstall.sh /koolshare/scripts/uninstall_${module}.sh

# Permissions
chmod 755 /koolshare/bin/${module} >/dev/null 2>&1
chmod 755 /koolshare/scripts/${module}_*.sh >/dev/null 2>&1

# intall different UI
install_ui

# dbus value
echo_date "设置插件默认参数..."
dbus set ${module}_version="${PLVER}"
dbus set softcenter_module_${module}_version="${PLVER}"
dbus set softcenter_module_${module}_install="1"
dbus set softcenter_module_${module}_name="${module}"
dbus set softcenter_module_${module}_title="${TITLE}"
dbus set softcenter_module_${module}_description="${DESCR}"

# re-enable
if [ "${ENABLE}" == "1" -a -f "/koolshare/scripts/ngrok_config.sh" ];then
    echo_date "安装完毕,重新启用${TITLE}插件!"
    sh /koolshare/scripts/frpc_config.sh start
fi

# finish
echo_date "${TITLE}插件安装完毕!"
exit_install

}

install(){
get_model
get_fw_type
platform_test
install_now
}

install

1
uninstall.sh

#!/bin/sh
export KSROOT=/koolshare
source $KSROOT/scripts/base.sh

killall frpc
find /koolshare/init.d/ -name “frpc“ | xargs rm -rf
rm -rf /koolshare/res/icon-frpc.png
rm -rf /koolshare/scripts/frpc*.sh
rm -rf /koolshare/webs/Module_frpc.asp
rm -f /koolshare/scripts/uninstall_frpc.sh

1
2

ngrok_config.sh

#!/bin/sh

eval dbus export ngrok_
source /koolshare/scripts/base.sh
mkdir -p /tmp/upload
NAME=ngrok
alias echo_date=’echo 【$(TZ=UTC-8 date -R +%Y年%m月%d日\ %X)】:’
BIN=/koolshare/bin/ngrok
INI_FILE=/tmp/upload/.ngrok.ini
LOG_FILE_TXT=/tmp/upload/ngrok_log.txt
STCP_INI_FILE=/tmp/upload/.ngrok_stcp.ini
PID_FILE=/var/run/ngrok.pid
lan_ip=nvram get lan_ipaddr
lan_port=”80”
ddns_flag=false

fun_ntp_sync(){
echo_date “[fun_ntp_sync调用]”| tee -a $LOG_FILE_TXT
ntp_server=nvram get ntp_server0
start_time=”date +%Y%m%d
ntpclient -h ${ntp_server} -i3 -l -s > /dev/null 2>&1
if [ “${start_time}”x = “date +%Y%m%d“x ]; then
ntpclient -h ntp1.aliyun.com -i3 -l -s > /dev/null 2>&1
fi
}
fun_start_stop(){
echo_date “[fun_start_stop调用]”| tee -a $LOG_FILE_TXT
#ngrok_enable=1
dbus set ngrok_client_version=${BIN} version
if [ “${ngrok_enable}”x = “1”x ];then
echo_date 当前为开启状态,即将进行程序执行| tee -a $LOG_FILE_TXT
if [ “dbus get ngrok_customize_conf“x = “1”x ];then
_ngrok_customize_conf=dbus get ngrok_config | base64_decode || “未发现配置文件”
cat > ${INI_FILE}<<-EOF
# ngrok custom configuration
${_ngrok_customize_conf}
EOF
else
stcp_en=dbus list ngrok_proto_node | grep stcp
cat > ${INI_FILE}<<-EOF
# ngrok configuration
[common]
server_addr = ${ngrok_common_server_addr}
#server_port = ${ngrok_common_server_port}
token = ${ngrok_common_privilege_token}
log_file = ${ngrok_common_log_file}
log_level = ${ngrok_common_log_level}
log_max_days = ${ngrok_common_log_max_days}
tcp_mux = ${ngrok_common_tcp_mux}
protocol = ${ngrok_common_protocol}
login_fail_exit = ${ngrok_common_login_fail_exit}
user = ${ngrok_common_user}
EOF

        if [[ "${stcp_en}" != "" ]]; then
            cat > ${STCP_INI_FILE}<<-EOF
                [common]
                server_addr = ${ngrok_common_server_addr}
                #server_port = ${ngrok_common_server_port}
                token = ${ngrok_common_privilege_token}
            EOF
        fi
        server_nu=`dbus list ngrok_localhost_node | sort -n -t "_" -k 4|cut -d "=" -f 1|cut -d "_" -f 4`
        for nu in ${server_nu}
        do
            array_subname=`dbus get ngrok_subname_node_$nu`
            array_type=`dbus get ngrok_proto_node_$nu`
            array_local_ip=`dbus get ngrok_localhost_node_$nu`
            array_local_port=`dbus get ngrok_localport_node_$nu`
            array_remote_port=`dbus get ngrok_remoteport_node_$nu`
            array_custom_domains=`dbus get ngrok_subdomain_node_$nu`
            array_use_encryption=`dbus get ngrok_encryption_node_$nu`
            array_use_gzip=`dbus get ngrok_gzip_node_$nu`
            if [[ "${array_type}" == "tcp" ]] || [[ "${array_type}" == "udp" ]]; then
                cat >> ${INI_FILE} <<-EOF
                    [${array_subname}]
                    type = ${array_type}
                    local_ip = ${array_local_ip}
                    local_port = ${array_local_port}
                    remote_port = ${array_remote_port}
                    use_encryption = ${array_use_encryption}
                    use_compression = ${array_use_gzip}
                EOF
            elif [[ "${array_type}" == "stcp" ]]; then
                cat >> ${INI_FILE} <<-EOF
                    [${array_subname}]
                    type = ${array_type}
                    sk = ${array_custom_domains}
                    local_ip = ${array_local_ip}
                    local_port = ${array_local_port}
                EOF
                cat >> ${STCP_INI_FILE}<<-EOF
                    [secret_tcp_vistor]
                    # ngrok role vistor -> frps -> ngrok role server
                    role = vistor
                    type = stcp
                    # the server name you want to vistor
                    server_name = ${ngrok_common_user}.${array_subname}
                    sk = ${array_custom_domains}
                    # connect this address to vistor stcp server
                    bind_addr = 127.0.0.1
                    bind_port = 9000
                EOF
            else
                cat >> ${INI_FILE} <<-EOF
                    [${array_subname}]
                    type = ${array_type}
                    local_ip = ${array_local_ip}
                    local_port = ${array_local_port}
                    remote_port = ${array_remote_port}
                    custom_domains = ${array_custom_domains}
                    use_encryption = ${array_use_encryption}
                    use_compression = ${array_use_gzip}
                EOF
            fi
        done
    fi
    killall ngrok || true
    echo_date "杀死原有进程"| tee -a $LOG_FILE_TXT
    sleep 1
    export GOGC=40
    echo_date "再次启动进程" | tee -a $LOG_FILE_TXT
    echo_date "执行命令 start-stop-daemon -S -q -b -m -p ${PID_FILE} -x ${BIN} clientid ${ngrok_common_privilege_token}"| tee -a $LOG_FILE_TXT
    start-stop-daemon -S -q -b -m -p ${PID_FILE} -x ${BIN} clientid ${ngrok_common_privilege_token}| tee -a $LOG_FILE_TXT
    sleep 3
    echo_date "输出等待结果${temp}" | tee -a $LOG_FILE_TXT
    #A=`curl -I "http://127.0.0.1"`
    A=`curl  "http://127.0.0.1:4040"`
    B=${A##*}
    echo_date "启动程序完成,尝试访问web接口结果\n$A"| tee -a $LOG_FILE_TXT
else
    echo_date "非开启状态,终止程序" | tee -a $LOG_FILE_TXT
    killall ngrok || true
fi

}
fun_nat_start(){ # 添加开机启动
echo_date “[fun_nat_start调用]”| tee -a $LOG_FILE_TXT
if [ “${ngrok_enable}”x = “1”x ];then
echo_date “添加开机自启动”| tee -a $LOG_FILE_TXT
[ ! -L “/koolshare/init.d/N99ngrok.sh” ] && ln -sf /koolshare/scripts/ngrok_config.sh /koolshare/init.d/N99ngrok.sh
else
echo_date “移除开机自启动”| tee -a $LOG_FILE_TXT
rm -rf /koolshare/init.d/N99ngrok.sh >/dev/null 2>&1
fi
}

fun_crontab(){
echo_date “[fun_crontab调用]”| tee -a $LOG_FILE_TXT
if [ “${ngrok_enable}”x = “1”x ];then

    if [ "${ngrok_common_cron_time}"x = "0"x ]; then
        echo_date 清除定时| tee -a $LOG_FILE_TXT
        cru d ngrok_monitor
    else
        if [ "${ngrok_common_cron_hour_min}"x = "min"x ]; then
            echo_date 添加定时-${ngrok_common_cron_time}分钟重启一次 tee -a $LOG_FILE_TXT
            cru a ngrok_monitor "*/"${ngrok_common_cron_time}" * * * * /bin/sh /koolshare/scripts/ngrok_config.sh start"
        elif [ "${ngrok_common_cron_hour_min}"x = "hour"x ]; then
            echo_date 添加定时-{ngrok_common_cron_time}小时重启一次 tee -a $LOG_FILE_TXT
            cru a ngrok_monitor "0 */"${ngrok_common_cron_time}" * * * /bin/sh /koolshare/scripts/ngrok_config.sh start"
        fi
    fi
else
    cru d ngrok_monitor
    echo_date 清除定时| tee -a $LOG_FILE_TXT
fi

}
fun_ddns_stop(){
nvram unset ddns_hostname_x
nvram set ddns_enable_x=0
nvram commit
}
fun_ddns_start(){
# ddns setting
if [ “${frpc_enable}”x = “1”x ];then
# ddns setting
if [[ “${frpc_common_ddns}” == “1” ]] && [[ “${frpc_domain}” != “” ]]; then
nvram set ddns_enable_x=1
nvram set ddns_hostname_x=${frpc_domain}
ddns_custom_updated 1
nvram commit
elif [[ “${frpc_common_ddns}” == “2” ]]; then
echo “ddns no setting”
else
fun_ddns_stop
fi
else
fun_ddns_stop
fi
}

#echo “action(supportStart):$ACTION”| tee -a $LOG_FILE_TXT

=============================================

this part for start up by post-mount

case $ACTION in
start)
true > $LOG_FILE_TXT #重置日志文件内容
echo XU6J03M6 | tee -a $LOG_FILE_TXT #实时刷新
echo_date “execAction” | tee -a $LOG_FILE_TXT
fun_ntp_sync
fun_start_stop
fun_nat_start
fun_crontab
http_response “$1”
echo “execOver$2” | tee -a $LOG_FILE_TXT
;;

esac

for web submit

case $2 in
1)
true > $LOG_FILE_TXT #清空日志

http_response "$1"
echo -e "参数Action${ACTION}\n参数0:${0}\n参数1:${1}\n参数2:${2}"| tee -a $LOG_FILE_TXT
echo XU6J03M6 | tee -a $LOG_FILE_TXT #有了这个标记就可以显示一个按钮.
echo_date "提交配置更新" | tee -a $LOG_FILE_TXT
fun_ntp_sync
fun_start_stop
fun_nat_start
fun_crontab
echo "execOver$1" | tee -a $LOG_FILE_TXT
;;

esac

case $3 in
web_submit)
true > $LOG_FILE_TXT #重置日志文件内容
echo XU6J03M6 | tee -a $LOG_FILE_TXT #实时刷新
fun_nat_start
http_response “$1”
echo “execOver$2” | tee -a $LOG_FILE_TXT
;;
esac

1
ngrok_status.sh

#! /bin/sh

export KSROOT=/koolshare
source $KSROOT/scripts/base.sh
ngrok_version=/koolshare/bin/ngrok version
ngrok_pid=pidof ngrok

if [ -n “$ngrok_pid” ];then
http_response “ngrok ${ngrok_version} 进程运行正常!PID:$ngrok_pid”
else
http_response “ngrok ${ngrok_version} 进程未运行!”
fi