我在google中进行搜索,发现Linux内核使用结构体来获取变量。
#define EMBEDDED_LEVELS 2
struct nameidata {
struct path path;
struct qstr last;
struct path root;
struct inode *inode; /* path.dentry.d_inode */
unsigned int flags;
unsigned seq, m_seq;
int last_type;
unsigned depth;
int total_link_count;
struct saved {
struct path link;
struct delayed_call done;
const char *name;
unsigned seq;
} *stack, internal[EMBEDDED_LEVELS];
struct filename *name;
struct nameidata *saved;
struct inode *link_inode;
unsigned root_seq;
int dfd;
} __randomize_layout;
例如execve
系统调用(在https://elixir.bootlin.com/linux/latest/source/fs/exec.c处找到)
此函数会将文件名指针作为 pathName 传递给另一个函数,并将 nameidata 结构名设置为此 pathName
static int __do_execve_file(int fd, struct filename *filename,
struct user_arg_ptr argv,
struct user_arg_ptr envp,
int flags, struct file *file)
我的问题是如何计算从堆栈(例如"/bin/sh"
)传递给该函数的参数的长度?
(编者注:execve(2)
的const char *pathname
arg 没有指向堆栈内存。我认为这个问题是假设您在shellcode用例中确实在用户空间堆栈上构造了一个路径并传递了指向该路径的指针。)
(我正在学习汇编,并且陷入了系统调用的参数传递部分)
答案 0 :(得分:2)
Linux使用零终止的字符串,这是C的标准字符串格式。字符串的结尾由零字节标记,字符串中第一个零字节之后的任何字节都不是字符串的一部分。值得注意的是,这意味着文件名中不能包含零字节。 (出于同样的原因,大多数shellcode不能有零字节,因为它们是为了利用某种字符串缓冲区溢出。)
在实践中,内核通常不需要知道文件名的长度,并使用strcmp
之类的函数逐字节比较字符串,停在比较不同的第一个字节或第一个零处停止遇到字节。但是,如有必要,可以使用strlen
之类的函数来计算字符串的长度。
答案 1 :(得分:0)
最后我找到了答案
这是我找到的源代码
#include <stdio.h>
char hello[] = "HelloA\0AAABB";
int main( void )
{
__asm__
(
"mov $hello , %eax;"
"push %eax;"
"call myprint;"
);
}
void myprint(char input[])
{
printf("%s" , input);
}
我发现'\ 0'是字符串终止符。因此上面的代码只会输出“ HelloA”。其余的将被忽略。
我发现的另一件有趣的事情是,当您在C语言中创建char数组时,编译器将在其末尾添加一个字节以用作空终止符。
所以如果我们创建这样的东西
char hello[] = "Hello"
它实际上是这样编译的:
char hello[] = "Hello\0"
,您好的大小将是6,而不是5。
最后,在汇编编程中,对于在系统调用中传递的参数,我们必须考虑使用空终止符。只要linux内核是用C编程语言编写的,我们就必须接受C编程语言的规则。
这是char hello []的Gdb结果
0x8049597 <hello>: 0x48 0x65 0x6c 0x6c 0x6f 0x41 0x00 0x41
0x804959f <hello+8>: 0x41 0x41 0x42 0x42 0x00 0x77 0x6f 0x72
0x8049597是我们字符串的起始地址(“ HelloA \ 0AAABB”)。
我们将\ 0放在A字符之后。字符“ A”等于Ascii表中的0x41十六进制数。并且\ 0为0x00。
这就是为什么printf函数将只显示字符串的前6个字符的原因。