Bug 实用技巧
记录一些有意思的bug成因, 积攒经验值. 不定期更新
printf fmt
这种就比较老旧了, 就是如果开发者在使用printf/snprintf之类的函数, 但是fmt是可控的, 就可以通过%n
来实现改写栈变量中指向的目的地址的值.
比如:
snprintf(buf, 0x10, fmt, var1, var2)
如果fmt是%0123s%n
, 就会把var2
指向的值设置为123.
实际的参考例子:
snprintf
1 | char src[9] = "bbbbbbbb"; |
一般来说, 这段代码是不会造成安全问题的, 因为并不能溢出dst, 而且函数会自动添加\0
截断, 所以也不存在越界读取非0字节的问题.
但是, snprintf的返回值是不截断的长度! 也就是返回了8
.
实际例子就是 CVE-2023-4966
1 | iVar3 = snprintf(print_temp_rule,0x20000, |
这里将iVar3
作为响应的长度, 导致越界读取了额外的内存, 从而send响应时造成信息泄露.
参考链接: https://www.assetnote.io/resources/research/citrix-bleed-leaking-session-tokens-with-cve-2023-4966
wcsncpy_s(dst,dstlen,src,srclen)
这是个安全拷贝函数, 但是它内部实现(msvcrt.dll)有个异常, 如果dstlen <= srclen, 且src的字符串长度大于等于dstlen
, 就会导致崩溃异常.
1 | wchar_t src[9] = L"bbbbbb"; |
这段代码我们就看不到输出”ok”
PathCanonicalize
函数功能是拼接windows的文件路径, 并移除..\
, 但是它不会移除../
, 如果不正确使用, 存在路径超越问题.
参考案例: 议题 Old School, New Story–Escape from Hyper-V by Path Traversal
strnicmp, wcsnicmp
这类函数有长度限制, 可能错误匹配. 比如 strnicmp(“abc”,”abc123”, 3) 就能通过匹配.
MmProbeAndLockPages 逻辑提权
假如MmProbeAndLockPages 的第二个参数是KernelMode(0)
, 而构造mdk的va地址来自用户态参数, 那就可以实现直接读写内核地址.
参考: https://big5-sec.github.io/posts/CVE-2023-29360-analysis/
irp->RequestorMode 逻辑问题
irp->RequestorMode
和irp->PreviousMode
. 当内核里调用ZwOpenFile 等Zw开头的api时, irp->PreviousMode
就会从UserMode
变成KernelMode
, 从而绕过一些条件检查. 而irp->RequestorMode
是指原始调用来自用户态还是内核态.
另一种情况是 IoBuildDeviceIoControlRequest , 它后续会调用 IofCallDriver , 默认情况下, 它会把irp->RequestorMode
变为KenrelMode
.
IDA 技巧
结构体字段命名
在定义结构体时, 可以在字段命名中添加关键词, 方便审计时提高注意力.
lock1_
开头, 表示字段的操作需要上锁.
a_
开头, 表示字段是个数组.
_14h
结尾, 标识字段在结构体中的偏移, 方便调试时识别.
选择变量, create new struct type...
创建结构体时, 如果有符号名称, 可以直接命名成那个名称, 这样有的函数有识别类型的时候, 会自动识别, 省去手动标识.
注意, 如果是c++的代码, 往往子类和父类名称一样, 导致类型识别错误. 需要自行辨别当前的类型应该是父类还是子类.
比如代码:
1
2
3
4
5
6
7
8
9
10
11
12 *((_DWORD *)this + 4) = 1;
*(_QWORD *)this = &CWSDSession::`vftable'{for `IWSDSessionInternal'};
v1 = (char *)this + 80;
*((_DWORD *)this + 13) = 1;
*((_QWORD *)this + 1) = &CWSDSession::`vftable'{for `IWSDMessageBusNotify'};
v2 = 31i64;
*((_QWORD *)this + 3) = 0i64;
*((_QWORD *)this + 4) = 0i64;
*((_QWORD *)this + 5) = 0i64;
*((_DWORD *)this + 12) = 0;
*((_QWORD *)this + 7) = 0i64;
*((_QWORD *)this + 8) = 0i64;很明显是两个类, 一个类的virtual table 是
IWSDMessageBusNotify
, 一个是IWSDSessionInternal
.因此, 先以this创建结构体, 假设名为
a1
,
1
2
3
4
5
6 struct a1{
_QWORD field_0;
_QWORD field_8;
....
_QWORD field_40;
};然后取+8开始字段, 创建新结构体, 假设命名成
a2
1
2
3
4
5 struct a2{
_QWORD field_8;
....
_QWORD field_40;
};将a1重新修改为
1
2
3
4 struct a1{
_QWORD field_0h;
struct a2 field_8h;
}之后遇到引用子类的函数时, 也好处理了.
会议参考
有时候想发议题了, 发现议题cfp结束了, 就很无语, 列一下我知道会议(毕竟了解的不多, 有遗漏的话, 只是因为我了解的不够多, 还望见谅), 以后可以关注一下
会议名称 | 2024年举办时间 | 举办地点 | CFP截止日期 | 演讲支持 | 备注 |
---|---|---|---|---|---|
CanSecWest | 3/20 | 加拿大 | 2023/12/30 | 差旅住宿 | 一个会场 |
Zer0Con | 4/4 | 韩国 | 3/5 | $2000+差旅住宿 | 硬核 |
Black Hat Asia | 4/17 | 新加坡 | 2023/12/22 | $1000+差旅住宿 | 多个会场(如果感兴趣议题冲突, 就无法都看) |
TyphoonCon | 5/30 | 韩国 | 3/1 | 差旅住宿 | |
OffensiveCon | 5/10 | 德国柏林 | 4/2 | 演讲费1000欧元+差旅住宿 | 硬核 |
GeekCon | 5/25 | 不固定(24年新加坡) | 4/20 | $1200+差旅住宿 | |
OffByOne | 6/26 | 新加坡 | 3/2 | 演讲费不详+差旅住宿 | |
REcon | 6/28 | 加拿大 | 4/26 | $1000演讲费+差旅住宿 | |
Black Hat USA | 8/7 | 美国洛杉矶 | 4/10 | $1000演讲费+差旅住宿 | |
HITBSecConf BangKok | 8/29 | 曼谷 | 4/30 | 演讲费不详+差旅住宿 | hitb 有多个会议, 这里是其中一个, 不同会议时间和地点都不一样 |
PoC | 11月 | 韩国 | 10/15 | 不详 | |
Black Hat Euro | 12/9 | 英国伦敦 | 不确定 | $1000演讲费+差旅住宿 | |
hexacon | 10/4 | 法国巴黎 | 6月或者5月 | 不确定 | |
HITCON | 8/19 | 台湾 | 不确定 | 不确定 | |
MOSEC | 11月 | 上海 | 不清楚 | 不清楚 | |
districtcon | 2025/2/21 | 美国华盛顿 | 2024/11/01 | 不清楚 | |
SAS2024 | 2024/10/21 | 印尼巴厘岛 | 2024/8/15 | 不清楚 |