关于一次在pwnable.kr中input题目的经历(python3)
本文为看雪论坛优秀文章
1
分析input.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
int main(int argc, char* argv[], char* envp[]){
printf("Welcome to pwnable.kr\n");
printf("Let's see if you know how to give input to program\n");
printf("Just give me correct inputs then you will get the flag :)\n");
// argv
if(argc != 100) return 0;
if(strcmp(argv['A'],"\x00")) return 0;
if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;
printf("Stage 1 clear!\n");
// stdio
char buf[4];
read(0, buf, 4);
if(memcmp(buf, "\x00\x0a\x00\xff", 4)) return 0;
read(2, buf, 4);
if(memcmp(buf, "\x00\x0a\x02\xff", 4)) return 0;
printf("Stage 2 clear!\n");
// env
if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
printf("Stage 3 clear!\n");
// file
FILE* fp = fopen("\x0a", "r");
if(!fp) return 0;
if( fread(buf, 4, 1, fp)!=1 ) return 0;
if( memcmp(buf, "\x00\x00\x00\x00", 4) ) return 0;
fclose(fp);
printf("Stage 4 clear!\n");
// network
int sd, cd;
struct sockaddr_in saddr, caddr;
sd = socket(AF_INET, SOCK_STREAM, 0);
if(sd == -1){
printf("socket error, tell admin\n");
return 0;
}
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = INADDR_ANY;
saddr.sin_port = htons( atoi(argv['C']) );
if(bind(sd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0){
printf("bind error, use another port\n");
return 1;
}
listen(sd, 1);
int c = sizeof(struct sockaddr_in);
cd = accept(sd, (struct sockaddr *)&caddr, (socklen_t*)&c);
if(cd < 0){
printf("accept error, tell admin\n");
return 0;
}
if( recv(cd, buf, 4, 0) != 4 ) return 0;
if(memcmp(buf, "\xde\xad\xbe\xef", 4)) return 0;
printf("Stage 5 clear!\n");
// here's your flag
system("/bin/cat flag");
return 0;
}
首先,这个题目分了5个阶段(Stage)而且使用//注释标注的很直白,分别为:
1、argv参数的传递
2、stdio标准输入输出
3、env环境变量
4、file文件操作
5、network网络服务(这里可以参考i乂大佬帖子中的network相关知识)
然后我们开始对以上的5个阶段逐一攻克。
2
argv参数的传递
目标:
// argv
if(argc != 100) return 0;
if(strcmp(argv['A'],"\x00")) return 0;
if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;
printf("Stage 1 clear!\n");
这代码很明显,首先argc是argv传递的参数的个数,这里如果没有参数,默认的情况为:argc=1,argv[0]='程序名';
简单的说,argv[0]是给命令行的,让命令行启动程序,之后argv[1]开始都是供给程序作为参数使用的,而argc则统计有多少个参数(包括argv[0])。
我们这里由于要使用process来完成,因此在结合之前的process可以用argv=[]来传参的用法。
我们这里就考虑,我们是否可以生成一个有100个成员的列表传递到argv中,并将对应的成员赋值成目标需求的内容。
说干就干,我们直接通过:
args=list('a'*100)
#print (args)
args[0]='./input'
args[ord('A')]=b'\x00'
args[ord('B')]=b'\x20\x0a\x0d'
使用ord的原因是,在C语言中,char类型(也就是单个字符,且必须使用单引号引用)和int类型是可以混用的,当一个字符作为int类型使用时,默认就会当做它对应的ascii码值。
举个例子:A的scill码值是65:
char x='A';
printf("x(int)=%d,x(char)=%c",x,x);
x(int)=65,x(char)=A
这里%d和%c分别是将后面对应的参数输出为整型和字符型。还有一点就是,由于本人使用的是python3版本,此版本的python对于bytes和str类型区分很严格。
因此对于'\x00'这种编码字符最好使用bytes类型,即引号前方加一个b进行声明,不然有些地方会出现一些问题。
然后我们对第一阶段的exp进行一次测试:
from pwn import *
args=list('a'*100)
#print(args)
args[0]='./input'
args[ord('A')]=b'\x00'
args[ord('B')]=b'\x20\x0a\x0d'
p=process(argv=args)
p.interactive()
3
stdio标准输入输出
目标:
// stdio
char buf[4];
read(0, buf, 4);
if(memcmp(buf, "\x00\x0a\x00\xff", 4)) return 0;
read(2, buf, 4);
if(memcmp(buf, "\x00\x0a\x02\xff", 4)) return 0;
printf("Stage 2 clear!\n");
read(int fd, void * buf, size_t count)
然后发现,python3里有一个名叫subprocess的模块,然后我发现好像这道题的stdio和env都能靠它来实现。
接着到该位置打开源码,然后发现,我的猜测是对的:
...
import subprocess
...
然后我意识到了,自己一直通过百度去查各模块用法仿佛像一个傻子(orz)。
stdin(int):
File object or file descriptor number to use for ``stdin``.
By default, a pipe is used. A pty can be used instead by setting
this to ``PTY``. This will cause programs to behave in an
interactive manner (e.g.., ``python`` will show a ``>>>`` prompt).
If the application reads from ``/dev/tty`` directly, use a pty.
stdout(int):
File object or file descriptor number to use for ``stdout``.
By default, a pty is used so that any stdout buffering by libc
routines is disabled.
May also be ``PIPE`` to use a normal pipe.
stderr(int):
File object or file descriptor number to use for ``stderr``.
By default, ``STDOUT`` is used.
May also be ``PIPE`` to use a separate pipe,
although the :class:`pwnlib.tubes.tube.tube` wrapper will not be able to read this data.
这三个的参数都是int类型,但下方的说明有些File object or file,因此我们可以考虑将要写入的数据先放到一个文件中然后直接将文件交给这三个流。
明确了要做的事情,我们就该开始写exp了。首先需要将目标 "\x00\x0a\x00\xff"和"\x00\x0a\x02\xff"写入到文件中:
sti=b'\x00\x0a\x00\xff'
write('/tmp/sti',sti)
ste=b'\x00\x0a\x02\xff'
write('/tmp/ste',ste)
然后我们尝试将这俩文件传递给stdin和stderr流:
p=process(argv=args,stdin=open('/tmp/sti'),stderr=open('/tmp/ste'))
from pwn import *
args=list('a'*100)
#print(args)
args[0]='./input'
args[ord('A')]=b'\x00'
args[ord('B')]=b'\x20\x0a\x0d'
sti=b'\x00\x0a\x00\xff'
write('/tmp/sti',sti)
ste=b'\x00\x0a\x02\xff'
write('/tmp/ste',ste)
p=process(argv=args,stdin=open('/tmp/sti'),stderr=open('/tmp/ste'))
p.interactive()
4
env环境变量
目标:
// env
if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
printf("Stage 3 clear!\n");
//首先环境变量是以键值对存在的,就是"名字(键)":"值"
//就以PATH环境变量来说,它就是"PATH":"/bin:xx1:xxx2:xx3"存在的
//而我们想要获取它后面的那些目录,我们只需要知道它的名字叫PATH就可以了
getenv("PATH")
//结果就是"/bin:xx1:xxx2:xx3"
这里,我们需要把"\xde\xad\xbe\xef":"\xca\xfe\xba\xbe"这一键值对放入到环境变量里边,而经过之前对process源码的查看,里面确实存在env这个参数来传入环境变量。
env(dict): Environment variables. By default, inherits from Python's environment.
env_={b"\xde\xad\xbe\xef":b"\xca\xfe\xba\xbe"}
p=process(argv=args,stdin=open('/tmp/sti'),stderr=open('/tmp/ste'),env=env_)
from pwn import *
args=list('a'*100)
#print(args)
args[0]='./input'
args[ord('A')]=b'\x00'
args[ord('B')]=b'\x20\x0a\x0d'
sti=b'\x00\x0a\x00\xff'
write('/tmp/sti',sti)
ste=b'\x00\x0a\x02\xff'
write('/tmp/ste',ste)
env_={b"\xde\xad\xbe\xef":b"\xca\xfe\xba\xbe"}
p=process(argv=args,stdin=open('/tmp/sti'),stderr=open('/tmp/ste'),env=env_)
p.interactive()
5
file文件操作
目标:
// file
FILE* fp = fopen("\x0a", "r");
if(!fp) return 0;
if( fread(buf, 4, 1, fp)!=1 ) return 0;
if( memcmp(buf, "\x00\x00\x00\x00", 4) ) return 0;
fclose(fp);
printf("Stage 4 clear!\n");
memcmp前面有说,fclose(fd)是关闭文件(这是一个好习惯)。所以,目的就是我们要从"\x0a"这个文件中读取4字节的内容,然后和"\x00\x00\x00\x00"一样。
我们要做的就是将"\x00\x00\x00\x00"写入到这个文件中,然后便可以通过阶段4。直接使用write即可:
buf=b'\x00\x00\x00\x00'
write('\x0a',buf)
于是,对上方的wirte的目录需要进行修改:
buf=b'\x00\x00\x00\x00'
write('/tmp/mydir/\x0a',buf)
p=process(argv=args,stdin=open('/tmp/sti'),stderr=open('/tmp/ste'),env=env_,cwd='/tmp')
//假设dir是input所在的目录
//1.直接创建软连接,将input链接到/tmp中去
ln -sb dir/input /tmp/input
//2.修改argv[0]为input的绝对路径
argv[0]='dir/input'
//3.使用process的一个参数executable
p=process(argv=args,stdin=open('/tmp/sti'),stderr=open('/tmp/ste'),env=env_,cwd='/tmp',executable='dir/input')
from pwn import *
args=list('a'*100)
#print(args)
args[0]='./input'
args[ord('A')]=b'\x00'
args[ord('B')]=b'\x20\x0a\x0d'
sti=b'\x00\x0a\x00\xff'
write('/tmp/sti',sti)
ste=b'\x00\x0a\x02\xff'
write('/tmp/ste',ste)
env_={b"\xde\xad\xbe\xef":b"\xca\xfe\xba\xbe"}
buf=b'\x00\x00\x00\x00'
write('/tmp/\x0a',buf)
#dir要修改为input的目录
p=process(argv=args,stdin=open('/tmp/sti'),stderr=open('/tmp/ste'),env=env_,cwd='/tmp',executable='dir/input')
p.interactive()
6
network网络服务
目标:
首先,分析代码吧,我想如果不是熟悉C语言的人,差不多都和我一样了,第一反应Excuse me?
不过还好,有network的提醒,而且下边靠着socket,addr,port相关的东西差不多可以联想到大概率和地址+端口的服务有关,然后我们看到几个大写的常量。
AF_INET,SOCK_STREAM,INADDR_ANY,直接上百度,找到了这篇相关的播客AF_INET。
然后得知,这大概是写的就是创建了一个地址为INADDR_ANY(经查是0.0.0.0,指本机),端口为argv['C']的一个socket接口。
其中bind是用于绑定socket和ip+端口用的,然后利用listen进行对该接口的监听,之后的accept是获取连接,并通过recv接受数据,如果数据为"\xde\xad\xbe\xef"则通过第五阶段。
总体来说,这一部分就是开了个本机的端口,其中端口为argv['C']的值,而我们的目的就是向这个端口传递"\xde\xad\xbe\xef"。
端口我们可以自主选择,我这里选择66666:
args[ord('C')]='66666'
r=remote('127.0.0.1',66666)r.send(b'\xde\xad\xbe\xef')r.close()
echo 'hello,this is flag!' > /tmp/flag
from pwn import *
args=list('a'*100)
#print(args)
args[0]='./input'
args[ord('A')]=b'\x00'
args[ord('B')]=b'\x20\x0a\x0d'
sti=b'\x00\x0a\x00\xff'
write('/tmp/sti',sti)
ste=b'\x00\x0a\x02\xff'
write('/tmp/ste',ste)
env_={b"\xde\xad\xbe\xef":b"\xca\xfe\xba\xbe"}
buf=b'\x00\x00\x00\x00'
write('/tmp/\x0a',buf)
args[ord('C')]='66666'
#dir要修改为input的目录
p=process(argv=args,stdin=open('/tmp/sti'),stderr=open('/tmp/ste'),env=env_,cwd='/tmp',executable='dir/input')
r=remote('127.0.0.1',66666)
r.send(b'\xde\xad\xbe\xef')
r.close()
p.interactive()
7
适应到服务器执行拿flag
通过前面的步骤我们已经可以拿到本地的flag了,接下来就是去服务器拿flag了,首先我们看一下我们的exp,我们将process的运行目录改好:
p=process(argv=args,stdin=open('/tmp/sti'),stderr=open('/tmp/ste'),env=env_,cwd='/tmp',executable='/home/input2/input')
import osos.system('ln -sb /home/input2/flag /tmp/flag')
from pwn import *
import os
os.system('ln -sb /home/input2/flag /tmp/flag')
args=list('a'*100)
#print(args)
args[0]='./input'
args[ord('A')]=b'\x00'
args[ord('B')]=b'\x20\x0a\x0d'
sti=b'\x00\x0a\x00\xff'
write('/tmp/sti',sti)
ste=b'\x00\x0a\x02\xff'
write('/tmp/ste',ste)
env_={b"\xde\xad\xbe\xef":b"\xca\xfe\xba\xbe"}
buf=b'\x00\x00\x00\x00'
write('/tmp/\x0a',buf)
args[ord('C')]='66666'
p=process(argv=args,stdin=open('/tmp/sti'),stderr=open('/tmp/ste'),env=env_,cwd='/tmp',executable='/home/input2/input')
r=remote('127.0.0.1',66666)
r.send(b'\xde\xad\xbe\xef')
r.close()
p.interactive()
scp -P2222 exp.py input2@pwnable.kr:/tmp/exp.pyssh input2@pwnable.kr -p2222python '/tmp/exp.py'
import os
os.system('mkdir /tmp/mydir')
os.system('ln -sb /home/input2/flag /tmp/mydir/flag')
from pwn import *
import os
os.system('mkdir /tmp/mydir')
os.system('ln -sb /home/input2/flag /tmp/mydir/flag')
args=list('a'*100)
#print(args)
args[0]='./input'
args[ord('A')]=b'\x00'
args[ord('B')]=b'\x20\x0a\x0d'
sti=b'\x00\x0a\x00\xff'
write('/tmp/mydir/sti',sti)
ste=b'\x00\x0a\x02\xff'
write('/tmp/mydir/ste',ste)
env_={b"\xde\xad\xbe\xef":b"\xca\xfe\xba\xbe"}
buf=b'\x00\x00\x00\x00'
write('/tmp/mydir/\x0a',buf)
args[ord('C')]='66666'
p=process(argv=args,stdin=open('/tmp/mydir/sti'),stderr=open('/tmp/mydir/ste'),env=env_,cwd='/tmp/mydir',executable='/home/input2/input')
r=remote('127.0.0.1',66666)
r.send(b'\xde\xad\xbe\xef')
r.close()
p.interactive()
8
小结
看雪ID:ybaibai
https://bbs.pediy.com/user-home-909398.htm
# 往期推荐
球分享
球点赞
球在看
点击“阅读原文”,了解更多!