题目
Mom? how can I pass my input to a computer program?
ssh input2@pwnable.kr -p2222 (pw:guest)
题解
#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;
}
说是pwn题, 不如说是程序传参教学, 要传5种类型的参数
(1) argv参数的传递
(2) stdio标准输入输出
(3) env环境变量
(4) file文件操作
(5) network网络服务
主要使用pwntools的process模块, (而process模块其实是用python的subprocess模块实现的
读一下process的源码
class process(tube):
r"""
Spawns a new process, and wraps it with a tube for communication.
Arguments:
argv(list):
List of arguments to pass to the spawned process.
shell(bool):
Set to `True` to interpret `argv` as a string
to pass to the shell for interpretation instead of as argv.
executable(str):
Path to the binary to execute. If :const:`None`, uses ``argv[0]``.
Cannot be used with ``shell``.
cwd(str):
Working directory. Uses the current working directory by default.
env(dict):
Environment variables. By default, inherits from Python's environment.
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.
close_fds(bool):
Close all open file descriptors except stdin, stdout, stderr.
By default, :const:`True` is used.
preexec_fn(callable):
Callable to invoke immediately before calling ``execve``.
raw(bool):
Set the created pty to raw mode (i.e. disable echo and control
characters). :const:`True` by default. If no pty is created, this
has no effect.
aslr(bool):
If set to :const:`False`, disable ASLR via ``personality`` (``setarch -R``)
and ``setrlimit`` (``ulimit -s unlimited``).
This disables ASLR for the target process. However, the ``setarch``
changes are lost if a ``setuid`` binary is executed.
The default value is inherited from ``context.aslr``.
See ``setuid`` below for additional options and information.
setuid(bool):
Used to control `setuid` status of the target binary, and the
corresponding actions taken.
By default, this value is :const:`None`, so no assumptions are made.
If :const:`True`, treat the target binary as ``setuid``.
This modifies the mechanisms used to disable ASLR on the process if
``aslr=False``.
This is useful for debugging locally, when the exploit is a
``setuid`` binary.
If :const:`False`, prevent ``setuid`` bits from taking effect on the
target binary. This is only supported on Linux, with kernels v3.5
or greater.
where(str):
Where the process is running, used for logging purposes.
display(list):
List of arguments to display, instead of the main executable name.
alarm(int):
Set a SIGALRM alarm timeout on the process.
Examples:
>>> p = process('python2')
>>> p.sendline(b"print 'Hello world'")
>>> p.sendline(b"print 'Wow, such data'");
>>> b'' == p.recv(timeout=0.01)
True
>>> p.shutdown('send')
>>> p.proc.stdin.closed
True
>>> p.connected('send')
False
>>> p.recvline()
b'Hello world\n'
>>> p.recvuntil(b',')
b'Wow,'
>>> p.recvregex(b'.*data')
b' such data'
>>> p.recv()
b'\n'
>>> p.recv() # doctest: +ELLIPSIS
Traceback (most recent call last):
...
EOFError
>>> p = process('cat')
>>> d = open('/dev/urandom', 'rb').read(4096)
>>> p.recv(timeout=0.1)
b''
>>> p.write(d)
>>> p.recvrepeat(0.1) == d
True
>>> p.recv(timeout=0.1)
b''
>>> p.shutdown('send')
>>> p.wait_for_close()
>>> p.poll()
0
>>> p = process('cat /dev/zero | head -c8', shell=True, stderr=open('/dev/null', 'w+b'))
>>> p.recv()
b'\x00\x00\x00\x00\x00\x00\x00\x00'
>>> p = process(['python','-c','import os; print(os.read(2,1024).decode())'],
... preexec_fn = lambda: os.dup2(0,2))
>>> p.sendline(b'hello')
>>> p.recvline()
b'hello\n'
>>> stack_smashing = ['python','-c','open("/dev/tty","wb").write(b"stack smashing detected")']
>>> process(stack_smashing).recvall()
b'stack smashing detected'
>>> process(stack_smashing, stdout=PIPE).recvall()
b''
>>> getpass = ['python','-c','import getpass; print(getpass.getpass("XXX"))']
>>> p = process(getpass, stdin=PTY)
>>> p.recv()
b'XXX'
>>> p.sendline(b'hunter2')
>>> p.recvall()
b'\nhunter2\n'
>>> process('echo hello 1>&2', shell=True).recvall()
b'hello\n'
>>> process('echo hello 1>&2', shell=True, stderr=PIPE).recvall()
b''
>>> a = process(['cat', '/proc/self/maps']).recvall()
>>> b = process(['cat', '/proc/self/maps'], aslr=False).recvall()
>>> with context.local(aslr=False):
... c = process(['cat', '/proc/self/maps']).recvall()
>>> a == b
False
>>> b == c
True
>>> process(['sh','-c','ulimit -s'], aslr=0).recvline()
b'unlimited\n'
>>> io = process(['sh','-c','sleep 10; exit 7'], alarm=2)
>>> io.poll(block=True) == -signal.SIGALRM
True
>>> binary = ELF.from_assembly('nop', arch='mips')
>>> p = process(binary.path)
>>> binary_dir, binary_name = os.path.split(binary.path)
>>> p = process('./{}'.format(binary_name), cwd=binary_dir)
>>> p = process(binary.path, cwd=binary_dir)
>>> p = process('./{}'.format(binary_name), cwd=os.path.relpath(binary_dir))
>>> p = process(binary.path, cwd=os.path.relpath(binary_dir))
"""
(1) 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");
arg参数要传入100个, 并且第’A’个和第’B’个为特定值
(2) 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");
stdin和stderr要为特殊值
(3) env
// env
if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
printf("Stage 3 clear!\n");
环境变量要求{"\xde\xad\xbe\xef":"\xca\xfe\xba\xbe"}
(4) 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");
读取文件’\x0a’的4字节, 且为"\x00\x00\x00\x00"
在服务器上不能直接写文件, guest权限不够, 但是/tmp
文件夹是可以写的, 所以可以用process的cwd改变运行目录, 然后写入文件
(5) network
// 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");
涉及C network编程, 现学一下, 大概就是开个本机的端口, 其中端口为argv[‘C’]的值, 需要向这个端口传递"\xde\xad\xbe\xef".
因为再第四步需要把运行环境切换到/tmp
目录下, 则第五步通过之后读取flag要通过软连接索引到/home/input2/flag, 但是/tmp
文件夹下guest无权限读取, 所以应该以guest用户创建一个文件夹, 在这个文件夹下guest用户拥有读写的权限(因为是拥有者)
综合以上, 可以整合得到exp
from pwn import *
import os
os.system('mkdir /tmp/pwn')
os.system('ln -s /home/input2/flag /tmp/pwn/flag')
args = list('z' * 100)
args[0] = './input'
args[ord('A')] = '\x00'
args[ord('B')] = '\x20\x0a\x0d'
sti = b'\x00\x0a\x00\xff'
write('/tmp/pwn/sti', sti)
ste = b'\x00\x0a\x02\xff'
write('/tmp/pwn/ste', ste)
env_dict = {b'\xde\xad\xbe\xef': b'\xca\xfe\xba\xbe'}
buf = b'\x00\x00\x00\x00'
write('/tmp/pwn/\x0a', buf)
args[ord('C')] = '2333'
p = process(argv=args, stdin=open('/tmp/pwn/sti'), stderr=open('/tmp/pwn/ste'),
env=env_dict, cwd='/tmp/pwn', executable='/home/input2/input')
io = remote('localhost', 2333)
io.send(b'\xde\xad\xbe\xef')
io.close()
p.interactive()
拷贝到服务器上运行
scp -P2222 ./exp.py input2@pwnable.kr:/tmp/exp.py