2021L3HCTF luuuuua Writeup

2021 L3HCTF luuuua Writeup

AAA:immortal

这题做完以后感觉和ByteCTF2021的language binding很像,这是我之前写的Writeup

首先打开java层,发现asserts/res/test.lua里的逻辑是假的。然后通过LoginActivity里的afterTextChanged方法找到关键的b.c.a.b.a.d。 注意它

import org.keplerproject.luajava.LuaState; 

import org.keplerproject.luajava.LuaStateFactory;

所以在网上找到了对应的github:luajava,和一些使用例子:

public void invokeFileScript(String scriptFilePath){
	LuaState luaState = LuaStateFactory.newLuaState();
	luaState.openLibs();
	this.initLuaContext(luaState);
	int error = luaState.LdoFile(scriptFilePath);
	if(error!=0){
		Logger.log("Read/Parse lua error. Exit");
		return;
	}
	luaState.close();
}

所以我们发现真正的逻辑在logo.jpg上,010editor打开找了一下发现真的有加密了的luac脚本。异或0x3c解密以后得到了luac脚本,unluac尝试反汇编发现报错。和byteCTF那道一样改了opcode的位置,因此需要逆向.so文件找到真正的opcode解析的方法。

with open('logo.jpg','rb') as f:
    con = f.read()        
con = con[0x3afa2:]
with open('dec.luac','wb') as fs:
    fs.write(b'\x1b')
    for c in con:
        fs.write(bytes([0x3c^c]))

使用Android里的ndk工具链重新编译了lua5.3的源码,得到了正确的.so文件。两者对照以后慢慢逆向恢复出正确的opcodes。

然后和ByteCTF一样,魔改一下unluac脚本,反编译出了lua脚本:

local base64 = {}
local extract = _G.bit32 and _G.bit32.extract
if not extract then
  if _G.bit then
    local shl, shr, band = _G.bit.lshift, _G.bit.rshift, _G.bit.band
    function extract(v, from, width)
      return band(shr(v, from), shl(1, width) - 1)
    end
  elseif _G._VERSION == "Lua 5.1" then
    function extract(v, from, width)
      local w = 0
      local flag = 2 ^ from
      for i = 0, width - 1 do
        local flag2 = flag + flag
        if flag <= v % flag2 then
          w = w + 2 ^ i
        end
        flag = flag2
      end
      return w
    end
  else
    extract = load([[
return function( v, from, width )
			return ( v >> from ) & ((1 << width) - 1)
		end]])()
  end
end
function base64.makeencoder(s62, s63, spad)
  local encoder = {}
  for b64code, char in pairs({
    [0] = "A",
    "B",
    "C",
    "D",
    "E",
    "F",
    "G",
    "H",
    "I",
    "J",
    "K",
    "L",
    "M",
    "N",
    "O",
    "P",
    "Q",
    "R",
    "S",
    "T",
    "U",
    "V",
    "W",
    "X",
    "Y",
    "Z",
    "a",
    "b",
    "c",
    "d",
    "e",
    "f",
    "g",
    "h",
    "i",
    "j",
    "k",
    "l",
    "m",
    "n",
    "o",
    "p",
    "q",
    "r",
    "s",
    "t",
    "u",
    "v",
    "w",
    "x",
    "y",
    "z",
    "0",
    "1",
    "2",
    "3",
    "4",
    "5",
    "6",
    "7",
    "8",
    "9",
    s62 or "+",
    s63 or "/",
    spad or "="
  }) do
    encoder[b64code] = char:byte()
  end
  return encoder
end
function base64.makedecoder(s62, s63, spad)
  local decoder = {}
  for b64code, charcode in pairs(base64.makeencoder(s62, s63, spad)) do
    decoder[charcode] = b64code
  end
  return decoder
end
local DEFAULT_ENCODER = base64.makeencoder()
local DEFAULT_DECODER = base64.makedecoder()
local char, concat = string.char, table.concat
function base64.encode(str, encoder, usecaching)
  encoder = encoder or DEFAULT_ENCODER
  local t, k, n = {}, 1, #str
  local lastn = n % 3
  local cache = {}
  for i = 1, n - lastn, 3 do
    local a, b, c = str:byte(i, i + 2)
    local v = a * 65536 + b * 256 + c
    local s
    if usecaching then
      s = cache[v]
      if not s then
        s = char(encoder[extract(v, 18, 6)], encoder[extract(v, 12, 6)], encoder[extract(v, 6, 6)], encoder[extract(v, 0, 6)])
        cache[v] = s
      end
    else
      s = char(encoder[extract(v, 18, 6)], encoder[extract(v, 12, 6)], encoder[extract(v, 6, 6)], encoder[extract(v, 0, 6)])
    end
    t[k] = s
    k = k + 1
  end
  if lastn == 2 then
    local a, b = str:byte(n - 1, n)
    local v = a * 65536 + b * 256
    t[k] = char(encoder[extract(v, 18, 6)], encoder[extract(v, 12, 6)], encoder[extract(v, 6, 6)], encoder[64])
  elseif lastn == 1 then
    local v = str:byte(n) * 65536
    t[k] = char(encoder[extract(v, 18, 6)], encoder[extract(v, 12, 6)], encoder[64], encoder[64])
  end
  return concat(t)
end
function base64.decode(b64, decoder, usecaching)
  decoder = decoder or DEFAULT_DECODER
  local pattern = "[^%w%+%/%=]"
  if decoder then
    local s62, s63
    for charcode, b64code in pairs(decoder) do
      if b64code == 62 then
        s62 = charcode
      elseif b64code == 63 then
        s63 = charcode
      end
    end
    pattern = ("[^%%w%%%s%%%s%%=]"):format(char(s62), char(s63))
  end
  b64 = b64:gsub(pattern, "")
  local cache = usecaching and {}
  local t, k = {}, 1
  local n = #b64
  local padding = b64:sub(-2) == "==" and 2 or b64:sub(-1) == "=" and 1 or 0
  for i = 1, padding > 0 and n - 4 or n, 4 do
    local a, b, c, d = b64:byte(i, i + 3)
    local s
    if usecaching then
      local v0 = a * 16777216 + b * 65536 + c * 256 + d
      s = cache[v0]
      if not s then
        local v = decoder[a] * 262144 + decoder[b] * 4096 + decoder[c] * 64 + decoder[d]
        s = char(extract(v, 16, 8), extract(v, 8, 8), extract(v, 0, 8))
        cache[v0] = s
      end
    else
      local v = decoder[a] * 262144 + decoder[b] * 4096 + decoder[c] * 64 + decoder[d]
      s = char(extract(v, 16, 8), extract(v, 8, 8), extract(v, 0, 8))
    end
    t[k] = s
    k = k + 1
  end
  if padding == 1 then
    local a, b, c = b64:byte(n - 3, n - 1)
    local v = decoder[a] * 262144 + decoder[b] * 4096 + decoder[c] * 64
    t[k] = char(extract(v, 16, 8), extract(v, 8, 8))
  elseif padding == 2 then
    local a, b = b64:byte(n - 3, n - 2)
    local v = decoder[a] * 262144 + decoder[b] * 4096
    t[k] = char(extract(v, 16, 8))
  end
  return concat(t)
end
local strf = string.format
local byte, char = string.byte, string.char
local spack, sunpack = string.pack, string.unpack
local app, concat = table.insert, table.concat
local stohex = function(s, ln, sep)
  if #s == 0 then
    return ""
  end
  if not ln then
    return (s:gsub(".", function(c)
      return strf("%02x", byte(c))
    end))
  end
  sep = sep or ""
  local t = {}
  for i = 1, #s - 1 do
    t[#t + 1] = strf("%02x%s", s:byte(i), i % ln == 0 and "\n" or sep)
  end
  t[#t + 1] = strf("%02x", s:byte(#s))
  return concat(t)
end
local hextos = function(hs, unsafe)
  local tonumber = tonumber
  if not unsafe then
    hs = string.gsub(hs, "%s+", "")
    if string.find(hs, "[^0-9A-Za-z]") or #hs % 2 ~= 0 then
      error("invalid hex string")
    end
  end
  return hs:gsub("(%x%x)", function(c)
    return char(tonumber(c, 16))
  end)
end
local stx = stohex
local xts = hextos
local ROUNDS = 64
local keysetup = function(key)
  assert(#key == 16)
  local kt = {
    0,
    0,
    0,
    0
  }
  kt[1], kt[2], kt[3], kt[4] = sunpack(">I4I4I4I4", key)
  local skt0 = {}
  local skt1 = {}
  local sum, delta = 0, 2654435769
  for i = 1, ROUNDS do
    skt0[i] = sum + kt[(sum & 3) + 1]
    sum = sum + delta & 4294967295
    skt1[i] = sum + kt[(sum >> 11 & 3) + 1]
  end
  return {skt0 = skt0, skt1 = skt1}
end
local encrypt_u64 = function(st, bu)
  local skt0, skt1 = st.skt0, st.skt1
  local v0, v1 = bu >> 32, bu & 4294967295
  local sum, delta = 0, 2654435769
  for i = 1, ROUNDS do
    v0 = v0 + ((v1 << 4 ~ v1 >> 5) + v1 ~ skt0[i]) & 4294967295
    v1 = v1 + ((v0 << 4 ~ v0 >> 5) + v0 ~ skt1[i]) & 4294967295
  end
  bu = v0 << 32 | v1
  return bu
end
local enc = function(key, iv, itxt)
  assert(#key == 16, "bad key length")
  assert(#iv == 8, "bad IV length")
  if #itxt == 0 then
    return ""
  end
  local ivu = sunpack("<I8", iv)
  local ot = {}
  local rbn = #itxt
  local ksu, ibu, ob
  local st = keysetup(key)
  for i = 1, #itxt, 8 do
    ksu = encrypt_u64(st, ivu ~ i)
    if rbn < 8 then
      local buffer = string.sub(itxt, i) .. string.rep("\000", 8 - rbn)
      ibu = sunpack("<I8", buffer)
      ob = string.sub(spack("<I8", ibu ~ ksu), 1, rbn)
    else
      ibu = sunpack("<I8", itxt, i)
      ob = spack("<I8", ibu ~ ksu)
      rbn = rbn - 8
    end
    app(ot, ob)
  end
  return concat(ot)
end
function check_login(username, password)
  local encoded = base64.encode(username)
  if encoded ~= "TDNIX1NlYw==" then --L3H_Sec
    return false
  end
  username = username .. "!@#$%^&*("
  local x = base64.encode(enc(username, "1qazxsw2", password))
  if x == "LKq2dSc30DKJo99bsFgTkQM9dor1gLl2rejdnkw2MBpOud+38vFkCCF13qY=" then
    return true
  end
  return false
end

仔细看了一下加密的原理,发现前面虽然很复杂是tea-cbc但其实最后还是异或,所以直接在解出的lua脚本里敲个print,dump出要异或的字节流就可以了。

from base64 import b64decode

cmp = b64decode('LKq2dSc30DKJo99bsFgTkQM9dor1gLl2rejdnkw2MBpOud+38vFkCCF13qY=')
print(len(cmp)) # 44
a = [
48256960675354976,
-4890521267755574343+2**64,
4792028846500240997,
9087511422306731418,
7661124995518270764,
-4844959183373908412+2**64
]
ba= list(b''.join(map(lambda x: x.to_bytes(8,"little"),a)))
for i in range(44):
    print(chr(cmp[i]^ba[i]),end='')
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值