- 当
printf(format)被调用时,printf会把栈上的数据当作它的参数 - 需要修改特定变量,那么就需要知道当前变量存在栈上的哪里,也就是offset是多少
如何探测offset:
- 例:输入
AAAA.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p - 如果在输出中看到了 0x41414141(即 AAAA 的十六进制),数一下它是第几个 %p,如果它是第 7 个出现的,那么你的偏移量就是 7。
- 例:输入
原理:
- format变量内容并不是在当前栈顶的,当前栈顶所存储的是format的变量地址
- 栈结构:
==== 低地址 (Low Address) | 栈顶方向 ====
|
| (printf 函数刚被调用,ESP 指向参数列表)
V
+---------------------+ <-- ESP (栈顶)
| format 字符串的地址 | (这是 printf 的第 0 个参数,它只当剧本读,不当数据写)
+---------------------+ <-- ESP + 4
| [ printf 参数 1 ] | (如果你写 %1$p,看到的就是这里的数据)
+---------------------+ <-- ESP + 8
| [ printf 参数 2 ] | (如果你写 %2$p,看到的就是这里的数据)
+---------------------+
| ... | (这里是栈上的“空隙”,存着一些函数残余数据或局部变量)
+---------------------+
| [ printf 参数 k-1 ]|
+---------------------+ <-- 🚨 偏移量 k 指向这里!(ebp - 0x6C)
| "AAAA" (0x41414141)| <-- 你的输入 format[100] 的开头就在这!
| 或 |
| num 的地址 (p32) | (改写 num 时,这里放 0x0804A030)
+---------------------+
| "BBBB" (填充字符) | (format 数组的后续部分)
+---------------------+
| "%k$n" (指令) | (format 数组的后续部分)
+---------------------+
| ... | (format[100] 剩下的空间)
+---------------------+ <-- 原本的 ebp - 8
| p_argc | (main 函数定义的局部变量)
+---------------------+ <-- 原本的 ebp + 0
| 旧 EBP (Old EBP) |
+---------------------+ <-- 原本的 ebp + 4
| 返回地址 |
+---------------------+
|
==== 高地址 (High Address) | 栈底方向 ====format数组的起始位置,和printf函数调用时压栈的位置,通常不是同一个地方。- 在 32 位 Linux 中,由于
main函数里还有p_argc、setvbuf的参数等局部变量压在栈上,你的format数组通常被分配在离栈顶有一段距离的地方,并不是直接挨着栈顶放。所以,printf必须得往后看很多位(例如跳过 108/4 = 27 步左右),才能看到你的 AAAA,所跳过的位也就是偏移量offset
如何修改栈上的目标变量?
- 在 C 语言的设计中,
%n是一个非常特殊的格式化字符。其他的字符(比如%d,%s,%x)都是用来输出(读)数据的,唯独%n是用来输入(写)数据的。它不打印任何东西,而是把 到目前为止已经打印出来的字符总数,写入到对应的参数(一个指针)所指向的内存地址中 - 例:
- 在 C 语言的设计中,
int count = 0;
// printf 遇到 %n 时,会把已经打印的字符数量,写进后面的变量指针里
printf("Hello World!%n", &count);- 在这个例子中,
printf打印了Hello World!(12 个字符)。当它遇到%n时,它会寻找下一个参数(也就是&count的地址),然后像这样在底层执行:*(&count) = 12;->于是,变量count的值就被改写成了 12。 - !! %n 的工作原理就是“找一个地址,把当前打印的总字数塞进去”。 !!
利用
$k:- 用
$符号可以直接指定读取第几个参数 printf("%3$d", a, b, c);:这里%3$d的意思是:“别管前面的a和b,直接给我把第 3 个参数(c)当作整数打印出来。”
- 用
- 于是把
%n(写内存)和$k(指定位置)结合起来,变成%k$n。它的意思是:“去栈上的第 k 个参数位置,拿到那里存放的地址,然后把计数值写进那个地址里。
评论已关闭