pwn5
基础知识
-
格式化字符串漏洞:
在c/c++的格式化输出中,使用的是printf函数,它的参数如下所示:
int printf(const char *format, ...);
使用方式如下:
char buf[10] = {}; read(0,buf,9); printf("%s",buf);
如果直接使用printf输出buf的话,则存在格式化字符串漏洞:
char buf[10] = {}; read(0,buf,9); printf("%s",buf);
原理:因为printf会从第一个参数读取格式化字符串,比如%x、%p,如果外部输入是“%x%x”这种字符串,那么printf就会从格式化字符串后面的数据读取对应格式的数据并输出,造成内存泄漏;
利用:在64位机器中,调用printf函数时,会把参数以此push到寄存器rdi、rsi、rdx、rcx、r8、r9中,更多的参数则是push到栈中,那么printf函数在处理格式化时,也是先从寄存器中读取参数,如果参数超过上述寄存器数,则从栈中读取数据。回到漏洞利用,如果输入的格式化字符串为%p%p,那么printf会依次读取rsi、rdx中的数据(在读取格式化字符串时是从rdi读取的,所以第一个%p是从rsi开始)。有一种格式化字符可以指定输出第几个参数,如%0 p 是 从 第 0 个 参 数 寄 存 器 读 取 , 也 就 是 r d i , p是从第0个参数寄存器读取,也就是rdi,%1 p是从第0个参数寄存器读取,也就是rdi,p是从第1个参数寄存器读取,也就是rsi,%6$p是从第6个参数读取,由于寄存器只有6个,那么它就会从printf函数的栈中读取;
-
libc
通常所说的libc就是一个so库,里面包含了众多基本函数,比如system等。在运行一个程序前,比如helloworld,会首先加载libc,然后使用libc_start_main函数来加载我们自己写的main函数;
解题
题目如上,本题存在两个漏洞1.格式化字符串漏洞,该漏洞可以用来泄漏libc的地址;2.栈溢出,该漏洞可以用来跳转到libc的system函数去,我们现在就要利用这两个漏洞拿到shell。
格式化字符串漏洞利用
首先需要利用格式化字符串漏洞获取libc的基地址,我们运行程序,运行到printf处,查看栈中的内容:
栈里面有个返回地址0x00007f14af6360b3,可以看下内容:
发现这个是__libc_start_main里面的代码,0x7f14af6360b3的地址为 __libc_start_main+243,因此__libc_start_main的地址为0x7f14af635fc0(0x7f14af6360b3-243)。
然后可以利用ida查看该函数在libc中的偏移,计算得出libc的地址:
因此可以由栈上的__libc_start_main函数的地址来计算的到libc的地址addr_libc=0x7f14af60f000=0x7f14af635fc0-0x0000000000026FC0。
我们可以使用vmmap命令验证下,看libc的地址是否为x7f14af60f000
验证正确。(由于这是在我ubuntu上运行的,所以libc的地址为我机器上的,要获取题目中的libc地址,那么需要拿到对应系统版本的libc文件)
栈溢出利用
题目后面存在一个栈溢出漏洞,缓存区s大小为0x20,但是读取了64个字节,所以可以控制栈。同时还需要注意要让代码跑到return 0语句,需要满足输入包含两个字符串needle和byte_4009BA,通过ida可以知道,这两个字符串为“真香”和“鸽子”:
needle:
byte_4009BA:
对应字符串:
所以之后输入真香鸽子即可跑到return 0语句。
这时候我们就需要构造ROP来拿到shell了。
上图为我们可以控制的栈空间,总共0x40个字节,前0x28个是要填充内容的,后面0x18个是要放置相应跳转地址;
-
找到human中pop rdi | ret的地址0x0000000000026b72;
ROPgadget --binary libc-2.31.so --only "pop|ret"
在libc中找到“/bin/sh”字符串的地址addr_libc+0x00000000001B75AA:
以及system函数的地址addr_libc+0x0000000000055410:
-
依次向栈中写入pop rdi |ret的地址、“/bin/sh”字符串的地址、system函数的地址;
当human运行完return 0之后,便会从栈中取出返回地址(已被修改为pop rdi|ret的地址),并跳转运行;
运行完pop rdi之后,“/bin/sh”的地址被存入rdi寄存器,ret运行之后,便从栈中取出返回地址(system函数地址)运行,这时候就相当于调用了system("/bin/sh"),拿到shell;