read函数(这里字符串只有0x80字节,而read需要从字符串中读取字节数远大于字符串),存在溢出条件。
点开shift+F12进入筛选字符串界面点击后即可进入汇编地址目录,寻找汇编操作的地址作为返回地址。
后门类型:libc,system,bin/sh,cat flag。
libc:
可执行文件里面保存的是 PLT 表的地址,对应 PLT 地址指向的是 GOT 的地址,GOT 表指向的就是 glibc 中的地址
做题要找对应函数在got中的地址
32位和64位的区别
libc相关工具使用
1 | from LibcSearcher import * |
如果遇到返回多个libc版本库的情况,可以通过
1 | add_condition(leaked_func, leaked_address) |
来添加限制条件,也可以手工选择其中一个libc版本(如果你确定的话)。
1 | r.sendlineafter('choice!\n','1') |
strlen函数遇到‘\0’就会停止。x为无符号整型大于等于0(unsigned int)
偏移地址:libc是Linux新系统下的C函数库,其中就会有system()函数、”/bin/sh”字符串,而libc库中存放的就是这些函数的偏移地址。换句话说,只要确定了libc库的版本,就可以确定其中system()函数、”/bin/sh”字符串的偏移地址。解题核心在于如何确定libc版本。
基地址:每次运行程序加载函数时,函数的基地址都会发生改变。这是一种地址随机化的保护机制,导致函数的真实地址每次运行都是不一样的。然而,哪怕每次运行时函数的真实地址一直在变,最后三位确始终相同。可以根据这最后三位是什么确定这个函数的偏移地址,从而反向推断出libc的版本(此处需要用到工具LibcSearcher库,本文忽略这个步骤)。那么如何求基地址呢?如果我们可以知道一个函数的真实地址,用公式:
这次运行程序的基地址 = 这次运行得到的某个函数func的真实地址 - 函数func的偏移地址
即可求出这次运行的基地址。
如何找到某个函数func的真实地址呢?
像puts(),write()这样的函数可以打印内容,我们可以直接利用这些打印函数,打印出某个函数的真实地址(即got表中存放的地址)。某个函数又指哪个函数呢?由于Linux的延迟绑定机制,我们必须选择一个main函数中已经执行过的函数(这样才能保证该函数在got表的地址可以被找到),选哪个都可以,当然也可以直接选puts和write,毕竟题目中像puts和write往往会直接出现在main函数中。
总结一下上面这段话,我们可以通过构造payload让程序执行puts(puts@got)或者write(1,write@got, 读取的字节数)打印puts函数/write函数的真实地址。
p32()
可以让我们转换整数到小端序格式. p32
转换4字节. p64
和 p16
则分别转换 8 bit 和 2 bit 数字.
1 | %d // 十进制 - 输出十进制整数 |
1 | atoi() |
函数会扫描参数 str 字符串,跳过前面的空白字符(例如空格,tab缩进等),直到遇上数字或正负符号才开始做转换,而再遇到 非数字 或 字符串结束时(’\0’) 才结束转换,并将结果返回。函数返回转换后的整型数;如果 str 不能转换成 int 或者 str 为空字符串,那么将返回 0。
1 | p.recvuntil("xxxxx") |
更类似于接收到xxxxx的消息再进行下一操作,便于两步输入的分割。
1 | read(int fd, void *buf, size_t count) |
1 | ssize_t read(int fd,void*buf,size_t count) |
参数说明:
fd: 是文件描述符
buf: 为读出数据的缓冲区;
count: 为每次读取的字节数(是请求读取的字节数,读上来的数据保
存在缓冲区buf中,同时文件的当前读写位置向后移)
使用Write()泄露函数实际地址
头文件: #include <unistd.h>
定义函数:ssize_t write (int fd, const void * buf, size_t count);
函数说明:write()会把参数buf 所指的内存写入count 个字节到参数fd 所指的文件内. 当然, 文件读写位置也会随之移动.write函数的特点在于其输出完全由其参数size决定,只要目标地址可读,size填多少就输出多少,不会受到诸如‘\0’, ‘\n’之类的字符影响。因此leak函数中对数据的读取和处理较为简单。
第一个参数fd=1:标准输出 STDOUT
返回值:如果顺利write()会返回实际写入的字节数. 当有错误发生时则返回-1, 错误代码存入errno 中.
Payload:‘a’ * 栈大小 + ebp + write_plt_addr + write执行后的返回地址 + fd + 要泄露的地址 + count
1 | from pwn import * |
使用Puts()泄露函数实际地址
头文件: #include<stdio.h>
定义函数:*int puts(const char string);
函数说明: **puts()**函数只能够输出字符串,以’\0’来确定字符串的结尾。
Payload:
1 | payload = b'' |
gadget:
安装了pwntools后,执行如下命令即可(此处以攻防世界的”pwn-100”为例):
1 | ROPgadget --binary ./pwn-100 --only "pop|ret" |