部分题目的binary下载:tsctf-bin.tar.gz
easy_adb
这题我们拿到的是一个.pcapng
格式的文件,用wireshark可以直接打开,发现是wireshark抓取的一系列的TCP包,分析内容后可以发现这是通过TCP建立的adb shell连接。
点击Analyze
->Follow TCP Stream
就可以看到TCP流信息:
我们只关注字符串部分,所有的raw:
后面的内容都是shell执行的指令。一开始执行了一些echo语句打印欢迎语。之后出现了getevent -lp
、getevent -p
、、getevent -l
、getevent -t
四条和输入事件有关 的指令。
通过分析得知,插入了一个Yubico Yubikey NEO OTP+U2F+CCID
设备,它通过模拟一个键盘向Android输入信息,键和事件ID表如下:
1 | add device 1: /dev/input/event4 |
1 | add device 1: /dev/input/event4 |
最后getevent -t
命令按照时间顺序给出了期间该设备的输入信息,经过简单的处理,我们从中剥离出所有的输出,大概类似于这样:
1 | [ 269463.944942] 0014 0000 00000000 |
通过在实机上测试我们发现,第一列是时间,第二列是事件id,第三列是事件的值,第四列是额外的参数,我们从中晒出第二列为0001
的行(表示KEY事件),然后筛出第四列为00000001
的行(表示按钮抬起的事件),取出第三列的事件id:
1 | ['002f','002f','0023','0020','0013','0014','0020','0020','0030','0031','002f','002f','0014','0026','0017','0012','0024','0020','0030','0022','0016','0031','0013','0017','002e','0017','0020','0024','0017','0016','002f','0023','0012','0021','0013','0020','0014','0017','0020','0026','0017','0024','0030','0013','001c'] |
再把上面的两个表做一个映射表出来,写一个python脚本去匹配这些id就能得到输入内容:
1 | ids_str = ['KEY_ESC','KEY_1','KEY_2','KEY_3','KEY_4','KEY_5','KEY_6','KEY_7','KEY_8','KEY_9','KEY_0','KEY_MINUS','KEY_EQUAL','KEY_BACKSPACE','KEY_TAB','KEY_Q','KEY_W','KEY_E','KEY_R','KEY_T','KEY_Y','KEY_U','KEY_I','KEY_O','KEY_P','KEY_LEFTBRACE','KEY_RIGHTBRACE','KEY_ENTER','KEY_LEFTCTRL','KEY_A','KEY_S','KEY_D','KEY_F','KEY_G','KEY_H','KEY_J','KEY_K','KEY_L','KEY_SEMICOLON','KEY_APOSTROPHE','KEY_GRAVE','KEY_LEFTSHIFT','KEY_BACKSLASH','KEY_Z','KEY_X','KEY_C','KEY_V','KEY_B','KEY_N','KEY_M','KEY_COMMA','KEY_DOT','KEY_SLASH','KEY_RIGHTSHIFT','KEY_KPASTERISK','KEY_LEFTALT','KEY_SPACE','KEY_CAPSLOCK','KEY_F1','KEY_F2','KEY_F3','KEY_F4','KEY_F5','KEY_F6','KEY_F7','KEY_F8','KEY_F9','KEY_F10','KEY_NUMLOCK','KEY_SCROLLLOCK','KEY_KP7','KEY_KP8','KEY_KP9','KEY_KPMINUS','KEY_KP4','KEY_KP5','KEY_KP6','KEY_KPPLUS','KEY_KP1','KEY_KP2','KEY_KP3','KEY_KP0','KEY_KPDOT','KEY_102ND','KEY_F11','KEY_F12','KEY_KPENTER','KEY_RIGHTCTRL','KEY_KPSLASH','KEY_SYSRQ','KEY_RIGHTALT','KEY_HOME','KEY_UP','KEY_PAGEUP','KEY_LEFT','KEY_RIGHT','KEY_END','KEY_DOWN','KEY_PAGEDOWN','KEY_INSERT','KEY_DELETE','KEY_PAUSE','KEY_LEFTMETA','KEY_RIGHTMETA','KEY_COMPOSE'] |
这是yubikey的OTP一次性密码。
分析一开始的欢迎语,其中有一条:
1 | echo Your flag will be tsctf{*data*} |
最后只要把输入内容套上tsctf{
}
就可以交了
super_easy_adb
这题我们拿到的依然是一个.pcapng
格式的文件,老套路用wireshark可以直接打开,发现这次抓取的是USB协议的包,分析协议发现依然是adb shell。
这次没有像TCP包那样的选项让我们可以导出包的内容,不过我们可以直接用vscode打开这个二进制文件,直接看其中的字符串:
用strings
命令可以提取出文件中的字符串内容:
1 | strings -s '' -w ./super_easy_adb.pcapng |
首先看raw:
后面的内容,依然是getevent -lp
、getevent -p
、、getevent -l
、getevent -t
,但是这次的输入设备变成了sec_touchscreen
,应该是一个触屏设备。
老规矩把映射表捞出来:
1 | add device 2: /dev/input/event2 |
1 | add device 2: /dev/input/event2 |
这次我们只看ABS (0003):
里的ABS_MT_POSITION_X
和ABS_MT_POSITION_Y
比如下面这两条输出:第二列为ABS (0003):
,第三列分别为ABS_MT_POSITION_X
和ABS_MT_POSITION_Y
,第三列的值则是屏幕像素点坐标。
1 | [ 270524.032103] 0003 0035 00000487 |
老规矩,写一个python脚本来解决这件事情
1 | import matplotlib.pyplot as plt |
需要注意的是,与数学中的二位坐标不同,Android屏幕的坐标原点在屏幕左上角,因此y轴的方向是向下的,我们用一个负号来翻转图像。
屏幕手势的内容就是flag啦
HelloARM
这题是一道arm的pwn题,思路是ROP,给的程序包含三个文件:
1 | Archive: HelloARM-d16411d0de1ce65a122d8f567ff53897.zip |
首先checksec看一下:
是aarch64小端的程序,可以发现开启了NX,意味着我们不能在栈上执行代码,没开PIE意味着地址不是随机的。
先用qemu跑一下程序:
1 | qemu-aarch64 -L ./ ./HelloARM |
总的来说程序先给出一个Magic number,然后会有两轮输入输出的交互。
接下来用ida分析,main函数先去执行init函数,其中会打开当前目录下的一个./flag
文件,并把文件描述符存到全局变量fd
里面,main函数内部在栈上开辟了一个缓冲区,Magic number就是这个缓冲区的栈地址,随后会读入大小为0x10的内容到bss段的name
数组中,随后打印这串内容,最后进入oooooo()
函数,函数内部开辟了0x100大小的栈上数组用来存用户输入的message,但这里存在溢出漏洞。
首先我们需要知道的是,aarch64用X30这个寄存器来存储函数调用的返回地址,并在进入子函数后将X30中的返回地址压入栈中。比较特别的是oooooo()
函数中,栈上数组的地址比存放X30内容的地址高,因此无法劫持oooooo()
的ret
指令,但是我们还可以劫持main()
的ret
指令。
找到切入点后,我们尝试寻找可用的gadget
,寻找一番没有找到合适的。但是该程序中包含一个__libc_csu_init()
函数,利用该函数中的特殊结构,我们可以指定参数并完成任意函数的调用。
最后这题的思路主要是:
- 用
oooooo()
的缓冲区溢出劫持main()
的ret
指令,让它跳转到__libc_csu_init()
函数内部.text:0000000000400AD0
地址处(上图)。 - 然后我们通过精心构造溢出部分的内容,让程序去调用.plt表中的
read()
函数,读出./flag
文件的内容(这里我们猜测服务器上fd的值是3,本地测试qemu上运行时fd是5),放到main()
函数栈上的缓冲区中(即magic number给出的地址)。 - 然后再一次利用
__libc_csu_init()
函数,调用一个write()
函数,将magic number处的内容写到标准输入里。
1 | from pwn import * |
看到flag了:
需要注意的是初始化时用alarm()
函数,设定了一个定时器,会在1分钟内结束程序,在gdb调试过程中回很难受。可以用ida给程序打patch,把对alarm()
的调用换成nop
HelloARMShell
这次我们需要在上一次的基础上get shell,即执行system("/bin/sh")
。
首先我们需要准备参数"/bin.sh"
和system()
函数的地址,我们可以把"/bin.sh"
放到第一次输入的name
字段,因为name
的地址是静态的,会方便很多。
至于system()
函数的地址,这个程序并没有连接到system()
函数,所以无法在plt表中找到对应的项,但是我们可以先获取到.got
表中write()
函数的 地址,然后通过偏移量算出system()
函数的地址。
偏移量的计算可以在本机通过解析libc.so
文件来解决。
最后,这题的思路主要是:
用
oooooo()
的缓冲区溢出劫持main()
的ret
指令,让它跳转到__libc_csu_init()
函数内部.text:0000000000400AD0
地址处(上图)。然后我们通过精心构造溢出部分的内容,让程序去调用.plt表中的
write()
函数,往标准输出中写出.got
表中write()
函数对应位置的,8个字节的内容,这就是运行时libc.so
中write()
函数的地址。- 本机的py脚本读取到地址后,加上偏移量计算出
system()
函数的地址,发送给程序。 - 对应的,我们再一次利用
__libc_csu_init()
函数,调用一个read()
函数,读入算好的system()
函数地址。 - 最后我们利用
__libc_csu_init()
函数,调用system()
函数,成功get shell
1 | from pwn import * |
执行后成功getshell:
HelloMIPS
这题和arm的那题比较相似,也是ROP,但是比arm的简单一些,给的程序包含三个文件:
1 | Archive: ./HelloMIPS-833e170bbe2429fe66aae530c279c67b.zip |
checksec看一下:
是mipsel小端的程序,没有开任何的选项,那么这题很可能要在栈上执行代码了。
先用qemu跑一下程序:
1 | qemu-mipsel -L ./ ./HelloMIPS |
总的来说程序先要求一份输入,然后给出一个Magic number,之后会有一次输入,但是没有任何回显。
启动ida来分析这个程序:
程序和HelloARM的比较相似,但是main函数中没有buff,第一次读取的0xF
个字节的内容被写入到bss段的NAME
数组中。之后调用的oooooo()
函数也存在缓冲区溢出,但是与ARM的汇编不同的是,存放返回地址的位置在缓冲区的上方,这使得我们可以直接劫持oooooo()
的返回指令。
首先我们需要mipsel的函数调用规则,这里有一篇比较好的文章:https://blog.csdn.net/gujing001/article/details/8476685
简单来说,mipsel的函数调用,返回地址存放在$ra
寄存器中。另外mipsel每次调用库函数时,会先从.got
表中对应的项中取出目标函数地址,放入$t9
寄存器然后用jalr $9
跳入该目标函数。如果是第一次调用该函数,.got
表中指向的会是该函数在.plt
表中的对应代码,加载完成后会修改.got
表项。
最后思路如下:
- 先劫持
oooooo()
的返回地址,跳转到bss段上,bss的地址是静态的,因此可以实现 。因此,我们要在第一次输入时,往bss上放置跳板代码。 - bss上的代码主要是读取栈指针
$sp
并跳转到栈上的地址执行代码 - 第二次输入时,需要在栈上填充代码,调用
system()
函数(它的地址就是给出的magic number的值),/bin/sh
字符串可以放在栈上,但是要放在$sp
指针的后面,否则在调用system()
函数时会覆盖掉。
完整代码如下:
1 | from pwn import * |