(1)原理:
将源字符串复制到目标缓冲区可能会导致off by one。当源字符串长度等于目标缓冲区长度时,单个NULL字节将被复制到目标缓冲区上方。这里由于目标缓冲区位于堆栈中,所以单个NULL字节可以覆盖存储在堆栈中的调用者的EBP的最低有效位(LSB),这可能导致任意的代码执行。
(2)漏洞代码
#include
#include
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 :push ebp
0x08048498 :mov ebp,esp
0x0804849a :push edi
0x0804849b :sub esp,0x8
0x0804849e :mov eax,DWORD PTR [ebp+0xc]
0x080484a1 :add eax,0x4
0x080484a4 :mov eax,DWORD PTR [eax]
0x080484a6 :mov DWORD PTR [ebp-0x8],0xffffffff
0x080484ad :mov edx,eax
0x080484af :mov eax,0x0
0x080484b4 :mov ecx,DWORD PTR [ebp-0x8]
0x080484b7 :mov edi,edx
0x080484b9 :repnz scas al,BYTE PTR es:[edi]
0x080484bb :mov eax,ecx
0x080484bd :not eax
0x080484bf :sub eax,0x1
0x080484c2 :cmp eax,0x100
0x080484c7 :jbe 0x80484e9
0x080484c9 :mov DWORD PTR [esp],0x80485e0
0x080484d0 :call 0x8048380
0x080484d5 :mov eax,ds:0x804a020
0x080484da :mov DWORD PTR [esp],eax
0x080484dd :call 0x8048360
0x080484e2 :mov eax,0xffffffff
0x080484e7 :jmp 0x80484fe
0x080484e9 :mov eax,DWORD PTR [ebp+0xc]
0x080484ec :add eax,0x4
0x080484ef :mov eax,DWORD PTR [eax]
0x080484f1 :mov DWORD PTR [esp],eax
0x080484f4 :call 0x8048464
0x080484f9 :mov eax,0x0
0x080484fe :add esp,0x8
0x08048501 :pop edi
0x08048502 :pop ebp
0x08048503 :ret
End of assembler dump.
gdb-peda$ disassemble foo
Dump of assembler code for function foo:
0x08048464 : push ebp
0x08048465 : mov ebp,esp
0x08048467 : sub esp,0x4
0x0804846a : mov eax,DWORD PTR [ebp+0x8]
0x0804846d : mov DWORD PTR [esp],eax
0x08048470 : call 0x8048477
0x08048475 : leave
0x08048476 : ret
End of assembler dump.
gdb-peda$ disassemble bar
Dump of assembler code for function bar:
0x08048477 : push ebp
0x08048478 : mov ebp,esp
0x0804847a : sub esp,0x108
0x08048480 : mov eax,DWORD PTR [ebp+0x8]
0x08048483 : mov DWORD PTR [esp+0x4],eax
0x08048487 : lea eax,[ebp-0x100]
0x0804848d : mov DWORD PTR [esp],eax
0x08048490 : call 0x8048370
0x08048495 : leave
0x08048496 : 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权限。