安全研究

前言

在做分析二进制文件的时候,难免会遇到需要在程序输入处输入一些不可显字符,一般我们会通过pwntools进行解决

from pwn import *

con = process('ret2lib')
con.recvuntil("input:")

con.send("\x01\x01\x00\x01")
con.interactive()

但是当脚本存在一些问题,我们需要通过gdb调试时,在程序输入中输入不可显字符就较为麻烦。于是有了这篇文章

正常方法

pwn师傅给我的方案是,首先输入正常字符,输入后,找到字符串地址,通过GDB 命令 set xxx=xxx对内存处进行更改
图片.png

网上找了一下GDB set命令,大部分教程都是修改整型

(gdb) set {unsigned int}0x8048a51=0x0

对于字符串的修改却没找到中文资料
于是我稍微仿照试了一下啊,最终发现

(gdb) set {char [6]}0x8048a51="12345"

可行,需要注意,[]数值包括了0,所以需要比字符串常量多1。
并且类型不能使用{char *},否则 对应地址处会继续存放一个字符串指针,而不是字符串值,如下图

图片.png
图片.png
------------------------------------------------------------------------------------------------------

图片.png
图片.png

此外,我在网上还搜到一个人写的GDB插件,不过我下载下来以后使用不了,看源码发现是使用了GDB的call命令重定向了文件描述符(call 命令还有这个功能?不太懂)没有深究
https://www.jianshu.com/p/78e77277ebb5

错误方法

但是一开始我用的方法不是PWN师傅教我的,当时,我自己的理解是每个文件下都有3个文件描述符,
0 -> stdin(标准输入)
1 -> stdout(标准输出)
2 -> stderr (标准错误)
而且在Linux中,万物皆文件,这三个文件描述符分别存储在 /proc/{pid}/fd/ 下
那我直接往 标准输入里面写数据不就可以了吗
我的做法如下
demo.c

#include<stdio.h>
#include <unistd.h>
int main(){
    pid_t pid = getpid();
    char s[100];
    printf("pid of this process:%d\n", pid);
    printf("please input string:\n");
    scanf("%s",s);
    printf("U input String is :%s",s);
    return 0;
}

运行过程
图片.png

这是我键盘输入的123456798,那如果我往标准输入写数据呢

下面是我的尝试
图片.png
keyboard input :123456是我在键盘上打出来的字符串,很明显可以看到,虽然我们往对应进程的标准输入描述符中写入的数据被打印到了终端上,但是程序进程的输出却告诉它并没有接收到这些数据。而我用键盘继续输入的字符串才真正被程序接收
PS: 由于scanf函数读取到空格会停止,所以keyboard后面的字符串并没有被接受

原因

虽然往标准输入写数据 理论上听上去没什么问题,但是结果告诉我们并不能成功,网上搜索的时候中文搜索引擎并没有相关的结果,但是谷歌一下就找到了原因

https://serverfault.com/questions/178457/can-i-send-some-text-to-the-stdin-of-an-active-process-running-in-a-screen-sessi#
中文翻译一下大概就是
提问者提出了linux服务器终端有个任务,怎么样才能写脚本代替手工往这个终端任务的标准输入写数据

而下面的回答就是,往/proc/{pid}/fd/0 写入数据只会回显到tty上,并不会被程序接受
原因是 正常的写文件操作并不能被程序读取,需要以一种特殊的方式发送输入文本以供过程读取。通过常规文件write方法发送输入文本将不会导致进程接收文本。这是因为这样做只会附加到该“文件”,而不会触发进程读取字节。
为了触发该过程以读取字节,必须对要发送的每个单个字节IOCTL执行类型的操作TIOCSTI。这会将字节放入进程的标准输入队列中。
我的理解是,这种输入不是正常文件读取,而是一种流式传输,所以我上面的粗暴写文件方法是无效的。

那怎么进行流式传输呢,系统肯定提供了对应的系统调用呀
系统调用 ioctl
图片.png

C demo

根据描述,ioctl是控制文件描述符 I/O通道的函数,答者根据这个系统调用写了一个小demo来往标准输入里面写数据
PS: 往其他文件的标准输入写数据需要root权限

对应的demo
https://raw.githubusercontent.com/grawity/code/master/thirdparty/writevt.c

/*
 * Mostly ripped off of console-tools' writevt.c
 */

#include <stdio.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <unistd.h>

char *progname;

static int usage() {
    printf("Usage: %s ttydev text\n", progname);
    return 2;
}

int main(int argc, char **argv) {
    int fd, argi;
    char *term = NULL;
    char *text = NULL;

    progname = argv[0];

    argi = 1;

    if (argi < argc)
        term = argv[argi++];
    else {
        fprintf(stderr, "%s: no tty specified\n", progname);
        return usage();
    }

    if (argi < argc)
        text = argv[argi++];
    else {
        fprintf(stderr, "%s: no text specified\n", progname);
        return usage();
    }

    if (argi != argc) {
        fprintf(stderr, "%s: too many arguments\n", progname);
        return usage();
    }

    fd = open(term, O_RDONLY);
    if (fd < 0) {
        perror(term);
        fprintf(stderr, "%s: could not open tty\n", progname);
        return 1;
    }

    while (*text) {
        if (ioctl(fd, TIOCSTI, text)) {
            perror("ioctl");
            return 1;
        }
        text++;
    }

    return 0;
}

编译完以后,只需要执行

writevt /proc/{pid}/fd/0 "you text"

图片.png

但是这个程序有个小bug,很明显,这个程序是把第二个命令行参数当作文本,第一个参数当成描述符。但是如果我们需要输入特殊字符,比如回车,我们一般会这么输入
图片.png
但是程序显示参数过多,因为回车会被当成命令行分隔符,123n456,运行结束后123会被当成第二个命令行参数,456会被当成第三个命令行参数,所以我们无法通过这个输入某些特殊字符。

这肯定不是我们想要的,但修改C代码稍微有点繁琐,好在,另一个回答提供了python demo

python demo

import fcntl
import sys
import termios

with open('/dev/tty1', 'w') as fd:
    for char in "ls -la\n":
        fcntl.ioctl(fd, termios.TIOCSTI, char)

稍微改成上面的形式就是

import fcntl
import sys
import termios

with open(sys.argv[1], 'w') as fd:
    for char in sys.argv[2]:
        fcntl.ioctl(fd, termios.TIOCSTI, char)

有了python就好办事了,我们可以规定命令行传入的特殊字符会编码一次,而程序中再解码一次即可

#!/usr/bin/python
# writev.py

import fcntl
import sys
import termios

with open(sys.argv[1], 'w') as fd:
    for char in eval("'"+raw_input()+"'"):
        fcntl.ioctl(fd, termios.TIOCSTI, char)

图片.png
PS:由于bash中会转义反斜杠,所以这里需要双反斜杠

成功的把数据输入到了进程的标准输入中,进程也成功接收到了数据

tty

触类旁通,我们知道linux中,每个终端就代表了一个tty,tty也是一个文件描述符,既然我们能控制输入输出,理论上就应该也能控制tty,
图片.png
确实如此,我们可以模拟tty的键盘输入,往tty里面写数据,但是如果要获取tty的标准输出,和获取正常输入的标准输入呢?

这里埋个坑。这方面的资料真的太少了,google搜到的资料也太杂了,暂时也没什么思绪,准备明天去看看pwntools的源码,先鸽了,一定更新,下次一定

Comment

This is just a placeholder img.