网络安全学习
…
综述
网络安全:要求数据在互联网上:真实,可靠,完整,可控的传输与存储。
工具
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 | # 设置 IP 和 网关 |
设置永久IP
1 | vi /etc/network/interfaces |
系统更新
切换更新源
1 | deb http://mirrors.ustc.edu.cn/kali kali main non-free contrib |
更新软件
1 | apt-get update |
浏览器插件
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 | # -v 显示详细输出内容 |
侦听端口
1 | # -l 侦听 |
传输文件目录
1 | # 接收 |
流媒体
1 | cat 1.mp4 | nc -lp 3333 |
端口扫描
1 | nc -nvz 192.168.0.1 1-65535 # TCP |
硬盘克隆
1 | nc -lp 3333 | dd of=/dev/sda |
远程控制
1 | # 正向 |
NCAT
由于NC缺乏加密能力,因此可以使用Ncat。
加密连接
1 | ncat -c bash --allow 192.168.20.14 -vnl 3333 -ssl |
Base64
1 | base64 content |
其他配置
- 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 | nslookup www.sina.com.cn |
DIG
1 | dig 163.com any |
主动信息收集
主机扫描
端口扫描
服务扫描
Banner 搜索,但是准确度不可靠。
- nc
- Python 通过 Socket 发起连接获取 Banner
- dmitry
- nmap 通过 banner 脚本实现
- amap 专门用来发现服务
1
2
3
4
5
6
7
8
9
10
11
12
13
14nc -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 | # 依据特征识别 |
操作系统扫描
依据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 | searchsploit tomcat |
或工具sandi-gui
弱点扫描器
主动扫描:
- 有身份验证
- 无身份验证
被动扫描:
- 镜像抓包
- 其他来源输入
基于Agent扫描:
- 应用场景受限
Nmap
1 | # 脚本位置 |
缓冲区溢出
漏洞来源:数据与程序的边界不清。
例如:Shell脚本。
挖掘漏洞:
- 源码审计
- 逆向工程
- 模糊测试:利用调试工具,发送任意包注入
Linux 缓冲区溢出
调试工具:EDB
1 | edb --run target |
探测溢出:
- 需要关注EIP。
- 尝试不同长度的数据填充,直到溢出,改变EIP。一旦发生溢出,EDB会报错。
- 如果EIP被修改为填充数据,则表示可以利用。如果被修改为其他数据,则无法利用。
1 | # 探测脚本 |
寻找溢出位置:
- 使用唯一的填充字符串,填充缓冲区。
- 通过查看EIP被修改的数据定位溢出位置。
1 | # 唯一字符串生成 |
构造攻击字符串:
- 一般情况下,C 部分(ESP)用来构造Shell Code。
- 如果不行,则先进入 C 部分,再跳转到 A 部分,执行Shell Code。
1
2# A B C
crash = '\x41' * 4368 + '\x42' * 7 + '\x43' * 7
1 | # 构造 汇编指令 |
使用EDB
插件Opcode Search
搜索ESP->EIP
得到JMP ESP
指令的地址(0x08134587)。
查找坏字符:不能出现在Shell Code中
- **
编写Shell Code
1 | # -b 坏字符表 |
1 | crash = shell_code + '\x41' * (4368 - len(shell_code)) |
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 | nikto -update # 升级数据库 |
手动挖洞
默认开通某些服务,可能被攻击:
- phpMyAdmin/setup
- Ubuntu 默认安装 PHP5-cgi
SQL 自动注入
SQLMap:
不仅可以发现SQL漏洞,还能检查跨站脚本漏洞。
五种检测技术
- 基于布尔的盲注
- 基于时间的盲注
- 基于错误的检测
- 基于Union的检测:适用于循环
- 基于堆叠查询的检测
GET 注入:
1 | # -u url -p 检测的参数 -f 检查服务器指纹信息 |
POST 注入:
1 | # 启动 burpsuit 作为代理,用浏览器连接代理,准备抓包 |
可以直接连接数据库,其他查询方式一样:
1 | sqlmap -d "mysql://root@xx.xx.xx.xx:3306/database_name" -f --users |
Request:
1 | # |
其他功能:
- 可以进行字典破解,来破解密码。
靶场
TurnKey
地址
有各种应用构成的虚拟机可供测试。
Metasploit
Metasploit
Metasploitable 2
集成各种渗透测试应用。
Metasploitable 2 中需要配置一下:
1 | vim /var/www/mutillidae/config.inc |
Vulhub
地址
基于docker和docker-compose的漏洞环境集合。
模拟防火墙
地址
用于模拟真实网络。
Linux 扫描与防御
主机扫描
netstat 查看端口
1 | -l # 正在监听的端口 |
禁用 ICMP 连接
1 | sysctl -w net.ipv4.icmp_echo_ignore_all=1 |
TCP Dump
1 | tcpdump -np -ieth0 |
fping 批量主机扫描工具(ICMP)
作用:批量,并行发送,结果易懂。
下载地址
1 | man fping # 查看帮助 |
hping 批量主机扫描工具(TCP/IP)
作用:探测TCP端口,模拟DDOS攻击。
下载地址
1 | hping -h # 帮助 |
路由扫描
Trace Route
让每一个路由返回一个ICMP包。
- Windows:发送 ICMP 包。
- Linux:发送 UDP 包,端口号大于30000。
1 | traceroute IP # 默认 UDP |
MTR
测试主机到每一个路由的连通性,实时。
1 | mtr IP |
扫描服务
NMAP
批量扫描主机和服务
1 | -P # ICMP ping 简单 |
NCAT
1 | -w # 超时时间 |
攻击防御
攻击者伪造源IP,发送SYN包,耗尽目标机器资源。
防御方式:
1 | # 方式1:设置重置次数,默认都是5次 |
关闭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 | # 查看当前配置 |
DEP
堆栈 Cookies
堆栈粉碎
Web 漏洞
XSS
跨站脚本攻击:通过在网站中提交含可执行代码的内容,被其他用户访问的时候执行。
类型包括:
反射型:URL带注入。可以用短网址隐藏。
存储型:存到数据库。其他用户访问时被攻击。
注入点
HTML 结点内容:用户输入的动态信息。
1 | <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 | str = str.replace(/</g, '<'); |
同时转义Javascript内部的代码:
1 | str = str.replace(/\\/g, '\\\\'); // 放在最前 |
Javascript内部的代码最保险的方法是使用Encode:
1 | JSON.stringify(); |
富文本的过滤:富文本最复杂,使用黑名单和白名单。一般在输入时过滤。
黑名单方法:
1 | // <script> |
白名单方法:先解析HTML,再将必要的标签输出
1 | const cheerio = require('cheerio') |
XSS 拦截集成库
1 | var xss = require('xss') |
CSP 内容安全策略
HTTP头,用于指定哪些可以执行。
1 | Content-Security-Policy: default-src 'self' http://www.com; content-src 'none'; script-src http://www.com |
CSRF 跨站请求伪造
用户打开某一个第三方网站,则第三方网站伪装成用户在某网站进行操作。甚至可以用来制造网络蠕虫。
攻击步骤:
- 用户登录A网站
- A网站确认身份
- B网站前端向A网站发起请求,附带身份
危害:
- 冒用用户的身份
- 用户不知情
- 盗取用户资金
防御
禁止第三方网站前端带Cookies访问:
1 | Set-Cookie: samesite=strict |
但该方法只有Chrome和苹果浏览器支持。
使用验证码和Token:
1 | // 验证码 |
验证Referer:Referer是HTTP请求头中的部分,可以通过验证该字段是否是A网站来判断请求的合法性。
但是有时一些浏览器没有Referer,因此这种验证是否需要要看需求。
1 | // 这种验证方法仍然有局限性,可以使用正则表达式 |
Cookies
特点:
- 存储在前端
- 后端可以通过设置HTTP头,设置Cookie
- 请求时,通过HTTP头,交给后端
- 前端也可以读写
- 遵守同源策略:协议,域名,端口必须一致。
- 有有效期
- 可以用路径(Path)分类
- HTTP-ONLY
- HTTPS-ONLY (Security)
操作:
- 删除:通过设置有效期为过去的时间
1
2var 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
2top !== window
top.location != window.location - 可以设置头部:X-FRAME-OPTIONS
1
2ctx.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
2
3curl https://get.acme.sh |sh
cd /root/.acme.sh
./acme.sh --issue -d mydomain.com -d www.mydomain.com --webroot /home/wwwroot/html/ - 得到证书, 私钥等。
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 # 合并证书 - 配置Nginx
1
2
3
4
5
6
7
8
9
10
11
12server {
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;
}
} - 重启Nginx
密码安全
泄露途径:
- 数据库被盗
- 服务器被入侵
- 通信链路被窃听
- 内部人员泄露
- 撞库(社工)
存储:
- 加密存储
- 单向变化
- 增强变化复杂度
- 增强密码复杂度
- 加盐
哈希算法
明文与密文一一对应。
雪崩效应:原文一点点不一样,密文完全不一样。
单向变化。
密文长度固定。
MD5 SHA1 SHA256:破解方法,彩虹表。最大支持破解8位数字加字母。
变换次数越多越安全,彩虹表失效,解密成本增大。
传输过程防范
使用HTTPS传输。
加入密码尝试次数限制。
前端加密,但是意义有限,仅防止明文泄露。
生物特征密码
私密性:容易泄露(照片,接触等)
安全性:碰撞
唯一性:无法修改
关系型数据库注入
常见数据库:
- Access:早期用,单文件
- Sqlite:嵌入式设备,单文件,并发弱
- Mysql:作为服务,支持大量数据和并发
- Mssql Server:作为服务,收费
SQL 注入
数据部分被插入了程序。
1 | select * from where id = '1' or '1' = '1'; |
常用注入条件:
1 | and 1 = 0 # 如果报错,则表示注入成功 |
SQL注入中,如果页面报错,则SQL查询失败。
危害:
- 猜解密码
- 获取数据
- 删库删表
- 拖库
防御:
- 关闭错误输出
- 检查数据类型
- 对数据进行转义,例如引号,等号等。
- 使用参数化查询(分解为两次传输)
- 使用ORM
上传问题
上传文件,再被解析为程序。多发生在PHP中。
防御:
- 过滤非法后缀文件:容易伪造
- 文件类型(Type)检查:由浏览器提供类型,也可以伪造
- 文件内容检查:无法拦截混合文件
- 使用程序输出:通过程序读取文件,再输出给用户。性能受影响
- 权限控制:可写可执行的目录互斥
- 使用路由机制
泄露信息
信息内容
- 系统敏感信息
- 用户敏感信息
- 用户密码
泄露途径
- 错误信息失控
- SQL注入
- 权限控制不当
- XSS / CSRF
OAuth 思想:
第三方平台借用QQ等平台的用户凭证。
一切用户由用户授权。
第三方平台只能拿到Token,其他信息拿不到。
Windows 逆向
OD
快捷键
打开程序:F3
执行单步语句:F8
断点:F2
插件
CmdBar
1 | db 0x12FFDC # d 表示查看数据 b 表示 byte |
汇编
寄存器
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 | 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 | ADD 目标操作数, 源操作数 |
源操作数和目标操作数不能同时是内存。
结果存入目标操作数的位置。
AND / OR / XOR / NOT
1 | AND 目标操作数, 源操作数 |
源操作数和目标操作数不能同时是内存。
结果存入目标操作数的位置。
ADC / SBB
通过标志寄存器的进位位计算结果。
1 | ANC 目标操作数, 源操作数 ; 考虑进位的加法 |
CMP / TEST
CMP功能和SUB一样,TEST功能和AND一样,但都只修改标志寄存器的值。
1 | CMP 目标操作数, 源操作数 |
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 | JE JZ 目标操作数 ; ZF=1 相等跳转 |
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 | MOV [0x12345678], 0xFFFF ;不推荐 |
每个程序都有一个独立的程序空间。以32位程序为例,程序独有一个4GB的程序空间。但是大多数空间不允许访问。
内存寻址方式:
1 | ; 立即数 |
堆栈
在Windows中,栈由高地址向低地址生长。增加元素时,栈顶指针减小。
1 | ; 创建栈顶栈底 |
专用栈:
ESP 栈顶
EBP 栈底
PUSH、POP 只能是16位或32位
栈顶指针移动的偏移量,如果是立即数,则偏移4,如果是容器,则偏移容器大小(push ax,偏移2个)
1 | PUSH 0x12345678 ; 立即数,寄存器,内存 |
标志寄存器
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 | ; 传入参数 |
Windows 堆栈
- 先进后出
- 向低地址扩展
裸函数
编译器完全不管的函数。由CALL调用后跳转到函数中,函数没有任何汇编代码,全部都是INT3。
1 | void __declspec(naked) Plus() |
C语言里面也可以加入汇编。
1 | void __declspec(naked) Plus() |
1 | void __declspec(naked) Plus(int a, int b) |
__declspec
关键字用法:
1 | // 规定内存对齐的位置,不规定占用的长度 |
调用约定
有如下调用约定,规定参数的传递,返回值的传递。
1 | int __cdecl plus(int a, int b) // C/C++默认,从右向左压入参数,外平栈 |
程序入口
控制台程序中,C语言程序运行后,函数堆栈如下:
1 | main(int 1, char**) // 编程入口 |
mainCRTStartup()
函数在main()
之前先初始化:
- 获取系统版本 GetVersion()
- 堆初始化 _heap_init()
- 获取命令行参数 GetCommandLineA()
- 获取环境变量 _crtGetEnvironmentStringA()
- 设置参数 _setargv()
- 设置环境PATH _setenvp()
- 初始化C _cinit()
- 调用
main(__argc, __argv, _environ)
找Main方法,就是去找3个参数的方法。例如:
1 | PUSH edx |
数据类型
四种数据类型:
- 基本类型:整数,浮点
- 构造类型:数组,结构体,联合体
- 指针类型
- 空类型:void
有符号的和无符号的数存放方式一样,在使用时候才会区分。一般是类型转换,比较大小,数学运算中会区别使用。
1 | ; 内存对齐 |
1 | ; 比较有符号数 |
变量
传入参数
传参时,由于使用PUSH,因此不论是何种类型,一律是传入32位数据。
返回值
一般情况下,返回值放到EAX中,如果是64位数据,则高位放到EDX,低位放到EAX中。
局部变量
一般直接分配32位的整数倍,不论是char,short还是int。
数组
一般是按照32位的整数倍为数组分配空间。例如:
1 | char x[5]; // 占8字节 |
结构体
如果遇到连续赋值,但内存单元不是等宽,那么很有可能是结构体。
传参数传递结构体时,将结构体复制到栈里面。
1 | SUB esp, 18h ; 结构体大小 |
做函数返回值时候,主函数首先创建缓冲区LEA eax, [ebp-30h]
,之后PUSH eax
,也就是将缓冲区地址传入子程序中。但是在函数执行完后,还要将数据从缓冲区复制到自己的局部变量里面。
结构体要数组对齐。
1 |
Sizeof
Sizeof是一个关键字,用于获取类型大小,结构体大小,数组占的内存大小。
内存图
内存区域:
- 代码区:可读,可执行
- 栈:参数,局部变量,临时数据
- 堆
- 全局变量:可读写
- 常量:可读
1 | ; 全局变量特点: |
分支语句
IF 语句
当遇到CMP/TEST等影响标志位的命令,之后紧接JCC,则为IF。
汇编中和C语言中一般是反过来的。汇编中,如果满足条件则跳出;C语言中,如果满足条件则执行。
1 | ; IF |
1 | ; IF (a > 1 && b > 1 && c > 1) |
SWITCH 语句
- 当情况数较少时候(2 Cases + Default)实现和IF一样。
- 当情况数较多时候(4个分支时候),则生成大情况表。它会将每一个分支地址都存到大表中,然后通过给定的参数直接计算分支的地址直接跳转。
1
2
3
4
5
6SUB 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] ; 依据表得到跳转地址 - 当 CASE 值连续相近时,才采取情况2的方式,如果中间断了一两个,那么空缺的地方则填入Default;如果 CASE 值混乱,则使用情况1方式。
- 当 CASE 值不太连续相近,但是又不够混乱时(例如CASE 字母),还会生产小表(CASE值转偏移值的表),之后通过小表查找偏移量,进一步再去查询大表(存放分支地址的表),此时大表中为连续的,不必插入Default。
1
2
3
4
5
6
7
8
9SUB 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 | MOV eax, dword ptr [ebp + 8] ; Start |
While语句:
1 | MOV eax, dword ptr [ebp + 8] ; Start |
For 语句
1 | MOV eax, dword ptr [ebp + 8] ; 取传入的参数 |
指针
32位机下,指针不论指向何种类型,都是32位4字节。
64位机下,指针不论指向何种类型,都是64位8字节。
1 | ; int x = *px; |
加壳
将需要保护的代码的硬编码放到数据区,并将代码区变为全零。
可以用位运算对代码加密,例如对执行代码取反后保存。
用一个进程执行另一个程序的代码,隐藏原程序。
PE
Windows程序在内存或硬盘中结构一般有这几部分:
- DOS 头
- 填充数据(无用,长度可变)
- NT 头(标准PE头,可选PE头)
- 节表(各个节的位置)
- 节 x N (数据段,代码段等)
程序在硬盘中和在运行时的二进制数据不一样。区别:
- 地址起始位置:硬盘中从0开始,内存中不是。
- 对齐方式:早期程序,在硬盘中对齐单位是200h。现代程序对齐单位一般是1000h。内存中对齐一般是1000h。
DOS 头:
1 | // 被注释的部分是16位程序使用的 |
NT 头:
1 | // NT 头 |
标准 PE 头:
1 | struct _IMAGE_FILE_HEADER{ |
可选 PE 头:
1 | struct _IMAGE_OPTIONAL_HEADER{ |
DataDictionary:
1 | struct _IMAGE_DATA_DIRECTORY{ |
节表:
1 |
|
空白区加代码
- 找到 MessageBoxA 函数。
- 在可选PE头中找到OEP,修改。
- 采用硬编码编写。
1
2
3
4
5
6PUSH ; 调用参数 ; 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的值是 要跳转的地址与此条指令的下一条指令地址之间的差
节
当要修改节的内容或新增节时(都是修改拉伸后的内容):
- 分配空间。(SizeOfImage)
- 复制头部:DOS, PE, Option PE, 节表。(SizeOfHeaders)
- 确定每一节的大小。(VirtualSize,SizeOfRawData,SizeOfImage)
- 确定节的位置。(PointToRawData,VirtualAddress)
- 修改代码时注意偏移。(ImageBase)
- 新加入一节,需要在节表中新增条目,修改PE头中节数量,以及SizeOfImage。
- 节表新加入节条目后,后面需要补一个全零的条目。(40个字节)
- 当扩大最后一节时,需要修改SizeOfRawData,VirtualSize,SizeOfImage。
- 当合并节时,需要修改节表。(SizeOfHeaders,SizeOfRawData,VirtualSize,PointToRawData)
注意:
- SizeOfHeaders不可以随便变,一旦改变,下面所有的地址都需要变。
- 如果节表空间不够,则将NT头网上移动,占据DOS STUB空间。
- 一旦节表空间不足,DOS STUB空间不足等情况发生,则扩大最后一个节添加数据。
- 有时节表的后面不一定是空闲空间,也可能是有用数据,就不增加节,而是直接修改现有节。
数据字典
记录了16种不同的信息:导出表,导入表,资源表,异常信息表,安全证书表,重定位表,调试信息表,版权所有表,全局指针表,TLS表,加载配置表,绑定导入表,IAT表,延迟导入表,COM信息表,未使用(作为保留)。
Data Dictionary:
1 | struct _IMAGE_DATA_DIRECTORY{ |
导出表
导入表
重定位表
IAT表
Windows API
关键字
IN OUT
1 | // 只是用来标记,没有任何作用 |
LPVOID:等于void *
LPSTR:字符串
FILE:文件
函数
1 | LPSTR file_name = "xxx" |
Windows API
参考:《Windows 黑客编程技术详解》 甘迪文。Write Bug 社区
案例
基础部分
1 | // 运行单一实例 |