Linux 手写汇编,用系统调用来读取文件夹和文件
TOC
鉴于大部分人不太熟悉 AT&T 汇编,这里我就不用原生的汇编来写,用的是 Intel 汇编。
在做pwn的时候,有时候你会遇到不能调用 SYS_execve 的情况,而且还可能遇到没有libc的库的情况,这时候这门技术就能让你如鱼得水。
实验中的所有文件都可打包下载:read_file_by_syscall.zip 。
系统调用简介
下面是我们会用到的系统调用:
rax | System call | rdi | rsi | rdx |
---|---|---|---|---|
0 | sys_read | unsigned int fd | char *buf | size_t count |
1 | sys_write | unsigned int fd | const char *buf | size_t count |
2 | sys_open | const char *filename | int flags | int mode |
78 | sys_getdents | unsigned int fd | struct linux_dirent *dirent | unsigned int count |
sys_read
官方文档:man2/read.2.html 。
NAME
read - read from a file descriptor
SYNOPSIS
|
DESCRIPTION
read() attempts to read up to count bytes from file descriptor fd into the buffer starting at buf.
On files that support seeking, the read operation commences at the file offset, and the file offset is incremented by the number of bytes read. If the file offset is at or past the end of file, no bytes are read, and read() returns zero.
If count is zero, read() may detect the errors described below. In the absence of any errors, or if read() does not check for errors, a read() with a count of 0 returns zero and has no other effects.
According to POSIX.1, if count is greater than SSIZE_MAX, the result is implementation-defined; see NOTES for the upper limit on Linux.
RETURN VALUE
On success, the number of bytes read is returned (zero indicates end of file), and the file position is advanced by this number. It is not an error if this number is smaller than the number of bytes requested; this may happen for example because fewer bytes are actually available right now (maybe because we were close to end-of-file, or because we are reading from a pipe, or from a terminal), or because read() was interrupted by a signal. See also NOTES.
On error, -1 is returned, and errno is set appropriately. In this case, it is left unspecified whether the file position (if any) changes.
简述
fd
是文件句柄,buf
是要读的内存地址,count
是要读的字节数量。
sys_write
官方文档:man2/write.2.html 。
NAME
write - write to a file descriptor
SYNOPSIS
|
DESCRIPTION
write() writes up to count bytes from the buffer starting at buf tothe file referred to by the file descriptor fd.
The number of bytes written may be less than count if, for example,there is insufficient space on the underlying physical medium, or theRLIMIT_FSIZE resource limit is encountered (see setrlimit(2)), or thecall was interrupted by a signal handler after having written lessthan count bytes. (See also pipe(7).)
For a seekable file (i.e., one to which lseek(2) may be applied, forexample, a regular file) writing takes place at the file offset, andthe file offset is incremented by the number of bytes actuallywritten. If the file was open(2)ed with O_APPEND, the file offset isfirst set to the end of the file before writing. The adjustment ofthe file offset and the write operation are performed as an atomicstep.
POSIX requires that a read(2) that can be proved to occur after awrite() has returned will return the new data. Note that not allfilesystems are POSIX conforming.
According to POSIX.1, if count is greater than SSIZE_MAX, the resultis implementation-defined; see NOTES for the upper limit on Linux.
RETURN VALUE
On success, the number of bytes written is returned. On error, -1 isreturned, and errno is set to indicate the cause of the error.
Note that a successful write() may transfer fewer than count bytes.Such partial writes can occur for various reasons; for example,because there was insufficient space on the disk device to write allof the requested bytes, or because a blocked write() to a socket,pipe, or similar was interrupted by a signal handler after it hadtransferred some, but before it had transferred all of the requestedbytes. In the event of a partial write, the caller can make anotherwrite() call to transfer the remaining bytes. The subsequent callwill either transfer further bytes or may result in an error (e.g.,if the disk is now full).
If count is zero and fd refers to a regular file, then write() mayreturn a failure status if one of the errors below is detected. Ifno errors are detected, or error detection is not performed, 0 willbe returned without causing any other effect. If count is zero andfd refers to a file other than a regular file, the results are notspecified.
简述
fd
是文件句柄,buf
是要写到fd
的内存地址,count
是要读的字节数量。
sys_open
官方文档:man2/open.2.html 。
SYNOPSIS
|
DESCRIPTION
The argument flags must include one of the following access modes:O_RDONLY
, O_WRONLY, or O_RDWR. These request opening the file read-only, write-only, or read/write, respectively.
In addition, zero or more file creation flags and file status flagscan be bitwise-or’d in flags. The file creation flags are O_CLOEXEC,O_CREAT, O_DIRECTORY
, O_EXCL, O_NOCTTY, O_NOFOLLOW, O_TMPFILE, andO_TRUNC. The file status flags are all of the remaining flags listedbelow. The distinction between these two groups of flags is that thefile creation flags affect the semantics of the open operationitself, while the file status flags affect the semantics ofsubsequent I/O operations. The file status flags can be retrievedand (in some cases) modified; see fcntl(2) for details.
这里我特别强调一下,O_RDONLY 的值是0,O_DIRECTORY的值是 0x10000 。
sys_getdents
官方文档:http://man7.org/linux/man-pages/man2/getdents.2.html 。
SYNOPSIS
int getdents(unsigned int fd, struct linux_dirent *dirp, |
DESCRIPTION
These are not the interfaces you are interested in. Look at readdir(3) for the POSIX-conforming C library interface. This page documents the bare kernel system call interfaces.
The system call getdents() reads several linux_dirent structures from the directory referred to by the open file descriptor fd into the buffer pointed to by dirp. The argument count specifies the size of that buffer.
The linux_dirent structure is declared as follows:
struct linux_dirent { |
d_ino
is an inode number. d_off is the distance from the start of the directory to the start of the next linux_dirent. d_reclen is the size of this entire linux_dirent. d_name is a null-terminated file‐name.
d_type
is a byte at the end of the structure that indicates the file type. It contains one of the following values (defined in <dirent.h>
):
- DT_BLK This is a block device.
- DT_CHR This is a character device.
- DT_DIR This is a directory.
- DT_FIFO This is a named pipe (FIFO).
- DT_LNK This is a symbolic link.
- DT_REG This is a regular file.
- DT_SOCK This is a UNIX domain socket.
- DT_UNKNOWN The file type is unknown.
The d_type field is implemented since Linux 2.6.4. It occupies a space that was previously a zero-filled padding byte in the linux_dirent structure. Thus, on kernels up to and including 2.6.3, attempting to access this field always provides the value 0 (DT_UNKNOWN).
Currently, only some filesystems (among them: Btrfs, ext2, ext3, and ext4) have full support for returning the file type in d_type. All applications must properly handle a return of DT_UNKNOWN.
简述
fd
是要解析的文件夹的句柄,buf
是装解析内容的内存地址,注意buf需要自己分配好内存
,count
是buf
的大小。
读文件夹
下面我来演示一下文件夹解析的例子。
源码:read_dir.s
;// gcc -c read_dir.s -o read_dir.o |
注释里已经说的很清楚了,具体功能就是读入输入的文件夹名,然后进行解析,输出解析的内容。
注意此时用的是 O_DIRECTORY ,如果是文件的话会返回错误。
然后我们在写出我们的解析函数。
解析:resolve.c
// compiled: gcc -g resolve.c -o resolve |
运行效果
接下来让我们来看看效果
ex@Ex:~/read_file_by_syscall$ ll test/ |
可以看到完美的解析出来了,而且没有用glibc库。
读文件夹
下面我来演示一下读文件的例子。
源码:read_file.s
;// gcc -c read_file.s -o read_file.o |
注释里已经说的很清楚了,具体功能就是打开文件,然后读到栈上,最后输出。
运行效果
ex@Ex:~/read_file_by_syscall$ cat test/file.txt |
完美的读取出了内容。
总结
其实系统调用和我们一般的编程是差不多的,并没有我们想象的那么难