首页 > 其他分享 >巅峰极客 2023 逆向 Writeup

巅峰极客 2023 逆向 Writeup

时间:2023-07-24 17:47:14浏览次数:40  
标签:反编译 极客 luajit Writeup v28 2023 slot1 slot0 uleb128

巅峰极客 WriteUp

m1_read

白盒 AES,其他师傅也写的比较清楚了。我当时用的 Frida

var baseAddr = Module.findBaseAddress("m1_read.exe");
var whiteAES = new NativeFunction(baseAddr.add(0x4BF0), 'pointer', ['pointer', 'pointer'])
var count = 9;
Interceptor.attach(baseAddr.add(0x4C2C), {
    onEnter: function(args) {
        count++;
        if(count == 9) {
            this.context.rdi.add(Math.floor(Math.random() * 16)).writeU8(Math.floor(Math.random() * 256))
        }
    },
    onLeave: (retval) => {

    }
})


for (let index = 0; index < 33; index++) {
    var l = Memory.allocAnsiString("1234567890abcdef");
    var b = Memory.alloc(16);
    whiteAES(l, b);
    console.log(b.readByteArray(16));
    count = 0;
}

把拿到的结果 xor 一下

str_table = ["ca429fdc6bfa9b5e540c8f14b03bae88", "ca429f436bfa575e541f8f14f73bae88", "ca709fdc77fa9b5e540c8faab03bc288",
 "ca1d9fdc24fa9b5e540c8f53b03b6388", "ca4231dc6bdd9b5ece0c8f14b03bae1f", "34429fdc6bfa9b2a540c4c14b025ae88",
 "ca4260dc6bfd9b5eb30c8f14b03baecb", "ca889fdc5dfa9b5e540c8f95b03b1e88", "d2429fdc6bfa9bba540c3314b03dae88",
 "fd429fdc6bfa9b72540ca814b048ae88", "ca429f6e6bfa355e54a18f14c23bae88", "ca42c6dc6b349b5e450c8f14b03bae15",
 "ca449fdc39fa9b5e540c8f26b03bbb88", "ca42fcdc6b3e9b5e7d0c8f14b03baecd", "ca429bdc6bb49b5eab0c8f14b03bae6c",
 "ca429fb46bface5e54798f14213bae88", "ca429f2d6bfa805e547b8f140c3bae88"]

for s in str_table:
    val = bytearray(bytes.fromhex(s))
    for i in range(16):
        val[i] ^= 0x66
    print(val.hex())

DFA 攻击白盒AES的最后一轮密钥

import phoenixAES

with open('tracefile', 'wb') as t:
    t.write("""
ac24f9ba0d9cfd38326ae972d65dc8ee
ac24f9250d9c31383279e972915dc8ee
ac16f9ba119cfd38326ae9ccd65da4ee
ac7bf9ba429cfd38326ae935d65d05ee
ac2457ba0dbbfd38a86ae972d65dc879
5224f9ba0d9cfd4c326a2a72d643c8ee
ac2406ba0d9bfd38d56ae972d65dc8ad
aceef9ba3b9cfd38326ae9f3d65d78ee
b424f9ba0d9cfddc326a5572d65bc8ee
9b24f9ba0d9cfd14326ace72d62ec8ee
ac24f9080d9c533832c7e972a45dc8ee
ac24a0ba0d52fd38236ae972d65dc873
ac22f9ba5f9cfd38326ae940d65dddee
ac249aba0d58fd381b6ae972d65dc8ab
ac24fdba0dd2fd38cd6ae972d65dc80a
ac24f9d20d9ca838321fe972475dc8ee
ac24f94b0d9ce638321de9726a5dc8ee
ac24f94b0d9ce638321de9726a5dc8ee
""".encode('utf8'))

phoenixAES.crack_file('tracefile')

使用 https://github.com/SideChannelMarvels/Stark 工具求得第一轮密钥为 00000000000000000000000000000000

求解脚本:

from Crypto.Cipher import AES

key = bytes.fromhex('00000000000000000000000000000000')
cipher = AES.new(key, AES.MODE_ECB)
final = bytes.fromhex('0B987EF5D94DD679592C4D2FADD4EB89')
final = bytearray(final)
for i in range(16):
    final[i] ^= 0x66
flag = cipher.decrypt(final)
print(flag)

g0Re

不想废话,直接脚本吧

import base64
import struct
from Crypto.Cipher import AES

v28 = [0] * 8
v28[0] = 0xC9F5C5CFC889CEE6
v28[1] = 0xCCAC7FCE91C0D9D2
v28[2] = 0x92EAD496C0B7CFE9
v28[3] = 0x93AEA5CB84DFD7E2
v28[4] = 0xC9F0CEDF97BECAA6
v28[5] = 0xDB65B1C46BAEE1B7
v28[6] = 0xC3ED8CD69392EDCE
v28[7] = 0xA7B5B2AAA594DAA3
target = struct.pack("<8Q", v28[0], v28[1], v28[2], v28[3], v28[4], v28[5], v28[6], v28[7])

my_b64_table =  '456789}#IJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123ABCDEFGH'
std_b64_table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'


key = b'wvgitbygwbk2b46d'
target = bytearray(target)
for i in range(len(target)):
    target[i] = target[i] - key[i % 16]
    target[i] ^= 0x1a
f = ''.join(chr(_) for _ in target)
m = str.maketrans(my_b64_table, std_b64_table)
f = f.translate(m)
f = base64.b64decode(f)
cipher = AES.new(key, mode=AES.MODE_ECB)
flag = cipher.decrypt(f)
print(flag)

ezlua

等了两天没看到有师傅发预期解法,只能自己动手了。

主程序东西不多,都在 main 函数了,主要就是把输入(40位hex 字符串),20位一组进行 hex 解码分别设置到要执行的 luajit 二进制代码中两处位置(中间隔着一个03,先剧透一下这是有用的)

第一步,我们要先判断输入的内容的作用,这里直接对输入内容下内存断点,看看会断到哪。

主程序调用 luaL_loadbuffer后,断到了下图所示位置

image

f5 一下,可以看出来这个函数是对 uleb128格式数据的解码

image

对照 luajit的源码搜索 uleb128后发现是函数 lj_buf_ruleb128

image

继续跟,来到上层函数 sub_7FFDA1E95EC0,在下面的一处 lj_buf_ruleb128中又读取了输入。

image

通过对 luaL_loadbuffer 源码的分析,发现这个函数是 bcread_kgc 函数(就是读取常量的一部分),代码如下:


/* Read GC constants of a prototype. */
static void bcread_kgc(LexState *ls, GCproto *pt, MSize sizekgc)
{
  MSize i;
  GCRef *kr = mref(pt->k, GCRef) - (ptrdiff_t)sizekgc;
  for (i = 0; i < sizekgc; i++, kr++)
  {
    MSize tp = bcread_uleb128(ls); // 读取数据类型
    ... // 此处省略一堆无关代码
    else if (tp != BCDUMP_KGC_CHILD)
    {
      CTypeID id = tp == BCDUMP_KGC_COMPLEX ? CTID_COMPLEX_DOUBLE : tp == BCDUMP_KGC_I64 ? CTID_INT64
                                                                                         : CTID_UINT64;
      CTSize sz = tp == BCDUMP_KGC_COMPLEX ? 16 : 8;
      GCcdata *cd = lj_cdata_new_(ls->L, id, sz);
      TValue *p = (TValue *)cdataptr(cd);
      setgcref(*kr, obj2gco(cd));
      p[0].u32.lo = bcread_uleb128(ls); // 读取一个uint32
      p[0].u32.hi = bcread_uleb128(ls);  // 读取一个uint32
      if (tp == BCDUMP_KGC_COMPLEX)
      {
        p[1].u32.lo = bcread_uleb128(ls);
        p[1].u32.hi = bcread_uleb128(ls);
      }
    ... // 此处省略一堆无关代码
    }
    else
    {
     ... // 此处省略一堆无关代码
    }
  }
}

根据调试结果,发现输入的数据其实是两个 u64 类型的数字使用 uleb128 编码成 4 个 32bit 的值。判断依据是 tp的值均为 3(这就是为啥前面中间要隔一个 03

image

到现在就知道了,输入的内容其实是两个 u64 类型的常量,也被存放在了 luajit文件的常量部分。

而且这两个常量的高32bit和低32bit被编码为uleb128 后长度都必须是5个字节,也就是前4个字节大于等于 0x80,第五个字节小于 0x80,否则就会解析失败报错

下面就是反汇编或者反编译了。经过可靠的对比,题目中附带的 dll 并没有改变 luajit 的 opcode 顺序,只是添加了一个导出函数

luaL_checkcdata,这个应该是作者基于 https://github.com/tarantool/tarantool/blob/03db58fb05baebeea22f8d764559d4b950ab6514/src/lua/utils.c#L278 这个代码改了一下加的,用于获取 lua 中的值到 C 语言,对反汇编没啥作用

验证是否修改 opcode 顺序的时候,可以对开源的版本加这个函数,编译出 dll 和题目附带的 dll 执行效果一样,说明没有改变 luajit 的 opcode 顺序。

反编译阶段,我先是用 https://github.com/bobsayshilol/luajit-decomp/tree/deprecated 工具拿到了,反汇编的结果(这个工具也反编译出来了两个版本的 lua 文件,但是反编译的一塌糊涂可以说不堪入目,而且经过后面对 luajit 指令执行代码的调试,发现其中的 CALL 指令参数全识别错了,估计按老版本的吧,但是还好反汇编结果是对的)

image

根据反汇编结果,对 luajit的 vm 进行调试

如何找每个指令对应的处理函数?

参考 in1t ✌ 的博客 https://in1t.top/2023/04/02/7th-xctf-final-super-flagio/

lua51.dll 的 0x7FFD9E6F3034(基地址 0x7FFD9E6F0000)处下断点,执行我这里提供的脚本即可为所有分支设置 Name

from idaapi import *

ins = ("ISLT", "ISGE", "ISLE", "ISGT", "ISEQV", "ISNEV", "ISEQS", "ISNES", "ISEQN", "ISNEN", "ISEQP", "ISNEP", "ISTC", "ISFC",
"IST", "ISF", "ISTYPE", "ISNUM", "MOV", "NOT", "UNM", "LEN", "ADDVN", "SUBVN", "MULVN", "DIVVN", "MODVN", "ADDNV",
"SUBNV", "MULNV", "DIVNV", "MODNV", "ADDVV", "SUBVV", "MULVV", "DIVVV", "MODVV", "POW", "CAT", "KSTR", "KCDATA",
"KSHORT", "KNUM", "KPRI", "KNIL", "UGET", "USETV", "USETS", "USETN", "USETP", "UCLO", "FNEW", "TNEW", "TDUP", "GGET",
"GSET", "TGETV", "TGETS", "TGETB", "TGETR", "TSETV", "TSETS", "TSETB", "TSETM", "TSETR", "CALLM", "CALL", "CALLMT",
"CALLT", "ITERC", "ITERN", "VARG", "ISNEXT", "RETM", "RET", "RET0", "RET1", "FORI", "JFORI", "FORL", "IFORL", "JFORL",
"ITERL", "IITERL", "JITERL", "LOOP", "ILOOP", "JLOOP", "JMP", "FUNCF", "IFUNCF", "JFUNCF", "FUNCV", "IFUNCV", "JFUNCV",
"FUNCC", "FUNCCW")

base_addr = get_reg_val('rbx')
ins_len = len(ins)
for i in range(ins_len):
    addr = base_addr + 8 * i
    create_qword(addr, 8)
    set_name(get_qword(addr), ins[i])

根据反汇编结果在 KCDATA 处下断点,发现第一个 KCDATA 加载的值是我们的输入的后半段 0x645F40745F4A3154,继续调试发现第二个 KCDATA 加载的值是我们的输入的前半段 0x755F72655F673030,第三个则是加载的一个常量 0xDEADBEEF12345678

调试 + 读汇编代码, 可以做,但是我看着那 4000 多行汇编代码,陷入了沉思....

反编译!必须反编译!听说这个项目 https://github.com/Dr-MTN/luajit-decompiler 好使,但是直接反编译报错了,

  File "D:\TaskFile\CTF\luajit-decompiler\ljd\ast\slotworks.py", line 585, in leave_assignment
    assert self._last_multres_value is None
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

调试后发现,出问题的代码是下面这个。

CALL     7   0   3 ; bit.lshift(9, 10)

他确实不太正常,第二个参数是 0,第二个参数正常是返回值个数 + 1,对于这个函数也就是说应该是 2

image

确实其他的大部分地方对于类似函数的调用都是 2,通过对虚拟机的调试,发现 20的效果好像一样,所以我让反编译器把它按2处理就是了!直接大力出奇迹!

image

结果啪一下的就出来了哈,很快哈,但是为啥一堆浮点数啊,我这是 u64 啊!

image

好好好,我继续改,发现这里有个转 double,我反手就是一个注释(对不对先不管,先爽了再说

image

啪的一下就好了,这次舒服了,运行一下?当场就得出了一个和原来的结果不一样的结果,然后发现是刚才爽过头了,应该是不支持 u64 ,但是无所谓逻辑没错就行(真没错吗?)

image

用 c 语言换 uint64 重写一遍,发现结果还是不对!这不是欺负老实人吗!还好我刚开始分析了一会汇编代码,发现是这个最长的操作,忽略了中间值。正确的写法应该是下面这样。

 slot1 = bor(rshift(slot1, 8), lshift(slot1, 56));
 slot1 = bxor(slot0 + slot1, slot2);
 slot0 = bor(lshift(slot0, 3), rshift(slot0, 61));
 slot0 = bxor(slot0, slot1);

终于,得到了预期的结果,反编译大业成功!!!!!!!!!

然后就是作者插入的一堆虚假控制流了,无所谓,操作只有一种只需要记录每次用的 slot2 运行一次即可获得正确的控制流顺序,然后根据结果逆序来一圈就行了。

image

反编译得到的完整代码太长了,就不贴了。

写个求解脚本


import leb128

def decrypt(_slot0, _slot1, _slot2):
    _slot0 ^= _slot1
    _slot0 = ((_slot0 >> 3) | (_slot0 << 61)) & 0xffffffffffffffff
    slot1_and_slot0 = _slot1 ^ _slot2
    _slot1 = slot1_and_slot0 - _slot0
    _slot1 &= 0xffffffffffffffff
    _slot1 = ((_slot1 >> 56) | (_slot1 << 8)) & 0xffffffffffffffff
    return _slot0, _slot1

def u64_to_uleb128(u):
    high = u >> 32
    low = u & 0xffffffff
    return leb128.u.encode(low).hex() + leb128.u.encode(high).hex()

slot2_array = [0xdeadbeef12345678, 0x28539dc5904d8141, 0xf2ac321ccf237a7b, 0xf03df21e866b1a36, 0x584cde754c325b4b, 0x97407269ac231f8b,
 0xd2960ba60ee82d09, 0xb34efc0e8d197592, 0x15011adba4d8613d, 0x1598470b72677cea, 0xb497efc6db87c606, 0xae0f3ba8a4eeb218,
 0xab6036ab64121254, 0x663ae5cc72c5eb7f, 0x71af0f7e9c371b0e, 0xeb97fc6b58f9eb33, 0x774108a83f7c75f6, 0x5a6542d5c9968681,
 0x5e6fb973117ccfb1, 0xea8134ba653ce534, 0xfc92946aa1cc9678, 0x38af8cc9553071e4, 0x99f7a1b258084992, 0x82e920e890bb99da,
 0xc67f72528ed05d6c, 0x4cab3a53d2598281, 0x517358620b3249f9, 0xcf3d41fd5e5e0786, 0x626be66ab995efe3, 0x24d85b01f54e2ab1,
 0xe9cd3a65e3f95992, 0x4bf5996751882d17]

slot2_array.reverse()


slot0 = 0xdd26c29515a28396
slot1 = 0xbd722d4baf99b9c7

for slot2 in slot2_array:
    slot0, slot1 = decrypt(slot0, slot1, slot2)

print(u64_to_uleb128(slot1) + u64_to_uleb128(slot0))

标签:反编译,极客,luajit,Writeup,v28,2023,slot1,slot0,uleb128
From: https://www.cnblogs.com/gaoyucan/p/17577858.html

相关文章

  • 【2023-07-23】周末计划
    23:00何以销烦暑,端居一院中。眼前无长物,窗下有清风。热散由心静,凉生为室空。此时身自得,难更与人同。                                                 ——白居易·《销......
  • Summer Training 2023 Mini Comp 1 (Experts)
    SummerTraining2023MiniComp1(Experts)2338Carnival-PCOIOnlineJudge(pcoij8.ddns.net)题目大意交互题,n个人穿着衣服,共有c种颜色,每一次可以询问一些人穿的衣服有多少种不同的颜色,最多可以询问3500次,请确定每个人穿的衣服是什么颜色做法第一眼可以看出来答案的上......
  • 【2023.07.23】本命年生日纪念
    缘起这次本命年生日和以往不一样,比较特别,和女生单独去约会了(说好的下半年好好读书,不约会了呢?怎么认识的呢?说来也是碰巧,就是月初我决定好好提升自己,不要再被感情困扰的时候刚搬到新宿舍,手边没电脑打个乱斗,于是我时隔好久又重新下载了王者上号就被小黑拉进了一个匹配房间,刚好遇......
  • 2023年Q2京东环境电器市场数据分析(京东数据产品)
    今年Q2,环境电器市场中不少类目表现亮眼,尤其是以净水器、空气净化器、除湿机等为代表的环境健康电器。此外,像冷风扇这类具有强季节性特征的电器也呈现出比较好的增长态势。接下来,结合具体数据我们一起来分析Q2环境电器市场中这些表现优异的增长类目。净水器不断普及,高端化发展成趋势......
  • 20230724日报
    简单的求和做题原因之前TLE30和TLE60,一直没有做出来做题过程想到了尺取法,重新听了尺取法的课,看了尺取法的模板。看了之前错误的代码,决定沿用之前使用map的方法,但是在找不出之前的错误,重新写了一遍代码交上去AC解题思路首先输入的元素是不需要按照它本来的顺序的(可以重......
  • 2023上半年软考成绩复查时间+方式
    2023年上半年软考成绩查询入口已开通,查过成绩之后相信有部分考生对于自己的成绩是有异议的,如果有异议可以申请复查,关于软考成绩复查的方法,这里给大家作简单介绍。  2023上半年计算机软考成绩复查时间汇总表浙江软考2023上半年成绩复查时间2023年7月27日止湖南软考2023上半年成......
  • 成语积累 20230724
    难兄难弟:nan4xiongnan4di:处于同样困境的人。(nan2:兄弟二人都非常好,难分高下。或讥讽两者同样低劣。出处:元方难为兄,季方难为弟。近义:难分伯仲)暴虎冯河:暴:通"虣",和老虎打斗;冯:通"淜",指无舟渡河。空手打虎,徒步过河。多用于比喻有勇无谋,冒险蛮干。也比喻勇猛果敢。例句:我原以为他......
  • 剑指offer_20230723
    剑指Offer50.第一个只出现一次的字符题目说明在字符串s中找出第一个只出现一次的字符。如果没有,返回一个单空格。s只包含小写字母。解题思路1:HashMap使用传统的HashMap,对整一个数组进行遍历,更新记录每个字母的出现次数。在遍历结束之后重新遍历一遍数组,获取每个字母对......
  • Toyota Programming Contest 2023#4(AtCoder Beginner Contest 311)
    ToyotaProgrammingContest2023#4(AtCoderBeginnerContest311)A-FirstABC(atcoder.jp)记录一下\(ABC\)是否都出现过了然后输出下标#include<bits/stdc++.h>#defineintlonglongusingnamespacestd;signedmain(){ios::sync_with_stdio(false);cin.tie(n......
  • Mac版多平台Java开发工具JetBrains IntelliJ IDEA 2023
    JetBrainsIntelliJ是一个多平台的Java开发工具,可以用于Java开发。它可以帮助您在Linux、Windows、Mac和Linux上开发基于Java的应用程序、软件和服务。它还提供了一个跨平台的工具包,可以为开发者提供Java开发者的基础设施设计支持。JetBrainsIntelliJ与Linux有很多相似之处:Java......