(1)原理:
将源字符串复制到目标缓冲区可能会导致 off by one. 当源字符串长度等于目标缓冲区长度时, 单个 NULL 字节将被复制到目标缓冲区上方. 这里由于目标缓冲区位于堆栈中, 所以单个 NULL 字节可以覆盖存储在堆栈中的调用者的 EBP 的最低有效位(LSB), 这可能导致任意的代码执行.
(2)漏洞代码
- #include <stdio.h>
- #include <string.h>
- void foo(char* arg);
- void bar(char* arg);
- void foo(char* arg) {
- bar(arg); /* [1] */
- }
- void bar(char* arg) {
- char buf[256];
- strcpy(buf, arg); /* [2] */
- }
- int main(int argc, char *argv[]) {
- if(strlen(argv[1])>256) { /* [3] */
- printf("Attempted Buffer Overflow\n");
- fflush(stdout);
- return -1;
- }
- foo(argv[1]); /* [4] */
- return 0;
- }
编译文件
(2)漏洞代码的第 [2] 行是可能发生 off by one 溢出的地方. 目标缓冲区长度为 256, 因此长度为 256 字节的源字符串可能导致任意代码执行. 如果调用者的 EBP 位于目标缓冲区之上, 则在 strcpy 之后, 单个 NULL 字节将覆盖调用者 EBP 的 LSB. 反汇编漏洞代码并绘制它的堆栈布局
- gdb-peda$ disassemble main
- Dump of assembler code for function main:
- 0x08048497 <+0>: push ebp
- 0x08048498 <+1>: mov ebp,esp
- 0x0804849a <+3>: push edi
- 0x0804849b <+4>: sub esp,0x8
- 0x0804849e <+7>: mov eax,DWORD PTR [ebp+0xc]
- 0x080484a1 <+10>: add eax,0x4
- 0x080484a4 <+13>: mov eax,DWORD PTR [eax]
- 0x080484a6 <+15>: mov DWORD PTR [ebp-0x8],0xffffffff
- 0x080484ad <+22>: mov edx,eax
- 0x080484af <+24>: mov eax,0x0
- 0x080484b4 <+29>: mov ecx,DWORD PTR [ebp-0x8]
- 0x080484b7 <+32>: mov edi,edx
- 0x080484b9 <+34>: repnz scas al,BYTE PTR es:[edi]
- 0x080484bb <+36>: mov eax,ecx
- 0x080484bd <+38>: not eax
- 0x080484bf <+40>: sub eax,0x1
- 0x080484c2 <+43>: cmp eax,0x100
- 0x080484c7 <+48>: jbe 0x80484e9 <main+82>
- 0x080484c9 <+50>: mov DWORD PTR [esp],0x80485e0
- 0x080484d0 <+57>: call 0x8048380 <[email protected]>
- 0x080484d5 <+62>: mov eax,ds:0x804a020
- 0x080484da <+67>: mov DWORD PTR [esp],eax
- 0x080484dd <+70>: call 0x8048360 <[email protected]>
- 0x080484e2 <+75>: mov eax,0xffffffff
- 0x080484e7 <+80>: jmp 0x80484fe <main+103>
- 0x080484e9 <+82>: mov eax,DWORD PTR [ebp+0xc]
- 0x080484ec <+85>: add eax,0x4
- 0x080484ef <+88>: mov eax,DWORD PTR [eax]
- 0x080484f1 <+90>: mov DWORD PTR [esp],eax
- 0x080484f4 <+93>: call 0x8048464 <foo>
- 0x080484f9 <+98>: mov eax,0x0
- 0x080484fe <+103>: add esp,0x8
- 0x08048501 <+106>: pop edi
- 0x08048502 <+107>: pop ebp
- 0x08048503 <+108>: ret
- End of assembler dump.
- gdb-peda$ disassemble foo
- Dump of assembler code for function foo:
- 0x08048464 <+0>: push ebp
- 0x08048465 <+1>: mov ebp,esp
- 0x08048467 <+3>: sub esp,0x4
- 0x0804846a <+6>: mov eax,DWORD PTR [ebp+0x8]
- 0x0804846d <+9>: mov DWORD PTR [esp],eax
- 0x08048470 <+12>: call 0x8048477 <bar>
- 0x08048475 <+17>: leave
- 0x08048476 <+18>: ret
- End of assembler dump.
- gdb-peda$ disassemble bar
- Dump of assembler code for function bar:
- 0x08048477 <+0>: push ebp
- 0x08048478 <+1>: mov ebp,esp
- 0x0804847a <+3>: sub esp,0x108
- 0x08048480 <+9>: mov eax,DWORD PTR [ebp+0x8]
- 0x08048483 <+12>: mov DWORD PTR [esp+0x4],eax
- 0x08048487 <+16>: lea eax,[ebp-0x100]
- 0x0804848d <+22>: mov DWORD PTR [esp],eax
- 0x08048490 <+25>: call 0x8048370 <[email protected]>
- 0x08048495 <+30>: leave
- 0x08048496 <+31>: ret
- End of assembler dump.
(3)256 字节的用户输入, 用空字节可以覆盖 foo 的 EBP 的 LSB. 所以当 foo 的存储在目标缓冲区 "buf" 之上的 EBP 被一个 NULL 字节所覆盖时, ebp 从 0xbffff2d8 变为 0xbffff200. 从堆栈布局我们可以看到堆栈位置 0xbffff200 是目标缓冲区 "buf" 的一部分, 由于用户输入被复制到该目标缓冲区, 攻击者可以控制这个堆栈位置 (0xbffff200), 因此他控制指令指针(eip ) 使用他可以实现任意代码执行.
测试: 可覆盖返回地址.
(4)尝试找出 ret_addr 的值.
将断点下在 Breakpoint 2, 0x08048495 in bar ()处. 运行查看内存情况
可以看到 EBP 的值从 0xbffff158 被覆盖成 0xbffff100.EIP 指向的是 0xbffff104 地址. EIP 所指地址与 buf 之间需要填充 172 个 A.
尝试找出 ret_addr 的地址.
(5)攻击代码
不知道为何, 尝试运行攻击代码失败了. 但是直接在调试界面输入, 可以获得普通用户的 shell 权限.
Linux (x86) Exploit 开发系列教程之三(Off-By-One 漏洞 (基于栈))
来源: http://www.bubuko.com/infodetail-3059399.html