lua 逆向学习 & RCTF复盘 picstore 还原代码块

打ctf的时候遇到了lua逆向,浅记一下遇到的知识点和逆向思路

一、luac命令

luac命令可以把.lua代码预编译为字节码文件
因为lua程序执行的核心是在JIT虚拟机上运行字节码,所以luac的作用就是把所有字节码打包成一个二进制文件
-o : 把所有字节码打包成一个文件,-o 后跟打包成的文件名
-l : 把所有字节码打包成一个文件,默认名为luac.out,并打印出所有字节码文件的信息,如下图
=>
0
 

二、反编译

1、unluac

处理lua5.0 - lua5.4
java -jar unluac.jar luac.out > 3.lua
java -jar unluac.jar --rawstring luac.out > 3.lua

2、LuaDec 

主要针对lua5.1,对lua5.2和lua5.3是实验性的 , 依赖lua源码
git clone https://github.com/viruscamp/luadec
cd luadec
git submodule update --init lua-5.3   # lua5.2 就替换成 lua-5.2 下面也是一样
cd lua-5.3
make linux
cd ../luadec
make LUAVER=5.3
参数介绍
-pn  : 打印函数嵌套结构
-dis : 反汇编luac.out或lua源码  # 这里的反汇编指的是生成字节码
luadec abc.lua 或 luadec  luac.out :  反编译lua源码或luac二进制文件  #这里的反编译指的是生成lua源码

三、API

API文档
 
API积累
1、lua_pushcclosure
lua_pushcclosure()函数是Lua C API提供注册C函数最基础的。其他注册方式都是在该函数上面拓展的
void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n);
fn为要注册的函数指针

2、luaL_dofile
加载并运行指定的文件。 它是用下列宏定义出来
 (luaL_loadfile(L, filename) || lua_pcall(L, 0, LUA_MULTRET, 0))

3、luaL_loadfilex
int luaL_loadfilex (lua_State *L, const char *filename, const char *mode);
把一个文件加载为 Lua 代码块,代码块的名字被命名为 filename , 如果 filename 为 NULL,它从标准输入加载

4、lua_load
int lua_load (lua_State *L,lua_Reader reader,void *data,const char *chunkname,const char *mode);
加载一段 Lua 代码块,但不运行它。 如果没有错误, lua_load 把一个编译好的代码块作为一个 Lua 函数压到栈顶。 否则,压入错误消息
参数 reader , 用来读取数据,比如 luaL_loadfilex 内部调用 lua_load 函数,reader 就是getF函数,其通过fread函数读取文件
参数 data , 是指向可选数据结构的指针,可以传递给reader函数。
参数 chunkname是一个字符串,标识了正在加载的块了名字
参数 mode是一个字符串,指定如何编译数据块。可能取值为:"b"(二进制):该块是预编译的二进制块,加载速度比源块快。"t" (text): chunk是一个文本块,在执行前被编译成字节码。"bt" (both):数据块可以是二进制数据块也可以是文本数据块,函数首先尝试将其作为二进制数据块加载,如果加载失败,则尝试将其作为文本数据块加载。

5、lua_pushfstring
const char *lua_pushfstring (lua_State *L, const char *fmt, ...);
把一个格式化过的字符串压栈,然后返回这个字符串的指针。 它和 C 函数 sprintf 比较像

6、lua_pushstring
const char *lua_pushstring (lua_State *L, const char *s);
将指针 s 指向的零结尾的字符串压栈。

7、lua_tolstring
const char *lua_tolstring (lua_State *L, int index, size_t *len);
把给定索引处的 Lua 值转换为一个 C 字符串。 如果 len 不为 NULL , 它还把字符串长度设到 *len 中。 这个 Lua 值必须是一个字符串或是一个数字; 否则返回返回 NULL 。 如果值是一个数字, lua_tolstring 还会 把堆栈中的那个值的实际类型转换为一个字符串。 

四、Lua文件读取调用链

(这里以读入text类型的代码块为例
0
其中lua_load 函数通过 luaD_protectedparser 保护方式来进行文件读取和语法树解析
/**
 * 文件解析函数(保护方式调用)
 * 调用:luaD_pcall方法
 */
int luaD_protectedparser (lua_State *L, ZIO *z, const char *name, const char *mode) {
  status = luaD_pcall(L, f_parser, &p, savestack(L, L->top), L->errfunc);
}
luaD_protectedparser 函数内部调用 luaD_pcall 函数
而 luaD_pcall 函数 回调了 f_parser 函数
static void f_parser (lua_State *L, void *ud) {
//通过数据头的signature来判断读取的数据是binary还是text的,如果是binary的数据,
//则调用luaU_undump来读取预编译好的lua chunks,如果是text数据,则调用luaY_parser来parse lua代码      

//也就是说,读取text源码文件要多了一步paser工作
    if (c == LUA_SIGNATURE[0]) {
      checkmode(L, p->mode, "binary");
      cl = luaU_undump(L, p->z, &p->buff, p->name);
    }
    else {
      checkmode(L, p->mode, "text");
      cl = luaY_parser(L, p->z, &p->buff, &p->dyd, p->name, c);
    }
}
f_parser 函数中真正做语法树解析的是luaY_parser 函数(如果是text数据的话)
 
LClosure *luaY_parser (lua_State *L, ZIO *z, Mbuffer *buff, Dyndata *dyd, const char *name, int firstchar) {
  LexState lexstate;
  FuncState funcstate;
 
  mainfunc(&lexstate, &funcstate);
}
该函数最后执行mainfunc方法,用于执行语法树的解析工作。
static void mainfunc (LexState *ls, FuncState *fs) {
  luaX_next(ls);  /* 读取第一个token read first token  */
  statlist(ls);  /* 语法树遍历解析 parse main body */
}
mainfunc函数中,有两个函数比较关键。luaX_next:主要用于语法TOKEN的分割,是语法分割器;statlist:主要根据luaX_next分割器分割出来的TOKEN,组装成语法块语句statement,最后将语句逐个组装成语法树
而luaX_next函数内部真正执行Token分割的函数是llex...
...
 
参考:

五、lua源码 luaU_undump 函数 -- luac文件头的检测

LClosure *luaU_undump(lua_State *L, ZIO *Z, const char *name) {
  LoadState S;
  LClosure *cl;
  if (*name == '@' || *name == '=')    // 加载的 chunkname
    S.name = name + 1;
  else if (*name == LUA_SIGNATURE[0])
    S.name = "binary string";
  else
    S.name = name;
  S.L = L;
  S.Z = Z;
  checkHeader(&S);
  cl = luaF_newLclosure(L, LoadByte(&S));
  setclLvalue(L, L->top, cl);
  luaD_inctop(L);
  cl->p = luaF_newproto(L);
  LoadFunction(&S, cl->p, NULL);
  lua_assert(cl->nupvalues == cl->p->sizeupvalues);
  luai_verifycode(L, buff, cl->p);
  return cl;
}
正常情况下,luaU_undump 函数调用 checkHeader 函数进行一系列的检查(不只是检查头部魔数)
checkHeader 函数源码如下:
static void checkHeader (LoadState *S) {
  checkliteral(S, LUA_SIGNATURE + 1, "not a");  /* 1st char already checked */
  if (LoadByte(S) != LUAC_VERSION)
    error(S, "version mismatch in");
  if (LoadByte(S) != LUAC_FORMAT)
    error(S, "format mismatch in");
  checkliteral(S, LUAC_DATA, "corrupted");
  checksize(S, int);
  checksize(S, size_t);
  checksize(S, Instruction);
  checksize(S, lua_Integer);
  checksize(S, lua_Number);
  if (LoadInteger(S) != LUAC_INT)
    error(S, "endianness mismatch in");
  if (LoadNumber(S) != LUAC_NUM)
    error(S, "float format mismatch in");
}
我们从上往下逐个分析

1、 检查二进制文件头部签名

checkliteral(S, LUA_SIGNATURE + 1, "not a");
LUA_SIGNATURE 的宏定义为:
#define LUA_SIGNATURE    "\x1bLua"
刚好对应luac文件的开头四字节

2、检查luac版本号

if (LoadByte(S) != LUAC_VERSION)
LUAC_VERSION 的宏定义如下:
#define LUAC_VERSION    (MYINT(LUA_VERSION_MAJOR)*16+MYINT(LUA_VERSION_MINOR))
MYINT(s) 宏定义如下
#define MYINT(s)    (s[0]-'0')
LUA_VERSION_MAJOR 代表了主版本号,LUA_VERSION_MINOR 代表了子版本号
比如,我们的lua版本号为5.3
那么 LUAC_VERSION 就等于5*16+3=83 <=> 0x53
那么luac文件开头的第4个字节应该为 0x53
 
 
0
 

3、检查二进制文件的格式号

if (LoadByte(S) != LUAC_FORMAT)
lua5.3.3源码给出的LUAC_FORMAT的宏定义为
#define LUAC_FORMAT    0    /* this is the official format */
这个值应该一般都为0
检查地址在开头的第5字节
0

4、检查LUAC_DATA

checkliteral(S, LUAC_DATA, "corrupted");
lua5.3.3源码给出的LUAC_DATA的宏定义为
#define LUAC_DATA    "\x19\x93\r\n\x1a\n"
这个值,不同版本应该也不怎么会变
检查地址在开头的第6 ~ 11 字节,检查长度为6字节
0

5、检查数据类型的宽度(包括int,size_t,Instruction,lua_Integer,lua_Number)

checksize(S, int);
checksize(S, size_t);
checksize(S, Instruction);
checksize(S, lua_Integer);
checksize(S, lua_Number);
程序用一个巧妙的宏定义完成了检测,#define后面加上一个#号表示将参数字符串化,将其转换成一个字符串常量
#define checksize(S,t)    fchecksize(S,sizeof(t),#t)

static void fchecksize (LoadState *S, size_t size, const char *tname) {
  if (LoadByte(S) != size)
    error(S, luaO_pushfstring(S->L, "%s size mismatch in", tname));
}
 
0
 

6、检测二进制块的大小端方式是否与虚拟机一致

if (LoadInteger(S) != LUAC_INT)
if (LoadNumber(S) != LUAC_NUM)
#define LUAC_INT    0x5678
#define LUAC_NUM    cast_num(370.5)
#define cast_num(i)    cast(lua_Number, (i))
#define cast(t, exp)    ((t)(exp))
LUAC_INT :
0
LUAC_NUM:
0
 

六、RCTF 实战例题 -- picStore

1、分析程序

题目中给了一个lua5.33打包的elf文件和一个被魔改的 luac文件
分析elf文件,其读入的代码块类型为binary,我们直接沿着Lua的文件读取过程寻找哪里被魔改了
luaU_undump
 
0
查看 luaU_undump 源码
0
 
0
对比程序的checksize的函数和源码:
0
 
0
可以发现,程序魔改了LoadByte函数,添加了这样一句判断
  if ( (unsigned __int8)(b - 1) <= 0xFDu )      // <=> b <= 0xfe
    return ~b;
经过一番研究,发现程序还魔改了 LoadInterger、LoadNumber、LoadInt等函数,既然有load,肯定有dump
对照源码,不难发现程序同样魔改了DumpByte、DumpInt、DumpNumber、DumpInteger函数

2、解题思路一

下面,我们有一种通用的思路来处理这道题目--魔改lua源码
如下图:
0
之后重新编译luadec,再进行反编译即可

3、解题思路二

这里我想介绍的是第二种方法--修复 picstore.bin 代码块
唯一的难题就是: 怎样知道哪些字节被修改了
通过研究,我发现:
(1)、程序load代码块的核心函数有且只有 一个函数 -- LoadBlock
(2)、程序dump代码块的核心函数有且只有 一个函数 --DumpBlock
(3)、这两个函数就像堆栈一样,Load代表 push ,Dump代表 pop
我们只需要记录 LoadBlock 和 DumpBlock 函数的执行次数和函数参数,就可以知道到哪些字节被改变了
 
我使用gdb_python 脚本进行了记录
import gdb
import time

startt = time.time()
fp = open(r"./log.log","w")
strr = ""
def pt(p):
    global strr
    strr += p + "\n"

Esp = 0
gdb.execute('b *0x55555559C078')    # LoadBlock 断点
gdb.execute('b *0x55555559C184')    # LoadByte
gdb.execute('b *0x55555559C1D1')    # LoadInt
gdb.execute('b *0x55555559C111')    # LoadNumber
gdb.execute('b *0x55555559C0A1')    # LoadInteger

gdb.execute('r')
while 1:

    
    frame = gdb.selected_frame()
    rip = frame.read_register("rip")
    
    if rip == 0x55555559C078 :
         
        rdx = frame.read_register("rdx")
        Esp += rdx
    
    elif rip == 0x55555559C184:
        pt(f"{hex(Esp).ljust(10,' ')} => 1")
    
    elif rip == 0x55555559C1D1:
        pt(f"{hex(Esp).ljust(10,' ')} => 4")

    elif rip == 0x55555559C111:
        pt(f"{hex(Esp).ljust(10,' ')} => 8")
    
    elif rip == 0x55555559C0A1:
        pt(f"{hex(Esp).ljust(10,' ')} => 8")
    
    if Esp == 0x19f0:
        fp.write(strr)
        fp.close()
        print("finish!!!")
        enddd = time.time()
        print(enddd - startt)
        break
        
    
    gdb.execute('c')      
// 尝试过用 frida ,但发现他Hook不了LoadBlock 函数
// 实践发现,程序运行过程中没有执行过一次  DumpBlock 函数,比想象中的还要简单
 
0
修复 picstore.bin
import struct
fp = open(r"C:\Users\Administrator\Desktop\RCTF\picstore\log.log","r")
fps = open(r"C:\Users\Administrator\Desktop\RCTF\picstore\picStore.bin","rb")
fix_bin = open(r"C:\Users\Administrator\Desktop\RCTF\picstore\fix_picStore.bin","wb")
data = fp.readlines()
data_bin = fps.read()
fix_data = b""


ans = 0
for i in range(len(data)):
    op_str = data[i]
    loa = int(op_str[2:6],16) + 1
    step = int(op_str[14:15],16)  

    for j in range(ans,loa):
        fix_data += struct.pack("B",data_bin[j])

    for j in range(loa,loa + step):
        if data_bin[j] != 0 and data_bin[j] != 0xff:
            t = data_bin[j] ^ 0xff
            fix_data += struct.pack("B",t)
        else:
            fix_data += struct.pack("B",data_bin[j])
    
    ans = loa + step
fix_bin.write(fix_data)
fix_bin.close()
反编译
java -jar ./unluac.jar ./fix_picStore.bin > ./oplua.lua
 
0
剩下的问题就比较常规了,z3求解即可

exp

from z3 import *
solver = Solver()

a1 = [Int(f'c{i}') for i in range(30)]
v1 = a1[0]
v2 = a1[1]
v3 = a1[2]
v4 = a1[3]
v5 = a1[4]
v6 = a1[5]
v7 = a1[6]
v8 = a1[7]
v10 = a1[8]
v24 = a1[9]
v25 = a1[10]
v26 = a1[11]
v27 = a1[12]
v28 = a1[13]
v29 = a1[14]
v30 = a1[15]
v31 = a1[16]
v32 = a1[17]
v33 = a1[18]
v34 = a1[19]
v35 = a1[20]
v36 = a1[21]
v37 = a1[22]
v38 = a1[23]
v39 = a1[24]
v40 = a1[25]
v20 = a1[26]
v41 = a1[27]
v22 = a1[28]
solver.add(255036*v7+-90989*v3+-201344*v4+122006*v5+-140538*v6+109859*v2-109457*v1-9396023 == 0)
solver.add(277432*v6+110191*v3+-186022*v4+175123*v2-75564*v5-252340*v1-12226612 == 0)
solver.add(127326*v4+260948*v2+-102835*v1+225038*v5-129683*v3-45564209 == 0)
solver.add(-170345*v2+217412*v3-26668*v1+38500*v4-27440782 == 0)
solver.add(25295*v2+69369*v3+191287*v1-24434293 == 0)
solver.add(72265*v1-2384745 == 0)
solver.add(264694*v1-190137*v2+19025100 == 0)
solver.add(101752*v24+67154*v8+-20311*v1+-30496*v6+-263329*v7+-99420*v10+255348*v3+169511*v4-121471*v2+231370*v5-33888892 == 0)
solver.add(17253*v8+-134891*v7+144501*v4+220594*v2+263746*v3+122495*v6+74297*v10+205480*v1-32973*v5-115484799 == 0)
solver.add(251337*v3+-198187*v6+-217900*v2+-62192*v8+-138306*v7+-165151*v4-118227*v1-22431*v5+72699617 == 0)
solver.add(243012*v27+-233931*v4+66595*v7+-273948*v5+-266708*v24+75344*v8-108115*v3-17090*v25+240281*v10+202327*v1-253495*v2+233118*v26+154680*v6+25687761 == 0)
solver.add(41011*v8+-198187*v1+-117171*v7+-178912*v3+9797*v24+118730*v10-193364*v5-36072*v6+10586*v25-110560*v4+173438*v2-176575*v26+54358815 == 0)
solver.add(-250878*v24+108430*v1+-136296*v5+11092*v8+154243*v7+-136624*v3+179711*v4+-128439*v6+22681*v25-42472*v10-80061*v2+34267161 == 0)  
solver.add(65716*v30+-18037*v26+-42923*v7+-33361*v4+161566*v6+194069*v25+-154262*v2+173240*v3-31821*v27-80881*v5+217299*v8-28162*v10+192716*v1+165565*v24+106863*v29-127658*v28-75839517 == 0)
solver.add(-236487*v24+-45384*v1+46984*v26+148196*v7+15692*v8+-193664*v6+6957*v10+103351*v29-217098*v28+78149*v4-237596*v5-236117*v3-142713*v25+24413*v27+232544*v2+78860648 == 0)
solver.add(-69129*v10+-161882*v3+-39324*v26+106850*v1+136394*v5+129891*v2+15216*v27+213245*v24-73770*v28+24056*v25-123372*v8-38733*v7-199547*v4-10681*v6+57424065 == 0)
solver.add(-268870*v30+103546*v24+-124986*v27+42015*v7+80222*v2+-77247*v10+-8838*v25+-273842*v4+-240751*v28-187146*v26-150301*v6-167844*v3+92327*v8+270212*v5-87705*v33-216624*v1+35317*v31+231278*v32-213030*v29+114317949 == 0)
solver.add(-207225*v1+-202035*v3+81860*v27+-114137*v5+265497*v30+-216722*v8+276415*v28+-201420*v10-266588*v32+174412*v6+249222*v24-191870*v4+100486*v2+37951*v25+67406*v26+55224*v31+101345*v7-76961*v29+33370551 == 0)
solver.add(175180*v29+25590*v4+-35354*v30+-173039*v31+145220*v25+6521*v7+99204*v24+72076*v27+207349*v2+123988*v5-64247*v8+169099*v6-54799*v3+53935*v1-223317*v26+215925*v10-119961*v28-83559622 == 0)
solver.add(43170*v3+-145060*v2+199653*v6+14728*v30+139827*v24+59597*v29+2862*v10+-171413*v31+-15355*v25-71692*v7-16706*v26+264615*v1-149167*v33+75391*v27-2927*v4-187387*v5-190782*v8-150865*v28+44238*v32-276353*v34+82818982 == 0)
solver.add(-3256*v27+-232013*v25+-261919*v29+-151844*v26+11405*v4+159913*v32+209002*v7+91932*v34+270180*v10+-195866*v3-135274*v33-261245*v1+24783*v35+262729*v8-81293*v24-156714*v2-93376*v28-163223*v31-144746*v5+167939*v6-120753*v30-13188886 == 0)
solver.add(-240655*v35+103437*v30+236610*v27+100948*v8+82212*v6+-60676*v5+-71032*v3+259181*v7+100184*v10+7797*v29+143350*v24+76697*v2-172373*v25-110023*v37-13673*v4+129100*v31+86759*v1-101103*v33-142195*v36+28466*v32-27211*v26-269662*v34+9103*v28-96428951 == 0)
solver.add(-92750*v28+-151740*v27+15816*v35+186592*v24+-156340*v29+-193697*v2+-108622*v8+-163956*v5+78044*v4+-280132*v36-73939*v33-216186*v3+168898*v30+81148*v34-200942*v32+1920*v1+131017*v26-229175*v10-247717*v31+232852*v25+25882*v7+144500*v6+175681562 == 0)
solver.add(234452*v34+-23111*v29+-40957*v2+-147076*v8+16151*v32+-250947*v35+-111913*v30+-233475*v24+-2485*v28+207006*v26+71474*v3+78521*v1-37235*v36+203147*v5+159297*v7-227257*v38+141894*v25-238939*v10-207324*v37-168960*v33+212325*v6+152097*v31-94775*v27+197514*v4+62343322 == 0) 
solver.add(-142909*v34+-111865*v31+258666*v36+-66780*v2+-13109*v35+-72310*v25+-278193*v26+-219709*v24+40855*v8+-270578*v38+96496*v5+-4530*v1+63129*v28-4681*v7-272799*v30-225257*v10+128712*v37-201687*v39+273784*v3+141128*v29+93283*v32+128210*v33+47550*v6-84027*v4+52764*v40-140487*v27+105279220 == 0)
solver.add(216020*v38+-248561*v29+-86516*v33+237852*v26+-132193*v31+-101471*v3+87552*v25+-122710*v8+234681*v5+-24880*v7+-245370*v1+-17836*v36-225714*v34-256029*v4+171199*v35+266838*v10-32125*v24-43141*v32-87051*v30-68893*v39-242483*v28-12823*v2-159262*v27+123816*v37-180694*v6+152819799 == 0)
solver.add(-116890*v3+67983*v27+-131934*v4+256114*v40+128119*v24+48593*v33+-41706*v2+-217503*v26+49328*v6+223466*v7+-31184*v5+-208422*v36+261920*v1+83055*v20+115813*v37+174499*v29-188513*v35+18957*v25+15794*v10-2906*v28-25315*v8+232180*v32-102442*v39-116930*v34-192552*v38-179822*v31+265749*v30-54143007 == 0)
solver.add(-215996*v4+-100890*v40+-177349*v7+-159264*v6+-227328*v27+-91901*v24+-28939*v10+206392*v41+6473*v25+-22051*v20+-112044*v34+-119414*v30+-225267*v35+223380*v3+275172*v5+95718*v39-115127*v29+85928*v26+169057*v38-204729*v1+178788*v36-85503*v31-121684*v2-18727*v32+109947*v33-138204*v8-245035*v28+134266*v37+110228962 == 0)
solver.add(-165644*v32+4586*v39+138195*v25+155259*v35+-185091*v3+-63869*v31+-23462*v30+150939*v41+-217079*v8+-122286*v6+5460*v38+-235719*v7+270987*v26+157806*v34+262004*v29-2963*v28-159217*v10+266021*v33-190702*v24-38473*v20+122617*v2+202211*v36-143491*v27-251332*v4+196932*v5-155172*v22+209759*v40-146511*v1+62542*v37+185928391 == 0)
solver.add(57177*v24+242367*v39+226332*v31+15582*v26+159461*v34+-260455*v22+-179161*v37+-251786*v32+-66932*v41+134581*v1+-65235*v29+-110258*v28+188353*v38+-108556*v6+178750*v40+-20482*v25+127145*v8+-203851*v5+-263419*v10+245204*v33+-62740*v20+103075*v2-229292*v36+142850*v30-1027*v27+264120*v3+264348*v4-41667*v35+130195*v7+127279*a1[29]-51967523 == 0)

assert solver.check() == sat
flag2 = []
mol = solver.model()
for i in a1:
    flag2.append(mol[i].as_long())

mappp = [105, 244, 63, 10, 24, 169, 248, 107, 129, 138, 25, 182, 96, 176, 14, 89, 56, 229, 206, 19, 23, 21, 22, 198, 179, 167, 152, 66, 28, 201, 213, 80, 162, 151, 102, 36, 91, 37, 50, 17, 170, 41, 3, 84, 85, 226, 131, 38, 71, 32, 18, 142, 70, 39, 112, 220, 16, 219, 159, 222, 11, 119, 99, 203, 47, 148, 185, 55, 93, 48, 153, 113, 1, 237, 35, 75, 67, 155, 161, 74, 108, 76, 181, 233, 186, 44, 125, 232, 88, 8, 95, 163, 200, 249, 120, 243, 174, 212, 252, 234, 58, 101, 228, 86, 109, 144, 104, 121, 117, 87, 15, 132, 12, 20, 165, 115, 136, 135, 118, 69, 68, 2, 82, 123, 250, 251, 53, 255, 51, 221, 211, 195, 145, 140, 254, 0, 116, 43, 29, 217, 197, 183, 168, 188, 34, 218, 146, 147, 98, 149, 246, 180, 103, 33, 40, 207, 208, 192, 143, 26, 154, 225, 100, 141, 175, 124, 230, 62, 177, 205, 110, 202, 253, 173, 46, 52, 114, 164, 166, 137, 158, 122, 13, 83, 178, 133, 189, 187, 7, 184, 77, 245, 216, 190, 194, 72, 157, 172, 171, 199, 160, 45, 49, 27, 204, 81, 6, 92, 59, 209, 239, 130, 97, 61, 214, 215, 73, 90, 126, 42, 30, 240, 79, 224, 78, 223, 111, 60, 4, 5, 196, 231, 106, 64, 139, 235, 150, 227, 238, 191, 127, 31, 156, 54, 241, 242, 134, 247, 128, 65, 94, 57, 210, 236, 9, 193]
flag = ""
for i in range(len(flag2)):
    ttt = mappp.index(flag2[i])
    ttt &= 0xFF
    flag += chr(ttt ^ 0xFF ^ i)

print(flag)

 

posted @ 2022-12-23 14:16  TLSN  阅读(1398)  评论(0编辑  收藏  举报